From 49b6ce84d3d44f00c28d1afc28d81ffc78fad59d Mon Sep 17 00:00:00 2001 From: S Pradeep Kumar Date: Mon, 3 Mar 2014 13:37:33 -0500 Subject: [PATCH] servo: Merge #1681 - Implement `position: absolute` for non-replaced elements (from pradeep90:absolute-position); r=pcwalton + Re-implement fixed positioning using the absolute positioning code. + Add reftests for absolute positioning and fixed positioning. + Refactor assign_widths in BlockFlow to isolate the calculation of widths and margins. + Pass down details of the Containing Block for absolute and fixed flows during layout. Use it to calculate the static position of absolute flows. + Defer calculation of absolute flow dimensions till we build the display list. This implements https://github.com/mozilla/servo/issues/1537 and https://github.com/mozilla/servo/issues/787 Source-Repo: https://github.com/servo/servo Source-Revision: ada9224d0ef5b403562ebaaeb2e5f66729ffc589 --- servo/src/components/main/layout/block.rs | 1170 ++++++++++++++--- servo/src/components/main/layout/box_.rs | 53 +- servo/src/components/main/layout/construct.rs | 144 +- servo/src/components/main/layout/flow.rs | 348 ++++- servo/src/components/main/layout/flow_list.rs | 8 +- servo/src/components/main/layout/inline.rs | 17 +- .../src/components/main/layout/layout_task.rs | 33 +- servo/src/components/main/layout/parallel.rs | 18 +- 8 files changed, 1531 insertions(+), 260 deletions(-) diff --git a/servo/src/components/main/layout/block.rs b/servo/src/components/main/layout/block.rs index ccaf15b456b1..0f965a80c28b 100644 --- a/servo/src/components/main/layout/block.rs +++ b/servo/src/components/main/layout/block.rs @@ -3,22 +3,31 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ //! CSS block formatting contexts. +//! +//! Terminology Note: +//! As per the CSS Spec, the term 'absolute positioning' here refers to +//! elements with position = 'absolute' or 'fixed'. +//! The term 'positioned element' refers to elements with position = +//! 'relative', 'absolute', or 'fixed'. use layout::box_::Box; -use layout::construct::FlowConstructor; +use layout::construct::{FlowConstructor, OptVector}; use layout::context::LayoutContext; use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; use layout::floats::{FloatKind, Floats, PlacementInfo}; use layout::flow::{BaseFlow, BlockFlowClass, FlowClass, Flow, ImmutableFlowUtils}; +use layout::flow::{mut_base, PreorderFlowTraversal, PostorderFlowTraversal, MutableFlowUtils}; use layout::flow; use layout::model::{MaybeAuto, Specified, Auto, specified_or_none, specified}; use layout::wrapper::ThreadSafeLayoutNode; +use style::computed_values::{position}; use std::cell::RefCell; use geom::{Point2D, Rect, SideOffsets2D, Size2D}; -use gfx::display_list::{DisplayList, DisplayListCollection}; +use gfx::display_list::{DisplayListCollection, DisplayList}; use servo_util::geometry::Au; use servo_util::geometry; +use servo_util::smallvec::{SmallVec, SmallVec0}; /// Information specific to floated blocks. pub struct FloatedBlockInfo { @@ -49,7 +58,214 @@ impl FloatedBlockInfo { } } -/// A block formatting context. +/// The solutions for the widths-and-margins constraint equation. +struct WidthConstraintSolution { + left: Au, + right: Au, + width: Au, + margin_left: Au, + margin_right: Au +} + +impl WidthConstraintSolution { + fn new(left: Au, right: Au, width: Au, margin_left: Au, margin_right: Au) + -> WidthConstraintSolution { + WidthConstraintSolution { + left: left, + right: right, + width: width, + margin_left: margin_left, + margin_right: margin_right, + } + } +} + +/// The solutions for the heights-and-margins constraint equation. +struct HeightConstraintSolution { + top: Au, + bottom: Au, + height: Au, + margin_top: Au, + margin_bottom: Au +} + +impl HeightConstraintSolution { + fn new(top: Au, bottom: Au, height: Au, margin_top: Au, margin_bottom: Au) + -> HeightConstraintSolution { + HeightConstraintSolution { + top: top, + bottom: bottom, + height: height, + margin_top: margin_top, + margin_bottom: margin_bottom, + } + } + + /// Solve the vertical constraint equation for absolute non-replaced elements. + /// + /// CSS Section 10.6.4 + /// Constraint equation: + /// top + bottom + height + margin-top + margin-bottom + /// = absolute containing block height - (vertical padding and border) + /// [aka available_height] + /// + /// Return the solution for the equation. + fn solve_vertical_constraints_abs_position(height: MaybeAuto, + top_margin: MaybeAuto, + bottom_margin: MaybeAuto, + top: MaybeAuto, + bottom: MaybeAuto, + content_height: Au, + available_height: Au, + static_y_offset: Au) + -> HeightConstraintSolution { + // Distance from the top edge of the Absolute Containing Block to the + // top margin edge of a hypothetical box that would have been the + // first box of the element. + let static_position_top = static_y_offset; +; + let (top, bottom, height, margin_top, margin_bottom) = match (top, bottom, height) { + (Auto, Auto, Auto) => { + let margin_top = top_margin.specified_or_zero(); + let margin_bottom = bottom_margin.specified_or_zero(); + let top = static_position_top; + // Now it is the same situation as top Specified and bottom + // and height Auto. + + let height = content_height; + let sum = top + height + margin_top + margin_bottom; + (top, available_height - sum, height, margin_top, margin_bottom) + } + (Specified(top), Specified(bottom), Specified(height)) => { + match (top_margin, bottom_margin) { + (Auto, Auto) => { + let total_margin_val = (available_height - top - bottom - height); + (top, bottom, height, + total_margin_val.scale_by(0.5), + total_margin_val.scale_by(0.5)) + } + (Specified(margin_top), Auto) => { + let sum = top + bottom + height + margin_top; + (top, bottom, height, margin_top, available_height - sum) + } + (Auto, Specified(margin_bottom)) => { + let sum = top + bottom + height + margin_bottom; + (top, bottom, height, available_height - sum, margin_bottom) + } + (Specified(margin_top), Specified(margin_bottom)) => { + // Values are over-constrained. Ignore value for 'bottom'. + let sum = top + height + margin_top + margin_bottom; + (top, available_height - sum, height, margin_top, margin_bottom) + } + } + } + + // For the rest of the cases, auto values for margin are set to 0 + + // If only one is Auto, solve for it + (Auto, Specified(bottom), Specified(height)) => { + let margin_top = top_margin.specified_or_zero(); + let margin_bottom = bottom_margin.specified_or_zero(); + let sum = bottom + height + margin_top + margin_bottom; + (available_height - sum, bottom, height, margin_top, margin_bottom) + } + (Specified(top), Auto, Specified(height)) => { + let margin_top = top_margin.specified_or_zero(); + let margin_bottom = bottom_margin.specified_or_zero(); + let sum = top + height + margin_top + margin_bottom; + (top, available_height - sum, height, margin_top, margin_bottom) + } + (Specified(top), Specified(bottom), Auto) => { + let margin_top = top_margin.specified_or_zero(); + let margin_bottom = bottom_margin.specified_or_zero(); + let sum = top + bottom + margin_top + margin_bottom; + (top, bottom, available_height - sum, margin_top, margin_bottom) + } + + // If height is auto, then height is content height. Solve for the + // non-auto value. + (Specified(top), Auto, Auto) => { + let margin_top = top_margin.specified_or_zero(); + let margin_bottom = bottom_margin.specified_or_zero(); + let height = content_height; + let sum = top + height + margin_top + margin_bottom; + (top, available_height - sum, height, margin_top, margin_bottom) + } + (Auto, Specified(bottom), Auto) => { + let margin_top = top_margin.specified_or_zero(); + let margin_bottom = bottom_margin.specified_or_zero(); + let height = content_height; + let sum = bottom + height + margin_top + margin_bottom; + (available_height - sum, bottom, height, margin_top, margin_bottom) + } + + (Auto, Auto, Specified(height)) => { + let margin_top = top_margin.specified_or_zero(); + let margin_bottom = bottom_margin.specified_or_zero(); + let top = static_position_top; + let sum = top + height + margin_top + margin_bottom; + (top, available_height - sum, height, margin_top, margin_bottom) + } + }; + HeightConstraintSolution::new(top, bottom, height, margin_top, margin_bottom) + } +} + +/// The real assign-heights traversal for flows with position 'absolute'. +/// +/// This is a traversal of an Absolute Flow tree. +/// - Relatively positioned flows and the Root flow start new Absolute flow trees. +/// - The kids of a flow in this tree will be the flows for which it is the +/// absolute Containing Block. +/// - Thus, leaf nodes and inner non-root nodes are all Absolute Flows. +/// +/// A Flow tree can have several Absolute Flow trees (depending on the number +/// of relatively positioned flows it has). +/// +/// Note that flows with position 'fixed' just form a flat list as they all +/// have the Root flow as their CB. +struct AbsoluteAssignHeightsTraversal<'a>(&'a mut LayoutContext); + +impl<'a> PreorderFlowTraversal for AbsoluteAssignHeightsTraversal<'a> { + #[inline] + fn process(&mut self, flow: &mut Flow) -> bool { + let block_flow = flow.as_block(); + + // The root of the absolute flow tree is definitely not absolutely + // positioned. Nothing to process here. + if block_flow.is_root_of_absolute_flow_tree() { + return true; + } + + block_flow.calculate_abs_height_and_margins(**self); + true + } +} + +/// The store-overflow traversal particular to absolute flows. +/// +/// Propagate overflow up the Absolute flow tree and update overflow up to and +/// not including the root of the Absolute flow tree. +/// After that, it is up to the normal store-overflow traversal to propagate +/// it further up. +struct AbsoluteStoreOverflowTraversal<'a>{ + layout_context: &'a mut LayoutContext, +} + +impl<'a> PostorderFlowTraversal for AbsoluteStoreOverflowTraversal<'a> { + #[inline] + fn process(&mut self, flow: &mut Flow) -> bool { + // This will be taken care of by the normal store-overflow traversal. + if flow.is_root_of_absolute_flow_tree() { + return true; + } + + flow.store_overflow(self.layout_context); + true + } +} + +// A block formatting context. pub struct BlockFlow { /// Data common to all flows. base: BaseFlow, @@ -57,11 +273,12 @@ pub struct BlockFlow { /// The associated box. box_: Option, - //TODO: is_fixed and is_root should be bit fields to conserve memory. + /// TODO: is_root should be a bit field to conserve memory. /// Whether this block flow is the root flow. is_root: bool, - is_fixed: bool, + /// Static y offset of an absolute flow from its CB. + static_y_offset: Au, /// Additional floating flow members. float: Option<~FloatedBlockInfo> @@ -69,14 +286,13 @@ pub struct BlockFlow { impl BlockFlow { pub fn from_node(constructor: &mut FlowConstructor, - node: &ThreadSafeLayoutNode, - is_fixed: bool) + node: &ThreadSafeLayoutNode) -> BlockFlow { BlockFlow { base: BaseFlow::new((*node).clone()), box_: Some(Box::new(constructor, node)), is_root: false, - is_fixed: is_fixed, + static_y_offset: Au::new(0), float: None } } @@ -89,7 +305,7 @@ impl BlockFlow { base: BaseFlow::new((*node).clone()), box_: Some(Box::new(constructor, node)), is_root: false, - is_fixed: false, + static_y_offset: Au::new(0), float: Some(~FloatedBlockInfo::new(float_kind)) } } @@ -99,7 +315,7 @@ impl BlockFlow { base: base, box_: None, is_root: true, - is_fixed: false, + static_y_offset: Au::new(0), float: None } } @@ -109,13 +325,103 @@ impl BlockFlow { base: base, box_: None, is_root: false, - is_fixed: false, + static_y_offset: Au::new(0), float: Some(~FloatedBlockInfo::new(float_kind)) } } - pub fn is_float(&self) -> bool { - self.float.is_some() + /// Return the static x offset from the appropriate Containing Block for this flow. + pub fn static_x_offset(&self) -> Au { + assert!(self.is_absolutely_positioned()); + if self.is_fixed() { + self.base.fixed_static_x_offset + } else { + self.base.absolute_static_x_offset + } + } + + /// Return the size of the Containing Block for this flow. + /// + /// Right now, this only gets the Containing Block size for absolutely + /// positioned elements. + /// Note: Assume this is called in a top-down traversal, so it is ok to + /// reference the CB. + pub fn containing_block_size(&mut self, viewport_size: Size2D) -> Size2D { + assert!(self.is_absolutely_positioned()); + if self.is_fixed() { + // Initial containing block is the CB for the root + viewport_size + } else { + let cb = self.base.absolute_cb.resolve().unwrap(); + cb.generated_cb_size() + } + } + + /// Traverse the Absolute flow tree in preorder. + /// + /// Traverse all your direct absolute descendants, who will then traverse + /// their direct absolute descendants. + /// Also, set the static y offsets for each descendant (using the value + /// which was bubbled up during normal assign-height). + /// + /// Return true if the traversal is to continue or false to stop. + fn traverse_preorder_absolute_flows(&mut self, + traversal: &mut T) + -> bool { + let flow = self as &mut Flow; + if traversal.should_prune(flow) { + return true + } + + if !traversal.process(flow) { + return false + } + + let cb_top_edge_offset = flow.generated_cb_position().y; + let mut descendant_offset_iter = mut_base(flow).abs_descendants.iter_with_offset(); + // Pass in the respective static y offset for each descendant. + for (ref mut descendant_link, ref y_offset) in descendant_offset_iter { + match descendant_link.resolve() { + Some(flow) => { + let block = flow.as_block(); + // The stored y_offset is wrt to the flow box. + // Translate it to the CB (which is the padding box). + block.static_y_offset = **y_offset - cb_top_edge_offset; + if !block.traverse_preorder_absolute_flows(traversal) { + return false + } + } + None => fail!("empty Rawlink to a descendant") + } + } + + true + } + + /// Traverse the Absolute flow tree in postorder. + /// + /// Return true if the traversal is to continue or false to stop. + fn traverse_postorder_absolute_flows(&mut self, + traversal: &mut T) + -> bool { + let flow = self as &mut Flow; + if traversal.should_prune(flow) { + return true + } + + for descendant_link in mut_base(flow).abs_descendants.iter() { + match descendant_link.resolve() { + Some(abs_flow) => { + let block = abs_flow.as_block(); + if !block.traverse_postorder_absolute_flows(traversal) { + return false + } + } + None => fail!("empty Rawlink to a descendant") + } + } + + traversal.process(flow) } pub fn teardown(&mut self) { @@ -189,7 +495,244 @@ impl BlockFlow { (width_Au, left_margin_Au, right_margin_Au) } - // Return (content width, left margin, right, margin) + /// Calculate and set the width, offsets, etc. for an Absolute Flow. + /// + /// Calculate: + /// + left margin, right margin, and content width for the flow's box + /// + x-coordinate of the flow's box + /// + x-coordinate of the absolute flow itself (wrt to its Containing Block) + fn calculate_abs_widths_and_margins(&mut self, ctx: &mut LayoutContext) { + let containing_block_width = self.containing_block_size(ctx.screen_size).width; + let static_x_offset = self.static_x_offset(); + for box_ in self.box_.iter() { + let style = box_.style(); + + // The text alignment of a block flow is the text alignment of its box's style. + self.base.flags_info.flags.set_text_align(style.InheritedText.get().text_align); + + box_.compute_padding(style, containing_block_width); + + let (width, margin_left, margin_right) = + (MaybeAuto::from_style(style.Box.get().width, containing_block_width), + MaybeAuto::from_style(style.Margin.get().margin_left, containing_block_width), + MaybeAuto::from_style(style.Margin.get().margin_right, containing_block_width)); + + let (left, right) = + (MaybeAuto::from_style(style.PositionOffsets.get().left, containing_block_width), + MaybeAuto::from_style(style.PositionOffsets.get().right, containing_block_width)); + let available_width = containing_block_width - box_.border_and_padding_horiz(); + // TODO: Extract this later into a SolveConstraints trait or something + let solution = self.solve_horiz_constraints_abs_position(width, + margin_left, + margin_right, + left, + right, + available_width, + static_x_offset); + + let mut margin = box_.margin.get(); + margin.left = solution.margin_left; + margin.right = solution.margin_right; + box_.margin.set(margin); + + // The associated box is the border box of this flow. + let mut position_ref = box_.border_box.borrow_mut(); + // Left border edge. + position_ref.get().origin.x = box_.margin.get().left; + + // Border box width + position_ref.get().size.width = solution.width + box_.border_and_padding_horiz(); + + // Set the x-coordinate of the absolute flow wrt to its containing block. + self.base.position.origin.x = solution.left; + } + } + + /// Calculate and set the width and horizontal margins for this BlockFlow's box. + /// + /// Also, set the x-coordinate for box_. + fn calculate_widths_and_margins(&mut self, + containing_block_width: Au, + ctx: &mut LayoutContext) { + if self.is_absolutely_positioned() { + self.calculate_abs_widths_and_margins(ctx); + return; + } + for box_ in self.box_.iter() { + let style = box_.style(); + + // The text alignment of a block flow is the text alignment of its box's style. + self.base.flags_info.flags.set_text_align(style.InheritedText.get().text_align); + + // Calculate used value of width for replaced element. + // After that, margin calculation is the same as for non-replaced content. + box_.assign_replaced_width_if_necessary(containing_block_width); + + // Can compute padding here since we know containing block width. + box_.compute_padding(style, containing_block_width); + + // Margins are 0 right now so base.noncontent_width() is just borders + padding. + let available_width = containing_block_width - box_.noncontent_width(); + + // Top and bottom margins for blocks are 0 if auto. + // FIXME: This is wrong. We shouldn't even be touching margin-top + // and margin-bottom here. + let margin_top = MaybeAuto::from_style(style.Margin.get().margin_top, + containing_block_width).specified_or_zero(); + let margin_bottom = MaybeAuto::from_style(style.Margin.get().margin_bottom, + containing_block_width).specified_or_zero(); + + let (width, margin_left, margin_right) = if self.is_float() { + self.compute_float_margins(box_, containing_block_width) + } else { + self.compute_block_margins(box_, containing_block_width, available_width) + }; + + box_.margin.set(SideOffsets2D::new(margin_top, + margin_right, + margin_bottom, + margin_left)); + + // The associated box is the border box of this flow. + let mut position_ref = box_.border_box.borrow_mut(); + position_ref.get().origin.x = box_.margin.get().left; + let padding_and_borders = box_.padding.get().left + box_.padding.get().right + + box_.border.get().left + box_.border.get().right; + position_ref.get().size.width = width + padding_and_borders; + } + } + + /// Solve the horizontal constraint equation for absolute non-replaced elements. + /// + /// `static_x_offset`: total offset of current flow's hypothetical + /// position (static position) from its actual Containing Block. + /// + /// CSS Section 10.3.7 + /// Constraint equation: + /// left + right + width + margin-left + margin-right + /// = absolute containing block width - (horizontal padding and border) + /// [aka available_width] + /// + /// Return the solution for the equation. + fn solve_horiz_constraints_abs_position(&self, + width: MaybeAuto, + left_margin: MaybeAuto, + right_margin: MaybeAuto, + left: MaybeAuto, + right: MaybeAuto, + available_width: Au, + static_x_offset: Au) + -> WidthConstraintSolution { + // TODO: Check for direction of parent flow (NOT Containing Block) + // when right-to-left is implemented. + // Assume direction is 'ltr' for now + + // Distance from the left edge of the Absolute Containing Block to the + // left margin edge of a hypothetical box that would have been the + // first box of the element. + let static_position_left = static_x_offset; + + let (left, right, width, margin_left, margin_right) = match (left, right, width) { + (Auto, Auto, Auto) => { + let margin_l = left_margin.specified_or_zero(); + let margin_r = right_margin.specified_or_zero(); + let left = static_position_left; + // Now it is the same situation as left Specified and right + // and width Auto. + + // Set right to zero to calculate width + let width = self.get_shrink_to_fit_width(available_width - (left + margin_l + margin_r)); + let sum = left + width + margin_l + margin_r; + (left, available_width - sum, width, margin_l, margin_r) + } + (Specified(left), Specified(right), Specified(width)) => { + match (left_margin, right_margin) { + (Auto, Auto) => { + let total_margin_val = (available_width - left - right - width); + if total_margin_val < Au(0) { + // margin-left becomes 0 because direction is 'ltr'. + // TODO: Handle 'rtl' when it is implemented. + (left, right, width, Au(0), total_margin_val) + } else { + // Equal margins + (left, right, width, + total_margin_val.scale_by(0.5), + total_margin_val.scale_by(0.5)) + } + } + (Specified(margin_l), Auto) => { + let sum = left + right + width + margin_l; + (left, right, width, margin_l, available_width - sum) + } + (Auto, Specified(margin_r)) => { + let sum = left + right + width + margin_r; + (left, right, width, available_width - sum, margin_r) + } + (Specified(margin_l), Specified(margin_r)) => { + // Values are over-constrained. + // Ignore value for 'right' cos direction is 'ltr'. + // TODO: Handle 'rtl' when it is implemented. + let sum = left + width + margin_l + margin_r; + (left, available_width - sum, width, margin_l, margin_r) + } + } + } + // For the rest of the cases, auto values for margin are set to 0 + + // If only one is Auto, solve for it + (Auto, Specified(right), Specified(width)) => { + let margin_l = left_margin.specified_or_zero(); + let margin_r = right_margin.specified_or_zero(); + let sum = right + width + margin_l + margin_r; + (available_width - sum, right, width, margin_l, margin_r) + } + (Specified(left), Auto, Specified(width)) => { + let margin_l = left_margin.specified_or_zero(); + let margin_r = right_margin.specified_or_zero(); + let sum = left + width + margin_l + margin_r; + (left, available_width - sum, width, margin_l, margin_r) + } + (Specified(left), Specified(right), Auto) => { + let margin_l = left_margin.specified_or_zero(); + let margin_r = right_margin.specified_or_zero(); + let sum = left + right + margin_l + margin_r; + (left, right, available_width - sum, margin_l, margin_r) + } + + // If width is auto, then width is shrink-to-fit. Solve for the + // non-auto value. + (Specified(left), Auto, Auto) => { + let margin_l = left_margin.specified_or_zero(); + let margin_r = right_margin.specified_or_zero(); + // Set right to zero to calculate width + let width = self.get_shrink_to_fit_width( + available_width - (left + margin_l + margin_r)); + let sum = left + width + margin_l + margin_r; + (left, available_width - sum, width, margin_l, margin_r) + } + (Auto, Specified(right), Auto) => { + let margin_l = left_margin.specified_or_zero(); + let margin_r = right_margin.specified_or_zero(); + // Set left to zero to calculate width + let width = self.get_shrink_to_fit_width( + available_width - (right + margin_l + margin_r)); + let sum = right + width + margin_l + margin_r; + (available_width - sum, right, width, margin_l, margin_r) + } + + (Auto, Auto, Specified(width)) => { + let margin_l = left_margin.specified_or_zero(); + let margin_r = right_margin.specified_or_zero(); + // Setting 'left' to static position because direction is 'ltr'. + // TODO: Handle 'rtl' when it is implemented. + let left = static_position_left; + let sum = left + width + margin_l + margin_r; + (left, available_width - sum, width, margin_l, margin_r) + } + }; + WidthConstraintSolution::new(left, right, width, margin_left, margin_right) + } + fn compute_block_margins(&self, box_: &Box, remaining_width: Au, available_width: Au) -> (Au, Au, Au) { let style = box_.style(); @@ -234,23 +777,103 @@ impl BlockFlow { return (width, margin_left, margin_right); } + /// Return shrink-to-fit width. + /// + /// This is where we use the preferred widths and minimum widths + /// calculated in the bubble-widths traversal. + fn get_shrink_to_fit_width(&self, available_width: Au) -> Au { + geometry::min(self.base.pref_width, + geometry::max(self.base.min_width, available_width)) + } + // CSS Section 10.3.5 + // TODO: This has to handle min-width and max-width. fn compute_float_margins(&self, box_: &Box, remaining_width: Au) -> (Au, Au, Au) { let style = box_.style(); let margin_left = MaybeAuto::from_style(style.Margin.get().margin_left, remaining_width).specified_or_zero(); let margin_right = MaybeAuto::from_style(style.Margin.get().margin_right, remaining_width).specified_or_zero(); - let shrink_to_fit = geometry::min(self.base.pref_width, - geometry::max(self.base.min_width, remaining_width)); + let shrink_to_fit = self.get_shrink_to_fit_width(remaining_width); let width = MaybeAuto::from_style(style.Box.get().width, remaining_width).specified_or_default(shrink_to_fit); debug!("assign_widths_float -- width: {}", width); return (width, margin_left, margin_right); } - // inline(always) because this is only ever called by in-order or non-in-order top-level - // methods + + /// Collect and update static y-offsets bubbled up by kids. + /// + /// This would essentially give us offsets of all absolutely positioned + /// direct descendants and all fixed descendants, in tree order. + /// + /// Assume that this is called in a bottom-up traversal (specifically, the + /// assign-height traversal). So, kids have their flow origin already set. + /// In the case of absolute flow kids, they have their hypothetical box + /// position already set. + fn collect_static_y_offsets_from_kids(&mut self) { + let mut abs_descendant_y_offsets = SmallVec0::new(); + let mut fixed_descendant_y_offsets = SmallVec0::new(); + + for kid in self.base.child_iter() { + let mut gives_abs_offsets = true; + if kid.is_block_like() { + let kid_block = kid.as_block(); + if kid_block.is_fixed() { + // It won't contribute any offsets for position 'absolute' + // descendants because it would be the CB for them. + gives_abs_offsets = false; + // Add the offset for the current fixed flow too. + fixed_descendant_y_offsets.push(kid_block.get_hypothetical_top_edge()); + } else if kid_block.is_absolutely_positioned() { + // It won't contribute any offsets for descendants because it + // would be the CB for them. + gives_abs_offsets = false; + // Give the offset for the current absolute flow alone. + abs_descendant_y_offsets.push(kid_block.get_hypothetical_top_edge()); + } else if kid_block.is_positioned() { + // It won't contribute any offsets because it would be the CB + // for the descendants. + gives_abs_offsets = false; + } + } + + if gives_abs_offsets { + let kid_base = flow::mut_base(kid); + // Consume all the static y-offsets bubbled up by kid. + for y_offset in kid_base.abs_descendants.static_y_offsets.move_iter() { + // The offsets are wrt the kid flow box. Translate them to current flow. + y_offset = y_offset + kid_base.position.origin.y; + abs_descendant_y_offsets.push(y_offset); + } + } + + // Get all the fixed offsets. + let kid_base = flow::mut_base(kid); + // Consume all the static y-offsets bubbled up by kid. + for y_offset in kid_base.fixed_descendants.static_y_offsets.move_iter() { + // The offsets are wrt the kid flow box. Translate them to current flow. + y_offset = y_offset + kid_base.position.origin.y; + fixed_descendant_y_offsets.push(y_offset); + } + } + self.base.abs_descendants.static_y_offsets = abs_descendant_y_offsets; + self.base.fixed_descendants.static_y_offsets = fixed_descendant_y_offsets; + } + + /// Assign height for current flow. + /// + /// + Collapse margins for flow's children and set in-flow child flows' + /// y-coordinates now that we know their heights. + /// + Calculate and set the height of the current flow. + /// + Calculate height, vertical margins, and y-coordinate for the flow's + /// box. Ideally, this should be calculated using CSS Section 10.6.7 + /// + /// For absolute flows, store the calculated content height for the flow. + /// Defer the calculation of the other values till a later traversal. + /// + /// inline(always) because this is only ever called by in-order or non-in-order top-level + /// methods #[inline(always)] fn assign_height_block_base(&mut self, ctx: &mut LayoutContext, inorder: bool) { let mut cur_y = Au::new(0); @@ -261,12 +884,15 @@ impl BlockFlow { let mut left_offset = Au::new(0); for box_ in self.box_.iter() { - clearance = match box_.clear() { - None => Au::new(0), - Some(clear) => { - self.base.floats.clearance(clear) - } - }; + // Note: Ignoring clearance for absolute flows as of now. + if !self.is_absolutely_positioned() { + clearance = match box_.clear() { + None => Au::new(0), + Some(clear) => { + self.base.floats.clearance(clear) + } + }; + } top_offset = clearance + box_.margin.get().top + box_.border.get().top + box_.padding.get().top; @@ -276,7 +902,8 @@ impl BlockFlow { left_offset = box_.offset(); } - if inorder { + // Note: Ignoring floats for absolute flow as of now. + if inorder && !self.is_absolutely_positioned() { // Floats for blocks work like this: // self.floats -> child[0].floats // visit child[0] @@ -309,41 +936,60 @@ impl BlockFlow { let mut top_margin_collapsible = false; let mut bottom_margin_collapsible = false; let mut first_in_flow = true; - for box_ in self.box_.iter() { - if !self.is_root && box_.border.get().top == Au(0) && box_.padding.get().top == Au(0) { - collapsible = box_.margin.get().top; - top_margin_collapsible = true; - } - if !self.is_root && box_.border.get().bottom == Au(0) && + // Margins for an absolutely positioned element do not collapse with + // its children. + if !self.is_absolutely_positioned() { + for box_ in self.box_.iter() { + if !self.is_root() && box_.border.get().top == Au(0) + && box_.padding.get().top == Au(0) { + + collapsible = box_.margin.get().top; + top_margin_collapsible = true; + } + if !self.is_root() && box_.border.get().bottom == Au(0) && box_.padding.get().bottom == Au(0) { - bottom_margin_collapsible = true; + bottom_margin_collapsible = true; + } + margin_top = box_.margin.get().top; + margin_bottom = box_.margin.get().bottom; } - margin_top = box_.margin.get().top; - margin_bottom = box_.margin.get().bottom; } // At this point, cur_y is at the content edge of the flow's box_ for kid in self.base.child_iter() { // At this point, cur_y is at bottom margin edge of previous kid - kid.collapse_margins(top_margin_collapsible, - &mut first_in_flow, - &mut margin_top, - &mut top_offset, - &mut collapsing, - &mut collapsible); - let child_node = flow::mut_base(kid); - cur_y = cur_y - collapsing; - // At this point, after moving up by `collapsing`, cur_y is at the - // top margin edge of kid - child_node.position.origin.y = cur_y; - cur_y = cur_y + child_node.position.size.height; - // At this point, cur_y is at the bottom margin edge of kid + if kid.is_absolutely_positioned() { + // Assume that the `hypothetical box` for an absolute flow + // starts immediately after the bottom margin edge of the + // previous flow. + kid.as_block().base.position.origin.y = cur_y; + // Skip the collapsing for absolute flow kids and continue + // with the next flow. + } else { + kid.collapse_margins(top_margin_collapsible, + &mut first_in_flow, + &mut margin_top, + &mut top_offset, + &mut collapsing, + &mut collapsible); + let child_node = flow::mut_base(kid); + cur_y = cur_y - collapsing; + // At this point, after moving up by `collapsing`, cur_y is at the + // top margin edge of kid + child_node.position.origin.y = cur_y; + cur_y = cur_y + child_node.position.size.height; + // At this point, cur_y is at the bottom margin edge of kid + } } + self.collect_static_y_offsets_from_kids(); + // The bottom margin collapses with its last in-flow block-level child's bottom margin - // if the parent has no bottom boder, no bottom padding. - collapsing = if bottom_margin_collapsible { + // if the parent has no bottom border, no bottom padding. + // The bottom margin for an absolutely positioned element does not + // collapse even with its children. + collapsing = if bottom_margin_collapsible && !self.is_absolutely_positioned() { if margin_bottom < collapsible { margin_bottom = collapsible; } @@ -358,27 +1004,37 @@ impl BlockFlow { let screen_height = ctx.screen_size.height; - let mut height = if self.is_root { + let mut height = if self.is_root() { // FIXME(pcwalton): The max is taken here so that you can scroll the page, but this is // not correct behavior according to CSS 2.1 § 10.5. Instead I think we should treat // the root element as having `overflow: scroll` and use the layers-based scrolling // infrastructure to make it scrollable. Au::max(screen_height, cur_y) } else { - // (cur_y - collapsing) will get you the bottom content edge - // top_offset will be at top content edge + // (cur_y - collapsing) will get you the the bottom margin-edge of + // the bottom-most child. + // top_offset: top margin-edge of the topmost child. // hence, height = content height cur_y - top_offset - collapsing }; + if self.is_absolutely_positioned() { + // Store the content height for use in calculating the absolute + // flow's dimensions later. + for box_ in self.box_.iter() { + let mut temp_position = box_.border_box.get(); + temp_position.size.height = height; + box_.border_box.set(temp_position); + } + return; + } + for box_ in self.box_.iter() { let style = box_.style(); // At this point, `height` is the height of the containing block, so passing `height` // as the second argument here effectively makes percentages relative to the containing // block per CSS 2.1 § 10.5. - // TODO: We need to pass in the correct containing block height - // for absolutely positioned elems height = match MaybeAuto::from_style(style.Box.get().height, height) { Auto => height, Specified(value) => value @@ -400,27 +1056,9 @@ impl BlockFlow { noncontent_height = box_.padding.get().top + box_.padding.get().bottom + box_.border.get().top + box_.border.get().bottom; - let (y, h) = box_.get_y_coord_and_new_height_if_fixed(screen_height, - height, - clearance + margin.top, - self.is_fixed); - - position.origin.y = y; - height = h; - - if self.is_fixed { - for kid in self.base.child_iter() { - let child_node = flow::mut_base(kid); - child_node.position.origin.y = position.origin.y + top_offset; - } - } - - position.size.height = if self.is_fixed { - height - } else { - // Border box height - height + noncontent_height - }; + position.origin.y = clearance + margin.top; + // Border box height + position.size.height = height + noncontent_height; noncontent_height = noncontent_height + clearance + margin.top + margin.bottom; @@ -428,23 +1066,69 @@ impl BlockFlow { box_.margin.set(margin); } - self.base.position.size.height = if self.is_fixed { - height - } else { - // Height of margin box + clearance - height + noncontent_height - }; + // Height of margin box + clearance + self.base.position.size.height = height + noncontent_height; if inorder { let extra_height = height - (cur_y - top_offset) + bottom_offset; self.base.floats.translate(Point2D(left_offset, -extra_height)); } + + if self.is_root_of_absolute_flow_tree() { + // Assign heights for all flows in this Absolute flow tree. + // This is preorder because the height of an absolute flow may depend on + // the height of its CB, which may also be an absolute flow. + self.traverse_preorder_absolute_flows(&mut AbsoluteAssignHeightsTraversal(ctx)); + // Store overflow for all absolute descendants. + self.traverse_postorder_absolute_flows(&mut AbsoluteStoreOverflowTraversal { + layout_context: ctx, + }); + } + if self.is_root() { + self.assign_height_store_overflow_fixed_flows(ctx); + } } - fn assign_height_float_inorder(&mut self) { - // assign_height_float was already called by the traversal function - // so this is well-defined + /// Assign height for all fixed descendants. + /// + /// A flat iteration over all fixed descendants, passing their respective + /// static y offsets. + /// Also, store overflow immediately because nothing else depends on a + /// fixed flow's height. + fn assign_height_store_overflow_fixed_flows(&mut self, ctx: &mut LayoutContext) { + assert!(self.is_root()); + let mut descendant_offset_iter = self.base.fixed_descendants.iter_with_offset(); + // Pass in the respective static y offset for each descendant. + for (ref mut descendant_link, ref y_offset) in descendant_offset_iter { + match descendant_link.resolve() { + Some(fixed_flow) => { + { + let block = fixed_flow.as_block(); + // The stored y_offset is wrt to the flow box (which + // will is also the CB, so it is the correct final value). + block.static_y_offset = **y_offset; + block.calculate_abs_height_and_margins(ctx); + } + fixed_flow.store_overflow(ctx); + } + None => fail!("empty Rawlink to a descendant") + } + } + } + /// Add placement information about current float flow for use by the parent. + /// + /// Also, use information given by parent about other floats to find out + /// our relative position. + /// + /// This does not give any information about any float descendants because + /// they do not affect elements outside of the subtree rooted at this + /// float. + /// + /// This function is called on a kid flow by a parent. + /// Therefore, assign_height_float was already called on this kid flow by + /// the traversal function. So, the values used are well-defined. + fn assign_height_float_inorder(&mut self) { let mut height = Au(0); let mut clearance = Au(0); let mut full_noncontent_width = Au(0); @@ -480,6 +1164,14 @@ impl BlockFlow { self.float.get_mut_ref().rel_pos = self.base.floats.last_float_pos().unwrap(); } + /// Assign height for current flow. + /// + /// + Set in-flow child flows' y-coordinates now that we know their + /// heights. This _doesn't_ do any margin collapsing for its children. + /// + Calculate height and y-coordinate for the flow's box. Ideally, this + /// should be calculated using CSS Section 10.6.7 + /// + /// It does not calculate the height of the flow itself. fn assign_height_float(&mut self, ctx: &mut LayoutContext) { // Now that we've determined our height, propagate that out. let has_inorder_children = self.base.num_floats > 0; @@ -502,9 +1194,12 @@ impl BlockFlow { cur_y = cur_y + top_offset; } + // cur_y is now at the top content edge + for kid in self.base.child_iter() { let child_base = flow::mut_base(kid); child_base.position.origin.y = cur_y; + // cur_y is now at the bottom margin edge of kid cur_y = cur_y + child_base.position.size.height; } @@ -531,26 +1226,31 @@ impl BlockFlow { box_.border_box.set(position); } + /// Add display items for current block. + /// + /// Set the absolute position for children after doing any offsetting for + /// position: relative. pub fn build_display_list_block( &mut self, builder: &DisplayListBuilder, container_block_size: &Size2D, + absolute_cb_abs_position: Point2D, dirty: &Rect, - mut index: uint, + index: uint, lists: &RefCell>) -> uint { + if self.is_float() { self.build_display_list_float(builder, container_block_size, dirty, index, lists); return index; + } else if self.is_absolutely_positioned() { + self.build_display_list_abs(builder, container_block_size, + absolute_cb_abs_position, + dirty, index, lists); + return index; } - if self.is_fixed { - lists.with_mut(|lists| { - index = lists.lists.len(); - lists.add_list(DisplayList::::new()); - }); - } - + // FIXME: Shouldn't this be the abs_rect _after_ relative positioning? let abs_rect = Rect(self.base.abs_position, self.base.position.size); if !abs_rect.intersects(dirty) { return index; @@ -572,11 +1272,12 @@ impl BlockFlow { // add box that starts block context for box_ in self.box_.iter() { - box_.build_display_list(builder, dirty, self.base.abs_position + rel_offset, (&*self) as &Flow, index, lists); + box_.build_display_list(builder, dirty, self.base.abs_position + rel_offset, + (&*self) as &Flow, index, lists); } + // TODO: handle any out-of-flow elements let this_position = self.base.abs_position; - for child in self.base.child_iter() { let child_base = flow::mut_base(child); child_base.abs_position = this_position + child_base.position.origin + rel_offset; @@ -629,6 +1330,119 @@ impl BlockFlow { false } + + /// Calculate and set the height, offsets, etc. for absolutely positioned flow. + /// + /// The layout for its in-flow children has been done during normal layout. + /// This is just the calculation of: + /// + height for the flow + /// + y-coordinate of the flow wrt its Containing Block. + /// + height, vertical margins, and y-coordinate for the flow's box. + fn calculate_abs_height_and_margins(&mut self, ctx: &LayoutContext) { + let containing_block_height = self.containing_block_size(ctx.screen_size).height; + let static_y_offset = self.static_y_offset; + + for box_ in self.box_.iter() { + // This is the stored content height value from assign-height + let content_height = box_.border_box.get().size.height; + + let style = box_.style(); + + let (height_used_val, margin_top, margin_bottom) = + (MaybeAuto::from_style(style.Box.get().height, containing_block_height), + MaybeAuto::from_style(style.Margin.get().margin_top, containing_block_height), + MaybeAuto::from_style(style.Margin.get().margin_right, containing_block_height)); + + let (top, bottom) = + (MaybeAuto::from_style(style.PositionOffsets.get().top, containing_block_height), + MaybeAuto::from_style(style.PositionOffsets.get().bottom, containing_block_height)); + let available_height = containing_block_height - box_.border_and_padding_vert(); + + let solution = HeightConstraintSolution::solve_vertical_constraints_abs_position( + height_used_val, + margin_top, + margin_bottom, + top, + bottom, + content_height, + available_height, + static_y_offset); + + let mut margin = box_.margin.get(); + margin.top = solution.margin_top; + margin.bottom = solution.margin_bottom; + box_.margin.set(margin); + + let mut position = box_.border_box.get(); + position.origin.y = box_.margin.get().top; + // Border box height + let border_and_padding = box_.border_and_padding_vert(); + position.size.height = solution.height + border_and_padding; + box_.border_box.set(position); + + self.base.position.origin.y = solution.top; + self.base.position.size.height = solution.height + border_and_padding + + solution.margin_top + solution.margin_bottom; + } + } + + /// Add display items for Absolutely Positioned flow. + pub fn build_display_list_abs( + &mut self, + builder: &DisplayListBuilder, + _: &Size2D, + absolute_cb_abs_position: Point2D, + dirty: &Rect, + mut index: uint, + lists: &RefCell>) + -> bool { + let flow_origin = if self.is_fixed() { + // The viewport is initially at (0, 0). + self.base.position.origin + } else { + // Absolute position of Containing Block + position of absolute flow + // wrt Containing Block + absolute_cb_abs_position + self.base.position.origin + }; + + if self.is_fixed() { + lists.with_mut(|lists| { + index = lists.lists.len(); + lists.add_list(DisplayList::::new()); + }); + } + + // Set the absolute position, which will be passed down later as part + // of containing block details for absolute descendants. + self.base.abs_position = flow_origin; + let abs_rect = Rect(flow_origin, self.base.position.size); + if !abs_rect.intersects(dirty) { + return true; + } + + for box_ in self.box_.iter() { + box_.build_display_list(builder, dirty, flow_origin, (&*self) as &Flow, index, lists); + } + + // Go deeper into the flow tree. + for child in self.base.child_iter() { + let child_base = flow::mut_base(child); + child_base.abs_position = flow_origin + child_base.position.origin; + } + + false + } + + /// Return the top outer edge of the Hypothetical Box for an absolute flow. + /// + /// This is wrt its parent flow box. + /// + /// During normal layout assign-height, the absolute flow's position is + /// roughly set to its static position (the position it would have had in + /// the normal flow). + fn get_hypothetical_top_edge(&self) -> Au { + self.base.position.origin.y + } } impl Flow for BlockFlow { @@ -640,6 +1454,10 @@ impl Flow for BlockFlow { self } + fn is_store_overflow_delayed(&mut self) -> bool { + self.is_absolutely_positioned() + } + /* Recursively (bottom-up) determine the context's preferred and minimum widths. When called on this context, all child contexts have had their min/pref widths set. This function must decide @@ -700,80 +1518,40 @@ impl Flow for BlockFlow { "block" }); - if self.is_root { + if self.is_root() { debug!("Setting root position"); self.base.position.origin = Au::zero_point(); self.base.position.size.width = ctx.screen_size.width; self.base.floats = Floats::new(); + // Root element is not floated self.base.flags_info.flags.set_inorder(false); } // The position was set to the containing block by the flow's parent. - let mut remaining_width = self.base.position.size.width; - let mut x_offset = Au::new(0); + let containing_block_width = self.base.position.size.width; + let mut left_content_edge = Au::new(0); + let mut content_width = containing_block_width; if self.is_float() { - self.float.get_mut_ref().containing_width = remaining_width; + self.float.get_mut_ref().containing_width = containing_block_width; // Parent usually sets this, but floats are never inorder self.base.flags_info.flags.set_inorder(false); } + self.calculate_widths_and_margins(containing_block_width, ctx); + for box_ in self.box_.iter() { - let style = box_.style(); - - // The text alignment of a block flow is the text alignment of its box's style. - self.base.flags_info.flags.set_text_align(style.InheritedText.get().text_align); - - box_.assign_width(remaining_width); - // Can compute padding here since we know containing block width. - box_.compute_padding(style, remaining_width); - - // Margins are 0 right now so base.noncontent_width() is just borders + padding. - let available_width = remaining_width - box_.noncontent_width(); - - // Top and bottom margins for blocks are 0 if auto. - let margin_top = MaybeAuto::from_style(style.Margin.get().margin_top, - remaining_width).specified_or_zero(); - let margin_bottom = MaybeAuto::from_style(style.Margin.get().margin_bottom, - remaining_width).specified_or_zero(); - - let (width, margin_left, margin_right) = if self.is_float() { - self.compute_float_margins(box_, remaining_width) - } else { - self.compute_block_margins(box_, remaining_width, available_width) - }; - - box_.margin.set(SideOffsets2D::new(margin_top, - margin_right, - margin_bottom, - margin_left)); - - let screen_size = ctx.screen_size; - let (x, w) = box_.get_x_coord_and_new_width_if_fixed(screen_size.width, - screen_size.height, - width, - box_.offset(), - self.is_fixed); - - x_offset = x; - remaining_width = w; - - // The associated box is the border box of this flow. - let mut position_ref = box_.border_box.borrow_mut(); - if self.is_fixed { - position_ref.get().origin.x = x_offset + box_.margin.get().left; - x_offset = x_offset + box_.padding.get().left; - } else { - position_ref.get().origin.x = box_.margin.get().left; - } + // Move in from the left border edge + left_content_edge = box_.border_box.get().origin.x + + box_.padding.get().left + box_.border.get().left; let padding_and_borders = box_.padding.get().left + box_.padding.get().right + box_.border.get().left + box_.border.get().right; - position_ref.get().size.width = remaining_width + padding_and_borders; + content_width = box_.border_box.get().size.width - padding_and_borders; } if self.is_float() { - self.base.position.size.width = remaining_width; + self.base.position.size.width = content_width; } let has_inorder_children = if self.is_float() { @@ -782,14 +1560,40 @@ impl Flow for BlockFlow { self.base.flags_info.flags.inorder() || self.base.num_floats > 0 }; + let kid_abs_cb_x_offset; + if self.is_positioned() { + match self.box_ { + Some(ref box_) => { + // Pass yourself as a new Containing Block + // The static x offset for any immediate kid flows will be the + // left padding + kid_abs_cb_x_offset = box_.padding.get().left; + } + None => fail!("BlockFlow: no principal box found"), + } + } else { + // For kids, the left margin edge will be at our left content edge. + // The current static offset is at our left margin + // edge. So move in to the left content edge. + kid_abs_cb_x_offset = self.base.absolute_static_x_offset + left_content_edge; + } + let kid_fixed_cb_x_offset = self.base.fixed_static_x_offset + left_content_edge; + // FIXME(ksh8281): avoid copy let flags_info = self.base.flags_info.clone(); for kid in self.base.child_iter() { assert!(kid.is_block_flow() || kid.is_inline_flow()); + if kid.is_block_flow() { + let kid_block = kid.as_block(); + kid_block.base.absolute_static_x_offset = kid_abs_cb_x_offset; + kid_block.base.fixed_static_x_offset = kid_fixed_cb_x_offset; + } let child_base = flow::mut_base(kid); - child_base.position.origin.x = x_offset; - child_base.position.size.width = remaining_width; + // Left margin edge of kid flow is at our left content edge + child_base.position.origin.x = left_content_edge; + // Width of kid flow is our content width + child_base.position.size.width = content_width; child_base.flags_info.flags.set_inorder(has_inorder_children); if !child_base.flags_info.flags.inorder() { @@ -805,6 +1609,10 @@ impl Flow for BlockFlow { } } + /// This is called on kid flows by a parent. + /// + /// Hence, we can assume that assign_height has already been called on the + /// kid (because of the bottom-up traversal). fn assign_height_inorder(&mut self, ctx: &mut LayoutContext) { if self.is_float() { debug!("assign_height_inorder_float: assigning height for float"); @@ -816,7 +1624,7 @@ impl Flow for BlockFlow { } fn assign_height(&mut self, ctx: &mut LayoutContext) { - //assign height for box + // Assign height for box if it is an image box. for box_ in self.box_.iter() { box_.assign_height(); } @@ -828,7 +1636,7 @@ impl Flow for BlockFlow { debug!("assign_height: assigning height for block"); // This is the only case in which a block flow can start an inorder // subtraversal. - if self.is_root && self.base.num_floats > 0 { + if self.is_root() && self.base.num_floats > 0 { self.assign_height_inorder(ctx); return; } @@ -837,6 +1645,10 @@ impl Flow for BlockFlow { } // CSS Section 8.3.1 - Collapsing Margins + // `self`: the Flow whose margins we want to collapse. + // `collapsing`: value to be set by this function. This tells us how much + // of the top margin has collapsed with a previous margin. + // `collapsible`: Potential collapsible margin at the bottom of this flow's box. fn collapse_margins(&mut self, top_margin_collapsible: bool, first_in_flow: &mut bool, @@ -877,10 +1689,64 @@ impl Flow for BlockFlow { self.is_root = true } + /// Return true if store overflow is delayed for this flow. + /// + /// Currently happens only for absolutely positioned flows. + fn is_store_overflow_delayed(&mut self) -> bool { + self.is_absolutely_positioned() + } + + fn is_root(&self) -> bool { + self.is_root + } + + fn is_float(&self) -> bool { + self.float.is_some() + } + + /// The 'position' property of this flow. + fn positioning(&self) -> position::T { + match self.box_ { + Some(ref box_) => { + box_.style.get().Box.get().position + } + None => fail!("BlockFlow does not have a box_") + } + } + + /// Return true if this is the root of an Absolute flow tree. + /// + /// It has to be either relatively positioned or the Root flow. + fn is_root_of_absolute_flow_tree(&self) -> bool { + self.is_relatively_positioned() || self.is_root() + } + + /// Return the dimensions of the CB generated _by_ this flow for absolute descendants. + /// + /// For Blocks, this will be the padding box. + fn generated_cb_size(&self) -> Size2D { + match self.box_ { + Some(ref box_) => { + box_.padding_box_size() + } + None => fail!("Containing Block must have a box") + } + } + + /// Return position of the CB generated by this flow from the start of this flow. + fn generated_cb_position(&self) -> Point2D { + match self.box_ { + Some(ref box_) => { + // Border box y coordinate + border top + box_.border_box.get().origin + Point2D(box_.border.get().left, box_.border.get().top)} + None => fail!("Containing Block must have a box") + } + } + fn debug_str(&self) -> ~str { let txt = if self.is_float() { ~"FloatFlow: " - } else if self.is_root { + } else if self.is_root() { ~"RootFlow: " } else { ~"BlockFlow: " diff --git a/servo/src/components/main/layout/box_.rs b/servo/src/components/main/layout/box_.rs index 7a230bf09c12..7b8062e5dfc6 100644 --- a/servo/src/components/main/layout/box_.rs +++ b/servo/src/components/main/layout/box_.rs @@ -605,6 +605,22 @@ impl Box { specified(padding, content_box_width) } + pub fn padding_box_size(&self) -> Size2D { + let border_box_size = self.border_box.get().size; + Size2D(border_box_size.width - self.border.get().left - self.border.get().right, + border_box_size.height - self.border.get().top - self.border.get().bottom) + } + + pub fn border_and_padding_horiz(&self) -> Au { + self.border.get().left + self.border.get().right + self.padding.get().left + + self.padding.get().right + } + + pub fn border_and_padding_vert(&self) -> Au { + self.border.get().top + self.border.get().bottom + self.padding.get().top + + self.padding.get().bottom + } + pub fn noncontent_width(&self) -> Au { self.noncontent_left() + self.noncontent_right() } @@ -613,6 +629,7 @@ impl Box { self.noncontent_top() + self.noncontent_bottom() } + // Return offset from original position because of `position: relative`. pub fn relative_position(&self, container_block_size: &Size2D) -> Point2D { fn left_right(style: &ComputedValues, block_width: Au) -> Au { // TODO(ksh8281) : consider RTL(right-to-left) culture @@ -651,6 +668,7 @@ impl Box { rel_pos.y = rel_pos.y + top_bottom(self.style(), container_block_size.height); } + // Go over the ancestor boxes and add all relative offsets (if any). let info = self.inline_info.borrow(); match info.get() { &Some(ref info) => { @@ -977,7 +995,7 @@ impl Box { /// Arguments: /// * `builder`: The display list builder, which manages the coordinate system and options. /// * `dirty`: The dirty rectangle in the coordinate system of the owning flow. - /// * `origin`: The total offset from the display list root flow to the owning flow of this + /// * `flow_origin`: Position of the origin of the owning flow wrt the display list root flow. /// box. /// * `list`: The display list to which items should be appended. /// @@ -990,15 +1008,16 @@ impl Box { &self, builder: &DisplayListBuilder, dirty: &Rect, - offset: Point2D, + flow_origin: Point2D, flow: &Flow, index: uint, lists: &RefCell>) { + // Box position wrt to the owning flow. let box_bounds = self.border_box.get(); - let absolute_box_bounds = box_bounds.translate(&offset); + let absolute_box_bounds = box_bounds.translate(&flow_origin); debug!("Box::build_display_list at rel={}, abs={}: {:s}", box_bounds, absolute_box_bounds, self.debug_str()); - debug!("Box::build_display_list: dirty={}, offset={}", *dirty, offset); + debug!("Box::build_display_list: dirty={}, flow_origin={}", *dirty, flow_origin); if self.style().InheritedBox.get().visibility != visibility::visible { return; @@ -1011,10 +1030,15 @@ impl Box { return; } - self.paint_inline_background_border_if_applicable(index, lists, &absolute_box_bounds, &offset); + self.paint_inline_background_border_if_applicable(index, lists, &absolute_box_bounds, &flow_origin); // Add the background to the list, if applicable. self.paint_background_if_applicable(builder, index, lists, &absolute_box_bounds); + // Add a border, if applicable. + // + // TODO: Outlines. + self.paint_borders_if_applicable(index, lists, &absolute_box_bounds); + match self.specific { UnscannedTextBox(_) => fail!("Shouldn't see unscanned boxes here."), ScannedTextBox(ref text_box) => { @@ -1122,6 +1146,7 @@ impl Box { // FIXME(pcwalton): This is a bit of an abuse of the logging infrastructure. We // should have a real `SERVO_DEBUG` system. debug!("{:?}", { + // This prints a debug border around the border of this box. let debug_border = SideOffsets2D::new_all_same(Au::from_px(1)); lists.with_mut(|lists| { @@ -1208,15 +1233,11 @@ impl Box { // iframe is actually going to be displayed. match self.specific { IframeBox(ref iframe_box) => { - self.finalize_position_and_size_of_iframe(iframe_box, offset, builder.ctx) + self.finalize_position_and_size_of_iframe(iframe_box, flow_origin, builder.ctx) } GenericBox | ImageBox(_) | ScannedTextBox(_) | UnscannedTextBox(_) => {} } - // Add a border, if applicable. - // - // TODO: Outlines. - self.paint_borders_if_applicable(index, lists, &absolute_box_bounds); } /// Returns the *minimum width* and *preferred width* of this box as defined by CSS 2.1. @@ -1432,8 +1453,10 @@ impl Box { } } - /// Assigns the appropriate width to this box. - pub fn assign_width(&self,container_width: Au) { + /// Assigns replaced width for this box only if it is replaced content. + /// + /// CSS 2.1 § 10.3.2. + pub fn assign_replaced_width_if_necessary(&self,container_width: Au) { match self.specific { GenericBox | IframeBox(_) => { } @@ -1469,7 +1492,8 @@ impl Box { image_box_info.computed_width.set(Some(width)); } ScannedTextBox(_) => { - // Scanned text boxes will have already had their content_widths assigned by this point. + // Scanned text boxes will have already had their + // content_widths assigned by this point. let mut position = self.border_box.borrow_mut(); position.get().size.width = position.get().size.width + self.noncontent_width() + self.noncontent_inline_left() + self.noncontent_inline_right(); @@ -1478,6 +1502,7 @@ impl Box { } } + /// Assign height for image and scanned text boxes. pub fn assign_height(&self) { match self.specific { GenericBox | IframeBox(_) => { @@ -1514,6 +1539,8 @@ impl Box { ScannedTextBox(_) => { // Scanned text boxes will have already had their widths assigned by this point let mut position = self.border_box.borrow_mut(); + // Scanned text boxes' content heights are calculated by the + // text run scanner during Flow construction. position.get().size.height = position.get().size.height + self.noncontent_height() } diff --git a/servo/src/components/main/layout/construct.rs b/servo/src/components/main/layout/construct.rs index d91d538464f5..745ee21c1738 100644 --- a/servo/src/components/main/layout/construct.rs +++ b/servo/src/components/main/layout/construct.rs @@ -28,6 +28,8 @@ use layout::box_::{UnscannedTextBoxInfo}; use layout::context::LayoutContext; use layout::floats::FloatKind; use layout::flow::{Flow, MutableOwnedFlowUtils}; +use layout::flow::{Descendants, AbsDescendants, FixedDescendants}; +use layout::flow_list::{Rawlink}; use layout::inline::InlineFlow; use layout::text::TextRunScanner; use layout::util::{LayoutDataAccess, OpaqueNode}; @@ -59,9 +61,10 @@ pub enum ConstructionResult { /// 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(~Flow), + /// This node contributed a flow at the proper position in the tree. + /// Nothing more needs to be done for this node. It has bubbled up fixed + /// and absolute descendant flows that have a CB above it. + FlowConstructionResult(~Flow, AbsDescendants, FixedDescendants), /// 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. @@ -72,7 +75,7 @@ impl ConstructionResult { fn destroy(&mut self) { match *self { NoConstructionResult => {} - FlowConstructionResult(ref mut flow) => flow.destroy(), + FlowConstructionResult(ref mut flow, _, _) => flow.destroy(), ConstructionItemConstructionResult(ref mut item) => item.destroy(), } } @@ -112,6 +115,12 @@ struct InlineBoxesConstructionResult { /// Any boxes that succeed the {ib} splits. boxes: ~[Box], + + /// Any absolute descendants that we're bubbling up. + abs_descendants: AbsDescendants, + + /// Any fixed descendants that we're bubbling up. + fixed_descendants: FixedDescendants, } /// Represents an {ib} split that has not yet found the containing block that it belongs to. This @@ -155,7 +164,7 @@ impl InlineBlockSplit { /// Methods on optional vectors. /// /// TODO(pcwalton): I think this will no longer be necessary once Rust #8981 lands. -trait OptVector { +pub trait OptVector { /// 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]; @@ -316,17 +325,27 @@ impl<'a> FlowConstructor<'a> { } } - /// 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(&mut self, flow: &mut ~Flow, node: &ThreadSafeLayoutNode) { + /// Build block flow for current node using information from children nodes. + /// + /// Consume results from children and combine them, handling {ib} splits. + /// Block flows and inline flows thus created will become the children of + /// this block flow. + /// Also, deal with the absolute and fixed descendants bubbled up by + /// children nodes. + fn build_block_flow_using_children(&mut self, + mut flow: ~Flow, + node: &ThreadSafeLayoutNode) + -> ConstructionResult { // 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; + // List of absolute descendants, in tree order. + let mut abs_descendants = Descendants::new(); + let mut fixed_descendants = Descendants::new(); for kid in node.children() { match kid.swap_out_construction_result() { NoConstructionResult => {} - FlowConstructionResult(kid_flow) => { + FlowConstructionResult(kid_flow, kid_abs_descendants, kid_fixed_descendants) => { // Strip ignorable whitespace from the start of this flow per CSS 2.1 § // 9.2.1.1. if first_box { @@ -340,14 +359,19 @@ impl<'a> FlowConstructor<'a> { opt_boxes_for_inline_flow.as_ref() .map_default(0, |boxes| boxes.len())); self.flush_inline_boxes_to_flow_if_necessary(&mut opt_boxes_for_inline_flow, - flow, + &mut flow, node); - flow.add_new_child(kid_flow) + flow.add_new_child(kid_flow); + abs_descendants.push_descendants(kid_abs_descendants); + fixed_descendants.push_descendants(kid_fixed_descendants); + } ConstructionItemConstructionResult(InlineBoxesConstructionItem( InlineBoxesConstructionResult { splits: opt_splits, - boxes: boxes + boxes: boxes, + abs_descendants: kid_abs_descendants, + fixed_descendants: kid_fixed_descendants, })) => { // Add any {ib} splits. match opt_splits { @@ -377,7 +401,7 @@ impl<'a> FlowConstructor<'a> { |boxes| boxes.len())); self.flush_inline_boxes_to_flow_if_necessary( &mut opt_boxes_for_inline_flow, - flow, + &mut flow, node); // Push the flow generated by the {ib} split onto our list of @@ -388,7 +412,9 @@ impl<'a> FlowConstructor<'a> { } // Add the boxes to the list we're maintaining. - opt_boxes_for_inline_flow.push_all_move(boxes) + opt_boxes_for_inline_flow.push_all_move(boxes); + abs_descendants.push_descendants(kid_abs_descendants); + fixed_descendants.push_descendants(kid_fixed_descendants); } ConstructionItemConstructionResult(WhitespaceConstructionItem(..)) => { // Nothing to do here. @@ -400,29 +426,45 @@ impl<'a> FlowConstructor<'a> { // 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, + &mut flow, node); - // The flow is done. If it ended up with no kids, add the flow to the leaf set. - flow.finish(self.layout_context) + // The flow is done. + flow.finish(self.layout_context); + let is_positioned = flow.as_block().is_positioned(); + let is_fixed_positioned = flow.as_block().is_fixed(); + let is_absolutely_positioned = flow.as_block().is_absolutely_positioned(); + if is_positioned { + // This is the CB for all the absolute descendants. + flow.set_abs_descendants(abs_descendants); + abs_descendants = Descendants::new(); + + if is_fixed_positioned { + // Send itself along with the other fixed descendants. + fixed_descendants.push(Rawlink::some(flow)); + } else if is_absolutely_positioned { + // This is now the only absolute flow in the subtree which hasn't yet + // reached its CB. + abs_descendants.push(Rawlink::some(flow)); + } + } + FlowConstructionResult(flow, abs_descendants, fixed_descendants) } /// 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(&mut self, node: &ThreadSafeLayoutNode, is_fixed: bool) -> ~Flow { - let mut flow = ~BlockFlow::from_node(self, node, is_fixed) as ~Flow; - self.build_children_of_block_flow(&mut flow, node); - flow + fn build_flow_for_block(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult { + let flow = ~BlockFlow::from_node(self, node) as ~Flow; + self.build_block_flow_using_children(flow, node) } /// Builds the flow for a node with `float: {left|right}`. This yields a float `BlockFlow` with /// a `BlockFlow` underneath it. fn build_flow_for_floated_block(&mut self, node: &ThreadSafeLayoutNode, float_kind: FloatKind) - -> ~Flow { - let mut flow = ~BlockFlow::float_from_node(self, node, float_kind) as ~Flow; - self.build_children_of_block_flow(&mut flow, node); - flow + -> ConstructionResult { + let flow = ~BlockFlow::float_from_node(self, node, float_kind) as ~Flow; + self.build_block_flow_using_children(flow, node) } @@ -433,24 +475,30 @@ impl<'a> FlowConstructor<'a> { -> ConstructionResult { let mut opt_inline_block_splits = None; let mut opt_box_accumulator = None; - + let mut abs_descendants = Descendants::new(); + let mut fixed_descendants = Descendants::new(); + // Concatenate all the boxes of our kids, creating {ib} splits as necessary. for kid in node.children() { match kid.swap_out_construction_result() { NoConstructionResult => {} - FlowConstructionResult(flow) => { + FlowConstructionResult(flow, kid_abs_descendants, kid_fixed_descendants) => { // {ib} split. Flush the accumulator to our new split and make a new // accumulator to hold any subsequent boxes 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) + opt_inline_block_splits.push(split); + abs_descendants.push_descendants(kid_abs_descendants); + fixed_descendants.push_descendants(kid_fixed_descendants); } ConstructionItemConstructionResult(InlineBoxesConstructionItem( InlineBoxesConstructionResult { splits: opt_splits, - boxes: boxes + boxes: boxes, + abs_descendants: kid_abs_descendants, + fixed_descendants: kid_fixed_descendants, })) => { // Bubble up {ib} splits. @@ -475,7 +523,9 @@ impl<'a> FlowConstructor<'a> { } // Push residual boxes. - opt_box_accumulator.push_all_move(boxes) + opt_box_accumulator.push_all_move(boxes); + abs_descendants.push_descendants(kid_abs_descendants); + fixed_descendants.push_descendants(kid_fixed_descendants); } ConstructionItemConstructionResult(WhitespaceConstructionItem(whitespace_node, whitespace_style)) @@ -533,10 +583,14 @@ impl<'a> FlowConstructor<'a> { } // Finally, make a new construction result. - if opt_inline_block_splits.len() > 0 || opt_box_accumulator.len() > 0 { + if opt_inline_block_splits.len() > 0 || opt_box_accumulator.len() > 0 + || abs_descendants.len() > 0 { + let construction_item = InlineBoxesConstructionItem(InlineBoxesConstructionResult { splits: opt_inline_block_splits, boxes: opt_box_accumulator.to_vec(), + abs_descendants: abs_descendants, + fixed_descendants: fixed_descendants, }); ConstructionItemConstructionResult(construction_item) } else { @@ -612,6 +666,8 @@ impl<'a> FlowConstructor<'a> { boxes: ~[ Box::new(self, node) ], + abs_descendants: Descendants::new(), + fixed_descendants: Descendants::new(), }); ConstructionItemConstructionResult(construction_item) } @@ -636,7 +692,7 @@ impl<'a> PostorderNodeMutTraversal for FlowConstructor<'a> { #[inline(always)] fn process(&mut self, node: &ThreadSafeLayoutNode) -> bool { // Get the `display` property for this node, and determine whether this node is floated. - let (display, float, position) = match node.type_id() { + let (display, float, positioning) = match node.type_id() { ElementNodeTypeId(_) => { let style = node.style().get(); (style.Box.get().display, style.Box.get().float, style.Box.get().position) @@ -652,7 +708,7 @@ impl<'a> PostorderNodeMutTraversal for FlowConstructor<'a> { debug!("building flow for node: {:?} {:?}", display, float); // Switch on display and floatedness. - match (display, float, position) { + match (display, float, positioning) { // `display: none` contributes no flow construction result. Nuke the flow construction // results of children. (display::none, _, _) => { @@ -662,6 +718,10 @@ impl<'a> PostorderNodeMutTraversal for FlowConstructor<'a> { } } + (_, _, position::absolute) | (_, _, position::fixed) => { + node.set_flow_construction_result(self.build_flow_for_block(node)) + } + // Inline items contribute inline box construction results. (display::inline, float::none, _) => { let construction_result = self.build_boxes_for_inline(node); @@ -673,20 +733,15 @@ impl<'a> PostorderNodeMutTraversal for FlowConstructor<'a> { // TODO(pcwalton): Make this only trigger for blocks and handle the other `display` // properties separately. - (_, _, position::fixed) => { - let flow = self.build_flow_for_block(node, true); - node.set_flow_construction_result(FlowConstructionResult(flow)) - } (_, float::none, _) => { - let flow = self.build_flow_for_block(node, false); - node.set_flow_construction_result(FlowConstructionResult(flow)) + node.set_flow_construction_result(self.build_flow_for_block(node)) } // Floated flows contribute float flow construction results. (_, float_value, _) => { let float_kind = FloatKind::from_property(float_value); - let flow = self.build_flow_for_floated_block(node, float_kind); - node.set_flow_construction_result(FlowConstructionResult(flow)) + node.set_flow_construction_result( + self.build_flow_for_floated_block(node, float_kind)) } } @@ -779,7 +834,7 @@ trait ObjectElement { /// Returns true if this node has object data that is correct uri. fn has_object_data(&self) -> bool; - /// Returns the "data" attribute value parsed as a URL + /// Returns the "data" attribute value parsed as a URL fn get_object_data(&self, base_url: &Url) -> Option; } @@ -793,7 +848,7 @@ impl<'ln> ObjectElement for ThreadSafeLayoutNode<'ln> { match self.get_type_and_data() { (None, Some(uri)) => is_image_data(uri), _ => false - } + } } fn get_object_data(&self, base_url: &Url) -> Option { @@ -854,4 +909,3 @@ fn strip_ignorable_whitespace_from_end(opt_boxes: &mut Option<~[Box]>) { *opt_boxes = None } } - diff --git a/servo/src/components/main/layout/flow.rs b/servo/src/components/main/layout/flow.rs index b476e8b07b7f..d8abda6d7e89 100644 --- a/servo/src/components/main/layout/flow.rs +++ b/servo/src/components/main/layout/flow.rs @@ -16,7 +16,7 @@ /// /// * `BlockFlow`: A flow that establishes a block context. It has several child flows, each of /// which are positioned according to block formatting context rules (CSS block boxes). Block -/// flows also contain a single `GenericBox` to represent their rendered borders, padding, etc. +/// flows also contain a single box to represent their rendered borders, padding, etc. /// The BlockFlow at the root of the tree has special behavior: it stretches to the boundaries of /// the viewport. /// @@ -26,9 +26,10 @@ /// similar methods. use css::node_style::StyledNode; -use layout::block::BlockFlow; +use layout::block::{BlockFlow}; use layout::box_::Box; use layout::context::LayoutContext; +use layout::construct::OptVector; use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; use layout::floats::Floats; use layout::incremental::RestyleDamage; @@ -45,12 +46,15 @@ use geom::rect::Rect; use gfx::display_list::{ClipDisplayItemClass, DisplayListCollection, DisplayList}; use layout::display_list_builder::ToGfxColor; use gfx::color::Color; +use servo_util::smallvec::{SmallVec, SmallVec0}; use servo_util::geometry::Au; use std::cast; use std::cell::RefCell; use std::sync::atomics::Relaxed; +use std::vec::VecMutIterator; +use std::iter::Zip; use style::ComputedValues; -use style::computed_values::text_align; +use style::computed_values::{text_align, position}; /// Virtual methods that make up a float context. /// @@ -116,6 +120,63 @@ pub trait Flow { /// Marks this flow as the root flow. The default implementation is a no-op. fn mark_as_root(&mut self) {} + // Note that the following functions are mostly called using static method + // dispatch, so it's ok to have them in this trait. Plus, they have + // different behaviour for different types of Flow, so they can't go into + // the Immutable / Mutable Flow Utils traits without additional casts. + + /// Return true if store overflow is delayed for this flow. + /// + /// Currently happens only for absolutely positioned flows. + fn is_store_overflow_delayed(&mut self) -> bool { + false + } + + fn is_root(&self) -> bool { + false + } + + fn is_float(&self) -> bool { + false + } + + /// The 'position' property of this flow. + fn positioning(&self) -> position::T { + position::static_ + } + + /// Return true if this flow has position 'fixed'. + fn is_fixed(&self) -> bool { + self.positioning() == position::fixed + } + + fn is_positioned(&self) -> bool { + self.is_relatively_positioned() || self.is_absolutely_positioned() + } + + fn is_relatively_positioned(&self) -> bool { + self.positioning() == position::relative + } + + fn is_absolutely_positioned(&self) -> bool { + self.positioning() == position::absolute || self.is_fixed() + } + + /// Return true if this is the root of an Absolute flow tree. + fn is_root_of_absolute_flow_tree(&self) -> bool { + false + } + + /// Return the dimensions of the CB generated _by_ this flow for absolute descendants. + fn generated_cb_size(&self) -> Size2D { + fail!("generated_cb_size not yet implemented") + } + + /// Return position of the CB generated by this flow from the start of this flow. + fn generated_cb_position(&self) -> Point2D { + fail!("this is not the CB-generating flow you're looking for") + } + /// Returns a debugging string describing this flow. fn debug_str(&self) -> ~str { ~"???" @@ -208,6 +269,7 @@ pub trait MutableFlowUtils { self, builder: &DisplayListBuilder, container_block_size: &Size2D, + absolute_cb_abs_position: Point2D, dirty: &Rect, index: uint, mut list: &RefCell>) @@ -230,6 +292,16 @@ pub trait MutableOwnedFlowUtils { /// properly computed. (This is not, however, a memory safety problem.) fn finish(&mut self, context: &mut LayoutContext); + /// Set absolute descendants for this flow. + /// + /// Set this flow as the Containing Block for all the absolute descendants. + fn set_abs_descendants(&mut self, abs_descendants: AbsDescendants); + + /// Set fixed descendants for this flow. + /// + /// Set yourself as the Containing Block for all the fixed descendants. + fn set_fixed_descendants(&mut self, fixed_descendants: AbsDescendants); + /// Destroys the flow. fn destroy(&mut self); } @@ -483,6 +555,61 @@ impl FlowFlags { } } +/// The Descendants of a flow. +/// +/// Also, details about their position wrt this flow. +/// FIXME: This should use @pcwalton's reference counting scheme (Coming Soon). +pub struct Descendants { + /// Links to every Descendant. + descendant_links: SmallVec0, + /// Static y offsets of all descendants from the start of this flow box. + static_y_offsets: SmallVec0, +} + +impl Descendants { + pub fn new() -> Descendants { + Descendants { + descendant_links: SmallVec0::new(), + static_y_offsets: SmallVec0::new(), + } + } + + pub fn len(&self) -> uint { + self.descendant_links.len() + } + + pub fn push(&mut self, given_descendant: Rawlink) { + self.descendant_links.push(given_descendant); + } + + /// Push the given descendants on to the existing descendants. + /// + /// Ignore any static y offsets, because they are None before layout. + pub fn push_descendants(&mut self, mut given_descendants: Descendants) { + for elem in given_descendants.descendant_links.move_iter() { + self.descendant_links.push(elem); + } + } + + /// Return an iterator over the descendant flows. + pub fn iter<'a>(&'a mut self) -> DescendantIter<'a> { + self.descendant_links.mut_slice_from(0).mut_iter() + } + + /// Return an iterator over (descendant, static y offset). + pub fn iter_with_offset<'a>(&'a mut self) -> DescendantOffsetIter<'a> { + self.descendant_links.mut_slice_from(0).mut_iter().zip( + self.static_y_offsets.mut_slice_from(0).mut_iter()) + } +} + +pub type AbsDescendants = Descendants; +pub type FixedDescendants = Descendants; + +type DescendantIter<'a> = VecMutIterator<'a, Rawlink>; + +type DescendantOffsetIter<'a> = Zip, VecMutIterator<'a, Au>>; + /// Data common to all flows. pub struct BaseFlow { restyle_damage: RestyleDamage, @@ -498,8 +625,9 @@ pub struct BaseFlow { min_width: Au, pref_width: Au, - /// The position of the upper left corner of the border box of this flow, relative to the - /// containing block. + /// The upper left corner of the box representing this flow, relative to + /// the box representing its parent flow. + /// For absolute flows, this represents the position wrt to its Containing Block. position: Rect, /// The amount of overflow of this flow, relative to the containing block. Must include all the @@ -514,12 +642,32 @@ pub struct BaseFlow { /// The floats next to this flow. floats: Floats, - /// The number of floated descendants of this flow (including this flow, if it's floated). + /// For normal flows, this is the number of floated descendants that are + /// not contained within any other floated descendant of this flow. For + /// floats, it is 1. + /// It is used to allocate float data if necessary and to + /// decide whether to do an in-order traversal for assign_height. num_floats: uint, /// The position of this flow in page coordinates, computed during display list construction. abs_position: Point2D, + /// Details about descendants with position 'absolute' for which we are + /// the CB. This is in tree order. This includes any direct children. + abs_descendants: AbsDescendants, + /// Details about descendants with position 'fixed'. + /// TODO: Optimize this, because this will be set only for the root. + fixed_descendants: FixedDescendants, + + /// Offset wrt the nearest positioned ancestor - aka the Containing Block + /// for any absolutely positioned elements. + absolute_static_x_offset: Au, + /// Offset wrt the Initial Containing Block. + fixed_static_x_offset: Au, + + /// Reference to the Containing Block, if this flow is absolutely positioned. + absolute_cb: Rawlink, + /// Whether this flow has been destroyed. /// /// TODO(pcwalton): Pack this into the flags? Need to be careful because manipulation of this @@ -576,6 +724,11 @@ impl BaseFlow { floats: Floats::new(), num_floats: 0, abs_position: Point2D(Au::new(0), Au::new(0)), + abs_descendants: Descendants::new(), + fixed_descendants: Descendants::new(), + absolute_static_x_offset: Au::new(0), + fixed_static_x_offset: Au::new(0), + absolute_cb: Rawlink::none(), destroyed: false, @@ -707,15 +860,56 @@ impl<'a> MutableFlowUtils for &'a mut Flow { f(mut_base(self).children.back_mut()) } + /// Calculate and set overflow for current flow. + /// + /// CSS Section 11.1 + /// This is the union of rectangles of the flows for which we define the + /// Containing Block. + /// + /// Assumption: This is called in a bottom-up traversal, so kids' overflows have + /// already been set. + /// Assumption: Absolute descendants have had their overflow calculated. fn store_overflow(self, _: &mut LayoutContext) { let my_position = mut_base(self).position; let mut overflow = my_position; - for kid in mut_base(self).child_iter() { - let mut kid_overflow = base(kid).overflow; - kid_overflow = kid_overflow.translate(&my_position.origin); - overflow = overflow.union(&kid_overflow) + + if self.is_block_container() { + for kid in child_iter(self) { + if kid.is_store_overflow_delayed() { + // Absolute flows will be handled by their CB. If we are + // their CB, they will show up in `abs_descendants`. + continue; + } + let mut kid_overflow = base(kid).overflow; + kid_overflow = kid_overflow.translate(&my_position.origin); + overflow = overflow.union(&kid_overflow) + } + + for descendant_link in mut_base(self).abs_descendants.iter() { + match descendant_link.resolve() { + Some(flow) => { + let mut kid_overflow = base(flow).overflow; + kid_overflow = kid_overflow.translate(&my_position.origin); + overflow = overflow.union(&kid_overflow) + } + None => fail!("empty Rawlink to a descendant") + } + } + + if self.is_root() { + for fixed_descendant_link in mut_base(self).fixed_descendants.iter() { + match fixed_descendant_link.resolve() { + Some(flow) => { + let mut kid_overflow = base(flow).overflow; + kid_overflow = kid_overflow.translate(&my_position.origin); + overflow = overflow.union(&kid_overflow) + } + None => fail!("empty Rawlink to a descendant") + } + } + } } - mut_base(self).overflow = overflow + mut_base(self).overflow = overflow; } /// Push display items for current flow and its children onto `list`. @@ -723,17 +917,29 @@ impl<'a> MutableFlowUtils for &'a mut Flow { /// For InlineFlow, add display items for all its boxes onto list`. /// For BlockFlow, add a ClipDisplayItemClass for itself and its children, /// plus any other display items like border. + /// + /// `container_block_size`: Size of the Containing Block for the current + /// flow. This is used for relative positioning (which resolves percentage + /// values for 'top', etc. after all Containing Block heights have been computed.) + /// `absolute_cb_abs_position`: Absolute position of the Containing Block + /// for the flow if it is absolutely positioned. fn build_display_lists( self, builder: &DisplayListBuilder, container_block_size: &Size2D, + absolute_cb_abs_position: Point2D, dirty: &Rect, mut index: uint, lists: &RefCell>) -> bool { debug!("Flow: building display list"); index = match self.class() { - BlockFlowClass => self.as_block().build_display_list_block(builder, container_block_size, dirty, index, lists), + BlockFlowClass => self.as_block().build_display_list_block(builder, + container_block_size, + absolute_cb_abs_position, + dirty, + index, + lists), InlineFlowClass => self.as_inline().build_display_list_inline(builder, container_block_size, dirty, index, lists), }; @@ -742,24 +948,68 @@ impl<'a> MutableFlowUtils for &'a mut Flow { } if self.is_block_container() { + let block = self.as_block(); let mut child_lists = DisplayListCollection::new(); child_lists.add_list(DisplayList::new()); let child_lists = RefCell::new(child_lists); - let container_block_size = match self.class() { - BlockFlowClass => { - if self.as_block().box_.is_some() { - self.as_block().box_.get_ref().border_box.get().size - } else { - base(self).position.size - } - }, - _ => { - base(self).position.size - } - }; + let container_block_size; + let abs_cb_position; + // TODO(pradeep): Move this into a generated CB function and stuff in Flow. + match block.box_ { + Some(ref box_) => { + // FIXME: This should be the size of the content box (which is the + // Containing Block formed by a BlockFlow), not the border box. + container_block_size = box_.border_box.get().size; - for kid in child_iter(self) { - kid.build_display_lists(builder, &container_block_size, dirty, 0u, &child_lists); + abs_cb_position = if block.is_positioned() { + block.base.abs_position + block.generated_cb_position() + } else { + absolute_cb_abs_position + }; + } + None => fail!("Flow: block container should have a box_") + } + + for kid in block.base.child_iter() { + if kid.is_absolutely_positioned() { + // All absolute flows will be handled by their CB. + continue; + } + kid.build_display_lists(builder, &container_block_size, + abs_cb_position, + dirty, 0u, &child_lists); + } + + // TODO: Maybe we should handle position 'absolute' and 'fixed' + // descendants before normal descendants just in case there is a + // problem when display-list building is parallel and both the + // original parent and this flow access the same absolute flow. + // Note that this can only be done once we have paint order + // working cos currently the later boxes paint over the absolute + // and fixed boxes :| + for abs_descendant_link in block.base.abs_descendants.iter() { + match abs_descendant_link.resolve() { + Some(flow) => { + // TODO(pradeep): Send in your abs_position directly. + flow.build_display_lists(builder, &container_block_size, + abs_cb_position, + dirty, 0u, &child_lists); + } + None => fail!("empty Rawlink to a descendant") + } + } + + if block.is_root() { + for fixed_descendant_link in block.base.fixed_descendants.iter() { + match fixed_descendant_link.resolve() { + Some(flow) => { + flow.build_display_lists(builder, &container_block_size, + abs_cb_position, + dirty, 0u, &child_lists); + } + None => fail!("empty Rawlink to a descendant") + } + } } let mut child_lists = Some(child_lists.unwrap()); @@ -822,10 +1072,54 @@ impl MutableOwnedFlowUtils for ~Flow { } } + /// Set absolute descendants for this flow. + /// + /// Set yourself as the Containing Block for all the absolute descendants. + /// + /// Assumption: This is called in a bottom-up traversal, so that nothing + /// else is accessing the descendant flows. + fn set_abs_descendants(&mut self, abs_descendants: AbsDescendants) { + let self_link = Rawlink::some(*self); + let block = self.as_block(); + block.base.abs_descendants = abs_descendants; + + for descendant_link in block.base.abs_descendants.iter() { + match descendant_link.resolve() { + Some(flow) => { + let base = mut_base(flow); + base.absolute_cb = self_link.clone(); + } + None => fail!("empty Rawlink to a descendant") + } + } + } + + /// Set fixed descendants for this flow. + /// + /// Set yourself as the Containing Block for all the fixed descendants. + /// + /// Assumption: This is called in a bottom-up traversal, so that nothing + /// else is accessing the descendant flows. + /// Assumption: This is the root flow. + fn set_fixed_descendants(&mut self, fixed_descendants: FixedDescendants) { + let self_link = Rawlink::some(*self); + let block = self.as_block(); + block.base.fixed_descendants = fixed_descendants; + + for descendant_link in block.base.fixed_descendants.iter() { + match descendant_link.resolve() { + Some(flow) => { + let base = mut_base(flow); + base.absolute_cb = self_link.clone(); + } + None => fail!("empty Rawlink to a descendant") + } + } + } + /// Destroys the flow. fn destroy(&mut self) { let self_borrowed: &mut Flow = *self; self_borrowed.destroy(); } } - diff --git a/servo/src/components/main/layout/flow_list.rs b/servo/src/components/main/layout/flow_list.rs index 232b718fba81..330bf78c2a8f 100644 --- a/servo/src/components/main/layout/flow_list.rs +++ b/servo/src/components/main/layout/flow_list.rs @@ -19,6 +19,10 @@ pub struct Rawlink { priv obj: *mut (), } +/// Doubly-linked list of Flows. +/// +/// The forward links are strong references. +/// The backward links are weak references. pub struct FlowList { priv length: uint, priv list_head: Link, @@ -51,7 +55,7 @@ impl Rawlink { } /// Like Option::Some for Rawlink - fn some(n: &mut Flow) -> Rawlink { + pub fn some(n: &mut Flow) -> Rawlink { unsafe { cast::transmute(n) } } @@ -65,7 +69,7 @@ impl Rawlink { } } - fn resolve(&mut self) -> Option<&mut Flow> { + pub fn resolve(&mut self) -> Option<&mut Flow> { if self.obj.is_null() { None } else { diff --git a/servo/src/components/main/layout/inline.rs b/servo/src/components/main/layout/inline.rs index 4292ab322748..6abfd6639e84 100644 --- a/servo/src/components/main/layout/inline.rs +++ b/servo/src/components/main/layout/inline.rs @@ -657,18 +657,13 @@ impl Flow for InlineFlow { { let this = &mut *self; for box_ in this.boxes.iter() { - box_.assign_width(self.base.position.size.width); + box_.assign_replaced_width_if_necessary(self.base.position.size.width); } } - // FIXME(ksh8281) avoid copy - let flags_info = self.base.flags_info.clone(); - for kid in self.base.child_iter() { - let child_base = flow::mut_base(kid); - child_base.position.size.width = self.base.position.size.width; - child_base.flags_info.flags.set_inorder(self.base.flags_info.flags.inorder()); - child_base.flags_info.propagate_text_alignment_from_parent(&flags_info) - } + assert!(self.base.children.len() == 0, + "InlineFlow: should not have children flows in the current layout implementation."); + // There are no child contexts, so stop here. // TODO(Issue #225): once there are 'inline-block' elements, this won't be @@ -685,6 +680,9 @@ impl Flow for InlineFlow { self.assign_height(ctx); } + /// Calculate and set the height of this Flow. + /// + /// CSS Section 10.6.1 fn assign_height(&mut self, _: &mut LayoutContext) { debug!("assign_height_inline: assigning height for flow"); @@ -901,4 +899,3 @@ impl Flow for InlineFlow { ~"InlineFlow: " + self.boxes.map(|s| s.debug_str()).connect(", ") } } - diff --git a/servo/src/components/main/layout/layout_task.rs b/servo/src/components/main/layout/layout_task.rs index b8e8b022c9ad..0fdacec78ade 100644 --- a/servo/src/components/main/layout/layout_task.rs +++ b/servo/src/components/main/layout/layout_task.rs @@ -23,6 +23,7 @@ use layout::wrapper::{LayoutNode, TLayoutNode, ThreadSafeLayoutNode}; use extra::url::Url; use extra::arc::{Arc, MutexArc}; +use geom::point::Point2D; use geom::rect::Rect; use geom::size::Size2D; use gfx::display_list::{ClipDisplayItemClass, DisplayItem, DisplayItemIterator}; @@ -211,7 +212,11 @@ impl<'a> PostorderFlowTraversal for AssignHeightsAndStoreOverflowTraversal<'a> { #[inline] fn process(&mut self, flow: &mut Flow) -> bool { flow.assign_height(self.layout_context); - flow.store_overflow(self.layout_context); + // Skip store-overflow for absolutely positioned flows. That will be + // done in a separate traversal. + if !flow.is_store_overflow_delayed() { + flow.store_overflow(self.layout_context); + } true } @@ -276,7 +281,7 @@ impl LayoutTask { chan: LayoutChan, constellation_chan: ConstellationChan, script_chan: ScriptChan, - render_chan: RenderChan, + render_chan: RenderChan, image_cache_task: ImageCacheTask, opts: &Opts, profiler_chan: ProfilerChan) @@ -402,7 +407,7 @@ impl LayoutTask { /// crash. fn exit_now(&mut self) { let (response_port, response_chan) = Chan::new(); - + match self.parallel_traversal { None => {} Some(ref mut traversal) => traversal.shutdown(), @@ -426,7 +431,17 @@ impl LayoutTask { None => fail!("no layout data for root node"), }; let mut flow = match result { - FlowConstructionResult(flow) => flow, + FlowConstructionResult(mut flow, abs_descendants, fixed_descendants) => { + // Note: Assuming that the root has display 'static' (as per + // CSS Section 9.3.1). Otherwise, if it were absolutely + // positioned, it would return a reference to itself in + // `abs_descendants` and would lead to a circular reference. + // Set Root as CB for any remaining absolute descendants. + flow.set_abs_descendants(abs_descendants); + // Set Root as CB for all fixed descendants. + flow.set_fixed_descendants(fixed_descendants); + flow + } _ => fail!("Flow construction didn't result in a flow at the root of the tree!"), }; flow.mark_as_root(); @@ -614,6 +629,7 @@ impl LayoutTask { if data.goal == ReflowForDisplay { profile(time::LayoutDispListBuildCategory, self.profiler_chan.clone(), || { let root_size = flow::base(layout_root).position.size; + let root_abs_position = Point2D(Au::new(0), Au::new(0)); let mut display_list_collection = DisplayListCollection::new(); display_list_collection.add_list(DisplayList::::new()); let display_list_collection = ~RefCell::new(display_list_collection); @@ -621,14 +637,16 @@ impl LayoutTask { let display_list_builder = DisplayListBuilder { ctx: &layout_ctx, }; - layout_root.build_display_lists(&display_list_builder, &root_size, &dirty, 0u, display_list_collection); + layout_root.build_display_lists(&display_list_builder, &root_size, + root_abs_position, + &dirty, 0u, display_list_collection); let display_list_collection = Arc::new(display_list_collection.unwrap()); let mut color = color::rgba(255.0, 255.0, 255.0, 255.0); for child in node.traverse_preorder() { - if child.type_id() == ElementNodeTypeId(HTMLHtmlElementTypeId) || + if child.type_id() == ElementNodeTypeId(HTMLHtmlElementTypeId) || child.type_id() == ElementNodeTypeId(HTMLBodyElementTypeId) { let element_bg_color = { let thread_safe_child = ThreadSafeLayoutNode::new(&child); @@ -768,7 +786,7 @@ impl LayoutTask { Au::from_frac_px(point.y as f64)); let resp = hit_test(x,y,display_list.list); if resp.is_some() { - reply_chan.send(Ok(resp.unwrap())); + reply_chan.send(Ok(resp.unwrap())); return } } @@ -842,4 +860,3 @@ impl LayoutTask { util::replace(layout_data_ref.get(), None)); } } - diff --git a/servo/src/components/main/layout/parallel.rs b/servo/src/components/main/layout/parallel.rs index 5e4da051dbc8..f03341ed6e0a 100644 --- a/servo/src/components/main/layout/parallel.rs +++ b/servo/src/components/main/layout/parallel.rs @@ -119,6 +119,18 @@ impl FlowParallelInfo { /// A parallel bottom-up flow traversal. trait ParallelPostorderFlowTraversal : PostorderFlowTraversal { + /// Process current flow and potentially traverse its ancestors. + /// + /// If we are the last child that finished processing, recursively process + /// our parent. Else, stop. + /// Also, stop at the root (obviously :P). + /// + /// Thus, if we start with all the leaves of a tree, we end up traversing + /// the whole tree bottom-up because each parent will be processed exactly + /// once (by the last child that finishes processing). + /// + /// The only communication between siblings is that they both + /// fetch-and-subtract the parent's children count. fn run_parallel(&mut self, mut unsafe_flow: UnsafeFlow, _: &mut WorkerProxy<*mut LayoutContext,PaddedUnsafeFlow>) { @@ -144,8 +156,9 @@ trait ParallelPostorderFlowTraversal : PostorderFlowTraversal { break } - // No, we're not at the root yet. Then are we the last sibling of our parent? If - // so, we can continue on with our parent; otherwise, we've gotta wait. + // No, we're not at the root yet. Then are we the last child + // of our parent to finish processing? If so, we can continue + // on with our parent; otherwise, we've gotta wait. let parent: &mut ~Flow = cast::transmute(&unsafe_parent); let parent_base = flow::mut_base(*parent); if parent_base.parallel.children_count.fetch_sub(1, SeqCst) == 1 { @@ -426,4 +439,3 @@ pub fn traverse_flow_tree_preorder(root: &mut ~Flow, queue.data = ptr::mut_null() } -