From c4d177a3541b55c4b979afc8e7171fe0cfb4817e Mon Sep 17 00:00:00 2001 From: S Pradeep Kumar Date: Wed, 29 Jan 2014 14:25:39 +0900 Subject: [PATCH] Implement `position: absolute` for non-replaced elements. + 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. --- src/components/main/layout/block.rs | 1006 ++++++++++++++--- src/components/main/layout/box_.rs | 25 +- src/components/main/layout/construct.rs | 22 +- src/components/main/layout/flow.rs | 76 +- src/components/main/layout/inline.rs | 17 +- src/components/main/layout/layout_task.rs | 15 +- src/components/main/layout/parallel.rs | 18 +- src/test/ref/position_abs_height_width_a.html | 26 + src/test/ref/position_abs_height_width_b.html | 29 + src/test/ref/position_abs_left_a.html | 30 + src/test/ref/position_abs_left_b.html | 18 + .../ref/position_abs_width_percentage_a.html | 26 + .../ref/position_abs_width_percentage_b.html | 27 + src/test/ref/position_fixed_simple_a.html | 38 + src/test/ref/position_fixed_simple_b.html | 24 + 15 files changed, 1203 insertions(+), 194 deletions(-) create mode 100644 src/test/ref/position_abs_height_width_a.html create mode 100644 src/test/ref/position_abs_height_width_b.html create mode 100644 src/test/ref/position_abs_left_a.html create mode 100644 src/test/ref/position_abs_left_b.html create mode 100644 src/test/ref/position_abs_width_percentage_a.html create mode 100644 src/test/ref/position_abs_width_percentage_b.html create mode 100644 src/test/ref/position_fixed_simple_a.html create mode 100644 src/test/ref/position_fixed_simple_b.html diff --git a/src/components/main/layout/block.rs b/src/components/main/layout/block.rs index ccaf15b456b..8d2b3e1fae9 100644 --- a/src/components/main/layout/block.rs +++ b/src/components/main/layout/block.rs @@ -3,6 +3,12 @@ * 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; @@ -13,10 +19,11 @@ use layout::flow::{BaseFlow, BlockFlowClass, FlowClass, Flow, ImmutableFlowUtils 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}; use servo_util::geometry::Au; use servo_util::geometry; @@ -49,7 +56,86 @@ impl FloatedBlockInfo { } } -/// A block formatting context. +/// Details of a potential Containing Block. +/// +/// If a flow has the block as its Containing Block, then this represents its +/// Containing Block's details. +/// Else, this represents intermediate data to be passed on to its descendants who +/// may have this block as their Containing Block (e.g., absolutely positioned +/// elements.) +/// +/// This allows us to calculate width, height, etc. for the flow for which +/// this represents the Containing Block. +#[deriving(Clone)] +pub struct ContainingBlockDetails { + width: Au, + height: Au, + // Absolute offset of the left margin edge of the current flow from the + // left edge of this Containing Block. This includes the margins, etc. of + // all the intermediate flows till the current flow. + static_x_offset: Au, + // Absolute offset of the top margin edge of the current flow from the top + // edge of this Containing Block. This includes the margins, etc. of all + // the intermediate flows till the current flow. + static_y_offset: Au, +} + +impl ContainingBlockDetails { + fn default() -> ContainingBlockDetails { + ContainingBlockDetails { + width: Au::new(0), + height: Au::new(0), + static_x_offset: Au::new(0), + static_y_offset: Au::new(0), + } + } +} + +/// 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, + } + } +} + +// A block formatting context. pub struct BlockFlow { /// Data common to all flows. base: BaseFlow, @@ -57,11 +143,17 @@ 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, + positioning: position::T, + + // Details of the nearest positioned ancestor - aka the Containing Block + // for any absolutely positioned elements. + absolute_cb_details: ContainingBlockDetails, + // Details about the Initial Containing Block and our offset wrt it + fixed_cb_details: ContainingBlockDetails, /// Additional floating flow members. float: Option<~FloatedBlockInfo> @@ -70,13 +162,15 @@ pub struct BlockFlow { impl BlockFlow { pub fn from_node(constructor: &mut FlowConstructor, node: &ThreadSafeLayoutNode, - is_fixed: bool) + positioning: position::T) -> BlockFlow { BlockFlow { base: BaseFlow::new((*node).clone()), box_: Some(Box::new(constructor, node)), is_root: false, - is_fixed: is_fixed, + positioning: positioning, + absolute_cb_details: ContainingBlockDetails::default(), + fixed_cb_details: ContainingBlockDetails::default(), float: None } } @@ -89,7 +183,9 @@ impl BlockFlow { base: BaseFlow::new((*node).clone()), box_: Some(Box::new(constructor, node)), is_root: false, - is_fixed: false, + positioning: position::static_, + absolute_cb_details: ContainingBlockDetails::default(), + fixed_cb_details: ContainingBlockDetails::default(), float: Some(~FloatedBlockInfo::new(float_kind)) } } @@ -99,7 +195,9 @@ impl BlockFlow { base: base, box_: None, is_root: true, - is_fixed: false, + positioning: position::static_, + absolute_cb_details: ContainingBlockDetails::default(), + fixed_cb_details: ContainingBlockDetails::default(), float: None } } @@ -109,7 +207,9 @@ impl BlockFlow { base: base, box_: None, is_root: false, - is_fixed: false, + positioning: position::static_, + absolute_cb_details: ContainingBlockDetails::default(), + fixed_cb_details: ContainingBlockDetails::default(), float: Some(~FloatedBlockInfo::new(float_kind)) } } @@ -118,6 +218,31 @@ impl BlockFlow { self.float.is_some() } + pub fn is_fixed(&self) -> bool { + self.positioning == position::fixed + } + + pub fn is_absolutely_positioned(&self) -> bool { + self.positioning == position::absolute || self.is_fixed() + } + + /// Return the appropriate Containing Block for this flow. + /// + /// Right now, this only gets the Containing Block for absolutely + /// positioned elements. + pub fn containing_block(&self) -> ContainingBlockDetails { + assert!(self.is_absolutely_positioned()); + if self.is_fixed() { + self.fixed_cb_details + } else { + self.absolute_cb_details + } + } + + pub fn is_positioned(&self) -> bool { + self.positioning == position::relative || self.is_absolutely_positioned() + } + pub fn teardown(&mut self) { for box_ in self.box_.iter() { box_.teardown(); @@ -189,7 +314,248 @@ 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) { + let absolute_cb = self.containing_block(); + let containing_block_width = absolute_cb.width; + let static_x_offset = absolute_cb.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); + + // Top and bottom margins for blocks are 0 if auto. + 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) = + (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); + + box_.margin.set(SideOffsets2D::new(margin_top, + solution.margin_right, + margin_bottom, + solution.margin_left)); + + // 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) { + if self.is_absolutely_positioned() { + self.calculate_abs_widths_and_margins(); + 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. + 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 Containing Block (NOT parent flow) + // 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 +600,43 @@ 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 + /// 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 +647,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 +665,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 +699,56 @@ 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_block_like() && kid.as_block().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 + } } // 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; } @@ -365,20 +770,30 @@ impl BlockFlow { // 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 +815,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,12 +825,8 @@ 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; @@ -441,10 +834,19 @@ impl BlockFlow { } } + /// 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) { - // assign_height_float was already called by the traversal function - // so this is well-defined - let mut height = Au(0); let mut clearance = Au(0); let mut full_noncontent_width = Au(0); @@ -480,6 +882,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 +912,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 +944,97 @@ impl BlockFlow { box_.border_box.set(position); } + /// Pass down static y offset for absolute containing block + /// + /// Assume this is called in a top-down traversal (specifically, during + /// the build display list traversal because assign-heights is bottom-up). + /// So, our static y offset value is already set. + fn pass_down_static_y_offset(&mut self) { + assert!(self.box_.is_some(), + "All BlockFlows have a box_ in the current implementation"); + for box_ in self.box_.iter() { + let parent_is_positioned = self.is_positioned(); + // Static offset to the top outer edge of this flow + let parent_abs_cb_static_y_offset = self.absolute_cb_details.static_y_offset; + let parent_fixed_cb_static_y_offset = self.fixed_cb_details.static_y_offset; + let kid_fixed_cb_height = self.fixed_cb_details.height; + let mut kid_abs_cb_height; + let parent_top_margin_edge = box_.border_box.get().origin.y - box_.margin.get().top; + let parent_top_padding_edge = box_.border_box.get().origin.y + box_.border.get().top; + if parent_is_positioned { + // Send in parent as the CB + // Padding box height + kid_abs_cb_height = box_.border_box.get().size.height + - box_.border.get().top - box_.border.get().bottom; + } else { + kid_abs_cb_height = self.absolute_cb_details.height; + } + + for child in self.base.child_iter() { + if child.is_block_flow() { + let child_block = child.as_block(); + child_block.absolute_cb_details.height = kid_abs_cb_height; + child_block.fixed_cb_details.height = kid_fixed_cb_height; + for box_ in child_block.box_.iter() { + let child_top_margin_edge = if child_block.is_absolutely_positioned() { + child_block.get_hypothetical_top_edge() + } else { + // Top margin edge = top border y-coordinate - margin-top + // Note: We don't consider top edge of the + // base.position box directly here because that + // box may include clearance. + let relative_top_margin_edge = box_.border_box.get().origin.y + - box_.margin.get().top; + // In the parent flow's coordinate system + child_block.base.position.origin.y + relative_top_margin_edge + }; + if parent_is_positioned { + // The static y offset will be the distance from the top padding + child_block.absolute_cb_details.static_y_offset = child_top_margin_edge + - parent_top_padding_edge; + } else { + let offset = parent_abs_cb_static_y_offset + + (child_top_margin_edge - parent_top_margin_edge); + child_block.absolute_cb_details.static_y_offset = offset; + } + child_block.fixed_cb_details.static_y_offset = parent_fixed_cb_static_y_offset + + (child_top_margin_edge - parent_top_margin_edge) + } + } + } + } + } + + /// 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); + self.pass_down_static_y_offset(); + return index; + } else if self.is_absolutely_positioned() { + self.build_display_list_abs(builder, container_block_size, + absolute_cb_abs_position, + dirty, index, lists); + // Pass down y-offset after calculating flow box dimensions. + self.pass_down_static_y_offset(); return index; } - if self.is_fixed { - lists.with_mut(|lists| { - index = lists.lists.len(); - lists.add_list(DisplayList::::new()); - }); - } + self.pass_down_static_y_offset(); + // 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 +1056,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 +1114,227 @@ impl BlockFlow { false } + + /// 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(&self, + 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) + } + + /// 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) { + let absolute_cb = self.containing_block(); + let containing_block_height = absolute_cb.height; + + 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_vertical(); + + let solution = self.solve_vertical_constraints_abs_position(height_used_val, + margin_top, + margin_bottom, + top, + bottom, + content_height, + available_height, + absolute_cb.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_vertical(); + 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. + /// + /// Note: We calculate the actual dimensions of the Absolute Flow here, + /// because only now do we have access to the used value for the + /// Containing Block's height. We couldn't do this during assign-heights + /// because it was a bottom-up traversal. + /// This is ok because absolute flows are out-of-flow and nothing else + /// depends on their height, etc. + pub fn build_display_list_abs( + &mut self, + builder: &DisplayListBuilder, + _: &Size2D, + absolute_cb_abs_position: Point2D, + dirty: &Rect, + index: uint, + lists: &RefCell>) + -> bool { + self.calculate_abs_height_and_margins(); + + let flow_origin = if self.is_fixed() { + 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 + }; + + // 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. + /// + /// 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 { @@ -705,75 +1411,46 @@ impl Flow for BlockFlow { 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); + // Initial containing block is the CB for the root + let initial_cb = ContainingBlockDetails { + width: ctx.screen_size.width, + height: ctx.screen_size.height, + // The left margin edge of the root touches the viewport (which is its CB). + static_x_offset: Au::new(0), + // The top margin edge of the root touches the viewport (which is its CB). + static_y_offset: Au::new(0), + }; + self.absolute_cb_details = initial_cb.clone(); + self.fixed_cb_details = initial_cb; } // 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); + 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 +1459,49 @@ impl Flow for BlockFlow { self.base.flags_info.flags.inorder() || self.base.num_floats > 0 }; + let kid_abs_cb_width; + let kid_abs_cb_x_offset; + if self.is_positioned() { + match self.box_ { + Some(ref box_) => { + // Pass yourself as a new Containing Block + // Padding box width + kid_abs_cb_width = box_.border_box.get().size.width - box_.border.get().left + - box_.border.get().right; + // 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 { + // Pass the original Containing Block, with updated static offset + kid_abs_cb_width = self.absolute_cb_details.width; + // 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.absolute_cb_details.static_x_offset + left_content_edge; + } + let kid_fixed_cb_width = self.fixed_cb_details.width; + let kid_fixed_cb_x_offset = self.fixed_cb_details.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.absolute_cb_details.width = kid_abs_cb_width; + kid_block.absolute_cb_details.static_x_offset = kid_abs_cb_x_offset; + kid_block.fixed_cb_details.width = kid_fixed_cb_width; + kid_block.fixed_cb_details.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 +1517,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 +1532,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(); } @@ -837,6 +1553,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, diff --git a/src/components/main/layout/box_.rs b/src/components/main/layout/box_.rs index 7a230bf09c1..6bebd15b5bc 100644 --- a/src/components/main/layout/box_.rs +++ b/src/components/main/layout/box_.rs @@ -605,6 +605,16 @@ impl Box { specified(padding, content_box_width) } + 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_vertical(&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 +623,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 +662,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) => { @@ -1122,6 +1134,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| { @@ -1432,8 +1445,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 +1484,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 +1494,7 @@ impl Box { } } + /// Assign height for image and scanned text boxes. pub fn assign_height(&self) { match self.specific { GenericBox | IframeBox(_) => { @@ -1514,6 +1531,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/src/components/main/layout/construct.rs b/src/components/main/layout/construct.rs index d91d538464f..d7da3a11592 100644 --- a/src/components/main/layout/construct.rs +++ b/src/components/main/layout/construct.rs @@ -410,8 +410,9 @@ impl<'a> FlowConstructor<'a> { /// 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; + fn build_flow_for_block(&mut self, node: &ThreadSafeLayoutNode, positioning: position::T) + -> ~Flow { + let mut flow = ~BlockFlow::from_node(self, node, positioning) as ~Flow; self.build_children_of_block_flow(&mut flow, node); flow } @@ -433,7 +434,7 @@ impl<'a> FlowConstructor<'a> { -> ConstructionResult { let mut opt_inline_block_splits = None; let mut opt_box_accumulator = None; - + // Concatenate all the boxes of our kids, creating {ib} splits as necessary. for kid in node.children() { match kid.swap_out_construction_result() { @@ -636,7 +637,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 +653,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, _, _) => { @@ -673,12 +674,12 @@ 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); + (_, _, position::absolute) | (_, _, position::fixed) => { + let flow = self.build_flow_for_block(node, positioning); node.set_flow_construction_result(FlowConstructionResult(flow)) } (_, float::none, _) => { - let flow = self.build_flow_for_block(node, false); + let flow = self.build_flow_for_block(node, positioning); node.set_flow_construction_result(FlowConstructionResult(flow)) } @@ -779,7 +780,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 +794,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 +855,3 @@ fn strip_ignorable_whitespace_from_end(opt_boxes: &mut Option<~[Box]>) { *opt_boxes = None } } - diff --git a/src/components/main/layout/flow.rs b/src/components/main/layout/flow.rs index b476e8b07b7..182a6ccb91b 100644 --- a/src/components/main/layout/flow.rs +++ b/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,7 +26,7 @@ /// 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::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; @@ -208,6 +208,7 @@ pub trait MutableFlowUtils { self, builder: &DisplayListBuilder, container_block_size: &Size2D, + absolute_cb_abs_position: Point2D, dirty: &Rect, index: uint, mut list: &RefCell>) @@ -498,8 +499,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,7 +516,11 @@ 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. @@ -707,6 +713,19 @@ 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 ideally the union of all flows for which we define the + /// Containing Block. + /// + /// Assumption: This is called in a bottom-up traversal, so kids' overflows have + /// already been set. + /// So, currently this is a union of the overflows of all kids and our own + /// flow rectangle. + /// FIXME: Handle the overflow of absolute flow descendants, because their + /// assign-heights happen after the normal + /// assign-height-and-store-overflow traversal fn store_overflow(self, _: &mut LayoutContext) { let my_position = mut_base(self).position; let mut overflow = my_position; @@ -723,17 +742,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), }; @@ -745,21 +776,31 @@ impl<'a> MutableFlowUtils for &'a mut Flow { 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 + let is_positioned = self.as_block().is_positioned(); + let container_block_size; + let abs_cb_position; + let flow_pos = base(self).abs_position; + match self.as_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; + + abs_cb_position = if is_positioned { + let padding_box_pos = flow_pos + box_.border_box.get().origin + + Point2D(box_.border.get().left, box_.border.get().top); + padding_box_pos } else { - base(self).position.size - } - }, - _ => { - base(self).position.size + absolute_cb_abs_position + }; } - }; + None => fail!("Flow: block container should have a box_") + } for kid in child_iter(self) { - kid.build_display_lists(builder, &container_block_size, dirty, 0u, &child_lists); + kid.build_display_lists(builder, &container_block_size, + abs_cb_position, + dirty, 0u, &child_lists); } let mut child_lists = Some(child_lists.unwrap()); @@ -828,4 +869,3 @@ impl MutableOwnedFlowUtils for ~Flow { self_borrowed.destroy(); } } - diff --git a/src/components/main/layout/inline.rs b/src/components/main/layout/inline.rs index 4292ab32274..6abfd6639e8 100644 --- a/src/components/main/layout/inline.rs +++ b/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/src/components/main/layout/layout_task.rs b/src/components/main/layout/layout_task.rs index b8e8b022c9a..8a52d93b284 100644 --- a/src/components/main/layout/layout_task.rs +++ b/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}; @@ -276,7 +277,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 +403,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(), @@ -614,6 +615,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 +623,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 +772,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 +846,3 @@ impl LayoutTask { util::replace(layout_data_ref.get(), None)); } } - diff --git a/src/components/main/layout/parallel.rs b/src/components/main/layout/parallel.rs index 5e4da051dbc..f03341ed6e0 100644 --- a/src/components/main/layout/parallel.rs +++ b/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() } - diff --git a/src/test/ref/position_abs_height_width_a.html b/src/test/ref/position_abs_height_width_a.html new file mode 100644 index 00000000000..667929c20bd --- /dev/null +++ b/src/test/ref/position_abs_height_width_a.html @@ -0,0 +1,26 @@ + + + + + +
+
+
+
+ + diff --git a/src/test/ref/position_abs_height_width_b.html b/src/test/ref/position_abs_height_width_b.html new file mode 100644 index 00000000000..ab53620a552 --- /dev/null +++ b/src/test/ref/position_abs_height_width_b.html @@ -0,0 +1,29 @@ + + + + + +
+
+
+
+
+
+ + diff --git a/src/test/ref/position_abs_left_a.html b/src/test/ref/position_abs_left_a.html new file mode 100644 index 00000000000..502f152e029 --- /dev/null +++ b/src/test/ref/position_abs_left_a.html @@ -0,0 +1,30 @@ + + + + + +
+ +
+
+ +
+ + diff --git a/src/test/ref/position_abs_left_b.html b/src/test/ref/position_abs_left_b.html new file mode 100644 index 00000000000..f1d4fcd65a8 --- /dev/null +++ b/src/test/ref/position_abs_left_b.html @@ -0,0 +1,18 @@ + + + + + +
+ +
+ + diff --git a/src/test/ref/position_abs_width_percentage_a.html b/src/test/ref/position_abs_width_percentage_a.html new file mode 100644 index 00000000000..956f8470f90 --- /dev/null +++ b/src/test/ref/position_abs_width_percentage_a.html @@ -0,0 +1,26 @@ + + + + + +
+
+
+
+ + diff --git a/src/test/ref/position_abs_width_percentage_b.html b/src/test/ref/position_abs_width_percentage_b.html new file mode 100644 index 00000000000..0352d787e69 --- /dev/null +++ b/src/test/ref/position_abs_width_percentage_b.html @@ -0,0 +1,27 @@ + + + + + +
+
+
+
+
+ + diff --git a/src/test/ref/position_fixed_simple_a.html b/src/test/ref/position_fixed_simple_a.html new file mode 100644 index 00000000000..83e7cc72a78 --- /dev/null +++ b/src/test/ref/position_fixed_simple_a.html @@ -0,0 +1,38 @@ + + + + + +
+
+
+ +
+
+
+
+ + diff --git a/src/test/ref/position_fixed_simple_b.html b/src/test/ref/position_fixed_simple_b.html new file mode 100644 index 00000000000..67ddd4208e6 --- /dev/null +++ b/src/test/ref/position_fixed_simple_b.html @@ -0,0 +1,24 @@ + + + + + +
+
+
+ +