diff options
author | Patrick Walton <pcwalton@mimiga.net> | 2013-11-09 21:39:39 -0800 |
---|---|---|
committer | Patrick Walton <pcwalton@mimiga.net> | 2013-11-18 11:24:11 -0800 |
commit | 155befe10dc56cfb2dfbf0cca7b652293dba9753 (patch) | |
tree | 43c3e51689cd42a3fb623eda84740f49dd667f5e /src/components | |
parent | 37f9427b6c53b90234e82d219217a97c10811243 (diff) | |
download | servo-155befe10dc56cfb2dfbf0cca7b652293dba9753.tar.gz servo-155befe10dc56cfb2dfbf0cca7b652293dba9753.zip |
Rewrite flow construction to be incrementalizable and parallelizable.
This replaces flow construction with a strict bottom-up tree traversal,
allowing for parallelism. Each step of the traversal creates a flow or
a `ConstructionItem`, similar to how Gecko works. {ib} splits are
handled by not creating `InlineFlow`s until the containing block is
reached.
This should be able to be incrementalized by storing the `Flow` from
layout to layout, and performing fixups during flow construction
and/or wiping containing blocks in a previous pass.
Diffstat (limited to 'src/components')
-rw-r--r-- | src/components/main/css/matching.rs | 41 | ||||
-rw-r--r-- | src/components/main/css/node_style.rs | 1 | ||||
-rw-r--r-- | src/components/main/css/node_util.rs | 31 | ||||
-rw-r--r-- | src/components/main/layout/block.rs | 12 | ||||
-rw-r--r-- | src/components/main/layout/box.rs | 2 | ||||
-rw-r--r-- | src/components/main/layout/box_builder.rs | 673 | ||||
-rw-r--r-- | src/components/main/layout/construct.rs | 614 | ||||
-rw-r--r-- | src/components/main/layout/extra.rs | 16 | ||||
-rw-r--r-- | src/components/main/layout/float.rs | 48 | ||||
-rw-r--r-- | src/components/main/layout/float_context.rs | 23 | ||||
-rw-r--r-- | src/components/main/layout/flow.rs | 4 | ||||
-rw-r--r-- | src/components/main/layout/inline.rs | 19 | ||||
-rw-r--r-- | src/components/main/layout/layout_task.rs | 200 | ||||
-rw-r--r-- | src/components/main/layout/util.rs | 60 | ||||
-rwxr-xr-x | src/components/main/servo.rc | 2 | ||||
-rw-r--r-- | src/components/script/dom/bindings/utils.rs | 34 | ||||
-rw-r--r-- | src/components/script/dom/node.rs | 128 | ||||
-rw-r--r-- | src/components/script/layout_interface.rs | 26 | ||||
-rw-r--r-- | src/components/script/script_task.rs | 119 | ||||
-rw-r--r-- | src/components/util/slot.rs | 16 |
20 files changed, 1181 insertions, 888 deletions
diff --git a/src/components/main/css/matching.rs b/src/components/main/css/matching.rs index c7f27b1a9ad..299147b01a2 100644 --- a/src/components/main/css/matching.rs +++ b/src/components/main/css/matching.rs @@ -6,13 +6,12 @@ use std::cell::Cell; use std::comm; -use std::rt::default_sched_threads; +use std::rt; use std::task; use std::vec; use extra::arc::RWArc; use css::node_style::StyledNode; -use css::node_util::NodeUtil; use layout::incremental; use layout::util::LayoutDataAccess; @@ -38,10 +37,16 @@ impl MatchMethods for AbstractNode<LayoutView> { }; stylist.get_applicable_declarations(self, style_attribute, None) }; - self.layout_data().applicable_declarations.set(applicable_declarations) + + match *self.mutate_layout_data().ptr { + Some(ref mut layout_data) => { + layout_data.applicable_declarations = applicable_declarations + } + None => fail!("no layout data") + } } fn match_subtree(&self, stylist: RWArc<Stylist>) { - let num_tasks = default_sched_threads() * 2; + let num_tasks = rt::default_sched_threads() * 2; let mut node_count = 0; let mut nodes_per_task = vec::from_elem(num_tasks, ~[]); @@ -83,18 +88,26 @@ impl MatchMethods for AbstractNode<LayoutView> { None => None }; - let layout_data = self.layout_data(); - let computed_values = cascade(*layout_data.applicable_declarations.borrow().ptr, - parent_style); - let style = layout_data.style.mutate(); - match *style.ptr { - None => (), - Some(ref previous_style) => { - self.set_restyle_damage(incremental::compute_damage(previous_style, - &computed_values)) + let computed_values = unsafe { + cascade(self.borrow_layout_data_unchecked().as_ref().unwrap().applicable_declarations, + parent_style) + }; + + match *self.mutate_layout_data().ptr { + None => fail!("no layout data"), + Some(ref mut layout_data) => { + let style = &mut layout_data.style; + match *style { + None => (), + Some(ref previous_style) => { + layout_data.restyle_damage = + Some(incremental::compute_damage(previous_style, + &computed_values).to_int()) + } + } + *style = Some(computed_values) } } - *style.ptr = Some(computed_values) } fn cascade_subtree(&self, parent: Option<AbstractNode<LayoutView>>) { diff --git a/src/components/main/css/node_style.rs b/src/components/main/css/node_style.rs index e4c40e1e3b4..33fdb8d43b2 100644 --- a/src/components/main/css/node_style.rs +++ b/src/components/main/css/node_style.rs @@ -17,6 +17,7 @@ pub trait StyledNode { } impl StyledNode for AbstractNode<LayoutView> { + #[inline] fn style(&self) -> &ComputedValues { // FIXME(pcwalton): Is this assertion needed for memory safety? It's slow. //assert!(self.is_element()); // Only elements can have styles diff --git a/src/components/main/css/node_util.rs b/src/components/main/css/node_util.rs index 104174b7b8c..5ca43c69243 100644 --- a/src/components/main/css/node_util.rs +++ b/src/components/main/css/node_util.rs @@ -27,22 +27,29 @@ impl<'self> NodeUtil<'self> for AbstractNode<LayoutView> { * FIXME: This isn't completely memory safe since the style is * stored in a box that can be overwritten */ + #[inline] fn get_css_select_results(self) -> &'self ComputedValues { - let layout_data = self.layout_data(); - match *layout_data.style.borrow().ptr { - None => fail!(~"style() called on node without a style!"), - Some(ref style) => unsafe { cast::transmute_region(style) } + unsafe { + cast::transmute_region(self.borrow_layout_data_unchecked() + .as_ref() + .unwrap() + .style + .as_ref() + .unwrap()) } } /// Does this node have a computed style yet? fn have_css_select_results(self) -> bool { - self.layout_data().style.borrow().ptr.is_some() + self.borrow_layout_data().ptr.as_ref().unwrap().style.is_some() } /// Update the computed style of an HTML element with a style specified by CSS. fn set_css_select_results(self, decl: ComputedValues) { - *self.layout_data().style.mutate().ptr = Some(decl) + match *self.mutate_layout_data().ptr { + Some(ref mut data) => data.style = Some(decl), + _ => fail!("no layout data for this node"), + } } /// Get the description of how to account for recent style changes. @@ -56,17 +63,21 @@ impl<'self> NodeUtil<'self> for AbstractNode<LayoutView> { RestyleDamage::none() }; - self.layout_data() - .restyle_damage - .borrow() + self.borrow_layout_data() .ptr + .as_ref() + .unwrap() + .restyle_damage .map(|x| RestyleDamage::from_int(x)) .unwrap_or(default) } /// Set the restyle damage field. fn set_restyle_damage(self, damage: RestyleDamage) { - *self.layout_data().restyle_damage.mutate().ptr = Some(damage.to_int()) + match *self.mutate_layout_data().ptr { + Some(ref mut data) => data.restyle_damage = Some(damage.to_int()), + _ => fail!("no layout data for this node"), + } } } diff --git a/src/components/main/layout/block.rs b/src/components/main/layout/block.rs index 24565896ff7..bae5add6f09 100644 --- a/src/components/main/layout/block.rs +++ b/src/components/main/layout/block.rs @@ -40,6 +40,14 @@ impl BlockFlow { } } + pub fn from_box(base: FlowData, box: @RenderBox) -> BlockFlow { + BlockFlow { + base: base, + box: Some(box), + is_root: false + } + } + pub fn new_root(base: FlowData) -> BlockFlow { BlockFlow { base: base, @@ -499,6 +507,10 @@ impl FlowContext for BlockFlow { *first_in_flow = false; } + fn mark_as_root(&mut self) { + self.is_root = true + } + fn debug_str(&self) -> ~str { if self.is_root { ~"BlockFlow(root)" diff --git a/src/components/main/layout/box.rs b/src/components/main/layout/box.rs index e2ca7b5d164..7f4514e3c4e 100644 --- a/src/components/main/layout/box.rs +++ b/src/components/main/layout/box.rs @@ -225,6 +225,7 @@ pub struct ImageRenderBox { } impl ImageRenderBox { + #[inline] pub fn new(base: RenderBoxBase, image_url: Url, local_image_cache: @mut LocalImageCache) -> ImageRenderBox { assert!(base.node.is_image_element()); @@ -523,6 +524,7 @@ pub struct UnscannedTextRenderBox { impl UnscannedTextRenderBox { /// Creates a new instance of `UnscannedTextRenderBox`. + #[inline(always)] pub fn new(base: RenderBoxBase) -> UnscannedTextRenderBox { assert!(base.node.is_text()); diff --git a/src/components/main/layout/box_builder.rs b/src/components/main/layout/box_builder.rs deleted file mode 100644 index ee492c85304..00000000000 --- a/src/components/main/layout/box_builder.rs +++ /dev/null @@ -1,673 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * 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/. */ - -//! Creates CSS boxes from a DOM tree. - -use layout::block::BlockFlow; -use layout::float::FloatFlow; -use layout::box::{GenericRenderBox, GenericRenderBoxClass, ImageRenderBox, ImageRenderBoxClass}; -use layout::box::{RenderBox, RenderBoxBase, RenderBoxClass, RenderBoxUtils, TextRenderBoxClass}; -use layout::box::{UnscannedTextRenderBox, UnscannedTextRenderBoxClass}; -use layout::context::LayoutContext; -use layout::float_context::FloatType; -use layout::flow::{AbsoluteFlow, BlockFlowClass, FloatFlowClass, FlowContext, FlowData}; -use layout::flow::{ImmutableFlowUtils, InlineBlockFlow, InlineBlockFlowClass, InlineFlowClass}; -use layout::flow::{MutableFlowUtils, TableFlow}; -use layout::flow; -use layout::inline::{InlineFlow}; -use layout::text::TextRunScanner; -use css::node_style::StyledNode; - -use style::computed_values::display; -use style::computed_values::float; -use layout::float_context::{FloatLeft, FloatRight}; -use script::dom::node::{AbstractNode, CommentNodeTypeId, DoctypeNodeTypeId}; -use script::dom::node::{ElementNodeTypeId, LayoutView, TextNodeTypeId, DocumentNodeTypeId}; -use script::dom::node::DocumentFragmentNodeTypeId; -use servo_util::range::Range; -use servo_util::tree::{TreeNodeRef, TreeNode}; -use std::cast; -use std::cell::Cell; - -enum FlowType { - AbsoluteFlowType, - BlockFlowType, - FloatFlowType(FloatType), - InlineBlockFlowType, - InlineFlowType, - RootFlowType, - TableFlowType, -} - -pub struct LayoutTreeBuilder { - next_cid: int, - next_bid: int, -} - -impl LayoutTreeBuilder { - pub fn new() -> LayoutTreeBuilder { - LayoutTreeBuilder { - next_cid: -1, - next_bid: -1, - } - } -} - -// helper object for building the initial box list and making the -// mapping between DOM nodes and boxes. -struct BoxGenerator<'self> { - flow: &'self mut FlowContext, - range_stack: @mut ~[uint], -} - -enum InlineSpacerSide { - LogicalBefore, - LogicalAfter, -} - -impl<'self> BoxGenerator<'self> { - /* Debug ids only */ - - fn new(flow: &'self mut FlowContext) -> BoxGenerator<'self> { - debug!("Creating box generator for flow: {:s}", flow.debug_str()); - BoxGenerator { - flow: flow, - range_stack: @mut ~[] - } - } - - fn with_clone<R>(&mut self, cb: &fn(BoxGenerator<'self>) -> R) -> R { - // FIXME(pcwalton): This is a hack; it can be done safely with linearity. - unsafe { - let gen = BoxGenerator { - flow: cast::transmute_copy(&self.flow), - range_stack: self.range_stack - }; - cb(gen) - } - } - - /* Whether "spacer" boxes are needed to stand in for this DOM node */ - fn inline_spacers_needed_for_node(_: AbstractNode<LayoutView>) -> bool { - return false; - } - - // TODO: implement this, generating spacer - fn make_inline_spacer_for_node_side(_: &LayoutContext, - _: AbstractNode<LayoutView>, - _: InlineSpacerSide) - -> Option<@RenderBox> { - None - } - - pub fn push_node(&mut self, - ctx: &LayoutContext, - node: AbstractNode<LayoutView>, - builder: &mut LayoutTreeBuilder) { - debug!("BoxGenerator[f{:d}]: pushing node: {:s}", flow::base(self.flow).id, node.debug_str()); - - // TODO: remove this once UA styles work - let box_type = self.decide_box_type(node); - - debug!("BoxGenerator[f{:d}]: point a", flow::base(self.flow).id); - - let range_stack = &mut self.range_stack; - // depending on flow, make a box for this node. - match self.flow.class() { - InlineFlowClass => { - let inline = self.flow.as_inline(); - let node_range_start = inline.boxes.len(); - range_stack.push(node_range_start); - - // if a leaf, make a box. - if node.is_leaf() { - let new_box = BoxGenerator::make_box(ctx, box_type, node, builder); - inline.boxes.push(new_box); - } else if BoxGenerator::inline_spacers_needed_for_node(node) { - // else, maybe make a spacer for "left" margin, border, padding - let inline_spacer = BoxGenerator::make_inline_spacer_for_node_side(ctx, node, LogicalBefore); - for spacer in inline_spacer.iter() { - inline.boxes.push(*spacer); - } - } - // TODO: cases for inline-block, etc. - }, - BlockFlowClass => { - let block = self.flow.as_block(); - debug!("BoxGenerator[f{:d}]: point b", block.base.id); - let new_box = BoxGenerator::make_box(ctx, box_type, node, builder); - - debug!("BoxGenerator[f{:d}]: attaching box[b{:d}] to block flow (node: {:s})", - block.base.id, - new_box.base().id(), - node.debug_str()); - - assert!(block.box.is_none()); - block.box = Some(new_box); - } - FloatFlowClass => { - let float = self.flow.as_float(); - debug!("BoxGenerator[f{:d}]: point b", float.base.id); - - let new_box = BoxGenerator::make_box(ctx, box_type, node, builder); - - debug!("BoxGenerator[f{:d}]: attaching box[b{:d}] to float flow (node: {:s})", - float.base.id, - new_box.base().id(), - node.debug_str()); - - assert!(float.box.is_none() && float.index.is_none()); - float.box = Some(new_box); - } - _ => warn!("push_node() not implemented for flow f{:d}", flow::base(self.flow).id), - } - } - - pub fn pop_node(&mut self, ctx: &LayoutContext, node: AbstractNode<LayoutView>) { - debug!("BoxGenerator[f{:d}]: popping node: {:s}", flow::base(self.flow).id, node.debug_str()); - - match self.flow.class() { - InlineFlowClass => { - let inline = self.flow.as_inline(); - let inline = &mut *inline; - - if BoxGenerator::inline_spacers_needed_for_node(node) { - // If this non-leaf box generates extra horizontal spacing, add a SpacerBox for - // it. - let result = BoxGenerator::make_inline_spacer_for_node_side(ctx, node, LogicalAfter); - for spacer in result.iter() { - let boxes = &mut inline.boxes; - boxes.push(*spacer); - } - } - let mut node_range: Range = Range::new(self.range_stack.pop(), 0); - node_range.extend_to(inline.boxes.len()); - - if node_range.length() == 0 { - warn!("node range length is zero?!") - } - - debug!("BoxGenerator: adding element range={}", node_range); - inline.elems.add_mapping(node, &node_range); - }, - BlockFlowClass => assert!(self.range_stack.len() == 0), - FloatFlowClass => assert!(self.range_stack.len() == 0), - _ => warn!("pop_node() not implemented for flow {:?}", flow::base(self.flow).id), - } - } - - /// Disambiguate between different methods here instead of inlining, since each case has very - /// different complexity. - fn make_box(layout_ctx: &LayoutContext, - ty: RenderBoxClass, - node: AbstractNode<LayoutView>, - builder: &mut LayoutTreeBuilder) - -> @RenderBox { - let base = RenderBoxBase::new(node, builder.next_box_id()); - let result = match ty { - GenericRenderBoxClass => @GenericRenderBox::new(base) as @RenderBox, - TextRenderBoxClass | UnscannedTextRenderBoxClass => { - @UnscannedTextRenderBox::new(base) as @RenderBox - } - ImageRenderBoxClass => BoxGenerator::make_image_box(layout_ctx, node, base), - }; - debug!("BoxGenerator: created box: {:s}", result.debug_str()); - result - } - - fn make_image_box(layout_ctx: &LayoutContext, - node: AbstractNode<LayoutView>, - base: RenderBoxBase) - -> @RenderBox { - assert!(node.is_image_element()); - - do node.with_imm_image_element |image_element| { - if image_element.image.is_some() { - // FIXME(pcwalton): Don't copy URLs. - let url = (*image_element.image.get_ref()).clone(); - @ImageRenderBox::new(base.clone(), url, layout_ctx.image_cache) as @RenderBox - } else { - info!("Tried to make image box, but couldn't find image. Made generic box \ - instead."); - @GenericRenderBox::new(base.clone()) as @RenderBox - } - } - } - - fn decide_box_type(&self, node: AbstractNode<LayoutView>) -> RenderBoxClass { - if node.is_text() { - TextRenderBoxClass - } else if node.is_image_element() { - do node.with_imm_image_element |image_element| { - match image_element.image { - Some(_) => ImageRenderBoxClass, - None => GenericRenderBoxClass, - } - } - } else if node.is_element() { - GenericRenderBoxClass - } else { - fail!("Hey, doctypes and comments shouldn't get here! They are display:none!") - } - } - -} - -enum BoxGenResult<'self> { - NoGenerator, - ParentGenerator, - SiblingGenerator, - NewGenerator(BoxGenerator<'self>), - /// Start a new generator, but also switch the parent out for the - /// grandparent, ending the parent generator. - ReparentingGenerator(BoxGenerator<'self>), - Mixed(BoxGenerator<'self>, ~BoxGenResult<'self>), -} - -/// Determines whether the result of child box construction needs to reparent -/// or not. Reparenting is needed when a block flow is a child of an inline; -/// in that case, we need to let the level up the stack no to end the parent -/// genertor and continue with the grandparent. -enum BoxConstructResult<'self> { - Normal(Option<BoxGenerator<'self>>), - Reparent(BoxGenerator<'self>), -} - -impl LayoutTreeBuilder { - /* Debug-only ids */ - pub fn next_flow_id(&mut self) -> int { self.next_cid += 1; self.next_cid } - pub fn next_box_id(&mut self) -> int { self.next_bid += 1; self.next_bid } - - /// Creates necessary box(es) and flow context(s) for the current DOM node, - /// and recurses on its children. - pub fn construct_recursively<'a>( - &mut self, - layout_ctx: &LayoutContext, - cur_node: AbstractNode<LayoutView>, - mut grandparent_generator: Option<BoxGenerator<'a>>, - mut parent_generator: BoxGenerator<'a>, - mut prev_sibling_generator: Option<BoxGenerator<'a>>) - -> BoxConstructResult<'a> { - debug!("Considering node: {:s}", cur_node.debug_str()); - let box_gen_result = { - let grandparent_gen_ref = match grandparent_generator { - Some(ref mut generator) => Some(generator), - None => None, - }; - let sibling_gen_ref = match prev_sibling_generator { - Some(ref mut generator) => Some(generator), - None => None, - }; - self.box_generator_for_node(cur_node, grandparent_gen_ref, &mut parent_generator, sibling_gen_ref) - }; - - let mut reparent = false; - - debug!("result from generator_for_node: {:?}", &box_gen_result); - // Skip over nodes that don't belong in the flow tree - let (this_generator, next_generator) = match box_gen_result { - NoGenerator => return Normal(prev_sibling_generator), - ParentGenerator => { - do parent_generator.with_clone |clone| { - (clone, None) - } - } - SiblingGenerator => (prev_sibling_generator.take_unwrap(), None), - NewGenerator(gen) => (gen, None), - ReparentingGenerator(gen) => { - reparent = true; - (gen, None) - } - Mixed(gen, next_gen) => (gen, Some(match *next_gen { - ParentGenerator => { - do parent_generator.with_clone |clone| { - clone - } - } - SiblingGenerator => prev_sibling_generator.take_unwrap(), - _ => fail!("Unexpect BoxGenResult") - })) - }; - - let mut this_generator = this_generator; - - debug!("point a: {:s}", cur_node.debug_str()); - this_generator.push_node(layout_ctx, cur_node, self); - debug!("point b: {:s}", cur_node.debug_str()); - - // recurse on child nodes. - let prev_gen_cell = Cell::new(Normal(None)); - for child_node in cur_node.children() { - do parent_generator.with_clone |grandparent_clone| { - let grandparent_clone_cell = Cell::new(Some(grandparent_clone)); - do this_generator.with_clone |parent_clone| { - match prev_gen_cell.take() { - Normal(prev_gen) => { - let prev_generator = self.construct_recursively(layout_ctx, - child_node, - grandparent_clone_cell.take(), - parent_clone, - prev_gen); - prev_gen_cell.put_back(prev_generator); - } - Reparent(prev_gen) => { - let prev_generator = self.construct_recursively(layout_ctx, - child_node, - None, - grandparent_clone_cell.take().unwrap(), - Some(prev_gen)); - prev_gen_cell.put_back(prev_generator); - } - } - } - } - } - - this_generator.pop_node(layout_ctx, cur_node); - self.simplify_children_of_flow(layout_ctx, this_generator.flow); - - match next_generator { - Some(n_gen) => Normal(Some(n_gen)), - None => { - if reparent { - Reparent(this_generator) - } else { - Normal(Some(this_generator)) - } - } - } - } - - pub fn box_generator_for_node<'a>(&mut self, - node: AbstractNode<LayoutView>, - grandparent_generator: Option<&mut BoxGenerator<'a>>, - parent_generator: &mut BoxGenerator<'a>, - mut sibling_generator: Option<&mut BoxGenerator<'a>>) - -> BoxGenResult<'a> { - let display = match node.type_id() { - ElementNodeTypeId(_) => match node.style().Box.display { - display::none => return NoGenerator, - display::table | display::inline_table | display::table_row_group - | display::table_header_group | display::table_footer_group - | display::table_row | display::table_column_group - | display::table_column | display::table_cell | display::table_caption - | display::list_item - => display::block, - display => display, - }, - TextNodeTypeId => display::inline, - DocumentNodeTypeId(_) | - DoctypeNodeTypeId | - DocumentFragmentNodeTypeId | - CommentNodeTypeId => return NoGenerator, - }; - - // FIXME(pcwalton): Unsafe. - let sibling_flow: Option<&mut FlowContext> = sibling_generator.as_mut().map(|gen| { - unsafe { - cast::transmute_copy(&gen.flow) - } - }); - - let is_float = if (node.is_element()) { - match node.style().Box.float { - float::none => None, - float::left => Some(FloatLeft), - float::right => Some(FloatRight) - } - } else { - None - }; - - let sibling_flow_class = match sibling_flow { - None => None, - Some(flow) => Some(flow.class()), - }; - - let new_generator = match (display, parent_generator.flow.class(), sibling_flow_class) { - // Floats - (display::block, BlockFlowClass, _) | - (display::block, FloatFlowClass, _) if is_float.is_some() => { - self.create_child_generator(node, - parent_generator, - FloatFlowType(is_float.unwrap())) - } - // If we're placing a float after an inline, append the float to the inline flow, - // then continue building from the inline flow in case there are more inlines - // afterward. - (display::block, _, Some(InlineFlowClass)) if is_float.is_some() => { - let float_type = FloatFlowType(is_float.unwrap()); - let float_generator = self.create_child_generator(node, - sibling_generator.unwrap(), - float_type); - return Mixed(float_generator, ~SiblingGenerator); - } - // This is a catch-all case for when: - // a) sibling_flow is None - // b) sibling_flow is a BlockFlow - (display::block, InlineFlowClass, _) if is_float.is_some() => { - self.create_child_generator(node, - parent_generator, - FloatFlowType(is_float.unwrap())) - } - - (display::block, BlockFlowClass, _) => { - match (parent_generator.flow.as_block().is_root, node.parent_node()) { - // If this is the root node, then use the root flow's - // context. Otherwise, make a child block context. - (true, Some(parent)) if !parent.is_document() => { - self.create_child_generator(node, parent_generator, BlockFlowType) - } - (true, None) | (true, Some(_)) => return ParentGenerator, - (false, _) => { - self.create_child_generator(node, parent_generator, BlockFlowType) - } - } - } - - (display::block, FloatFlowClass, _) => { - self.create_child_generator(node, parent_generator, BlockFlowType) - } - - // Inlines that are children of inlines are part of the same flow - (display::inline, InlineFlowClass, _) => return ParentGenerator, - (display::inline_block, InlineFlowClass, _) => return ParentGenerator, - - // Inlines that are children of blocks create new flows if their - // previous sibling was a block. - (display::inline, BlockFlowClass, Some(BlockFlowClass)) | - (display::inline_block, BlockFlowClass, Some(BlockFlowClass)) => { - self.create_child_generator(node, parent_generator, InlineFlowType) - } - - // The first two cases should only be hit when a FloatFlow - // is the first child of a BlockFlow. Other times, we will - (display::inline, _, Some(FloatFlowClass)) | - (display::inline_block, _, Some(FloatFlowClass)) | - (display::inline, FloatFlowClass, _) | - (display::inline_block, FloatFlowClass, _) => { - self.create_child_generator(node, parent_generator, InlineFlowType) - } - - // Inlines whose previous sibling was not a block try to use their - // sibling's flow context. - (display::inline, BlockFlowClass, _) | - (display::inline_block, BlockFlowClass, _) => { - return match sibling_generator { - None => NewGenerator(self.create_child_generator(node, - parent_generator, - InlineFlowType)), - Some(*) => SiblingGenerator - } - } - - // blocks that are children of inlines need to split their parent - // flows. - (display::block, InlineFlowClass, _) => { - match grandparent_generator { - None => fail!("expected to have a grandparent block flow"), - Some(grandparent_gen) => { - assert!(grandparent_gen.flow.is_block_like()); - - let block_gen = self.create_child_generator(node, - grandparent_gen, - BlockFlowType); - return ReparentingGenerator(block_gen); - } - } - } - - _ => return ParentGenerator - }; - - NewGenerator(new_generator) - } - - pub fn create_child_generator<'a>( - &mut self, - node: AbstractNode<LayoutView>, - parent_generator: &mut BoxGenerator<'a>, - ty: FlowType) - -> BoxGenerator<'a> { - let new_flow = self.make_flow(ty, node); - parent_generator.flow.add_new_child(new_flow); - let flow_ref = flow::last_child(parent_generator.flow).unwrap(); - BoxGenerator::new(*flow_ref) - } - - /// Fix up any irregularities such as: - /// - /// * split inlines (CSS 2.1 Section 9.2.1.1) - /// * elide non-preformatted whitespace-only text boxes and their flows (CSS 2.1 Section - /// 9.2.2.1). - /// - /// The latter can only be done immediately adjacent to, or at the beginning or end of a block - /// flow. Otherwise, the whitespace might affect whitespace collapsing with adjacent text. - pub fn simplify_children_of_flow(&self, ctx: &LayoutContext, parent_flow: &mut FlowContext) { - match parent_flow.class() { - InlineFlowClass => { - let mut found_child_inline = false; - let mut found_child_block = false; - - for child_ctx in flow::child_iter(parent_flow) { - match child_ctx.class() { - InlineFlowClass | InlineBlockFlowClass => found_child_inline = true, - BlockFlowClass => found_child_block = true, - _ => {} - } - } - - if found_child_block && found_child_inline { - self.fixup_split_inline(parent_flow) - } - } - BlockFlowClass | FloatFlowClass => { - // check first/last child for whitespace-ness - let mut do_remove = false; - let p_id = flow::base(parent_flow).id; - do parent_flow.with_first_child |mut first_child| { - for first_flow in first_child.mut_iter() { - if first_flow.starts_inline_flow() { - // FIXME: workaround for rust#6393 - { - let first_inline_flow = first_flow.as_inline(); - let boxes = &first_inline_flow.boxes; - if boxes.len() == 1 { - let first_box = boxes[0]; // FIXME(pcwalton): Rust bug - if first_box.is_whitespace_only() { - debug!("LayoutTreeBuilder: pruning whitespace-only first \ - child flow f{:d} from parent f{:d}", - first_inline_flow.base.id, - p_id); - do_remove = true; - } - } - } - } - } - } - if (do_remove) { - parent_flow.remove_first(); - } - - - do_remove = false; - let p_id = flow::base(parent_flow).id; - do parent_flow.with_last_child |mut last_child| { - for last_flow in last_child.mut_iter() { - if last_flow.starts_inline_flow() { - // FIXME: workaround for rust#6393 - { - let last_inline_flow = last_flow.as_inline(); - let boxes = &last_inline_flow.boxes; - if boxes.len() == 1 && boxes.last().is_whitespace_only() { - let last_box = boxes.last(); // FIXME(pcwalton): Rust bug - if last_box.is_whitespace_only() { - debug!("LayoutTreeBuilder: pruning whitespace-only last \ - child flow f{:d} from parent f{:d}", - last_inline_flow.base.id, - p_id); - do_remove = true; - } - } - } - } - } - } - if (do_remove) { - parent_flow.remove_last(); - } - - // Issue 543: We only need to do this if there are inline child - // flows, but there's not a quick way to check at the moment. - for child_flow in flow::child_iter(parent_flow) { - match child_flow.class() { - InlineFlowClass | InlineBlockFlowClass => { - let mut scanner = TextRunScanner::new(); - scanner.scan_for_runs(ctx, *child_flow); - } - _ => {} - } - } - } - _ => {} - } - } - - pub fn fixup_split_inline(&self, _: &mut FlowContext) { - // TODO: finish me. - fail!(~"TODO: handle case where an inline is split by a block") - } - - /// Entry point for box creation. Should only be called on the root DOM element. - pub fn construct_trees(&mut self, layout_ctx: &LayoutContext, root: AbstractNode<LayoutView>) - -> Result<~FlowContext:, ()> { - debug!("Constructing flow tree for DOM: "); - debug!("{:?}", root.dump()); - - let mut new_flow = self.make_flow(RootFlowType, root); - { - let new_generator = BoxGenerator::new(new_flow); - self.construct_recursively(layout_ctx, root, None, new_generator, None); - } - return Ok(new_flow) - } - - /// Creates a flow of the given type for the supplied node. - pub fn make_flow(&mut self, flow_type: FlowType, node: AbstractNode<LayoutView>) - -> ~FlowContext: { - let info = FlowData::new(self.next_flow_id(), node); - let result = match flow_type { - AbsoluteFlowType => ~AbsoluteFlow::new(info) as ~FlowContext:, - BlockFlowType => ~BlockFlow::new(info) as ~FlowContext:, - FloatFlowType(f_type) => ~FloatFlow::new(info, f_type) as ~FlowContext:, - InlineBlockFlowType => ~InlineBlockFlow::new(info) as ~FlowContext:, - InlineFlowType => ~InlineFlow::new(info) as ~FlowContext:, - RootFlowType => ~BlockFlow::new_root(info) as ~FlowContext:, - TableFlowType => ~TableFlow::new(info) as ~FlowContext:, - }; - debug!("LayoutTreeBuilder: created flow: {:s}", result.debug_str()); - result - } -} diff --git a/src/components/main/layout/construct.rs b/src/components/main/layout/construct.rs new file mode 100644 index 00000000000..a190783f8d5 --- /dev/null +++ b/src/components/main/layout/construct.rs @@ -0,0 +1,614 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +//! Creates flows and boxes from a DOM tree via a bottom-up, incremental traversal of the DOM. +//! +//! Each step of the traversal considers the node and existing flow, if there is one. If a node is +//! not dirty and an existing flow exists, then the traversal reuses that flow. Otherwise, it +//! proceeds to construct either a flow or a `ConstructionItem`. A construction item is a piece of +//! intermediate data that goes with a DOM node and hasn't found its "home" yet—maybe it's a render +//! box, maybe it's an absolute or fixed position thing that hasn't found its containing block yet. +//! Construction items bubble up the tree from children to parents until they find their homes. +//! +//! TODO(pcwalton): There is no incremental reflow yet. This scheme requires that nodes either have +//! weak references to flows or that there be some mechanism to efficiently (O(1) time) "blow +//! apart" a flow tree and have the flows migrate "home" to their respective DOM nodes while we +//! perform flow tree construction. The precise mechanism for this will take some experimentation +//! to get right. +//! +//! TODO(pcwalton): This scheme should be amenable to parallelization, but, of course, that's not +//! yet implemented. + +use css::node_style::StyledNode; +use layout::block::BlockFlow; +use layout::box::{GenericRenderBox, ImageRenderBox, RenderBox, RenderBoxBase}; +use layout::box::{UnscannedTextRenderBox}; +use layout::context::LayoutContext; +use layout::float::FloatFlow; +use layout::float_context::FloatType; +use layout::flow::{FlowContext, FlowData, MutableFlowUtils}; +use layout::inline::InlineFlow; +use layout::text::TextRunScanner; +use layout::util::LayoutDataAccess; + +use script::dom::element::HTMLImageElementTypeId; +use script::dom::node::{AbstractNode, CommentNodeTypeId, DoctypeNodeTypeId}; +use script::dom::node::{DocumentFragmentNodeTypeId, DocumentNodeTypeId, ElementNodeTypeId}; +use script::dom::node::{LayoutView, PostorderNodeTraversal, TextNodeTypeId}; +use servo_util::slot::Slot; +use servo_util::tree::TreeNodeRef; +use std::util; +use style::computed_values::{display, float}; + +/// The results of flow construction for a DOM node. +pub enum ConstructionResult { + /// This node contributes nothing at all (`display: none`). Alternately, this is what newly + /// created nodes have their `ConstructionResult` set to. + NoConstructionResult, + + /// This node contributed a flow at the proper position in the tree. Nothing more needs to be + /// done for this node. + FlowConstructionResult(~FlowContext:), + + /// This node contributed some object or objects that will be needed to construct a proper flow + /// later up the tree, but these objects have not yet found their home. + ConstructionItemConstructionResult(ConstructionItem), +} + +/// Represents the output of flow construction for a DOM node that has not yet resulted in a +/// complete flow. Construction items bubble up the tree until they find a `FlowContext` to be +/// attached to. +enum ConstructionItem { + /// Inline boxes and associated {ib} splits that have not yet found flows. + InlineBoxesConstructionItem(InlineBoxesConstructionResult), +} + +/// Represents inline boxes and {ib} splits that are bubbling up from an inline. +struct InlineBoxesConstructionResult { + /// Any {ib} splits that we're bubbling up. + /// + /// TODO(pcwalton): Small vector optimization. + splits: Option<~[InlineBlockSplit]>, + + /// Any render boxes that succeed the {ib} splits. + boxes: ~[@RenderBox], +} + +/// Represents an {ib} split that has not yet found the containing block that it belongs to. This +/// is somewhat tricky. An example may be helpful. For this DOM fragment: +/// +/// <span> +/// A +/// <div>B</div> +/// C +/// </span> +/// +/// The resulting `ConstructionItem` for the outer `span` will be: +/// +/// InlineBoxesConstructionItem(Some(~[ +/// InlineBlockSplit { +/// predecessor_boxes: ~[ +/// A +/// ], +/// block: ~BlockFlow { +/// B +/// }, +/// }),~[ +/// C +/// ]) +struct InlineBlockSplit { + /// The inline render boxes that precede the flow. + /// + /// TODO(pcwalton): Small vector optimization. + predecessor_boxes: ~[@RenderBox], + + /// The flow that caused this {ib} split. + flow: ~FlowContext:, +} + +/// Methods on optional vectors. +/// +/// TODO(pcwalton): I think this will no longer be necessary once Rust #8981 lands. +trait OptVector<T> { + /// Turns this optional vector into an owned one. If the optional vector is `None`, then this + /// simply returns an empty owned vector. + fn to_vec(self) -> ~[T]; + + /// Pushes a value onto this vector. + fn push(&mut self, value: T); + + /// Pushes a vector onto this vector, consuming the original. + fn push_all_move(&mut self, values: ~[T]); + + /// Pushes an optional vector onto this vector, consuming the original. + fn push_opt_vec_move(&mut self, values: Self); + + /// Returns the length of this optional vector. + fn len(&self) -> uint; +} + +impl<T> OptVector<T> for Option<~[T]> { + #[inline] + fn to_vec(self) -> ~[T] { + match self { + None => ~[], + Some(vector) => vector, + } + } + + #[inline] + fn push(&mut self, value: T) { + match *self { + None => *self = Some(~[value]), + Some(ref mut vector) => vector.push(value), + } + } + + #[inline] + fn push_all_move(&mut self, values: ~[T]) { + match *self { + None => *self = Some(values), + Some(ref mut vector) => vector.push_all_move(values), + } + } + + #[inline] + fn push_opt_vec_move(&mut self, values: Option<~[T]>) { + match values { + None => {} + Some(values) => self.push_all_move(values), + } + } + + #[inline] + fn len(&self) -> uint { + match *self { + None => 0, + Some(ref vector) => vector.len(), + } + } +} + +/// An object that knows how to create flows. +pub struct FlowConstructor<'self> { + /// The layout context. + /// + /// FIXME(pcwalton): Why does this contain `@`??? That destroys parallelism!!! + layout_context: &'self LayoutContext, + + /// The next flow ID to assign. + /// + /// FIXME(pcwalton): This is going to have to be atomic; can't we do something better? + next_flow_id: Slot<int>, + + /// The next box ID to assign. + /// + /// FIXME(pcwalton): This is going to have to be atomic; can't we do something better? + next_box_id: Slot<int>, +} + +impl<'self> FlowConstructor<'self> { + /// Creates a new flow constructor. + pub fn init<'a>(layout_context: &'a LayoutContext) -> FlowConstructor<'a> { + FlowConstructor { + layout_context: layout_context, + next_flow_id: Slot::init(0), + next_box_id: Slot::init(0), + } + } + + /// Returns the next flow ID and bumps the internal counter. + fn next_flow_id(&self) -> int { + let id = self.next_flow_id.get(); + self.next_flow_id.set(id + 1); + id + } + + /// Returns the next render box ID and bumps the internal counter. + fn next_box_id(&self) -> int { + let id = self.next_box_id.get(); + self.next_box_id.set(id + 1); + id + } + + /// Builds a `RenderBox` for the given image. This is out of line to guide inlining. + fn build_box_for_image(&self, base: RenderBoxBase, node: AbstractNode<LayoutView>) + -> @RenderBox { + // FIXME(pcwalton): Don't copy URLs. + let url = node.with_imm_image_element(|image_element| { + image_element.image.as_ref().map(|url| (*url).clone()) + }); + + match url { + None => @GenericRenderBox::new(base) as @RenderBox, + Some(url) => { + // FIXME(pcwalton): The fact that image render boxes store the cache in the + // box makes little sense to me. + @ImageRenderBox::new(base, url, self.layout_context.image_cache) as @RenderBox + } + } + } + + /// Builds a `RenderBox` for the given node. + fn build_box_for_node(&self, node: AbstractNode<LayoutView>) -> @RenderBox { + let base = RenderBoxBase::new(node, self.next_box_id()); + match node.type_id() { + ElementNodeTypeId(HTMLImageElementTypeId) => self.build_box_for_image(base, node), + TextNodeTypeId => @UnscannedTextRenderBox::new(base) as @RenderBox, + _ => @GenericRenderBox::new(base) as @RenderBox, + } + } + + /// Creates an inline flow from a set of inline boxes and adds it as a child of the given flow. + /// + /// `#[inline(always)]` because this is performance critical and LLVM will not inline it + /// otherwise. + #[inline(always)] + fn flush_inline_boxes_to_flow(&self, + boxes: ~[@RenderBox], + flow: &mut ~FlowContext:, + node: AbstractNode<LayoutView>) { + if boxes.len() > 0 { + let inline_base = FlowData::new(self.next_flow_id(), node); + let mut inline_flow = ~InlineFlow::from_boxes(inline_base, boxes) as ~FlowContext:; + TextRunScanner::new().scan_for_runs(self.layout_context, inline_flow); + flow.add_new_child(inline_flow) + } + } + + /// Creates an inline flow from a set of inline boxes, if present, and adds it as a child of + /// the given flow. + fn flush_inline_boxes_to_flow_if_necessary(&self, + opt_boxes: &mut Option<~[@RenderBox]>, + flow: &mut ~FlowContext:, + node: AbstractNode<LayoutView>) { + let opt_boxes = util::replace(opt_boxes, None); + if opt_boxes.len() > 0 { + self.flush_inline_boxes_to_flow(opt_boxes.to_vec(), flow, node) + } + } + + /// Builds the children flows underneath a node with `display: block`. After this call, + /// other `BlockFlow`s or `InlineFlow`s will be populated underneath this node, depending on + /// whether {ib} splits needed to happen. + fn build_children_of_block_flow(&self, + flow: &mut ~FlowContext:, + node: AbstractNode<LayoutView>) { + // Gather up boxes for the inline flows we might need to create. + let mut opt_boxes_for_inline_flow = None; + let mut first_box = true; + for kid in node.children() { + match kid.swap_out_construction_result() { + NoConstructionResult => {} + FlowConstructionResult(kid_flow) => { + // Strip ignorable whitespace from the start of this flow per CSS 2.1 § + // 9.2.1.1. + if first_box { + strip_ignorable_whitespace_from_start(&mut opt_boxes_for_inline_flow); + first_box = false + } + + // Flush any inline boxes that we were gathering up. This allows us to handle + // {ib} splits. + self.flush_inline_boxes_to_flow_if_necessary(&mut opt_boxes_for_inline_flow, + flow, + node); + flow.add_new_child(kid_flow); + } + ConstructionItemConstructionResult(InlineBoxesConstructionItem( + InlineBoxesConstructionResult { + splits: opt_splits, + boxes: boxes + })) => { + // Add any {ib} splits. + match opt_splits { + None => {} + Some(splits) => { + for split in splits.move_iter() { + // Pull apart the {ib} split object and push its predecessor boxes + // onto the list. + let InlineBlockSplit { + predecessor_boxes: predecessor_boxes, + flow: kid_flow + } = split; + opt_boxes_for_inline_flow.push_all_move(predecessor_boxes); + + // If this is the first box in flow, then strip ignorable + // whitespace per CSS 2.1 § 9.2.1.1. + if first_box { + strip_ignorable_whitespace_from_start( + &mut opt_boxes_for_inline_flow); + first_box = false + } + + // Flush any inline boxes that we were gathering up. + self.flush_inline_boxes_to_flow_if_necessary( + &mut opt_boxes_for_inline_flow, + flow, + node); + + // Push the flow generated by the {ib} split onto our list of + // flows. + flow.add_new_child(kid_flow); + } + } + } + + // Add the boxes to the list we're maintaining. + opt_boxes_for_inline_flow.push_all_move(boxes) + } + } + } + + // Perform a final flush of any inline boxes that we were gathering up to handle {ib} + // splits, after stripping ignorable whitespace. + strip_ignorable_whitespace_from_end(&mut opt_boxes_for_inline_flow); + self.flush_inline_boxes_to_flow_if_necessary(&mut opt_boxes_for_inline_flow, + flow, + node); + } + + /// Builds a flow for a node with `display: block`. This yields a `BlockFlow` with possibly + /// other `BlockFlow`s or `InlineFlow`s underneath it, depending on whether {ib} splits needed + /// to happen. + fn build_flow_for_block(&self, node: AbstractNode<LayoutView>) -> ~FlowContext: { + let base = FlowData::new(self.next_flow_id(), node); + let box = self.build_box_for_node(node); + let mut flow = ~BlockFlow::from_box(base, box) as ~FlowContext:; + self.build_children_of_block_flow(&mut flow, node); + flow + } + + /// Builds the flow for a node with `float: {left|right}`. This yields a `FloatFlow` with a + /// `BlockFlow` underneath it. + fn build_flow_for_floated_block(&self, node: AbstractNode<LayoutView>, float_type: FloatType) + -> ~FlowContext: { + let base = FlowData::new(self.next_flow_id(), node); + let box = self.build_box_for_node(node); + let mut flow = ~FloatFlow::from_box(base, float_type, box) as ~FlowContext:; + self.build_children_of_block_flow(&mut flow, node); + flow + } + + /// Concatenates the boxes of kids, adding in our own borders/padding/margins if necessary. + /// Returns the `InlineBoxesConstructionResult`, if any. There will be no + /// `InlineBoxesConstructionResult` if this node consisted entirely of ignorable whitespace. + fn build_boxes_for_nonreplaced_inline_content(&self, node: AbstractNode<LayoutView>) + -> ConstructionResult { + let mut opt_inline_block_splits = None; + let mut opt_box_accumulator = None; + + // Concatenate all the render boxes of our kids, creating {ib} splits as necessary. + for kid in node.children() { + match kid.swap_out_construction_result() { + NoConstructionResult => {} + FlowConstructionResult(flow) => { + // {ib} split. Flush the accumulator to our new split and make a new + // accumulator to hold any subsequent `RenderBox`es we come across. + let split = InlineBlockSplit { + predecessor_boxes: util::replace(&mut opt_box_accumulator, None).to_vec(), + flow: flow, + }; + opt_inline_block_splits.push(split) + } + ConstructionItemConstructionResult(InlineBoxesConstructionItem( + InlineBoxesConstructionResult { + splits: opt_splits, + boxes: boxes + })) => { + // Bubble up {ib} splits. + match opt_splits { + None => {} + Some(splits) => { + for split in splits.move_iter() { + let InlineBlockSplit { + predecessor_boxes: boxes, + flow: kid_flow + } = split; + opt_box_accumulator.push_all_move(boxes); + + let split = InlineBlockSplit { + predecessor_boxes: util::replace(&mut opt_box_accumulator, + None).to_vec(), + flow: kid_flow, + }; + opt_inline_block_splits.push(split) + } + } + } + + // Push residual boxes. + opt_box_accumulator.push_all_move(boxes) + } + } + } + + // TODO(pcwalton): Add in our own borders/padding/margins if necessary. + + // Finally, make a new construction result. + if opt_inline_block_splits.len() > 0 || opt_box_accumulator.len() > 0 { + let construction_item = InlineBoxesConstructionItem(InlineBoxesConstructionResult { + splits: opt_inline_block_splits, + boxes: opt_box_accumulator.to_vec(), + }); + ConstructionItemConstructionResult(construction_item) + } else { + NoConstructionResult + } + } + + /// Creates an `InlineBoxesConstructionResult` for replaced content. Replaced content doesn't + /// render its children, so this just nukes a child's boxes and creates a `RenderBox`. + fn build_boxes_for_replaced_inline_content(&self, node: AbstractNode<LayoutView>) + -> ConstructionResult { + for kid in node.children() { + kid.set_flow_construction_result(NoConstructionResult) + } + + let construction_item = InlineBoxesConstructionItem(InlineBoxesConstructionResult { + splits: None, + boxes: ~[ + self.build_box_for_node(node) + ], + }); + ConstructionItemConstructionResult(construction_item) + } + + /// Builds one or more render boxes for a node with `display: inline`. This yields an + /// `InlineBoxesConstructionResult`. + fn build_boxes_for_inline(&self, node: AbstractNode<LayoutView>) -> ConstructionResult { + // Is this node replaced content? + if !node.is_replaced_content() { + // Go to a path that concatenates our kids' boxes. + self.build_boxes_for_nonreplaced_inline_content(node) + } else { + // Otherwise, just nuke our kids' boxes, create our `RenderBox` if any, and be done + // with it. + self.build_boxes_for_replaced_inline_content(node) + } + } +} + +impl<'self> PostorderNodeTraversal for FlowConstructor<'self> { + // `#[inline(always)]` because this is always called from the traversal function and for some + // reason LLVM's inlining heuristics go awry here. + #[inline(always)] + fn process(&self, node: AbstractNode<LayoutView>) -> bool { + // Get the `display` property for this node, and determine whether this node is floated. + let (display, float) = match node.type_id() { + ElementNodeTypeId(_) => (node.style().Box.display, node.style().Box.float), + TextNodeTypeId => (display::inline, float::none), + CommentNodeTypeId | + DoctypeNodeTypeId | + DocumentFragmentNodeTypeId | + DocumentNodeTypeId(_) => (display::none, float::none), + }; + + // Switch on display and floatedness. + match (display, float) { + // `display: none` contributes no flow construction result. Nuke the flow construction + // results of children. + (display::none, _) => { + for child in node.children() { + child.set_flow_construction_result(NoConstructionResult) + } + } + + // Inline items contribute inline render box construction results. + (display::inline, float::none) => { + let construction_result = self.build_boxes_for_inline(node); + node.set_flow_construction_result(construction_result) + } + + // Block flows that are not floated contribute block flow construction results. + // + // TODO(pcwalton): Make this only trigger for blocks and handle the other `display` + // properties separately. + (_, float::none) => { + let flow = self.build_flow_for_block(node); + node.set_flow_construction_result(FlowConstructionResult(flow)) + } + + // Floated flows contribute float flow construction results. + (_, float_value) => { + let float_type = FloatType::from_property(float_value); + let flow = self.build_flow_for_floated_block(node, float_type); + node.set_flow_construction_result(FlowConstructionResult(flow)) + } + } + + true + } +} + +/// A utility trait with some useful methods for node queries. +trait NodeUtils { + /// Returns true if this node doesn't render its kids and false otherwise. + fn is_replaced_content(self) -> bool; + + /// Sets the construction result of a flow. + fn set_flow_construction_result(self, result: ConstructionResult); + + /// Replaces the flow construction result in a node with `NoConstructionResult` and returns the + /// old value. + fn swap_out_construction_result(self) -> ConstructionResult; + + /// Returns true if this node consists entirely of ignorable whitespace and false otherwise. + /// Ignorable whitespace is defined as whitespace that would be removed per CSS 2.1 § 16.6.1. + fn is_ignorable_whitespace(self) -> bool; +} + +impl NodeUtils for AbstractNode<LayoutView> { + fn is_replaced_content(self) -> bool { + match self.type_id() { + TextNodeTypeId | + CommentNodeTypeId | + DoctypeNodeTypeId | + DocumentFragmentNodeTypeId | + DocumentNodeTypeId(_) | + ElementNodeTypeId(HTMLImageElementTypeId) => true, + ElementNodeTypeId(_) => false, + } + } + + #[inline(always)] + fn set_flow_construction_result(self, result: ConstructionResult) { + match *self.mutate_layout_data().ptr { + Some(ref mut layout_data) => layout_data.flow_construction_result = result, + None => fail!("no layout data"), + } + } + + #[inline(always)] + fn swap_out_construction_result(self) -> ConstructionResult { + match *self.mutate_layout_data().ptr { + Some(ref mut layout_data) => { + util::replace(&mut layout_data.flow_construction_result, NoConstructionResult) + } + None => fail!("no layout data"), + } + } + + fn is_ignorable_whitespace(self) -> bool { + self.is_text() && self.with_imm_text(|text| text.element.data.is_whitespace()) + } +} + +/// Strips ignorable whitespace from the start of a list of boxes. +fn strip_ignorable_whitespace_from_start(opt_boxes: &mut Option<~[@RenderBox]>) { + match util::replace(opt_boxes, None) { + None => return, + Some(boxes) => { + // FIXME(pcwalton): This is slow because vector shift is broken. :( + let mut found_nonwhitespace = false; + let mut result = ~[]; + for box in boxes.move_iter() { + if !found_nonwhitespace && box.is_whitespace_only() { + continue + } + + found_nonwhitespace = true; + result.push(box) + } + + *opt_boxes = Some(result) + } + } +} + +/// Strips ignorable whitespace from the end of a list of boxes. +fn strip_ignorable_whitespace_from_end(opt_boxes: &mut Option<~[@RenderBox]>) { + match *opt_boxes { + None => {} + Some(ref mut boxes) => { + while boxes.len() > 0 && boxes.last().is_whitespace_only() { + let _ = boxes.pop(); + } + } + } + if opt_boxes.len() == 0 { + *opt_boxes = None + } +} + diff --git a/src/components/main/layout/extra.rs b/src/components/main/layout/extra.rs index 0bb1cbb4cd2..2683d8d0b34 100644 --- a/src/components/main/layout/extra.rs +++ b/src/components/main/layout/extra.rs @@ -8,7 +8,6 @@ use layout::util::{DisplayBoxes, LayoutData, LayoutDataAccess}; use script::dom::node::{AbstractNode, LayoutView}; use servo_util::tree::TreeNodeRef; -use std::cast; /// Functionality useful for querying the layout-specific data on DOM nodes. pub trait LayoutAuxMethods { @@ -18,20 +17,17 @@ pub trait LayoutAuxMethods { impl LayoutAuxMethods for AbstractNode<LayoutView> { /// Resets layout data and styles for the node. - /// - /// FIXME(pcwalton): Do this as part of box building instead of in a traversal. fn initialize_layout_data(self) { - unsafe { - let node = cast::transmute_mut(self.node()); - if node.layout_data.is_none() { - node.layout_data = Some(~LayoutData::new() as ~Any) - } else { - self.layout_data().boxes.set(DisplayBoxes::init()); - } + let layout_data_handle = self.mutate_layout_data(); + match *layout_data_handle.ptr { + None => *layout_data_handle.ptr = Some(~LayoutData::new()), + Some(ref mut layout_data) => layout_data.boxes = DisplayBoxes::init(), } } /// Resets layout data and styles for a Node tree. + /// + /// FIXME(pcwalton): Do this as part of box building instead of in a traversal. fn initialize_style_for_subtree(self) { for n in self.traverse_preorder() { n.initialize_layout_data(); diff --git a/src/components/main/layout/float.rs b/src/components/main/layout/float.rs index 3559ca4e842..be7ac50524b 100644 --- a/src/components/main/layout/float.rs +++ b/src/components/main/layout/float.rs @@ -53,6 +53,18 @@ impl FloatFlow { } } + pub fn from_box(base: FlowData, float_type: FloatType, box: @RenderBox) -> FloatFlow { + FloatFlow { + base: base, + containing_width: Au(0), + box: Some(box), + index: None, + float_type: float_type, + rel_pos: Point2D(Au(0), Au(0)), + floated_children: 0, + } + } + pub fn teardown(&mut self) { for box in self.box.iter() { box.teardown(); @@ -252,7 +264,7 @@ impl FlowContext for FloatFlow { } fn assign_height(&mut self, ctx: &mut LayoutContext) { - debug!("assign_height_float: assigning height for float {}", self.base.id); + // Now that we've determined our height, propagate that out. let has_inorder_children = self.base.num_floats > 0; if has_inorder_children { let mut float_ctx = FloatContext::new(self.floated_children); @@ -262,7 +274,7 @@ impl FlowContext for FloatFlow { float_ctx = flow::mut_base(*kid).floats_out.clone(); } } - + debug!("assign_height_float: assigning height for float {}", self.base.id); let mut cur_y = Au(0); let mut top_offset = Au(0); @@ -281,26 +293,25 @@ impl FlowContext for FloatFlow { let mut height = cur_y - top_offset; let mut noncontent_height; - for box in self.box.iter() { - let base = box.base(); - let mut position_ref = base.position.mutate(); - let position = &mut position_ref.ptr; + let box = self.box.as_ref().unwrap(); + let base = box.base(); + let mut position_ref = base.position.mutate(); + let position = &mut position_ref.ptr; - // The associated box is the border box of this flow. - position.origin.y = base.margin.top; + // The associated box is the border box of this flow. + position.origin.y = base.margin.top; - noncontent_height = base.padding.top + base.padding.bottom + base.border.top + - base.border.bottom; - - //TODO(eatkinson): compute heights properly using the 'height' property. - let height_prop = MaybeAuto::from_style(base.style().Box.height, - Au::new(0)).specified_or_zero(); + noncontent_height = base.padding.top + base.padding.bottom + base.border.top + + base.border.bottom; + + //TODO(eatkinson): compute heights properly using the 'height' property. + let height_prop = MaybeAuto::from_style(base.style().Box.height, + Au::new(0)).specified_or_zero(); - height = geometry::max(height, height_prop) + noncontent_height; - debug!("assign_height_float -- height: {}", height); + height = geometry::max(height, height_prop) + noncontent_height; + debug!("assign_height_float -- height: {}", height); - position.size.height = height; - } + position.size.height = height; } @@ -314,6 +325,7 @@ impl FlowContext for FloatFlow { // Margins between a floated box and any other box do not collapse. *collapsing = Au::new(0); } + fn debug_str(&self) -> ~str { ~"FloatFlow" } diff --git a/src/components/main/layout/float_context.rs b/src/components/main/layout/float_context.rs index 45e142b24b9..7a92554401c 100644 --- a/src/components/main/layout/float_context.rs +++ b/src/components/main/layout/float_context.rs @@ -3,12 +3,13 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use geom::point::Point2D; -use geom::size::Size2D; use geom::rect::Rect; +use geom::size::Size2D; use servo_util::geometry::{Au, max, min}; +use std::i32::max_value; use std::util::replace; use std::vec; -use std::i32::max_value; +use style::computed_values::float; #[deriving(Clone)] pub enum FloatType { @@ -16,6 +17,16 @@ pub enum FloatType { FloatRight } +impl FloatType { + pub fn from_property(property: float::T) -> FloatType { + match property { + float::none => fail!("can't create a float type from an unfloated property"), + float::left => FloatLeft, + float::right => FloatRight, + } + } +} + pub enum ClearType { ClearLeft, ClearRight, @@ -31,13 +42,13 @@ struct FloatContextBase { } #[deriving(Clone)] -struct FloatData{ +struct FloatData { bounds: Rect<Au>, f_type: FloatType } /// All information necessary to place a float -pub struct PlacementInfo{ +pub struct PlacementInfo { width: Au, // The dimensions of the float height: Au, ceiling: Au, // The minimum top of the float, as determined by earlier elements @@ -126,7 +137,7 @@ impl FloatContext { } } -impl FloatContextBase{ +impl FloatContextBase { fn new(num_floats: uint) -> FloatContextBase { debug!("Creating float context of size {}", num_floats); FloatContextBase { @@ -335,7 +346,7 @@ impl FloatContextBase{ match maybe_location { // If there are no floats blocking us, return the current location - // TODO(eatknson): integrate with overflow + // TODO(eatkinson): integrate with overflow None => return match info.f_type { FloatLeft => Rect(Point2D(Au(0), float_y), Size2D(info.max_width, Au(max_value))), diff --git a/src/components/main/layout/flow.rs b/src/components/main/layout/flow.rs index afa0f80b258..27d61f9dd84 100644 --- a/src/components/main/layout/flow.rs +++ b/src/components/main/layout/flow.rs @@ -111,6 +111,9 @@ pub trait FlowContext { fail!("collapse_margins not yet implemented") } + /// Marks this flow as the root flow. The default implementation is a no-op. + fn mark_as_root(&mut self) {} + /// Returns a debugging string describing this flow. fn debug_str(&self) -> ~str { ~"???" @@ -360,6 +363,7 @@ impl Iterator<@RenderBox> for BoxIterator { } impl FlowData { + #[inline] pub fn new(id: int, node: AbstractNode<LayoutView>) -> FlowData { FlowData { node: node, diff --git a/src/components/main/layout/inline.rs b/src/components/main/layout/inline.rs index 39fda3b7961..2a0c412fb32 100644 --- a/src/components/main/layout/inline.rs +++ b/src/components/main/layout/inline.rs @@ -196,14 +196,14 @@ impl LineboxScanner { debug!("LineboxScanner: Trying to place first box of line {}", self.lines.len()); let first_box_size = first_box.base().position.get().size; - debug!("LineboxScanner: box size: {}", first_box_size); - let splitable = first_box.can_split(); + let splittable = first_box.can_split(); + debug!("LineboxScanner: box size: {}, splittable: {}", first_box_size, splittable); let line_is_empty: bool = self.pending_line.range.length() == 0; - // Initally, pretend a splitable box has 0 width. + // Initally, pretend a splittable box has 0 width. // We will move it later if it has nonzero width // and that causes problems. - let placement_width = if splitable { + let placement_width = if splittable { Au::new(0) } else { first_box_size.width @@ -231,7 +231,7 @@ impl LineboxScanner { // If not, but we can't split the box, then we'll place // the line here and it will overflow. - if !splitable { + if !splittable { debug!("LineboxScanner: case=line doesn't fit, but is unsplittable"); return (line_bounds, first_box_size.width); } @@ -467,6 +467,15 @@ impl InlineFlow { } } + pub fn from_boxes(base: FlowData, boxes: ~[@RenderBox]) -> InlineFlow { + InlineFlow { + base: base, + boxes: boxes, + lines: ~[], + elems: ElementMapping::new(), + } + } + pub fn teardown(&mut self) { for box in self.boxes.iter() { box.teardown(); diff --git a/src/components/main/layout/layout_task.rs b/src/components/main/layout/layout_task.rs index f2bbfe55b04..4542b01323d 100644 --- a/src/components/main/layout/layout_task.rs +++ b/src/components/main/layout/layout_task.rs @@ -7,66 +7,87 @@ use css::matching::MatchMethods; use css::select::new_stylist; -use layout::extra::LayoutAuxMethods; -use layout::box_builder::LayoutTreeBuilder; +use layout::construct::{FlowConstructionResult, FlowConstructor, NoConstructionResult}; use layout::context::LayoutContext; use layout::display_list_builder::{DisplayListBuilder}; +use layout::extra::LayoutAuxMethods; use layout::flow::{FlowContext, ImmutableFlowUtils, MutableFlowUtils, PreorderFlowTraversal}; use layout::flow::{PostorderFlowTraversal}; use layout::flow; use layout::incremental::{RestyleDamage, BubbleWidths}; -use layout::util::LayoutDataAccess; +use layout::util::{LayoutData, LayoutDataAccess}; -use std::cast::transmute; -use std::cell::Cell; -use std::comm::Port; -use std::task; use extra::arc::{Arc, RWArc}; use geom::point::Point2D; use geom::rect::Rect; use geom::size::Size2D; use gfx::display_list::DisplayList; use gfx::font_context::FontContext; -use servo_util::geometry::Au; use gfx::opts::Opts; use gfx::render_task::{RenderMsg, RenderChan, RenderLayer}; use gfx::render_task; -use style::Stylist; -use style::Stylesheet; -use style::AuthorOrigin; use script::dom::event::ReflowEvent; -use script::dom::node::{AbstractNode, LayoutView}; +use script::dom::node::{AbstractNode, LayoutDataRef, LayoutView}; use script::layout_interface::{AddStylesheetMsg, ContentBoxQuery}; +use script::layout_interface::{ContentBoxesQuery, ContentBoxesResponse, ExitNowMsg, LayoutQuery}; use script::layout_interface::{HitTestQuery, ContentBoxResponse, HitTestResponse}; -use script::layout_interface::{ContentBoxesQuery, ContentBoxesResponse, ExitMsg, LayoutQuery}; -use script::layout_interface::{MatchSelectorsDocumentDamage, Msg}; -use script::layout_interface::{QueryMsg, Reflow, ReflowDocumentDamage}; +use script::layout_interface::{MatchSelectorsDocumentDamage, Msg, PrepareToExitMsg}; +use script::layout_interface::{QueryMsg, ReapLayoutDataMsg, Reflow, ReflowDocumentDamage}; use script::layout_interface::{ReflowForDisplay, ReflowMsg}; use script::script_task::{ReflowCompleteMsg, ScriptChan, SendEventMsg}; use servo_msg::constellation_msg::{ConstellationChan, PipelineId}; use servo_net::image_cache_task::{ImageCacheTask, ImageResponseMsg}; use servo_net::local_image_cache::{ImageResponder, LocalImageCache}; -use servo_util::tree::TreeNodeRef; +use servo_util::geometry::Au; +use servo_util::range::Range; use servo_util::time::{ProfilerChan, profile}; use servo_util::time; -use servo_util::range::Range; -use extra::url::Url; +use servo_util::tree::TreeNodeRef; +use std::cast::transmute; +use std::cast; +use std::cell::Cell; +use std::comm::Port; +use std::task; +use std::util; +use style::AuthorOrigin; +use style::Stylesheet; +use style::Stylist; +/// Information needed by the layout task. struct LayoutTask { + /// The ID of the pipeline that we belong to. id: PipelineId, + + /// The port on which we receive messages. port: Port<Msg>, + + /// The channel on which messages can be sent to the constellation. constellation_chan: ConstellationChan, + + /// The channel on which messages can be sent to the script task. script_chan: ScriptChan, + + /// The channel on which messages can be sent to the painting task. render_chan: RenderChan<AbstractNode<()>>, + + /// The channel on which messages can be sent to the image cache. image_cache_task: ImageCacheTask, + + /// The local image cache. local_image_cache: @mut LocalImageCache, + + /// The local font context. font_ctx: @mut FontContext, - doc_url: Option<Url>, + + /// The size of the viewport. screen_size: Option<Size2D<Au>>, + /// A cached display list. display_list: Option<Arc<DisplayList<AbstractNode<()>>>>, stylist: RWArc<Stylist>, + + /// The channel on which messages can be sent to the profiler. profiler_chan: ProfilerChan, } @@ -195,6 +216,7 @@ impl ImageResponder for LayoutImageResponder { } impl LayoutTask { + /// Spawns a new layout task. pub fn create(id: PipelineId, port: Port<Msg>, constellation_chan: ConstellationChan, @@ -217,6 +239,7 @@ impl LayoutTask { }); } + /// Creates a new `LayoutTask` structure. fn new(id: PipelineId, port: Port<Msg>, constellation_chan: ConstellationChan, @@ -237,7 +260,6 @@ impl LayoutTask { image_cache_task: image_cache_task.clone(), local_image_cache: @mut LocalImageCache(image_cache_task), font_ctx: fctx, - doc_url: None, screen_size: None, display_list: None, @@ -247,6 +269,7 @@ impl LayoutTask { } } + /// Starts listening on the port. fn start(&mut self) { while self.handle_request() { // Loop indefinitely. @@ -266,6 +289,7 @@ impl LayoutTask { } } + /// Receives and dispatches messages from the port. fn handle_request(&mut self) -> bool { match self.port.recv() { AddStylesheetMsg(sheet) => self.handle_add_stylesheet(sheet), @@ -282,11 +306,17 @@ impl LayoutTask { self.handle_query(query.take()); } } - ExitMsg => { - debug!("layout: ExitMsg received"); - let (response_port, response_chan) = stream(); - self.render_chan.send(render_task::ExitMsg(response_chan)); - response_port.recv(); + ReapLayoutDataMsg(dead_layout_data) => { + unsafe { + self.handle_reap_layout_data(dead_layout_data) + } + } + PrepareToExitMsg(response_chan) => { + self.prepare_to_exit(response_chan) + } + ExitNowMsg => { + debug!("layout: ExitNowMsg received"); + self.exit_now(); return false } } @@ -294,6 +324,32 @@ impl LayoutTask { true } + /// Enters a quiescent state in which no new messages except for `ReapLayoutDataMsg` will be + /// processed until an `ExitNowMsg` is received. A pong is immediately sent on the given + /// response channel. + fn prepare_to_exit(&mut self, response_chan: Chan<()>) { + response_chan.send(()); + match self.port.recv() { + ReapLayoutDataMsg(dead_layout_data) => { + unsafe { + self.handle_reap_layout_data(dead_layout_data) + } + } + ExitNowMsg => self.exit_now(), + _ => { + fail!("layout: message that wasn't `ExitNowMsg` received after `PrepareToExitMsg`") + } + } + } + + /// Shuts down the layout task now. If there are any DOM nodes left, layout will now (safely) + /// crash. + fn exit_now(&mut self) { + let (response_port, response_chan) = stream(); + self.render_chan.send(render_task::ExitMsg(response_chan)); + response_port.recv() + } + fn handle_add_stylesheet(&mut self, sheet: Stylesheet) { let sheet = Cell::new(sheet); do self.stylist.write |stylist| { @@ -301,6 +357,31 @@ impl LayoutTask { } } + /// Builds the flow tree. + /// + /// This corresponds to the various `nsCSSFrameConstructor` methods in Gecko or + /// `createRendererIfNeeded` in WebKit. Note, however that in WebKit `createRendererIfNeeded` + /// is intertwined with selector matching, making it difficult to compare directly. It is + /// marked `#[inline(never)]` to aid benchmarking in sampling profilers. + #[inline(never)] + fn construct_flow_tree(&self, layout_context: &LayoutContext, node: AbstractNode<LayoutView>) + -> ~FlowContext: { + node.traverse_postorder(&FlowConstructor::init(layout_context)); + + let result = match *node.mutate_layout_data().ptr { + Some(ref mut layout_data) => { + util::replace(&mut layout_data.flow_construction_result, NoConstructionResult) + } + None => fail!("no layout data for root node"), + }; + let mut flow = match result { + FlowConstructionResult(flow) => flow, + _ => fail!("Flow construction didn't result in a flow at the root of the tree!"), + }; + flow.mark_as_root(); + flow + } + /// Performs layout constraint solving. /// /// This corresponds to `Reflow()` in Gecko and `layout()` in WebKit/Blink and should be @@ -331,17 +412,14 @@ impl LayoutTask { transmute(&data.document_root) }; - // FIXME: Bad copy! - let doc_url = data.url.clone(); - - debug!("layout: received layout request for: {:s}", doc_url.to_str()); + debug!("layout: received layout request for: {:s}", data.url.to_str()); debug!("layout: damage is {:?}", data.damage); debug!("layout: parsed Node tree"); debug!("{:?}", node.dump()); + // Reset the image cache. self.local_image_cache.next_round(self.make_on_image_available_cb()); - self.doc_url = Some(doc_url); let screen_size = Size2D(Au::from_px(data.window_size.width as int), Au::from_px(data.window_size.height as int)); let resized = self.screen_size != Some(screen_size); @@ -370,16 +448,9 @@ impl LayoutTask { } // Construct the flow tree. - let mut layout_root: ~FlowContext: = do profile(time::LayoutTreeBuilderCategory, - self.profiler_chan.clone()) { - let mut builder = LayoutTreeBuilder::new(); - let layout_root: ~FlowContext: = match builder.construct_trees(&layout_ctx, *node) { - Ok(root) => root, - Err(*) => fail!(~"Root flow should always exist") - }; - - layout_root - }; + let mut layout_root = profile(time::LayoutTreeBuilderCategory, + self.profiler_chan.clone(), + || self.construct_flow_tree(&layout_ctx, *node)); // Propagate damage. layout_root.traverse_preorder(&mut PropagateDamageTraversal { @@ -421,22 +492,26 @@ impl LayoutTask { }; // FIXME(pcwalton): Why are we cloning the display list here?! - let layout_data = node.layout_data(); - let boxes = layout_data.boxes.mutate(); - boxes.ptr.display_list = Some(display_list.clone()); - - if boxes.ptr.range.is_none() { - debug!("Creating initial range for node"); - boxes.ptr.range = Some(Range::new(i,1)); - } else { - debug!("Appending item to range"); - unsafe { - let old_node: AbstractNode<()> = transmute(node); - assert!(old_node == display_list.get().list[i-1].base().extra, - "Non-contiguous arrangement of display items"); - } + match *node.mutate_layout_data().ptr { + Some(ref mut layout_data) => { + let boxes = &mut layout_data.boxes; + boxes.display_list = Some(display_list.clone()); + + if boxes.range.is_none() { + debug!("Creating initial range for node"); + boxes.range = Some(Range::new(i,1)); + } else { + debug!("Appending item to range"); + unsafe { + let old_node: AbstractNode<()> = transmute(node); + assert!(old_node == display_list.get().list[i-1].base().extra, + "Non-contiguous arrangement of display items"); + } - boxes.ptr.range.unwrap().extend_by(1); + boxes.range.unwrap().extend_by(1); + } + } + None => fail!("no layout data"), } } @@ -472,8 +547,8 @@ impl LayoutTask { fn box_for_node(node: AbstractNode<LayoutView>) -> Option<Rect<Au>> { // FIXME(pcwalton): Why are we cloning the display list here?! - let boxes = node.layout_data().boxes.borrow(); - let boxes = boxes.ptr; + let layout_data = node.borrow_layout_data(); + let boxes = &layout_data.ptr.as_ref().unwrap().boxes; match (boxes.display_list.clone(), boxes.range) { (Some(display_list), Some(range)) => { let mut rect: Option<Rect<Au>> = None; @@ -516,8 +591,8 @@ impl LayoutTask { fn boxes_for_node(node: AbstractNode<LayoutView>, mut box_accumulator: ~[Rect<Au>]) -> ~[Rect<Au>] { - let boxes = node.layout_data().boxes.borrow(); - let boxes = boxes.ptr; + let layout_data = node.borrow_layout_data(); + let boxes = &layout_data.ptr.as_ref().unwrap().boxes; match (boxes.display_list.clone(), boxes.range) { (Some(display_list), Some(range)) => { for i in range.eachi() { @@ -589,5 +664,12 @@ impl LayoutTask { script_chan: self.script_chan.clone(), } as @ImageResponder } + + /// Handles a message to destroy layout data. Layout data must be destroyed on *this* task + /// because it contains local managed pointers. + unsafe fn handle_reap_layout_data(&self, layout_data: LayoutDataRef) { + let ptr: &mut Option<~LayoutData> = cast::transmute(layout_data.borrow_unchecked()); + *ptr = None + } } diff --git a/src/components/main/layout/util.rs b/src/components/main/layout/util.rs index eba5270489b..e6409bb6dbc 100644 --- a/src/components/main/layout/util.rs +++ b/src/components/main/layout/util.rs @@ -3,14 +3,15 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use layout::box::{RenderBox, RenderBoxUtils}; +use layout::construct::{ConstructionResult, NoConstructionResult}; use extra::arc::Arc; use gfx::display_list::DisplayList; use script::dom::node::{AbstractNode, LayoutView}; use servo_util::range::Range; -use servo_util::slot::Slot; +use servo_util::slot::{MutSlotRef, SlotRef}; use servo_util::tree::TreeNodeRef; -use std::any::AnyRefExt; +use std::cast; use std::iter::Enumerate; use std::vec::VecIterator; use style::{ComputedValues, PropertyDeclaration}; @@ -140,47 +141,66 @@ impl ElementMapping { /// Data that layout associates with a node. pub struct LayoutData { /// The results of CSS matching for this node. - applicable_declarations: Slot<~[Arc<~[PropertyDeclaration]>]>, + applicable_declarations: ~[Arc<~[PropertyDeclaration]>], /// The results of CSS styling for this node. - style: Slot<Option<ComputedValues>>, + style: Option<ComputedValues>, /// Description of how to account for recent style changes. - restyle_damage: Slot<Option<int>>, + restyle_damage: Option<int>, /// The boxes assosiated with this flow. /// Used for getBoundingClientRect and friends. - boxes: Slot<DisplayBoxes>, + boxes: DisplayBoxes, + + /// The current results of flow construction for this node. This is either a flow or a + /// `ConstructionItem`. See comments in `construct.rs` for more details. + flow_construction_result: ConstructionResult, } impl LayoutData { /// Creates new layout data. pub fn new() -> LayoutData { LayoutData { - applicable_declarations: Slot::init(~[]), - style: Slot::init(None), - restyle_damage: Slot::init(None), - boxes: Slot::init(DisplayBoxes::init()), + applicable_declarations: ~[], + style: None, + restyle_damage: None, + boxes: DisplayBoxes::init(), + flow_construction_result: NoConstructionResult, } } } -// This serves as a static assertion that layout data remains sendable. If this is not done, then -// we can have memory unsafety, which usually manifests as shutdown crashes. -fn assert_is_sendable<T:Send>(_: T) {} -fn assert_layout_data_is_sendable() { - assert_is_sendable(LayoutData::new()) -} - /// A trait that allows access to the layout data of a DOM node. pub trait LayoutDataAccess { - fn layout_data<'a>(&'a self) -> &'a LayoutData; + /// Borrows the layout data without checks. + /// + /// FIXME(pcwalton): Make safe. + unsafe fn borrow_layout_data_unchecked<'a>(&'a self) -> &'a Option<~LayoutData>; + /// Borrows the layout data immutably. Fails on a conflicting borrow. + fn borrow_layout_data<'a>(&'a self) -> SlotRef<'a,Option<~LayoutData>>; + /// Borrows the layout data mutably. Fails on a conflicting borrow. + fn mutate_layout_data<'a>(&'a self) -> MutSlotRef<'a,Option<~LayoutData>>; } impl LayoutDataAccess for AbstractNode<LayoutView> { #[inline(always)] - fn layout_data<'a>(&'a self) -> &'a LayoutData { - self.node().layout_data.as_ref().unwrap().as_ref().unwrap() + unsafe fn borrow_layout_data_unchecked<'a>(&'a self) -> &'a Option<~LayoutData> { + cast::transmute(self.node().layout_data.borrow_unchecked()) + } + + #[inline(always)] + fn borrow_layout_data<'a>(&'a self) -> SlotRef<'a,Option<~LayoutData>> { + unsafe { + cast::transmute(self.node().layout_data.borrow()) + } + } + + #[inline(always)] + fn mutate_layout_data<'a>(&'a self) -> MutSlotRef<'a,Option<~LayoutData>> { + unsafe { + cast::transmute(self.node().layout_data.mutate()) + } } } diff --git a/src/components/main/servo.rc b/src/components/main/servo.rc index 11e146f6008..dfe78f8c082 100755 --- a/src/components/main/servo.rc +++ b/src/components/main/servo.rc @@ -80,7 +80,7 @@ pub mod pipeline; pub mod layout { pub mod block; pub mod box; - pub mod box_builder; + pub mod construct; pub mod context; pub mod display_list_builder; pub mod float_context; diff --git a/src/components/script/dom/bindings/utils.rs b/src/components/script/dom/bindings/utils.rs index f79e8ae633a..b076810785a 100644 --- a/src/components/script/dom/bindings/utils.rs +++ b/src/components/script/dom/bindings/utils.rs @@ -863,26 +863,38 @@ pub fn CreateDOMGlobal(cx: *JSContext, class: *JSClass) -> *JSObject { } } +/// Returns the global object of the realm that the given JS object was created in. #[fixed_stack_segment] -fn cx_for_dom_reflector(obj: *JSObject) -> *JSContext { +fn global_object_for_js_object(obj: *JSObject) -> *Box<window::Window> { unsafe { let global = GetGlobalForObjectCrossCompartment(obj); let clasp = JS_GetClass(global); assert!(((*clasp).flags & (JSCLASS_IS_DOMJSCLASS | JSCLASS_IS_GLOBAL)) != 0); - //XXXjdm either don't hardcode or sanity assert prototype stuff - let win = unwrap_object::<*Box<window::Window>>(global, PrototypeList::id::Window, 1); - match win { - Ok(win) => { - match (*win).data.page.js_info { - Some(ref info) => info.js_context.ptr, - None => fail!("no JS context for DOM global") - } - } - Err(_) => fail!("found DOM global that doesn't unwrap to Window") + // FIXME(jdm): Either don't hardcode or sanity assert prototype stuff. + match unwrap_object::<*Box<window::Window>>(global, PrototypeList::id::Window, 1) { + Ok(win) => win, + Err(_) => fail!("found DOM global that doesn't unwrap to Window"), } } } +#[fixed_stack_segment] +fn cx_for_dom_reflector(obj: *JSObject) -> *JSContext { + unsafe { + let win = global_object_for_js_object(obj); + match (*win).data.page.js_info { + Some(ref info) => info.js_context.ptr, + None => fail!("no JS context for DOM global") + } + } +} + +/// Returns the global object of the realm that the given DOM object was created in. +#[fixed_stack_segment] +pub fn global_object_for_dom_object<T: Reflectable>(obj: &mut T) -> *Box<window::Window> { + global_object_for_js_object(obj.reflector().get_jsobject()) +} + pub fn cx_for_dom_object<T: Reflectable>(obj: &mut T) -> *JSContext { cx_for_dom_reflector(obj.reflector().get_jsobject()) } diff --git a/src/components/script/dom/node.rs b/src/components/script/dom/node.rs index 78533b2b48c..9709680300d 100644 --- a/src/components/script/dom/node.rs +++ b/src/components/script/dom/node.rs @@ -7,6 +7,7 @@ use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object}; use dom::bindings::utils::{DOMString, null_str_as_empty}; use dom::bindings::utils::{ErrorResult, Fallible, NotFound, HierarchyRequest}; +use dom::bindings::utils; use dom::characterdata::CharacterData; use dom::document::{AbstractDocument, DocumentTypeId}; use dom::documenttype::DocumentType; @@ -18,11 +19,13 @@ use dom::htmlimageelement::HTMLImageElement; use dom::htmliframeelement::HTMLIFrameElement; use dom::text::Text; -use std::cast; -use std::cast::transmute; -use std::unstable::raw::Box; use js::jsapi::{JSObject, JSContext}; +use servo_util::slot::{MutSlotRef, Slot, SlotRef}; use servo_util::tree::{TreeNode, TreeNodeRef, TreeNodeRefAsElement}; +use std::cast::transmute; +use std::cast; +use std::unstable::raw::Box; +use std::util; // // The basic Node structure @@ -89,9 +92,87 @@ pub struct Node<View> { child_list: Option<@mut NodeList>, /// Layout information. Only the layout task may touch this data. - layout_data: Option<~Any>, + /// + /// FIXME(pcwalton): We need to send these back to the layout task to be destroyed when this + /// node is finalized. + layout_data: LayoutDataRef, +} + +#[unsafe_destructor] +impl<T> Drop for Node<T> { + fn drop(&mut self) { + unsafe { + let this: &mut Node<ScriptView> = cast::transmute(self); + this.reap_layout_data() + } + } +} + +/// Encapsulates the abstract layout data. +pub struct LayoutDataRef { + priv data: Slot<Option<*()>>, +} + +impl LayoutDataRef { + #[inline] + pub fn init() -> LayoutDataRef { + LayoutDataRef { + data: Slot::init(None), + } + } + + /// Creates a new piece of layout data from a value. + #[inline] + pub unsafe fn from_data<T>(data: ~T) -> LayoutDataRef { + LayoutDataRef { + data: Slot::init(Some(cast::transmute(data))), + } + } + + /// Returns true if this layout data is present or false otherwise. + #[inline] + pub fn is_present(&self) -> bool { + self.data.get().is_some() + } + + /// Borrows the layout data immutably, *asserting that there are no mutators*. Bad things will + /// happen if you try to mutate the layout data while this is held. This is the only thread- + /// safe layout data accessor. + /// + /// FIXME(pcwalton): Enforce this invariant via the type system. Will require traversal + /// functions to be trusted, but c'est la vie. + #[inline] + pub unsafe fn borrow_unchecked<'a>(&'a self) -> &'a () { + cast::transmute(self.data.borrow_unchecked()) + } + + /// Borrows the layout data immutably. This function is *not* thread-safe. + #[inline] + pub fn borrow<'a>(&'a self) -> SlotRef<'a,()> { + unsafe { + cast::transmute(self.data.borrow()) + } + } + + /// Borrows the layout data mutably. This function is *not* thread-safe. + /// + /// FIXME(pcwalton): We should really put this behind a `MutLayoutView` phantom type, to + /// prevent CSS selector matching from mutably accessing nodes it's not supposed to and racing + /// on it. This has already resulted in one bug! + #[inline] + pub fn mutate<'a>(&'a self) -> MutSlotRef<'a,()> { + unsafe { + cast::transmute(self.data.mutate()) + } + } } +/// A trait that represents abstract layout data. +/// +/// FIXME(pcwalton): Very very unsafe!!! We need to send these back to the layout task to be +/// destroyed when this node is finalized. +pub trait TLayoutData {} + /// The different types of nodes. #[deriving(Eq)] pub enum NodeTypeId { @@ -319,12 +400,24 @@ impl<'self, View> AbstractNode<View> { self.transmute_mut(f) } + #[inline] pub fn is_comment(self) -> bool { - self.type_id() == CommentNodeTypeId + // FIXME(pcwalton): Temporary workaround for the lack of inlining of autogenerated `Eq` + // implementations in Rust. + match self.type_id() { + CommentNodeTypeId => true, + _ => false, + } } + #[inline] pub fn is_text(self) -> bool { - self.type_id() == TextNodeTypeId + // FIXME(pcwalton): Temporary workaround for the lack of inlining of autogenerated `Eq` + // implementations in Rust. + match self.type_id() { + TextNodeTypeId => true, + _ => false, + } } pub fn with_imm_text<R>(self, f: &fn(&Text) -> R) -> R { @@ -364,8 +457,12 @@ impl<'self, View> AbstractNode<View> { self.transmute_mut(f) } + #[inline] pub fn is_image_element(self) -> bool { - self.type_id() == ElementNodeTypeId(HTMLImageElementTypeId) + match self.type_id() { + ElementNodeTypeId(HTMLImageElementTypeId) => true, + _ => false, + } } pub fn with_imm_image_element<R>(self, f: &fn(&HTMLImageElement) -> R) -> R { @@ -543,7 +640,16 @@ impl Node<ScriptView> { owner_doc: doc, child_list: None, - layout_data: None, + layout_data: LayoutDataRef::init(), + } + } + + /// Sends layout data, if any, back to the script task to be destroyed. + pub unsafe fn reap_layout_data(&mut self) { + if self.layout_data.is_present() { + let layout_data = util::replace(&mut self.layout_data, LayoutDataRef::init()); + let js_window = utils::global_object_for_dom_object(self); + (*js_window).data.page.reap_dead_layout_data(layout_data) } } } @@ -1095,12 +1201,12 @@ impl Reflectable for Node<ScriptView> { /// A bottom-up, parallelizable traversal. pub trait PostorderNodeTraversal { /// The operation to perform. Return true to continue or false to stop. - fn process(&mut self, node: AbstractNode<LayoutView>) -> bool; + fn process(&self, node: AbstractNode<LayoutView>) -> bool; /// Returns true if this node should be pruned. If this returns true, we skip the operation /// entirely and do not process any descendant nodes. This is called *before* child nodes are /// visited. The default implementation never prunes any nodes. - fn should_prune(&mut self, _node: AbstractNode<LayoutView>) -> bool { + fn should_prune(&self, _node: AbstractNode<LayoutView>) -> bool { false } } @@ -1109,7 +1215,7 @@ impl AbstractNode<LayoutView> { /// Traverses the tree in postorder. /// /// TODO(pcwalton): Offer a parallel version with a compatible API. - pub fn traverse_postorder<T:PostorderNodeTraversal>(self, traversal: &mut T) -> bool { + pub fn traverse_postorder<T:PostorderNodeTraversal>(self, traversal: &T) -> bool { if traversal.should_prune(self) { return true } diff --git a/src/components/script/layout_interface.rs b/src/components/script/layout_interface.rs index 348594ca162..3d9cc5e96e2 100644 --- a/src/components/script/layout_interface.rs +++ b/src/components/script/layout_interface.rs @@ -6,15 +6,16 @@ /// coupling between these two components, and enables the DOM to be placed in a separate crate /// from layout. -use dom::node::{AbstractNode, ScriptView, LayoutView}; -use script_task::{ScriptChan}; -use std::comm::{Chan, SharedChan}; +use dom::node::{AbstractNode, LayoutDataRef, LayoutView, ScriptView}; + +use extra::url::Url; +use geom::point::Point2D; use geom::rect::Rect; use geom::size::Size2D; -use geom::point::Point2D; +use script_task::{ScriptChan}; use servo_util::geometry::Au; +use std::comm::{Chan, SharedChan}; use style::Stylesheet; -use extra::url::Url; /// Asynchronous messages that script can send to layout. /// @@ -31,8 +32,19 @@ pub enum Msg { /// FIXME(pcwalton): As noted below, this isn't very type safe. QueryMsg(LayoutQuery), - /// Requests that the layout task shut down and exit. - ExitMsg, + /// Destroys layout data associated with a DOM node. + /// + /// TODO(pcwalton): Maybe think about batching to avoid message traffic. + ReapLayoutDataMsg(LayoutDataRef), + + /// Requests that the layout task enter a quiescent state in which no more messages are + /// accepted except `ExitMsg`. A response message will be sent on the supplied channel when + /// this happens. + PrepareToExitMsg(Chan<()>), + + /// Requests that the layout task immediately shut down. There must be no more nodes left after + /// this, or layout will crash. + ExitNowMsg, } /// Synchronous messages that script can send to layout. diff --git a/src/components/script/script_task.rs b/src/components/script/script_task.rs index 515dd1b0338..438f5429f06 100644 --- a/src/components/script/script_task.rs +++ b/src/components/script/script_task.rs @@ -2,11 +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/. */ -/// The script task is the task that owns the DOM in memory, runs JavaScript, and spawns parsing -/// and layout tasks. +//! The script task is the task that owns the DOM in memory, runs JavaScript, and spawns parsing +//! and layout tasks. -use servo_msg::compositor_msg::{ScriptListener, Loading, PerformingLayout}; -use servo_msg::compositor_msg::FinishedLoading; use dom::bindings::codegen::RegisterBindings; use dom::bindings::utils::{Reflectable, GlobalStaticData}; use dom::document::AbstractDocument; @@ -15,29 +13,20 @@ use dom::event::{Event_, ResizeEvent, ReflowEvent, ClickEvent, MouseDownEvent, M use dom::event::Event; use dom::eventtarget::AbstractEventTarget; use dom::htmldocument::HTMLDocument; -use dom::window::Window; +use dom::node::{AbstractNode, LayoutDataRef}; +use dom::window::{TimerData, Window}; +use html::hubbub_html_parser::HtmlParserResult; +use html::hubbub_html_parser::{HtmlDiscoveredStyle, HtmlDiscoveredIFrame, HtmlDiscoveredScript}; +use html::hubbub_html_parser; use layout_interface::{AddStylesheetMsg, DocumentDamage}; use layout_interface::{DocumentDamageLevel, HitTestQuery, HitTestResponse, LayoutQuery}; -use layout_interface::{LayoutChan, MatchSelectorsDocumentDamage, QueryMsg, Reflow}; -use layout_interface::{ReflowDocumentDamage, ReflowForDisplay, ReflowGoal}; -use layout_interface::ReflowMsg; +use layout_interface::{LayoutChan, MatchSelectorsDocumentDamage, QueryMsg, ReapLayoutDataMsg}; +use layout_interface::{Reflow, ReflowDocumentDamage, ReflowForDisplay, ReflowGoal, ReflowMsg}; use layout_interface; -use servo_msg::constellation_msg::{ConstellationChan, LoadUrlMsg, NavigationDirection}; -use servo_msg::constellation_msg::{PipelineId, SubpageId}; -use servo_msg::constellation_msg::{LoadIframeUrlMsg, IFrameSandboxed, IFrameUnsandboxed}; -use servo_msg::constellation_msg; -use std::cell::Cell; -use std::comm; -use std::comm::{Port, SharedChan}; -use std::ptr; -use std::task::{spawn_sched, SingleThreaded}; -use std::util::replace; -use dom::window::TimerData; +use extra::future::Future; +use extra::url::Url; use geom::size::Size2D; -use html::hubbub_html_parser::HtmlParserResult; -use html::hubbub_html_parser::{HtmlDiscoveredStyle, HtmlDiscoveredIFrame, HtmlDiscoveredScript}; -use html::hubbub_html_parser; use js::JSVAL_NULL; use js::global::debug_fns; use js::glue::RUST_JSVAL_TO_OBJECT; @@ -45,12 +34,21 @@ use js::jsapi::{JSContext, JSObject}; use js::jsapi::{JS_CallFunctionValue, JS_GetContextPrivate}; use js::rust::{Compartment, Cx}; use js; +use servo_msg::compositor_msg::{FinishedLoading, Loading, PerformingLayout, ScriptListener}; +use servo_msg::constellation_msg::{ConstellationChan, IFrameSandboxed, IFrameUnsandboxed}; +use servo_msg::constellation_msg::{LoadIframeUrlMsg, LoadUrlMsg, NavigationDirection, PipelineId}; +use servo_msg::constellation_msg::{SubpageId}; +use servo_msg::constellation_msg; use servo_net::image_cache_task::ImageCacheTask; use servo_net::resource_task::ResourceTask; -use servo_util::tree::{TreeNodeRef, ElementLike}; +use servo_util::tree::{TreeNode, TreeNodeRef, ElementLike}; use servo_util::url::make_url; -use extra::url::Url; -use extra::future::Future; +use std::cell::Cell; +use std::comm::{Port, SharedChan}; +use std::comm; +use std::ptr; +use std::task::{spawn_sched, SingleThreaded}; +use std::util::replace; /// Messages used to control the script task. pub enum ScriptMsg { @@ -86,6 +84,7 @@ pub struct NewLayoutInfo { /// Encapsulates external communication with the script task. #[deriving(Clone)] pub struct ScriptChan(SharedChan<ScriptMsg>); + impl ScriptChan { /// Creates a new script chan. pub fn new(chan: Chan<ScriptMsg>) -> ScriptChan { @@ -93,7 +92,7 @@ impl ScriptChan { } } -/// Encapsulates a handle to a frame and its associate layout information +/// Encapsulates a handle to a frame and its associated layout information. pub struct Page { /// Pipeline id associated with this page. id: PipelineId, @@ -216,8 +215,7 @@ impl<'self> Iterator<@mut Page> for PageTreeIterator<'self> { impl Page { /// Adds the given damage. fn damage(&mut self, level: DocumentDamageLevel) { - let root = self.frame.get_ref().document.document(). - GetDocumentElement(); + let root = self.frame.get_ref().document.document().GetDocumentElement(); match root { None => {}, Some(root) => { @@ -359,13 +357,18 @@ impl Page { }); } + /// Sends the given layout data back to the layout task to be destroyed. + pub unsafe fn reap_dead_layout_data(&self, layout_data: LayoutDataRef) { + self.layout_chan.send(ReapLayoutDataMsg(layout_data)) + } } /// Information for one frame in the browsing context. pub struct Frame { + /// The document for this frame. document: AbstractDocument, + /// The window object for this frame. window: @mut Window, - } /// Encapsulation of the javascript information associated with each frame. @@ -452,15 +455,16 @@ impl ScriptTask { } } - pub fn create<C: ScriptListener + Send>(id: PipelineId, - compositor: C, - layout_chan: LayoutChan, - port: Port<ScriptMsg>, - chan: ScriptChan, - constellation_chan: ConstellationChan, - resource_task: ResourceTask, - image_cache_task: ImageCacheTask, - initial_size: Future<Size2D<uint>>) { + pub fn create<C:ScriptListener + Send>( + id: PipelineId, + compositor: C, + layout_chan: LayoutChan, + port: Port<ScriptMsg>, + chan: ScriptChan, + constellation_chan: ConstellationChan, + resource_task: ResourceTask, + image_cache_task: ImageCacheTask, + initial_size: Future<Size2D<uint>>) { let parms = Cell::new((compositor, layout_chan, port, chan, constellation_chan, resource_task, image_cache_task, initial_size)); // Since SpiderMonkey is blocking it needs to run in its own thread. @@ -627,23 +631,23 @@ impl ScriptTask { true } + /// Handles a request to exit the script task and shut down layout. /// Returns true if the script task should shut down and false otherwise. fn handle_exit_pipeline_msg(&mut self, id: PipelineId) -> bool { // If root is being exited, shut down all pages if self.page_tree.page.id == id { for page in self.page_tree.iter() { - page.join_layout(); - page.layout_chan.send(layout_interface::ExitMsg); + shut_down_layout(page) } return true } + // otherwise find just the matching page and exit all sub-pages match self.page_tree.remove(id) { Some(ref mut page_tree) => { for page in page_tree.iter() { - page.join_layout(); - page.layout_chan.send(layout_interface::ExitMsg); + shut_down_layout(page) } false } @@ -862,3 +866,34 @@ impl ScriptTask { } } +/// Shuts down layout for the given page. +fn shut_down_layout(page: @mut Page) { + page.join_layout(); + + // Tell the layout task to begin shutting down. + let (response_port, response_chan) = comm::stream(); + page.layout_chan.send(layout_interface::PrepareToExitMsg(response_chan)); + response_port.recv(); + + // Destroy all nodes. + // + // If there was a leak, the layout task will soon crash safely when it detects that local data + // is missing from its heap. + // + // FIXME(pcwalton): *But*, for now, because we use `@mut` boxes to hold onto Nodes, if this + // didn't destroy all the nodes there will be an *exploitable* security vulnerability as the + // nodes try to access the destroyed JS context. We need to change this so that the only actor + // who can judge a JS object dead (and thus run its drop glue) is the JS engine itself; thus it + // will be impossible (absent a serious flaw in the JS engine) for the JS context to be dead + // before nodes are. + unsafe { + let document_node = AbstractNode::from_document(page.frame.as_ref().unwrap().document); + for node in document_node.traverse_preorder() { + node.mut_node().reap_layout_data() + } + } + + // Destroy the layout task. If there were node leaks, layout will now crash safely. + page.layout_chan.send(layout_interface::ExitNowMsg); +} + diff --git a/src/components/util/slot.rs b/src/components/util/slot.rs index 6c8eca9068f..34c12f2a0f5 100644 --- a/src/components/util/slot.rs +++ b/src/components/util/slot.rs @@ -6,6 +6,7 @@ //! well, this type should be upstreamed to the Rust standard library. use std::cast; +use std::util; #[unsafe_no_drop_flag] #[no_freeze] @@ -74,6 +75,13 @@ impl<T> Slot<T> { } } + /// Borrows the data immutably. This function is thread-safe, but *bad things will happen if + /// you try to mutate the data while one of these pointers is held*. + #[inline] + pub unsafe fn borrow_unchecked<'a>(&'a self) -> &'a T { + &self.value + } + #[inline] pub fn borrow<'a>(&'a self) -> SlotRef<'a,T> { unsafe { @@ -109,8 +117,14 @@ impl<T> Slot<T> { *self.mutate().ptr = value } + /// Replaces the slot's value with the given value and returns the old value. + #[inline] + pub fn replace(&self, value: T) -> T { + util::replace(self.mutate().ptr, value) + } + #[inline(never)] - pub fn fail(&self) { + pub fn fail(&self) -> ! { fail!("slot is borrowed") } } |