aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorPatrick Walton <pcwalton@mimiga.net>2014-03-28 13:22:23 -0700
committerPatrick Walton <pcwalton@mimiga.net>2014-04-03 14:50:57 -0700
commit10aed5bc1fd3966fccfa9999580229baedf00564 (patch)
treebce4a0cd51e82e17b027c13aa170de595b412fc5 /src
parent392afdb014246bf1824b8d5682c6e61a62cc9036 (diff)
downloadservo-10aed5bc1fd3966fccfa9999580229baedf00564.tar.gz
servo-10aed5bc1fd3966fccfa9999580229baedf00564.zip
layout: Rewrite the margin collapse code to work with negative margins.
Diffstat (limited to 'src')
-rw-r--r--src/components/main/layout/block.rs784
-rw-r--r--src/components/main/layout/flow.rs30
-rw-r--r--src/components/main/layout/inline.rs14
-rw-r--r--src/components/main/layout/model.rs246
4 files changed, 592 insertions, 482 deletions
diff --git a/src/components/main/layout/block.rs b/src/components/main/layout/block.rs
index 944fd16f12d..0711e43818a 100644
--- a/src/components/main/layout/block.rs
+++ b/src/components/main/layout/block.rs
@@ -737,19 +737,11 @@ impl BlockFlow {
/// 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() {
+ if kid_block.is_fixed() || 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;
@@ -771,356 +763,291 @@ impl BlockFlow {
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;
}
- /// Calculate clearance, top_offset, bottom_offset, and left_offset for the box.
- /// If `ignore_clear` is true, clearance does not need to be calculated.
- pub fn initialize_offsets(&mut self, ignore_clear: bool) -> (Au, Au, Au, Au) {
- match self.box_ {
- None => (Au(0), Au(0), Au(0), Au(0)),
- Some(ref box_) => {
- let clearance = match box_.clear() {
- Some(clear) if !ignore_clear => self.base.floats.clearance(clear),
- _ => Au::new(0)
- };
+ /// If this is the root flow, shifts all kids down and adjusts our size to account for
+ /// collapsed margins.
+ ///
+ /// TODO(pcwalton): This is somewhat inefficient (traverses kids twice); can we do better?
+ fn adjust_boxes_for_collapsed_margins_if_root(&mut self) {
+ if !self.is_root() {
+ return
+ }
- // Offsets to content edge of box_
- let top_offset = clearance + box_.top_offset();
- let bottom_offset = box_.bottom_offset();
- let left_offset = box_.left_offset();
-
- (clearance, top_offset, bottom_offset, left_offset)
- }
- }
- }
-
- /// In case of inorder assign_height traversal and not absolute flow,
- /// 'assign_height's of all children are visited
- /// and Float info is shared between adjacent children.
- /// Float info of the last child is saved in parent flow.
- pub fn handle_children_floats_if_necessary(&mut self,
- ctx: &mut LayoutContext,
- inorder: bool,
- left_offset: Au,
- top_offset: Au) {
- // 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]
- // child[i-1].floats -> child[i].floats
- // visit child[i]
- // repeat until all children are visited.
- // last_child.floats -> self.floats (done at the end of this method)
- self.base.floats.translate(Point2D(-left_offset, -top_offset));
- let mut floats = self.base.floats.clone();
+ let (top_margin_value, bottom_margin_value) = match self.base.collapsible_margins {
+ MarginsCollapseThrough(margin) => (Au(0), margin.collapse()),
+ MarginsCollapse(top_margin, bottom_margin) => {
+ (top_margin.collapse(), bottom_margin.collapse())
+ }
+ NoCollapsibleMargins(top, bottom) => (top, bottom),
+ };
+
+ // Shift all kids down (or up, if margins are negative) if necessary.
+ if top_margin_value != Au(0) {
for kid in self.base.child_iter() {
- flow::mut_base(kid).floats = floats;
- kid.assign_height_inorder(ctx);
- floats = flow::mut_base(kid).floats.clone();
+ let kid_base = flow::mut_base(kid);
+ kid_base.position.origin.y = kid_base.position.origin.y + top_margin_value
}
- self.base.floats = floats;
+ }
+
+ self.base.position.size.height = self.base.position.size.height + top_margin_value +
+ bottom_margin_value;
+
+ for fragment in self.box_.iter() {
+ let mut position = fragment.border_box.get();
+ position.size.height = position.size.height + top_margin_value + bottom_margin_value;
+ fragment.border_box.set(position);
}
}
- /// Compute margin_top and margin_bottom. Also, it is decided whether top margin and
- /// bottom margin are collapsible according to CSS 2.1 § 8.3.1.
- pub fn precompute_margin(&mut self) -> (Au, Au, bool, bool) {
- match self.box_ {
- // Margins for an absolutely positioned element do not collapse with
- // its children.
- Some(ref box_) if !self.is_absolutely_positioned() => {
- let top_margin_collapsible = !self.is_root &&
- box_.border.get().top == Au(0) &&
- box_.padding.get().top == Au(0);
-
- let bottom_margin_collapsible = !self.is_root &&
- box_.border.get().bottom == Au(0) &&
- box_.padding.get().bottom == Au(0);
-
- let margin_top = box_.margin.get().top;
- let margin_bottom = box_.margin.get().bottom;
-
- (margin_top, margin_bottom, top_margin_collapsible, bottom_margin_collapsible)
- },
- _ => (Au(0), Au(0), false, false)
- }
- }
-
- /// Compute collapsed margins between adjacent children or between the first/last child and parent
- /// according to CSS 2.1 § 8.3.1. Current y position(cur_y) is continually updated for collapsing result.
- pub fn compute_margin_collapse(&mut self,
- cur_y: &mut Au,
- top_offset: &mut Au,
- margin_top: &mut Au,
- margin_bottom: &mut Au,
- top_margin_collapsible: bool,
- bottom_margin_collapsible: bool) -> Au {
- // How much to move up by to get to the beginning of
- // current kid flow.
- // Example: if previous sibling's margin-bottom is 20px and your
- // margin-top is 12px, the collapsed margin will be 20px. Since cur_y
- // will be at the bottom margin edge of the previous sibling, we have
- // to move up by 12px to get to our top margin edge. So, `collapsing`
- // will be set to 12px
- let mut first_in_flow = true;
- let mut collapsing = Au::new(0);
- // The amount of margin that we can potentially collapse with
- let mut collapsible = if top_margin_collapsible {
- *margin_top
- } else {
- Au(0)
- };
+ /// 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 § 10.6.7.
+ ///
+ /// For absolute flows, we store the calculated content height for the flow. We defer the
+ /// calculation of the other values until a later traversal.
+ ///
+ /// `inline(always)` because this is only ever called by in-order or non-in-order top-level
+ /// methods
+ #[inline(always)]
+ pub fn assign_height_block_base(&mut self,
+ layout_context: &mut LayoutContext,
+ inorder: bool,
+ margins_may_collapse: MarginsMayCollapseFlag) {
+ // Our current border-box position.
+ let mut cur_y = Au(0);
+
+ // The sum of our top border and top padding.
+ let mut top_offset = Au(0);
+
+ // Absolute positioning establishes a block formatting context. Don't propagate floats
+ // in or out. (But do propagate them between kids.)
+ if inorder && (self.is_absolutely_positioned()) {
+ self.base.floats = Floats::new();
+ }
+ if margins_may_collapse != MarginsMayCollapse {
+ self.base.floats = Floats::new();
+ }
+
+ let mut margin_collapse_info = MarginCollapseInfo::new();
+ for fragment in self.box_.iter() {
+ self.base.floats.translate(Point2D(-fragment.left_offset(), Au(0)));
+
+ top_offset = fragment.border.get().top + fragment.padding.get().top;
+ translate_including_floats(&mut cur_y, top_offset, inorder, &mut self.base.floats);
- // At this point, cur_y is at the content edge of the flow's box_
+ let can_collapse_top_margin_with_kids =
+ margins_may_collapse == MarginsMayCollapse &&
+ !self.is_absolutely_positioned() &&
+ fragment.border.get().top == Au(0) &&
+ fragment.padding.get().top == Au(0);
+ margin_collapse_info.initialize_top_margin(fragment,
+ can_collapse_top_margin_with_kids);
+ }
+
+ // At this point, cur_y is at the content edge of the flow's box.
+ let mut floats = self.base.floats.clone();
+ let mut layers_needed_for_descendants = false;
for kid in self.base.child_iter() {
- // At this point, cur_y is at bottom margin edge of previous 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
+ // Assume that the *hypothetical box* for an absolute flow starts immediately after
+ // the bottom border edge of the previous flow.
+ kid.as_block().base.position.origin.y = cur_y;
+
+ if inorder {
+ kid.assign_height_inorder(layout_context)
+ }
+
+ propagate_layer_flag_from_child(&mut layers_needed_for_descendants, kid);
+
+ // Skip the collapsing and float processing for absolute flow kids and continue
// with the next flow.
- } else {
- kid.collapse_margins(top_margin_collapsible,
- &mut first_in_flow,
- margin_top,
- 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
+ continue
+ }
+
+ // Assign height now for the child if it was impacted by floats and we couldn't before.
+ let mut floats_out = None;
+ if inorder {
+ if !kid.is_float() {
+ let kid_base = flow::mut_base(kid);
+ if kid_base.clear != clear::none {
+ // We have clearance, so assume there are no floats in and perform layout.
+ //
+ // FIXME(pcwalton): This could be wrong if we have `clear: left` or
+ // `clear: right` and there are still floats to impact, of course. But this
+ // gets complicated with margin collapse. Possibly the right thing to do is
+ // to lay out the block again in this rare case. (Note that WebKit can lay
+ // blocks out twice; this may be related, although I haven't looked into it
+ // closely.)
+ kid_base.floats = Floats::new()
+ } else {
+ kid_base.floats = floats.clone()
+ }
+ } else {
+ let kid_base = flow::mut_base(kid);
+ kid_base.position.origin.y = margin_collapse_info.current_float_ceiling();
+ kid_base.floats = floats.clone()
+ }
+
+ kid.assign_height_inorder(layout_context);
+
+ floats_out = Some(flow::mut_base(kid).floats.clone());
+
+ // FIXME(pcwalton): A horrible hack that has to do with the fact that `origin.y`
+ // is used for something else later (containing block for float).
+ if kid.is_float() {
+ flow::mut_base(kid).position.origin.y = cur_y;
+ }
}
- }
- self.collect_static_y_offsets_from_kids();
+ propagate_layer_flag_from_child(&mut layers_needed_for_descendants, kid);
- // The bottom margin collapses with its last in-flow block-level child's bottom margin
- // 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;
+ // If the child was a float, stop here.
+ if kid.is_float() {
+ if inorder {
+ floats = floats_out.take_unwrap();
+ }
+ continue
}
- collapsible
- } else {
- Au::new(0)
- };
- collapsing
- }
+ // Handle any (possibly collapsed) top margin.
+ let kid_base = flow::mut_base(kid);
+ let delta = margin_collapse_info.advance_top_margin(&kid_base.collapsible_margins);
+ translate_including_floats(&mut cur_y, delta, inorder, &mut floats);
+
+ // Clear past floats, if necessary.
+ if inorder {
+ let clearance = match kid_base.clear {
+ clear::none => Au(0),
+ clear::left => floats.clearance(ClearLeft),
+ clear::right => floats.clearance(ClearRight),
+ clear::both => floats.clearance(ClearBoth),
+ };
+ cur_y = cur_y + clearance
+ }
+
+ // At this point, `cur_y` is at the border edge of the child.
+ assert!(kid_base.position.origin.y == Au(0));
+ kid_base.position.origin.y = cur_y;
+
+ // If this was an inorder traversal, grab the child's floats now.
+ if inorder {
+ floats = floats_out.take_unwrap()
+ }
+
+ // Move past the child's border box. Do not use the `translate_including_floats`
+ // function here because the child has already translated floats past its border box.
+ cur_y = cur_y + kid_base.position.size.height;
+
+ // Handle any (possibly collapsed) bottom margin.
+ let delta = margin_collapse_info.advance_bottom_margin(&kid_base.collapsible_margins);
+ translate_including_floats(&mut cur_y, delta, inorder, &mut floats);
+ }
+
+ self.base
+ .flags_info
+ .flags
+ .set_layers_needed_for_descendants(layers_needed_for_descendants);
+
+ self.collect_static_y_offsets_from_kids();
+
+ // Add in our bottom margin and compute our collapsible margins.
+ for fragment in self.box_.iter() {
+ let can_collapse_bottom_margin_with_kids =
+ margins_may_collapse == MarginsMayCollapse &&
+ !self.is_absolutely_positioned() &&
+ fragment.border.get().bottom == Au(0) &&
+ fragment.padding.get().bottom == Au(0);
+ let (collapsible_margins, delta) =
+ margin_collapse_info.finish_and_compute_collapsible_margins(
+ fragment,
+ can_collapse_bottom_margin_with_kids);
+ self.base.collapsible_margins = collapsible_margins;
+ translate_including_floats(&mut cur_y, delta, inorder, &mut floats);
+ }
+
+ // 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.
+ let mut height = cur_y - top_offset;
+ if self.is_root() {
+ height = Au::max(layout_context.screen_size.height, height)
+ }
- /// For an absolutely positioned element, store the content height for use in calculating
- /// the absolute flow's dimensions later.
- pub fn store_content_height_if_absolutely_positioned(&mut self,
- height: Au) -> bool {
if self.is_absolutely_positioned() {
+ // The content height includes all the floats per CSS 2.1 § 10.6.7. The easiest way to
+ // handle this is to just treat this as clearance.
+ height = height + floats.clearance(ClearBoth);
+
+ // Fixed position layers get layers.
+ if self.is_fixed() {
+ self.base.flags_info.flags.set_needs_layer(true)
+ }
+
+ // 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 true;
+ return
}
- false
- }
- /// Compute the box height and set border_box and margin of the box.
- pub fn compute_height_position(&mut self,
- height: &mut Au,
- border_and_padding: Au,
- margin_top: Au,
- margin_bottom: Au,
- clearance: Au) {
- // Here, height is content height of box_
- let mut noncontent_height = border_and_padding;
- for box_ in self.box_.iter() {
- let mut position = box_.border_box.get();
- let mut margin = box_.margin.get();
+ for fragment in self.box_.iter() {
+ // FIXME(pcwalton): Is this right?
+ let containing_block_height = height;
- // The associated box is the border box of this flow.
- // Margin after collapse
- margin.top = margin_top;
- margin.bottom = margin_bottom;
+ let mut candidate_height_iterator =
+ CandidateHeightIterator::new(fragment.style(), containing_block_height, false);
+ for (candidate_height, new_candidate_height) in candidate_height_iterator {
+ *new_candidate_height = match candidate_height {
+ Auto => height,
+ Specified(value) => value
+ }
+ }
- position.origin.y = clearance + margin.top;
- // Border box height
- position.size.height = *height + noncontent_height;
+ // Adjust `cur_y` as necessary to account for the explicitly-specified height.
+ height = candidate_height_iterator.candidate_value;
+ let delta = height - (cur_y - top_offset);
+ translate_including_floats(&mut cur_y, delta, inorder, &mut floats);
- noncontent_height = noncontent_height + clearance + margin.top + margin.bottom;
+ // Compute content height and noncontent height.
+ let bottom_offset = fragment.border.get().bottom + fragment.padding.get().bottom;
+ translate_including_floats(&mut cur_y, bottom_offset, inorder, &mut floats);
- box_.border_box.set(position);
- box_.margin.set(margin);
+ // Now that `cur_y` is at the bottom of the border box, compute the final border box
+ // position.
+ let mut border_box = fragment.border_box.get();
+ border_box.size.height = cur_y;
+ border_box.origin.y = Au(0);
+ fragment.border_box.set(border_box);
+ self.base.position.size.height = cur_y;
}
- // Height of margin box + clearance
- self.base.position.size.height = *height + noncontent_height;
- }
-
- /// Set floats_out at the last step of the assign height calculation.
- pub fn set_floats_out_if_inorder(&mut self,
- inorder: bool,
- height: Au,
- cur_y: Au,
- top_offset: Au,
- bottom_offset: Au,
- left_offset: Au) {
- if inorder {
- let extra_height = height - (cur_y - top_offset) + bottom_offset;
- self.base.floats.translate(Point2D(left_offset, -extra_height));
- }
- }
+ self.base.floats = floats.clone();
+ self.adjust_boxes_for_collapsed_margins_if_root();
- /// Assign heights for all flows in absolute flow tree and store overflow for all
- /// absolute descendants.
- pub fn assign_height_absolute_flows(&mut self, ctx: &mut LayoutContext) {
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));
+ self.traverse_preorder_absolute_flows(&mut AbsoluteAssignHeightsTraversal(
+ layout_context));
// Store overflow for all absolute descendants.
self.traverse_postorder_absolute_flows(&mut AbsoluteStoreOverflowTraversal {
- layout_context: ctx,
+ layout_context: layout_context,
});
}
}
- /// 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) {
-
- // Note: Ignoring clearance for absolute flows as of now.
- let ignore_clear = self.is_absolutely_positioned();
- let (clearance, mut top_offset, bottom_offset, left_offset) =
- self.initialize_offsets(ignore_clear);
-
- self.handle_children_floats_if_necessary(ctx, inorder,
- left_offset, top_offset);
-
- let (mut margin_top, mut margin_bottom,
- top_margin_collapsible, bottom_margin_collapsible) = self.precompute_margin();
-
- let mut cur_y = top_offset;
- let collapsing = self.compute_margin_collapse(&mut cur_y,
- &mut top_offset,
- &mut margin_top,
- &mut margin_bottom,
- top_margin_collapsible,
- bottom_margin_collapsible);
-
- // TODO: A box's own margins collapse if the 'min-height' property is zero, and it has neither
- // top or bottom borders nor top or bottom padding, and it has a 'height' of either 0 or 'auto',
- // and it does not contain a line box, and all of its in-flow children's margins (if any) collapse.
-
- let screen_height = ctx.screen_size.height;
-
- 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 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
- };
-
- // For an absolutely positioned element, store the content height and stop the function.
- if self.store_content_height_if_absolutely_positioned(height) {
- return;
- }
-
- let mut border_and_padding = Au::new(0);
- 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.
- height = match MaybeAuto::from_style(style.Box.get().height, height) {
- Auto => height,
- Specified(value) => value
- };
-
- border_and_padding = box_.padding.get().top + box_.padding.get().bottom +
- box_.border.get().top + box_.border.get().bottom;
- }
-
- self.compute_height_position(&mut height,
- border_and_padding,
- margin_top,
- margin_bottom,
- clearance);
-
- self.set_floats_out_if_inorder(inorder, height, cur_y, top_offset, bottom_offset, left_offset);
- self.assign_height_absolute_flows(ctx);
- if self.is_root() {
- self.assign_height_store_overflow_fixed_flows(ctx);
- }
- }
-
- /// 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
@@ -1157,7 +1084,7 @@ impl BlockFlow {
let info = PlacementInfo {
size: Size2D(self.base.position.size.width + full_noncontent_width,
height + margin_height),
- ceiling: clearance,
+ ceiling: clearance + self.base.position.origin.y,
max_width: self.float.get_ref().containing_width,
kind: self.float.get_ref().float_kind,
};
@@ -1208,7 +1135,7 @@ impl BlockFlow {
cur_y = cur_y + child_base.position.size.height;
}
- let mut height = cur_y - top_offset;
+ let content_height = cur_y - top_offset;
let mut noncontent_height;
let box_ = self.box_.as_ref().unwrap();
@@ -1220,107 +1147,92 @@ impl BlockFlow {
noncontent_height = box_.padding.get().top + box_.padding.get().bottom +
box_.border.get().top + box_.border.get().bottom;
- //TODO(eatkinson): compute heights properly using the 'height' property.
- let height_prop = MaybeAuto::from_style(box_.style().Box.get().height,
- Au::new(0)).specified_or_zero();
+ // Calculate content height, taking `min-height` and `max-height` into account.
- height = geometry::max(height, height_prop) + noncontent_height;
- debug!("assign_height_float -- height: {}", height);
+ let mut candidate_height_iterator = CandidateHeightIterator::new(box_.style(),
+ content_height,
+ false);
+ for (candidate_height, new_candidate_height) in candidate_height_iterator {
+ *new_candidate_height = match candidate_height {
+ Auto => content_height,
+ Specified(value) => value,
+ }
+ }
- position.size.height = height;
- box_.border_box.set(position);
- }
+ let content_height = candidate_height_iterator.candidate_value;
- /// In case of float, initialize containing_width at the beginning step of assign_width.
- pub fn set_containing_width_if_float(&mut self, containing_block_width: Au) {
- if self.is_float() {
- self.float.get_mut_ref().containing_width = containing_block_width;
+ debug!("assign_height_float -- height: {}", content_height + noncontent_height);
- // Parent usually sets this, but floats are never inorder
- self.base.flags_info.flags.set_inorder(false);
- }
+ position.size.height = content_height + noncontent_height;
+ box_.border_box.set(position);
}
- /// Assign the computed left_content_edge and content_width to children.
- pub fn propagate_assigned_width_to_children(&mut self, left_content_edge: Au,
- content_width: Au,
- opt_col_widths: Option<~[Au]>) {
- let has_inorder_children = if self.is_float() {
- self.base.num_floats > 0
- } else {
- 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();
-
- // Left margin edge of kid flow is at our left content edge
- let mut kid_left_margin_edge = left_content_edge;
- // Width of kid flow is our content width
- let mut kid_width = content_width;
- for (i, kid) in self.base.child_iter().enumerate() {
- assert!(kid.is_block_flow() || kid.is_inline_flow() || kid.is_table_kind());
- match opt_col_widths {
- Some(ref col_widths) => {
- // If kid is table_rowgroup or table_row, the column widths info should be
- // copied from its parent.
- if kid.is_table_rowgroup() {
- kid.as_table_rowgroup().col_widths = col_widths.clone()
- } else if kid.is_table_row() {
- kid.as_table_row().col_widths = col_widths.clone()
- } else if kid.is_table_cell() {
- // If kid is table_cell, the x offset and width for each cell should be
- // calculated from parent's column widths info.
- kid_left_margin_edge = if i == 0 {
- Au(0)
- } else {
- kid_left_margin_edge + col_widths[i-1]
- };
- kid_width = col_widths[i]
- }
- }
- None => {}
- }
-
- 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;
+ fn build_display_list_block_common(&mut self,
+ stacking_context: &mut StackingContext,
+ builder: &mut DisplayListBuilder,
+ info: &DisplayListBuildingInfo,
+ offset: Point2D<Au>,
+ background_border_level: BackgroundAndBorderLevel) {
+ let mut info = *info;
+ let mut rel_offset = Point2D(Au(0), Au(0));
+ for fragment in self.box_.iter() {
+ rel_offset = fragment.relative_position(&info.containing_block_size);
+
+ // Add the box that starts the block context.
+ fragment.build_display_list(stacking_context,
+ builder,
+ &info,
+ self.base.abs_position + rel_offset + offset,
+ (&*self) as &Flow,
+ background_border_level);
+
+ // For relatively-positioned descendants, the containing block formed by a block is
+ // just the content box. The containing block for absolutely-positioned descendants,
+ // on the other hand, only established if we are positioned.
+ let container_block_size = fragment.content_box_size();
+ if self.is_positioned() {
+ info.absolute_containing_block_position = self.base.abs_position +
+ self.generated_cb_position() +
+ fragment.relative_position(&container_block_size)
+ }
+ }
+
+ let this_position = self.base.abs_position;
+ for kid in self.base.child_iter() {
+ {
+ let kid_base = flow::mut_base(kid);
+ kid_base.abs_position = this_position + kid_base.position.origin + rel_offset +
+ offset;
}
- let child_base = flow::mut_base(kid);
- child_base.position.origin.x = kid_left_margin_edge;
- child_base.position.size.width = kid_width;
- child_base.flags_info.flags.set_inorder(has_inorder_children);
- if !child_base.flags_info.flags.inorder() {
- child_base.floats = Floats::new();
+ if kid.is_absolutely_positioned() {
+ // All absolute flows will be handled by their containing block.
+ continue
}
- // Per CSS 2.1 § 16.3.1, text decoration propagates to all children in flow.
- //
- // TODO(pcwalton): When we have out-of-flow children, don't unconditionally propagate.
+ kid.build_display_list(stacking_context, builder, &info);
+ }
- child_base.flags_info.propagate_text_decoration_from_parent(&flags_info);
- child_base.flags_info.propagate_text_alignment_from_parent(&flags_info)
+ // Process absolute descendant links.
+ //
+ // 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 :|
+ let mut absolute_info = info;
+ absolute_info.layers_needed_for_positioned_flows =
+ self.base.flags_info.flags.layers_needed_for_descendants();
+ for abs_descendant_link in self.base.abs_descendants.iter() {
+ match abs_descendant_link.resolve() {
+ Some(flow) => {
+ // TODO(pradeep): Send in our absolute position directly.
+ flow.build_display_list(stacking_context, builder, &absolute_info)
+ }
+ None => fail!("empty Rawlink to a descendant")
+ }
}
}
@@ -1374,12 +1286,10 @@ impl BlockFlow {
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 content_height = box_.border_box.get().size.height - box_.noncontent_height();
let style = box_.style();
- let height_used_val = MaybeAuto::from_style(style.Box.get().height, containing_block_height);
-
// Non-auto margin-top and margin-bottom values have already been
// calculated during assign-width.
let margin = box_.margin.get();
@@ -1397,7 +1307,8 @@ impl BlockFlow {
MaybeAuto::from_style(style.PositionOffsets.get().bottom, containing_block_height));
let available_height = containing_block_height - box_.noncontent_height();
- let solution = if self.is_replaced_content() {
+ let mut solution = None;
+ 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.
@@ -1618,6 +1529,10 @@ impl Flow for BlockFlow {
self.compute_used_width(ctx, containing_block_width);
for box_ in self.box_.iter() {
+ // Assign `clear` now so that the assign-heights pass will have the correct value for
+ // it.
+ self.base.clear = box_.style().Box.get().clear;
+
// Move in from the left border edge
left_content_edge = box_.border_box.get().origin.x
+ box_.padding.get().left + box_.border.get().left;
@@ -1643,7 +1558,7 @@ impl Flow for BlockFlow {
self.assign_height_float_inorder();
} else {
debug!("assign_height_inorder: assigning height for block");
- self.assign_height_block_base(ctx, true);
+ self.assign_height_block_base(ctx, true, MarginsMayCollapse);
}
}
@@ -1664,49 +1579,8 @@ impl Flow for BlockFlow {
self.assign_height_inorder(ctx);
return;
}
- self.assign_height_block_base(ctx, false);
- }
- }
-
- // 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,
- margin_top: &mut Au,
- top_offset: &mut Au,
- collapsing: &mut Au,
- collapsible: &mut Au) {
- if self.is_float() {
- // Margins between a floated box and any other box do not collapse.
- *collapsing = Au::new(0);
- return;
- }
-
- for box_ in self.box_.iter() {
- // The top margin collapses with its first in-flow block-level child's
- // top margin if the parent has no top border, no top padding.
- if *first_in_flow && top_margin_collapsible {
- // If top-margin of parent is less than top-margin of its first child,
- // the parent box goes down until its top is aligned with the child.
- if *margin_top < box_.margin.get().top {
- // TODO: The position of child floats should be updated and this
- // would influence clearance as well. See #725
- let extra_margin = box_.margin.get().top - *margin_top;
- *top_offset = *top_offset + extra_margin;
- *margin_top = box_.margin.get().top;
- }
- }
- // The bottom margin of an in-flow block-level element collapses
- // with the top margin of its next in-flow block-level sibling.
- *collapsing = geometry::min(box_.margin.get().top, *collapsible);
- *collapsible = box_.margin.get().bottom;
+ self.assign_height_block_base(ctx, false, MarginsMayCollapse);
}
-
- *first_in_flow = false;
}
fn mark_as_root(&mut self) {
@@ -1792,6 +1666,10 @@ impl Flow for BlockFlow {
None => ~"",
})
}
+
+ fn is_absolute_containing_block(&self) -> bool {
+ self.is_positioned()
+ }
}
/// The inputs for the widths-and-margins constraint equation.
diff --git a/src/components/main/layout/flow.rs b/src/components/main/layout/flow.rs
index 60d9dd82ed9..0d1eff03f8c 100644
--- a/src/components/main/layout/flow.rs
+++ b/src/components/main/layout/flow.rs
@@ -148,15 +148,10 @@ pub trait Flow {
fail!("assign_height_inorder not yet implemented")
}
- /// Collapses margins with the parent flow. This runs as part of assign-heights.
- fn collapse_margins(&mut self,
- _top_margin_collapsible: bool,
- _first_in_flow: &mut bool,
- _margin_top: &mut Au,
- _top_offset: &mut Au,
- _collapsing: &mut Au,
- _collapsible: &mut Au) {
- fail!("collapse_margins not yet implemented")
+ fn compute_collapsible_top_margin(&mut self,
+ _layout_context: &mut LayoutContext,
+ _margin_collapse_info: &mut MarginCollapseInfo) {
+ // The default implementation is a no-op.
}
/// Marks this flow as the root flow. The default implementation is a no-op.
@@ -721,12 +716,16 @@ pub struct BaseFlow {
/* layout computations */
// TODO: min/pref and position are used during disjoint phases of
// layout; maybe combine into a single enum to save space.
- min_width: Au,
- pref_width: Au,
+ intrinsic_widths: IntrinsicWidths,
- /// 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.
+ /// 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 with respect to its *containing block*.
+ ///
+ /// This does not include margins in the block flow direction, because those can collapse. So
+ /// for the block direction (usually vertical), this represents the *border box*. For the
+ /// inline direction (usually horizontal), this represents the *margin box*.
position: Rect<Au>,
/// The amount of overflow of this flow, relative to the containing block. Must include all the
@@ -741,6 +740,9 @@ pub struct BaseFlow {
/// The floats next to this flow.
floats: Floats,
+ /// The value of this flow's `clear` property, if any.
+ clear: clear::T,
+
/// 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.
diff --git a/src/components/main/layout/inline.rs b/src/components/main/layout/inline.rs
index 26466a33ac5..e162a270651 100644
--- a/src/components/main/layout/inline.rs
+++ b/src/components/main/layout/inline.rs
@@ -884,20 +884,6 @@ impl Flow for InlineFlow {
self.base.floats.translate(Point2D(Au::new(0), -self.base.position.size.height));
}
- fn collapse_margins(&mut self,
- _: bool,
- _: &mut bool,
- _: &mut Au,
- _: &mut Au,
- collapsing: &mut Au,
- collapsible: &mut Au) {
- *collapsing = Au::new(0);
- // Non-empty inline flows prevent collapsing between the previous margion and the next.
- if self.base.position.size.height > Au::new(0) {
- *collapsible = Au::new(0);
- }
- }
-
fn debug_str(&self) -> ~str {
~"InlineFlow: " + self.boxes.map(|s| s.debug_str()).connect(", ")
}
diff --git a/src/components/main/layout/model.rs b/src/components/main/layout/model.rs
index 2a8b6af2f69..205ccf122d1 100644
--- a/src/components/main/layout/model.rs
+++ b/src/components/main/layout/model.rs
@@ -4,8 +4,252 @@
//! Borders, padding, and margins.
-use servo_util::geometry::Au;
+use layout::box_::Box;
+
use computed = style::computed_values;
+use servo_util::geometry::Au;
+use servo_util::geometry;
+
+/// A collapsible margin. See CSS 2.1 § 8.3.1.
+pub struct AdjoiningMargins {
+ /// The value of the greatest positive margin.
+ most_positive: Au,
+
+ /// The actual value (not the absolute value) of the negative margin with the largest absolute
+ /// value. Since this is not the absolute value, this is always zero or negative.
+ most_negative: Au,
+}
+
+impl AdjoiningMargins {
+ pub fn new() -> AdjoiningMargins {
+ AdjoiningMargins {
+ most_positive: Au(0),
+ most_negative: Au(0),
+ }
+ }
+
+ pub fn from_margin(margin_value: Au) -> AdjoiningMargins {
+ if margin_value >= Au(0) {
+ AdjoiningMargins {
+ most_positive: margin_value,
+ most_negative: Au(0),
+ }
+ } else {
+ AdjoiningMargins {
+ most_positive: Au(0),
+ most_negative: margin_value,
+ }
+ }
+ }
+
+ pub fn union(&mut self, other: AdjoiningMargins) {
+ self.most_positive = geometry::max(self.most_positive, other.most_positive);
+ self.most_negative = geometry::min(self.most_negative, other.most_negative)
+ }
+
+ pub fn collapse(&self) -> Au {
+ self.most_positive + self.most_negative
+ }
+}
+
+/// Represents the top and bottom margins of a flow with collapsible margins. See CSS 2.1 § 8.3.1.
+pub enum CollapsibleMargins {
+ /// Margins may not collapse with this flow.
+ NoCollapsibleMargins(Au, Au),
+
+ /// Both the top and bottom margins (specified here in that order) may collapse, but the
+ /// margins do not collapse through this flow.
+ MarginsCollapse(AdjoiningMargins, AdjoiningMargins),
+
+ /// Margins collapse *through* this flow. This means, essentially, that the flow is empty.
+ MarginsCollapseThrough(AdjoiningMargins),
+}
+
+impl CollapsibleMargins {
+ pub fn new() -> CollapsibleMargins {
+ NoCollapsibleMargins(Au(0), Au(0))
+ }
+}
+
+enum FinalMarginState {
+ MarginsCollapseThroughFinalMarginState,
+ BottomMarginCollapsesFinalMarginState,
+}
+
+pub struct MarginCollapseInfo {
+ state: MarginCollapseState,
+ top_margin: AdjoiningMargins,
+ margin_in: AdjoiningMargins,
+}
+
+impl MarginCollapseInfo {
+ /// TODO(pcwalton): Remove this method once `box_` is not an `Option`.
+ pub fn new() -> MarginCollapseInfo {
+ MarginCollapseInfo {
+ state: AccumulatingCollapsibleTopMargin,
+ top_margin: AdjoiningMargins::new(),
+ margin_in: AdjoiningMargins::new(),
+ }
+ }
+
+ pub fn initialize_top_margin(&mut self,
+ fragment: &Box,
+ can_collapse_top_margin_with_kids: bool) {
+ if !can_collapse_top_margin_with_kids {
+ self.state = AccumulatingMarginIn
+ }
+
+ self.top_margin = AdjoiningMargins::from_margin(fragment.margin.get().top)
+ }
+
+ pub fn finish_and_compute_collapsible_margins(mut self,
+ fragment: &Box,
+ can_collapse_bottom_margin_with_kids: bool)
+ -> (CollapsibleMargins, Au) {
+ let state = match self.state {
+ AccumulatingCollapsibleTopMargin => {
+ match MaybeAuto::from_style(fragment.style().Box.get().height, Au(0)) {
+ Auto | Specified(Au(0)) => MarginsCollapseThroughFinalMarginState,
+ Specified(_) => {
+ // If the box has an explicitly specified height, margins may not collapse
+ // through it.
+ BottomMarginCollapsesFinalMarginState
+ }
+ }
+ }
+ AccumulatingMarginIn => BottomMarginCollapsesFinalMarginState,
+ };
+
+ // Different logic is needed here depending on whether this flow can collapse its bottom
+ // margin with its children.
+ let bottom_margin = fragment.margin.get().bottom;
+ if !can_collapse_bottom_margin_with_kids {
+ match state {
+ MarginsCollapseThroughFinalMarginState => {
+ let advance = self.top_margin.collapse();
+ self.margin_in.union(AdjoiningMargins::from_margin(bottom_margin));
+ (MarginsCollapse(self.top_margin, self.margin_in), advance)
+ }
+ BottomMarginCollapsesFinalMarginState => {
+ let advance = self.margin_in.collapse();
+ self.margin_in.union(AdjoiningMargins::from_margin(bottom_margin));
+ (MarginsCollapse(self.top_margin, self.margin_in), advance)
+ }
+ }
+ } else {
+ match state {
+ MarginsCollapseThroughFinalMarginState => {
+ self.top_margin.union(AdjoiningMargins::from_margin(bottom_margin));
+ (MarginsCollapseThrough(self.top_margin), Au(0))
+ }
+ BottomMarginCollapsesFinalMarginState => {
+ self.margin_in.union(AdjoiningMargins::from_margin(bottom_margin));
+ (MarginsCollapse(self.top_margin, self.margin_in), Au(0))
+ }
+ }
+ }
+ }
+
+ pub fn current_float_ceiling(&mut self) -> Au {
+ match self.state {
+ AccumulatingCollapsibleTopMargin => self.top_margin.collapse(),
+ AccumulatingMarginIn => self.margin_in.collapse(),
+ }
+ }
+
+ /// Adds the child's potentially collapsible top margin to the current margin state and
+ /// advances the Y offset by the appropriate amount to handle that margin. Returns the amount
+ /// that should be added to the Y offset during block layout.
+ pub fn advance_top_margin(&mut self, child_collapsible_margins: &CollapsibleMargins) -> Au {
+ match (self.state, *child_collapsible_margins) {
+ (AccumulatingCollapsibleTopMargin, NoCollapsibleMargins(top, _)) => {
+ self.state = AccumulatingMarginIn;
+ top
+ }
+ (AccumulatingCollapsibleTopMargin, MarginsCollapse(top, _)) => {
+ self.top_margin.union(top);
+ self.state = AccumulatingMarginIn;
+ Au(0)
+ }
+ (AccumulatingMarginIn, NoCollapsibleMargins(top, _)) => {
+ let previous_margin_value = self.margin_in.collapse();
+ self.margin_in = AdjoiningMargins::new();
+ previous_margin_value + top
+ }
+ (AccumulatingMarginIn, MarginsCollapse(top, _)) => {
+ self.margin_in.union(top);
+ let margin_value = self.margin_in.collapse();
+ self.margin_in = AdjoiningMargins::new();
+ margin_value
+ }
+ (_, MarginsCollapseThrough(_)) => {
+ // For now, we ignore this; this will be handled by `advance_bottom_margin` below.
+ Au(0)
+ }
+ }
+ }
+
+ /// Adds the child's potentially collapsible bottom margin to the current margin state and
+ /// advances the Y offset by the appropriate amount to handle that margin. Returns the amount
+ /// that should be added to the Y offset during block layout.
+ pub fn advance_bottom_margin(&mut self, child_collapsible_margins: &CollapsibleMargins) -> Au {
+ match (self.state, *child_collapsible_margins) {
+ (AccumulatingCollapsibleTopMargin, NoCollapsibleMargins(..)) |
+ (AccumulatingCollapsibleTopMargin, MarginsCollapse(..)) => {
+ // Can't happen because the state will have been replaced with
+ // `AccumulatingMarginIn` above.
+ fail!("should not be accumulating collapsible top margins anymore!")
+ }
+ (AccumulatingCollapsibleTopMargin, MarginsCollapseThrough(margin)) => {
+ self.top_margin.union(margin);
+ Au(0)
+ }
+ (AccumulatingMarginIn, NoCollapsibleMargins(_, bottom)) => {
+ // Margin-in should have been set to zero above.
+ bottom
+ }
+ (AccumulatingMarginIn, MarginsCollapse(_, bottom)) |
+ (AccumulatingMarginIn, MarginsCollapseThrough(bottom)) => {
+ self.margin_in.union(bottom);
+ Au(0)
+ }
+ }
+ }
+}
+
+enum MarginCollapseState {
+ AccumulatingCollapsibleTopMargin,
+ AccumulatingMarginIn,
+}
+
+/// Intrinsic widths, which consist of minimum and preferred.
+pub struct IntrinsicWidths {
+ /// The *minimum width* of the content.
+ minimum_width: Au,
+ /// The *preferred width* of the content.
+ preferred_width: Au,
+ /// The estimated sum of borders, padding, and margins. Some calculations use this information
+ /// when computing intrinsic widths.
+ surround_width: Au,
+}
+
+impl IntrinsicWidths {
+ pub fn new() -> IntrinsicWidths {
+ IntrinsicWidths {
+ minimum_width: Au(0),
+ preferred_width: Au(0),
+ surround_width: Au(0),
+ }
+ }
+
+ pub fn total_minimum_width(&self) -> Au {
+ self.minimum_width + self.surround_width
+ }
+
+ pub fn total_preferred_width(&self) -> Au {
+ self.preferred_width + self.surround_width
+ }
+}
/// Useful helper data type when computing values for blocks and positioned elements.
pub enum MaybeAuto {