diff --git a/src/components/main/layout/block.rs b/src/components/main/layout/block.rs index 0f965a80c28b..73ee6cc284b2 100644 --- a/src/components/main/layout/block.rs +++ b/src/components/main/layout/block.rs @@ -10,8 +10,8 @@ //! The term 'positioned element' refers to elements with position = //! 'relative', 'absolute', or 'fixed'. -use layout::box_::Box; -use layout::construct::{FlowConstructor, OptVector}; +use layout::box_::{Box, ImageBox, ScannedTextBox}; +use layout::construct::FlowConstructor; use layout::context::LayoutContext; use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; use layout::floats::{FloatKind, Floats, PlacementInfo}; @@ -78,6 +78,94 @@ impl WidthConstraintSolution { margin_right: margin_right, } } + + /// Solve the horizontal constraint equation for absolute replaced elements. + /// + /// `static_x_offset`: total offset of current flow's hypothetical + /// position (static position) from its actual Containing Block. + /// + /// Assumption: The used value for width has already been calculated. + /// + /// CSS Section 10.3.8 + /// 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_replaced(width: Au, + left_margin: MaybeAuto, + right_margin: MaybeAuto, + left: MaybeAuto, + right: MaybeAuto, + available_width: Au, + static_x_offset: Au) + -> WidthConstraintSolution { + // TODO: Check for direction of static-position Containing Block (aka + // parent flow, _not_ the actual Containing Block) when right-to-left + // is implemented + // Assume direction is 'ltr' for now + // TODO: Handle all the cases for 'rtl' direction. + + // 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) { + (Auto, Auto) => { + let left = static_position_left; + 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) + } + // If only one is Auto, solve for it + (Auto, Specified(right)) => { + 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) => { + 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)) => { + 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'. + (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'. + 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) + } } /// The solutions for the heights-and-margins constraint equation. @@ -110,20 +198,20 @@ impl HeightConstraintSolution { /// [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) + fn solve_vertical_constraints_abs_nonreplaced(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(); @@ -209,6 +297,80 @@ impl HeightConstraintSolution { }; HeightConstraintSolution::new(top, bottom, height, margin_top, margin_bottom) } + + /// Solve the vertical constraint equation for absolute replaced elements. + /// + /// Assumption: The used value for height has already been calculated. + /// + /// CSS Section 10.6.5 + /// 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_replaced(height: Au, + top_margin: MaybeAuto, + bottom_margin: MaybeAuto, + top: MaybeAuto, + bottom: MaybeAuto, + _: 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) { + (Auto, Auto) => { + 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) + } + (Specified(top), Specified(bottom)) => { + 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) + } + } + } + + // If only one is Auto, solve for it + (Auto, Specified(bottom)) => { + 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) => { + 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) + } + }; + HeightConstraintSolution::new(top, bottom, height, margin_top, margin_bottom) + } } /// The real assign-heights traversal for flows with position 'absolute'. @@ -424,6 +586,22 @@ impl BlockFlow { traversal.process(flow) } + /// Return true if this has a replaced box. + /// + /// The only two types of replaced boxes currently are text boxes and + /// image boxes. + fn is_replaced_content(&self) -> bool { + match self.box_ { + Some(ref box_) => { + match box_.specific { + ScannedTextBox(_) | ImageBox(_) => true, + _ => false, + } + } + None => false, + } + } + pub fn teardown(&mut self) { for box_ in self.box_.iter() { box_.teardown(); @@ -521,14 +699,28 @@ impl BlockFlow { (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 solution = if self.is_replaced_content() { + // Calculate used value of width just like we do for inline replaced elements. + box_.assign_replaced_width_if_necessary(containing_block_width); + let width = box_.border_box.get().size.width; + WidthConstraintSolution::solve_horiz_constraints_abs_replaced(width, + margin_left, + margin_right, + left, + right, + available_width, + static_x_offset) + } else { + // TODO: Extract this later into a SolveConstraints trait or something + self.solve_horiz_constraints_abs_nonreplaced(width, + margin_left, + margin_right, + left, + right, + available_width, + static_x_offset) + }; let mut margin = box_.margin.get(); margin.left = solution.margin_left; @@ -614,17 +806,18 @@ impl BlockFlow { /// [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. + fn solve_horiz_constraints_abs_nonreplaced(&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 static-position Containing Block (aka + // parent flow, _not_ the actual 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 @@ -1358,15 +1551,34 @@ impl BlockFlow { 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 solution = if self.is_replaced_content() { + // Calculate used value of height just like we do for inline replaced elements. + // TODO: Pass in the containing block height when Box's + // assign-height can handle it correctly. + box_.assign_replaced_height_if_necessary(); + // TODO: Right now, this content height value includes the + // margin because of erroneous height calculation in Box_. + // Check this when that has been fixed. + let height_used_val = box_.border_box.get().size.height; + HeightConstraintSolution::solve_vertical_constraints_abs_replaced(height_used_val, + margin_top, + margin_bottom, + top, + bottom, + content_height, + available_height, + static_y_offset) + } else { + HeightConstraintSolution::solve_vertical_constraints_abs_nonreplaced( + 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; @@ -1626,7 +1838,7 @@ impl Flow for BlockFlow { fn assign_height(&mut self, ctx: &mut LayoutContext) { // Assign height for box if it is an image box. for box_ in self.box_.iter() { - box_.assign_height(); + box_.assign_replaced_height_if_necessary(); } if self.is_float() { diff --git a/src/components/main/layout/box_.rs b/src/components/main/layout/box_.rs index 7b8062e5dfc6..f0019d12095c 100644 --- a/src/components/main/layout/box_.rs +++ b/src/components/main/layout/box_.rs @@ -166,6 +166,10 @@ impl ImageBoxInfo { Au::from_px(image_ref.get().get_size().unwrap_or(Size2D(0,0)).width) } + // Return used value for width or height. + // + // `dom_length`: width or height as specified in the `img` tag. + // `style_length`: width as given in the CSS pub fn style_length(style_length: LengthOrPercentageOrAuto, dom_length: Option, container_width: Au) -> MaybeAuto { @@ -1455,6 +1459,7 @@ impl Box { /// Assigns replaced width for this box only if it is replaced content. /// + /// This assigns only the width, not margin or anything else. /// CSS 2.1 § 10.3.2. pub fn assign_replaced_width_if_necessary(&self,container_width: Au) { match self.specific { @@ -1502,8 +1507,10 @@ impl Box { } } - /// Assign height for image and scanned text boxes. - pub fn assign_height(&self) { + /// Assign height for this box if it is replaced content. + /// + /// Ideally, this should follow CSS 2.1 § 10.6.2 + pub fn assign_replaced_height_if_necessary(&self) { match self.specific { GenericBox | IframeBox(_) => { } diff --git a/src/components/main/layout/construct.rs b/src/components/main/layout/construct.rs index 745ee21c1738..ffccaa02f690 100644 --- a/src/components/main/layout/construct.rs +++ b/src/components/main/layout/construct.rs @@ -687,6 +687,13 @@ impl<'a> FlowConstructor<'a> { } impl<'a> PostorderNodeMutTraversal for FlowConstructor<'a> { + // Construct Flow based on 'display', 'position', and 'float' values. + // + // CSS 2.1 Section 9.7 + // + // TODO: This should actually consult the table in that section to get the + // final computed value for 'display'. + // // `#[inline(always)]` because this is always called from the traversal function and for some // reason LLVM's inlining heuristics go awry here. #[inline(always)] @@ -718,6 +725,10 @@ impl<'a> PostorderNodeMutTraversal for FlowConstructor<'a> { } } + // Absolutely positioned elements will have computed value of + // `float` as 'none' and `display` as per the table. + // Currently, for original `display` value of 'inline', the new + // `display` value is 'block'. (_, _, position::absolute) | (_, _, position::fixed) => { node.set_flow_construction_result(self.build_flow_for_block(node)) } diff --git a/src/components/main/layout/inline.rs b/src/components/main/layout/inline.rs index 6abfd6639e84..8cbc5a9eef4f 100644 --- a/src/components/main/layout/inline.rs +++ b/src/components/main/layout/inline.rs @@ -698,7 +698,7 @@ impl Flow for InlineFlow { debug!("assign_height_inline: floats in: {:?}", self.base.floats); // assign height for inline boxes for box_ in self.boxes.iter() { - box_.assign_height(); + box_.assign_replaced_height_if_necessary(); } let scanner_floats = self.base.floats.clone(); let mut scanner = LineboxScanner::new(scanner_floats); diff --git a/src/test/ref/basic.list b/src/test/ref/basic.list index f30a45531e25..206a3319a7d7 100644 --- a/src/test/ref/basic.list +++ b/src/test/ref/basic.list @@ -38,6 +38,8 @@ == position_abs_cb_with_non_cb_kid_a.html position_abs_cb_with_non_cb_kid_b.html == position_abs_height_width_a.html position_abs_height_width_b.html == position_abs_left_a.html position_abs_left_b.html +== position_abs_nested_a.html position_abs_nested_b.html +== position_abs_replaced_simple_a.html position_abs_replaced_simple_b.html == position_abs_static_y_a.html position_abs_static_y_b.html == position_abs_width_percentage_a.html position_abs_width_percentage_b.html == position_fixed_a.html position_fixed_b.html diff --git a/src/test/ref/position_abs_nested_a.html b/src/test/ref/position_abs_nested_a.html new file mode 100644 index 000000000000..4a417402362d --- /dev/null +++ b/src/test/ref/position_abs_nested_a.html @@ -0,0 +1,34 @@ + + + + + +
+
+
+
+
+
+ + diff --git a/src/test/ref/position_abs_nested_b.html b/src/test/ref/position_abs_nested_b.html new file mode 100644 index 000000000000..b539f248c8a9 --- /dev/null +++ b/src/test/ref/position_abs_nested_b.html @@ -0,0 +1,36 @@ + + + + + +
+
+
+
+
+
+
+
+ + diff --git a/src/test/ref/position_abs_replaced_simple_a.html b/src/test/ref/position_abs_replaced_simple_a.html new file mode 100644 index 000000000000..0bc4feb135cb --- /dev/null +++ b/src/test/ref/position_abs_replaced_simple_a.html @@ -0,0 +1,25 @@ + + + + + +
+ Rust Logo +
+ + diff --git a/src/test/ref/position_abs_replaced_simple_b.html b/src/test/ref/position_abs_replaced_simple_b.html new file mode 100644 index 000000000000..da942fa82b67 --- /dev/null +++ b/src/test/ref/position_abs_replaced_simple_b.html @@ -0,0 +1,32 @@ + + + + + +
+
+
+
+ Rust Logo +
+
+
+
+ +