From 357463864b573b989486616be92f6b2bd587349f Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Fri, 18 Sep 2015 11:30:45 +0200 Subject: [PATCH 1/8] Avoid a transmute that relied on undefined struct layout. --- components/layout/layout_thread.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/layout/layout_thread.rs b/components/layout/layout_thread.rs index 6f28c18ee9c..206bfe4c82d 100644 --- a/components/layout/layout_thread.rs +++ b/components/layout/layout_thread.rs @@ -55,7 +55,6 @@ use std::borrow::ToOwned; use std::cell::RefCell; use std::collections::HashMap; use std::collections::hash_state::DefaultState; -use std::mem::transmute; use std::ops::{Deref, DerefMut}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::mpsc::{channel, Sender, Receiver}; @@ -1313,7 +1312,8 @@ impl LayoutThread { /// Handles a message to destroy layout data. Layout data must be destroyed on *this* thread /// because the struct type is transmuted to a different type on the script side. unsafe fn handle_reap_style_and_layout_data(&self, data: OpaqueStyleAndLayoutData) { - let non_opaque: NonOpaqueStyleAndLayoutData = transmute(data.ptr); + let ptr: *mut () = *data.ptr; + let non_opaque: NonOpaqueStyleAndLayoutData = ptr as *mut _; let _ = Box::from_raw(non_opaque); } From da2b4ab3812605580187373d5fb1ff3c8de78fb4 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Fri, 18 Sep 2015 15:27:08 +0200 Subject: [PATCH 2/8] Disable incremental reflow for multicol and their descendants. Fragmentation with dynamic updates is hard. --- components/layout/construct.rs | 3 +++ components/layout/wrapper.rs | 16 +++++++++++++++- components/script/dom/node.rs | 3 +++ components/style/dom.rs | 4 ++++ components/style/matching.rs | 5 +++++ ports/geckolib/wrapper.rs | 11 +++++++++++ 6 files changed, 41 insertions(+), 1 deletion(-) diff --git a/components/layout/construct.rs b/components/layout/construct.rs index 89c895ca4f2..2e05b1ed694 100644 --- a/components/layout/construct.rs +++ b/components/layout/construct.rs @@ -1318,6 +1318,9 @@ impl<'a, 'ln, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode<'ln>> return false } + if node.in_fragmentation_container() { + return false + } let mut style = node.style().clone(); let mut data = node.mutate_layout_data().unwrap(); diff --git a/components/layout/wrapper.rs b/components/layout/wrapper.rs index 8d4f818d2b6..652dd8c6359 100644 --- a/components/layout/wrapper.rs +++ b/components/layout/wrapper.rs @@ -50,7 +50,7 @@ use script::dom::htmlimageelement::LayoutHTMLImageElementHelpers; use script::dom::htmlinputelement::{HTMLInputElement, LayoutHTMLInputElementHelpers}; use script::dom::htmltextareaelement::{HTMLTextAreaElement, LayoutHTMLTextAreaElementHelpers}; use script::dom::node::{HAS_CHANGED, HAS_DIRTY_DESCENDANTS, IS_DIRTY}; -use script::dom::node::{LayoutNodeHelpers, Node, OpaqueStyleAndLayoutData}; +use script::dom::node::{IN_FRAGMENTATION_CONTAINER, LayoutNodeHelpers, Node, OpaqueStyleAndLayoutData}; use script::dom::text::Text; use script::layout_interface::TrustedNodeAddress; use selectors::matching::DeclarationBlock; @@ -226,6 +226,14 @@ impl<'ln> TNode<'ln> for ServoLayoutNode<'ln> { self.node.set_flag(HAS_DIRTY_DESCENDANTS, value) } + fn in_fragmentation_container(&self) -> bool { + unsafe { self.node.get_flag(IN_FRAGMENTATION_CONTAINER) } + } + + unsafe fn set_in_fragmentation_container(&self, value: bool) { + self.node.set_flag(IN_FRAGMENTATION_CONTAINER, value) + } + unsafe fn borrow_data_unchecked(&self) -> Option<*const PrivateStyleData> { self.borrow_layout_data_unchecked().map(|d| &(*d).style_data as *const PrivateStyleData) } @@ -753,6 +761,8 @@ pub trait ThreadSafeLayoutNode<'ln> : Clone + Copy + Sized { } } + fn in_fragmentation_container(&self) -> bool; + /// If this is a text node, generated content, or a form element, copies out /// its content. Otherwise, panics. /// @@ -929,6 +939,10 @@ impl<'ln> ThreadSafeLayoutNode<'ln> for ServoThreadSafeLayoutNode<'ln> { } } + fn in_fragmentation_container(&self) -> bool { + self.node.in_fragmentation_container() + } + fn text_content(&self) -> TextContent { if self.pseudo != PseudoElementType::Normal { let data = &self.borrow_layout_data().unwrap().style_data; diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index e5d1367e993..3f8bcefeef8 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -144,6 +144,9 @@ bitflags! { #[doc = "Specifies whether this node is focusable and whether it is supposed \ to be reachable with using sequential focus navigation."] const SEQUENTIALLY_FOCUSABLE = 0x20, + + /// Whether any ancestor is a fragmentation container + const IN_FRAGMENTATION_CONTAINER = 0x40 } } diff --git a/components/style/dom.rs b/components/style/dom.rs index 25656b22e5c..63c645901b3 100644 --- a/components/style/dom.rs +++ b/components/style/dom.rs @@ -129,6 +129,10 @@ pub trait TNode<'ln> : Sized + Copy + Clone { } } + fn in_fragmentation_container(&self) -> bool; + + unsafe fn set_in_fragmentation_container(&self, value: bool); + /// Borrows the PrivateStyleData without checks. #[inline(always)] unsafe fn borrow_data_unchecked(&self) -> Option<*const PrivateStyleData>; diff --git a/components/style/matching.rs b/components/style/matching.rs index b8f555edc02..f2a1515ef07 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -700,6 +700,11 @@ pub trait MatchMethods<'ln> : TNode<'ln> { // This method needs to borrow the data as mutable, so make sure data_ref goes out of // scope first. self.set_restyle_damage(damage); + + self.set_in_fragmentation_container( + parent.as_ref().map_or(false, |p| p.in_fragmentation_container()) || + self.borrow_data().unwrap().style.as_ref().unwrap().is_multicol() + ); } } } diff --git a/ports/geckolib/wrapper.rs b/ports/geckolib/wrapper.rs index 1c71181e795..77563f91ee3 100644 --- a/ports/geckolib/wrapper.rs +++ b/ports/geckolib/wrapper.rs @@ -182,6 +182,17 @@ impl<'ln> TNode<'ln> for GeckoNode<'ln> { unimplemented!() } + fn in_fragmentation_container(&self) -> bool { + // FIXME(SimonSapin): Servo uses this to implement CSS multicol / fragmentation + // Maybe this isn’t useful for Gecko? + false + } + + unsafe fn set_in_fragmentation_container(&self, _value: bool) { + // FIXME(SimonSapin): Servo uses this to implement CSS multicol / fragmentation + // Maybe this isn’t useful for Gecko? + } + #[inline(always)] unsafe fn borrow_data_unchecked(&self) -> Option<*const PrivateStyleData> { self.get_node_data().as_ref().map(|d| d.as_unsafe_cell().get() as *const PrivateStyleData) From 9abbd1b5d1506b9183915f7c44e436ebd79471ee Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Tue, 29 Dec 2015 17:44:50 +0000 Subject: [PATCH 3/8] Sequentialize assign_block_size for flows that can be fragmented. Fragmentation will be intertwined with block size calculation. --- components/layout/construct.rs | 12 +++++++++--- components/layout/flow.rs | 24 ++++++++++++++++++++++++ components/layout/multicol.rs | 6 +++++- components/layout/traversal.rs | 7 +++++-- components/layout/wrapper.rs | 18 +++++++++--------- components/script/dom/node.rs | 2 +- components/style/dom.rs | 4 ++-- components/style/matching.rs | 8 ++++---- ports/geckolib/wrapper.rs | 4 ++-- 9 files changed, 61 insertions(+), 24 deletions(-) diff --git a/components/layout/construct.rs b/components/layout/construct.rs index 2e05b1ed694..53ddc2bed04 100644 --- a/components/layout/construct.rs +++ b/components/layout/construct.rs @@ -18,7 +18,7 @@ use context::LayoutContext; use data::{HAS_NEWLY_CONSTRUCTED_FLOW, PrivateLayoutData}; use flex::FlexFlow; use floats::FloatKind; -use flow::{MutableFlowUtils, MutableOwnedFlowUtils}; +use flow::{MutableFlowUtils, MutableOwnedFlowUtils, CAN_BE_FRAGMENTED}; use flow::{self, AbsoluteDescendants, Flow, IS_ABSOLUTELY_POSITIONED, ImmutableFlowUtils}; use flow_ref::{self, FlowRef}; use fragment::{CanvasFragmentInfo, ImageFragmentInfo, InlineAbsoluteFragmentInfo}; @@ -1318,7 +1318,7 @@ impl<'a, 'ln, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode<'ln>> return false } - if node.in_fragmentation_container() { + if node.can_be_fragmented() || node.style().is_multicol() { return false } @@ -1616,7 +1616,13 @@ impl<'ln, ConcreteThreadSafeLayoutNode> NodeUtils for ConcreteThreadSafeLayoutNo } #[inline(always)] - fn set_flow_construction_result(self, result: ConstructionResult) { + fn set_flow_construction_result(self, mut result: ConstructionResult) { + if self.can_be_fragmented() { + if let ConstructionResult::Flow(ref mut flow, _) = result { + flow::mut_base(flow_ref::deref_mut(flow)).flags.insert(CAN_BE_FRAGMENTED); + } + } + let mut layout_data = self.mutate_layout_data().unwrap(); let dst = self.construction_result_mut(&mut *layout_data); diff --git a/components/layout/flow.rs b/components/layout/flow.rs index 2df03d43233..4a8b206193a 100644 --- a/components/layout/flow.rs +++ b/components/layout/flow.rs @@ -201,6 +201,27 @@ pub trait Flow: fmt::Debug + Sync + Send + 'static { panic!("assign_block_size not yet implemented") } + /// Like `assign_block_size`, but is recurses explicitly into descendants. + /// Fit as much content as possible within `available_block_size`. + /// If that’s not all of it, + /// return an indication of where in the tree to break and start the next fragment. + /// + /// FIXME: replace `()` in the return value with something meaningful. + /// + /// The default is to make a flow "atomic": it can not be fragmented. + fn fragment<'a>(&mut self, ctx: &'a LayoutContext<'a>, _available_block_size: Au) + -> Option<()> { + fn recursive_assign_block_size<'a, F: ?Sized + Flow> + (flow: &mut F, ctx: &'a LayoutContext<'a>) { + for child in mut_base(flow).children.iter_mut() { + recursive_assign_block_size(child, ctx) + } + flow.assign_block_size(ctx); + } + recursive_assign_block_size(self, ctx); + None + } + /// If this is a float, places it. The default implementation does nothing. fn place_float_if_applicable<'a>(&mut self, _: &'a LayoutContext<'a>) {} @@ -614,6 +635,9 @@ bitflags! { static` and `position: relative` as well as absolutely-positioned flows with \ unconstrained positions in the block direction."] const BLOCK_POSITION_IS_STATIC = 0b0100_0000_0000_0000_0000, + + /// Whether any ancestor is a fragmentation container + const CAN_BE_FRAGMENTED = 0b1000_0000_0000_0000_0000, } } diff --git a/components/layout/multicol.rs b/components/layout/multicol.rs index 3db1479dc52..f0c4acd09ea 100644 --- a/components/layout/multicol.rs +++ b/components/layout/multicol.rs @@ -11,7 +11,7 @@ use block::BlockFlow; use context::LayoutContext; use euclid::{Point2D, Rect}; use floats::FloatKind; -use flow::{Flow, FlowClass, OpaqueFlow}; +use flow::{Flow, FlowClass, OpaqueFlow, mut_base}; use fragment::{Fragment, FragmentBorderBoxIterator}; use std::fmt; use std::sync::Arc; @@ -60,6 +60,10 @@ impl Flow for MulticolFlow { fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) { debug!("assign_block_size: assigning block_size for multicol"); + let available_block_size = Au(0); + for child in mut_base(self).children.iter_mut() { + child.fragment(ctx, available_block_size); + } self.block_flow.assign_block_size(ctx); } diff --git a/components/layout/traversal.rs b/components/layout/traversal.rs index b5f7505d834..69e85c39303 100644 --- a/components/layout/traversal.rs +++ b/components/layout/traversal.rs @@ -10,7 +10,7 @@ use construct::FlowConstructor; use context::{LayoutContext, SharedLayoutContext}; use flow::{PostorderFlowTraversal, PreorderFlowTraversal}; -use flow::{self, Flow}; +use flow::{self, Flow, CAN_BE_FRAGMENTED}; use gfx::display_list::OpaqueNode; use incremental::{BUBBLE_ISIZES, REFLOW, REFLOW_OUT_OF_FLOW, REPAINT, RestyleDamage}; use std::mem; @@ -194,7 +194,10 @@ impl<'a> PostorderFlowTraversal for AssignBSizesAndStoreOverflow<'a> { #[inline] fn should_process(&self, flow: &mut Flow) -> bool { - flow::base(flow).restyle_damage.intersects(REFLOW_OUT_OF_FLOW | REFLOW) + let base = flow::base(flow); + base.restyle_damage.intersects(REFLOW_OUT_OF_FLOW | REFLOW) + // The fragmentation countainer is responsible for calling Flow::fragment recursively + && !base.flags.contains(CAN_BE_FRAGMENTED) } } diff --git a/components/layout/wrapper.rs b/components/layout/wrapper.rs index 652dd8c6359..cf02c66ed63 100644 --- a/components/layout/wrapper.rs +++ b/components/layout/wrapper.rs @@ -49,8 +49,8 @@ use script::dom::htmliframeelement::HTMLIFrameElement; use script::dom::htmlimageelement::LayoutHTMLImageElementHelpers; use script::dom::htmlinputelement::{HTMLInputElement, LayoutHTMLInputElementHelpers}; use script::dom::htmltextareaelement::{HTMLTextAreaElement, LayoutHTMLTextAreaElementHelpers}; -use script::dom::node::{HAS_CHANGED, HAS_DIRTY_DESCENDANTS, IS_DIRTY}; -use script::dom::node::{IN_FRAGMENTATION_CONTAINER, LayoutNodeHelpers, Node, OpaqueStyleAndLayoutData}; +use script::dom::node::{CAN_BE_FRAGMENTED, HAS_CHANGED, HAS_DIRTY_DESCENDANTS, IS_DIRTY}; +use script::dom::node::{LayoutNodeHelpers, Node, OpaqueStyleAndLayoutData}; use script::dom::text::Text; use script::layout_interface::TrustedNodeAddress; use selectors::matching::DeclarationBlock; @@ -226,12 +226,12 @@ impl<'ln> TNode<'ln> for ServoLayoutNode<'ln> { self.node.set_flag(HAS_DIRTY_DESCENDANTS, value) } - fn in_fragmentation_container(&self) -> bool { - unsafe { self.node.get_flag(IN_FRAGMENTATION_CONTAINER) } + fn can_be_fragmented(&self) -> bool { + unsafe { self.node.get_flag(CAN_BE_FRAGMENTED) } } - unsafe fn set_in_fragmentation_container(&self, value: bool) { - self.node.set_flag(IN_FRAGMENTATION_CONTAINER, value) + unsafe fn set_can_be_fragmented(&self, value: bool) { + self.node.set_flag(CAN_BE_FRAGMENTED, value) } unsafe fn borrow_data_unchecked(&self) -> Option<*const PrivateStyleData> { @@ -761,7 +761,7 @@ pub trait ThreadSafeLayoutNode<'ln> : Clone + Copy + Sized { } } - fn in_fragmentation_container(&self) -> bool; + fn can_be_fragmented(&self) -> bool; /// If this is a text node, generated content, or a form element, copies out /// its content. Otherwise, panics. @@ -939,8 +939,8 @@ impl<'ln> ThreadSafeLayoutNode<'ln> for ServoThreadSafeLayoutNode<'ln> { } } - fn in_fragmentation_container(&self) -> bool { - self.node.in_fragmentation_container() + fn can_be_fragmented(&self) -> bool { + self.node.can_be_fragmented() } fn text_content(&self) -> TextContent { diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index 3f8bcefeef8..33dadfe527b 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -146,7 +146,7 @@ bitflags! { const SEQUENTIALLY_FOCUSABLE = 0x20, /// Whether any ancestor is a fragmentation container - const IN_FRAGMENTATION_CONTAINER = 0x40 + const CAN_BE_FRAGMENTED = 0x40 } } diff --git a/components/style/dom.rs b/components/style/dom.rs index 63c645901b3..ce538f1b21f 100644 --- a/components/style/dom.rs +++ b/components/style/dom.rs @@ -129,9 +129,9 @@ pub trait TNode<'ln> : Sized + Copy + Clone { } } - fn in_fragmentation_container(&self) -> bool; + fn can_be_fragmented(&self) -> bool; - unsafe fn set_in_fragmentation_container(&self, value: bool); + unsafe fn set_can_be_fragmented(&self, value: bool); /// Borrows the PrivateStyleData without checks. #[inline(always)] diff --git a/components/style/matching.rs b/components/style/matching.rs index f2a1515ef07..ea25d518a99 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -701,10 +701,10 @@ pub trait MatchMethods<'ln> : TNode<'ln> { // scope first. self.set_restyle_damage(damage); - self.set_in_fragmentation_container( - parent.as_ref().map_or(false, |p| p.in_fragmentation_container()) || - self.borrow_data().unwrap().style.as_ref().unwrap().is_multicol() - ); + self.set_can_be_fragmented(parent.map_or(false, |p| { + p.can_be_fragmented() || + parent_style.as_ref().unwrap().is_multicol() + })); } } } diff --git a/ports/geckolib/wrapper.rs b/ports/geckolib/wrapper.rs index 77563f91ee3..a4d97c8a17c 100644 --- a/ports/geckolib/wrapper.rs +++ b/ports/geckolib/wrapper.rs @@ -182,13 +182,13 @@ impl<'ln> TNode<'ln> for GeckoNode<'ln> { unimplemented!() } - fn in_fragmentation_container(&self) -> bool { + fn can_be_fragmented(&self) -> bool { // FIXME(SimonSapin): Servo uses this to implement CSS multicol / fragmentation // Maybe this isn’t useful for Gecko? false } - unsafe fn set_in_fragmentation_container(&self, _value: bool) { + unsafe fn set_can_be_fragmented(&self, _value: bool) { // FIXME(SimonSapin): Servo uses this to implement CSS multicol / fragmentation // Maybe this isn’t useful for Gecko? } From 48270b58bf5d94baad6943b32475ffd21d001738 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Tue, 29 Dec 2015 18:20:09 +0000 Subject: [PATCH 4/8] Move part of BlockFlow::assign_inline_sizes to separate function. --- components/layout/block.rs | 83 ++++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/components/layout/block.rs b/components/layout/block.rs index 2633205b8b4..40e2042ba67 100644 --- a/components/layout/block.rs +++ b/components/layout/block.rs @@ -1549,46 +1549,8 @@ impl BlockFlow { _ => {} } } -} - -impl Flow for BlockFlow { - fn class(&self) -> FlowClass { - FlowClass::Block - } - - fn as_mut_block(&mut self) -> &mut BlockFlow { - self - } - - fn as_block(&self) -> &BlockFlow { - self - } - - /// Pass 1 of reflow: computes minimum and preferred inline-sizes. - /// - /// Recursively (bottom-up) determine the flow's minimum and preferred inline-sizes. When - /// called on this flow, all child flows have had their minimum and preferred inline-sizes set. - /// This function must decide minimum/preferred inline-sizes based on its children's - /// inline-sizes and the dimensions of any fragments it is responsible for flowing. - fn bubble_inline_sizes(&mut self) { - // If this block has a fixed width, just use that for the minimum and preferred width, - // rather than bubbling up children inline width. - let consult_children = match self.fragment.style().get_box().width { - LengthOrPercentageOrAuto::Length(_) => false, - _ => true, - }; - self.bubble_inline_sizes_for_block(consult_children); - self.fragment.restyle_damage.remove(BUBBLE_ISIZES); - } - - /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. - /// When called on this context, the context has had its inline-size set by the parent context. - /// - /// Dual fragments consume some inline-size first, and the remainder is assigned to all child - /// (block) contexts. - fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) { - let _scope = layout_debug_scope!("block::assign_inline_sizes {:x}", self.base.debug_id()); + pub fn compute_inline_sizes(&mut self, layout_context: &LayoutContext) { if !self.base.restyle_damage.intersects(REFLOW_OUT_OF_FLOW | REFLOW) { return } @@ -1644,6 +1606,49 @@ impl Flow for BlockFlow { self.base.flags.remove(IMPACTED_BY_RIGHT_FLOATS); } } + } +} + +impl Flow for BlockFlow { + fn class(&self) -> FlowClass { + FlowClass::Block + } + + fn as_mut_block(&mut self) -> &mut BlockFlow { + self + } + + fn as_block(&self) -> &BlockFlow { + self + } + + /// Pass 1 of reflow: computes minimum and preferred inline-sizes. + /// + /// Recursively (bottom-up) determine the flow's minimum and preferred inline-sizes. When + /// called on this flow, all child flows have had their minimum and preferred inline-sizes set. + /// This function must decide minimum/preferred inline-sizes based on its children's + /// inline-sizes and the dimensions of any fragments it is responsible for flowing. + fn bubble_inline_sizes(&mut self) { + // If this block has a fixed width, just use that for the minimum and preferred width, + // rather than bubbling up children inline width. + let consult_children = match self.fragment.style().get_box().width { + LengthOrPercentageOrAuto::Length(_) => false, + _ => true, + }; + self.bubble_inline_sizes_for_block(consult_children); + self.fragment.restyle_damage.remove(BUBBLE_ISIZES); + } + + /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. + /// When called on this context, the context has had its inline-size set by the parent context. + /// + /// Dual fragments consume some inline-size first, and the remainder is assigned to all child + /// (block) contexts. + fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) { + let _scope = layout_debug_scope!("block::assign_inline_sizes {:x}", self.base.debug_id()); + + self.compute_inline_sizes(layout_context); + // Move in from the inline-start border edge. let inline_start_content_edge = self.fragment.border_box.start.i + self.fragment.border_padding.inline_start; From 359b98434843feab752a1f0268339babd9759633 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Wed, 20 May 2015 14:01:52 -0400 Subject: [PATCH 5/8] Multicol: compute the width of columns based on `column-width` and `column-count`. --- components/layout/multicol.rs | 45 +++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/components/layout/multicol.rs b/components/layout/multicol.rs index f0c4acd09ea..3ac17412a76 100644 --- a/components/layout/multicol.rs +++ b/components/layout/multicol.rs @@ -13,6 +13,7 @@ use euclid::{Point2D, Rect}; use floats::FloatKind; use flow::{Flow, FlowClass, OpaqueFlow, mut_base}; use fragment::{Fragment, FragmentBorderBoxIterator}; +use std::cmp::{min, max}; use std::fmt; use std::sync::Arc; use style::properties::ComputedValues; @@ -53,9 +54,49 @@ impl Flow for MulticolFlow { self.block_flow.bubble_inline_sizes(); } - fn assign_inline_sizes(&mut self, ctx: &LayoutContext) { + fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) { debug!("assign_inline_sizes({}): assigning inline_size for flow", "multicol"); - self.block_flow.assign_inline_sizes(ctx); + self.block_flow.compute_inline_sizes(layout_context); + + // Move in from the inline-start border edge. + let inline_start_content_edge = self.block_flow.fragment.border_box.start.i + + self.block_flow.fragment.border_padding.inline_start; + + // Distance from the inline-end margin edge to the inline-end content edge. + let inline_end_content_edge = + self.block_flow.fragment.margin.inline_end + + self.block_flow.fragment.border_padding.inline_end; + + self.block_flow.assign_inline_sizes(layout_context); + let padding_and_borders = self.block_flow.fragment.border_padding.inline_start_end(); + let content_inline_size = + self.block_flow.fragment.border_box.size.inline - padding_and_borders; + let column_width; + { + let column_style = self.block_flow.fragment.style.get_column(); + + // `None` is 'normal': "UA-specified length. A value of 1em is suggested." + let column_gap = column_style.column_gap.0.unwrap_or_else(|| + self.block_flow.fragment.style.get_font().font_size); + let mut column_count; + if let Some(column_width) = column_style.column_width.0 { + column_count = + max(1, (content_inline_size + column_gap).0 / (column_width + column_gap).0); + if let Some(specified_column_count) = column_style.column_count.0 { + column_count = min(column_count, specified_column_count as i32); + } + } else { + column_count = column_style.column_count.0.unwrap() as i32; + } + column_width = + max(Au(0), (content_inline_size + column_gap) / column_count - column_gap); + } + + self.block_flow.fragment.border_box.size.inline = content_inline_size + padding_and_borders; + + self.block_flow.propagate_assigned_inline_size_to_children( + layout_context, inline_start_content_edge, inline_end_content_edge, column_width, + |_, _, _, _, _, _| {}); } fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) { From 5498b5434716ac2a464532c0455aac68c8ed06b2 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Wed, 30 Dec 2015 23:50:24 +0000 Subject: [PATCH 6/8] Add Multicolumn support block fragmentation. --- components/layout/block.rs | 79 +++++++++- components/layout/construct.rs | 67 +++++++-- components/layout/display_list_builder.rs | 2 + components/layout/flow.rs | 45 ++++-- components/layout/flow_list.rs | 18 +++ components/layout/fragment.rs | 26 +++- components/layout/model.rs | 2 +- components/layout/multicol.rs | 171 ++++++++++++++++++++-- components/layout/table_cell.rs | 6 +- components/layout/table_wrapper.rs | 8 +- 10 files changed, 373 insertions(+), 51 deletions(-) diff --git a/components/layout/block.rs b/components/layout/block.rs index 40e2042ba67..2ec619607a3 100644 --- a/components/layout/block.rs +++ b/components/layout/block.rs @@ -39,8 +39,10 @@ use flow::{HAS_LEFT_FLOATED_DESCENDANTS, HAS_RIGHT_FLOATED_DESCENDANTS}; use flow::{IMPACTED_BY_LEFT_FLOATS, IMPACTED_BY_RIGHT_FLOATS, INLINE_POSITION_IS_STATIC}; use flow::{IS_ABSOLUTELY_POSITIONED}; use flow::{ImmutableFlowUtils, LateAbsolutePositionInfo, MutableFlowUtils, OpaqueFlow}; -use flow::{NEEDS_LAYER, PostorderFlowTraversal, PreorderFlowTraversal}; +use flow::{NEEDS_LAYER, PostorderFlowTraversal, PreorderFlowTraversal, FragmentationContext}; use flow::{self, BaseFlow, EarlyAbsolutePositionInfo, Flow, FlowClass, ForceNonfloatedFlag}; +use flow_list::FlowList; +use flow_ref::FlowRef; use fragment::{CoordinateSystem, Fragment, FragmentBorderBoxIterator, HAS_LAYER}; use fragment::{SpecificFragmentInfo}; use gfx::display_list::{ClippingRegion, DisplayList}; @@ -726,6 +728,18 @@ impl BlockFlow { block_end_margin_value; } + // FIXME: Record enough info to deal with fragmented decorations. + // See https://drafts.csswg.org/css-break/#break-decoration + // For borders, this might be `enum FragmentPosition { First, Middle, Last }` + fn clone_with_children(&self, new_children: FlowList) -> BlockFlow { + BlockFlow { + base: self.base.clone_with_children(new_children), + fragment: self.fragment.clone(), + float: self.float.clone(), + ..*self + } + } + /// Assign block-size for current flow. /// /// * Collapse margins for flow's children and set in-flow child flows' block offsets now that @@ -742,10 +756,13 @@ impl BlockFlow { #[inline(always)] pub fn assign_block_size_block_base<'a>(&mut self, layout_context: &'a LayoutContext<'a>, - margins_may_collapse: MarginsMayCollapseFlag) { + mut fragmentation_context: Option, + margins_may_collapse: MarginsMayCollapseFlag) + -> Option { let _scope = layout_debug_scope!("assign_block_size_block_base {:x}", self.base.debug_id()); + let mut break_at = None; if self.base.restyle_damage.contains(REFLOW) { self.determine_if_layer_needed(); @@ -779,7 +796,7 @@ impl BlockFlow { // At this point, `cur_b` is at the content edge of our box. Now iterate over children. let mut floats = self.base.floats.clone(); let thread_id = self.base.thread_id; - for kid in self.base.child_iter() { + for (child_index, kid) in self.base.child_iter().enumerate() { if flow::base(kid).flags.contains(IS_ABSOLUTELY_POSITIONED) { // Assume that the *hypothetical box* for an absolute flow starts immediately // after the block-end border edge of the previous flow. @@ -799,6 +816,17 @@ impl BlockFlow { continue } + let previous_b = cur_b; + if let Some(ctx) = fragmentation_context { + let child_ctx = FragmentationContext { + available_block_size: ctx.available_block_size - cur_b, + this_fragment_is_empty: ctx.this_fragment_is_empty, + }; + if let Some(remaining) = kid.fragment(layout_context, Some(child_ctx)) { + break_at = Some((child_index + 1, Some(remaining))); + } + } + // Assign block-size now for the child if it was impacted by floats and we couldn't // before. flow::mut_base(kid).floats = floats.clone(); @@ -867,6 +895,19 @@ impl BlockFlow { let delta = margin_collapse_info.advance_block_end_margin(&kid_base.collapsible_margins); translate_including_floats(&mut cur_b, delta, &mut floats); + + if break_at.is_some() { + break + } + + if let Some(ref mut ctx) = fragmentation_context { + if cur_b > ctx.available_block_size && !ctx.this_fragment_is_empty { + break_at = Some((child_index, None)); + cur_b = previous_b; + break + } + ctx.this_fragment_is_empty = false + } } // Add in our block-end margin and compute our collapsible margins. @@ -920,7 +961,7 @@ impl BlockFlow { } if self.base.flags.contains(IS_ABSOLUTELY_POSITIONED) { - return + return None } // Compute any explicitly-specified block size. @@ -985,6 +1026,18 @@ impl BlockFlow { self.base.restyle_damage.remove(REFLOW_OUT_OF_FLOW | REFLOW); self.fragment.restyle_damage.remove(REFLOW_OUT_OF_FLOW | REFLOW); } + + break_at.and_then(|(i, child_remaining)| { + if i == self.base.children.len() && child_remaining.is_none() { + None + } else { + let mut children = self.base.children.split_off(i); + if let Some(child) = child_remaining { + children.push_front(child); + } + Some(Arc::new(self.clone_with_children(children)) as FlowRef) + } + }) } /// Add placement information about current float flow for use by the parent. @@ -1711,6 +1764,13 @@ impl Flow for BlockFlow { } fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) { + let remaining = Flow::fragment(self, ctx, None); + debug_assert!(remaining.is_none()); + } + + fn fragment(&mut self, layout_context: &LayoutContext, + fragmentation_context: Option) + -> Option { if self.is_replaced_content() { let _scope = layout_debug_scope!("assign_replaced_block_size_if_necessary {:x}", self.base.debug_id()); @@ -1722,15 +1782,22 @@ impl Flow for BlockFlow { if !self.base.flags.contains(IS_ABSOLUTELY_POSITIONED) { self.base.position.size.block = self.fragment.border_box.size.block; } + None } else if self.is_root() || self.base.flags.is_float() || self.is_inline_block() { // Root element margins should never be collapsed according to CSS § 8.3.1. debug!("assign_block_size: assigning block_size for root flow {:?}", flow::base(self).debug_id()); - self.assign_block_size_block_base(ctx, MarginsMayCollapseFlag::MarginsMayNotCollapse); + self.assign_block_size_block_base( + layout_context, + fragmentation_context, + MarginsMayCollapseFlag::MarginsMayNotCollapse) } else { debug!("assign_block_size: assigning block_size for block {:?}", flow::base(self).debug_id()); - self.assign_block_size_block_base(ctx, MarginsMayCollapseFlag::MarginsMayCollapse); + self.assign_block_size_block_base( + layout_context, + fragmentation_context, + MarginsMayCollapseFlag::MarginsMayCollapse) } } diff --git a/components/layout/construct.rs b/components/layout/construct.rs index 53ddc2bed04..36c5742e053 100644 --- a/components/layout/construct.rs +++ b/components/layout/construct.rs @@ -31,7 +31,7 @@ use incremental::{BUBBLE_ISIZES, RECONSTRUCT_FLOW, RestyleDamage}; use inline::{FIRST_FRAGMENT_OF_ELEMENT, InlineFlow, InlineFragmentNodeFlags}; use inline::{InlineFragmentNodeInfo, LAST_FRAGMENT_OF_ELEMENT}; use list_item::{ListItemFlow, ListStyleTypeContent}; -use multicol::MulticolFlow; +use multicol::{MulticolFlow, MulticolColumnFlow}; use parallel; use script::dom::bindings::inheritance::{CharacterDataTypeId, ElementTypeId}; use script::dom::bindings::inheritance::{HTMLElementTypeId, NodeTypeId}; @@ -754,12 +754,12 @@ impl<'a, 'ln, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode<'ln>> /// to happen. fn build_flow_for_block(&mut self, node: &ConcreteThreadSafeLayoutNode, float_kind: Option) -> ConstructionResult { - let fragment = self.build_fragment_for_block(node); - let flow: FlowRef = if node.style().is_multicol() { - Arc::new(MulticolFlow::from_fragment(fragment, float_kind)) - } else { - Arc::new(BlockFlow::from_fragment(fragment, float_kind)) - }; + if node.style().is_multicol() { + return self.build_flow_for_multicol(node, float_kind) + } + + let flow: FlowRef = Arc::new( + BlockFlow::from_fragment(self.build_fragment_for_block(node), float_kind)); self.build_flow_for_block_like(flow, node) } @@ -1078,6 +1078,53 @@ impl<'a, 'ln, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode<'ln>> flow.add_new_child(anonymous_flow); } + /// Builds a flow for a node with `column-count` or `column-width` non-`auto`. + /// This yields a `MulticolFlow` with a single `MulticolColumnFlow` underneath it. + fn build_flow_for_multicol(&mut self, node: &ConcreteThreadSafeLayoutNode, + float_kind: Option) + -> ConstructionResult { + let fragment = Fragment::new(node, SpecificFragmentInfo::Multicol); + let mut flow: FlowRef = Arc::new(MulticolFlow::from_fragment(fragment, float_kind)); + + let column_fragment = Fragment::new(node, SpecificFragmentInfo::MulticolColumn); + let column_flow = Arc::new(MulticolColumnFlow::from_fragment(column_fragment)); + + // First populate the column flow with its children. + let construction_result = self.build_flow_for_block_like(column_flow, node); + + let mut abs_descendants = AbsoluteDescendants::new(); + let mut fixed_descendants = AbsoluteDescendants::new(); + + if let ConstructionResult::Flow(column_flow, column_abs_descendants) = construction_result { + flow.add_new_child(column_flow); + abs_descendants.push_descendants(column_abs_descendants); + } + + // The flow is done. + flow.finish(); + let contains_positioned_fragments = flow.contains_positioned_fragments(); + if contains_positioned_fragments { + // This is the containing block for all the absolute descendants. + flow.set_absolute_descendants(abs_descendants); + + abs_descendants = AbsoluteDescendants::new(); + + let is_fixed_positioned = flow.as_block().is_fixed(); + let is_absolutely_positioned = + flow::base(&*flow).flags.contains(IS_ABSOLUTELY_POSITIONED); + if is_fixed_positioned { + // Send itself along with the other fixed descendants. + fixed_descendants.push(flow.clone()); + } else if is_absolutely_positioned { + // This is now the only absolute flow in the subtree which hasn't yet + // reached its containing block. + abs_descendants.push(flow.clone()); + } + } + + ConstructionResult::Flow(flow, abs_descendants) + } + /// Builds a flow for a node with `display: table`. This yields a `TableWrapperFlow` with /// possibly other `TableCaptionFlow`s or `TableFlow`s underneath it. fn build_flow_for_table_wrapper(&mut self, node: &ConcreteThreadSafeLayoutNode, float_value: float::T) @@ -1115,15 +1162,15 @@ impl<'a, 'ln, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode<'ln>> // The flow is done. wrapper_flow.finish(); let contains_positioned_fragments = wrapper_flow.contains_positioned_fragments(); - let is_fixed_positioned = wrapper_flow.as_block().is_fixed(); - let is_absolutely_positioned = - flow::base(&*wrapper_flow).flags.contains(IS_ABSOLUTELY_POSITIONED); if contains_positioned_fragments { // This is the containing block for all the absolute descendants. wrapper_flow.set_absolute_descendants(abs_descendants); abs_descendants = AbsoluteDescendants::new(); + let is_fixed_positioned = wrapper_flow.as_block().is_fixed(); + let is_absolutely_positioned = + flow::base(&*wrapper_flow).flags.contains(IS_ABSOLUTELY_POSITIONED); if is_fixed_positioned { // Send itself along with the other fixed descendants. fixed_descendants.push(wrapper_flow.clone()); diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs index 82293921bbb..bab234d8b6b 100644 --- a/components/layout/display_list_builder.rs +++ b/components/layout/display_list_builder.rs @@ -1082,6 +1082,8 @@ impl FragmentDisplayListBuilding for Fragment { SpecificFragmentInfo::TableCell | SpecificFragmentInfo::TableRow | SpecificFragmentInfo::TableWrapper | + SpecificFragmentInfo::Multicol | + SpecificFragmentInfo::MulticolColumn | SpecificFragmentInfo::InlineBlock(_) | SpecificFragmentInfo::InlineAbsoluteHypothetical(_) | SpecificFragmentInfo::InlineAbsolute(_) => { diff --git a/components/layout/flow.rs b/components/layout/flow.rs index 4a8b206193a..215e25bd731 100644 --- a/components/layout/flow.rs +++ b/components/layout/flow.rs @@ -35,7 +35,7 @@ use flow_ref::{self, FlowRef, WeakFlowRef}; use fragment::{Fragment, FragmentBorderBoxIterator, SpecificFragmentInfo}; use gfx::display_list::{ClippingRegion, DisplayList}; use gfx_traits::{LayerId, LayerType}; -use incremental::{RECONSTRUCT_FLOW, REFLOW, REFLOW_OUT_OF_FLOW, RestyleDamage}; +use incremental::{RECONSTRUCT_FLOW, REFLOW, REFLOW_OUT_OF_FLOW, REPAINT, RestyleDamage}; use inline::InlineFlow; use model::{CollapsibleMargins, IntrinsicISizes, MarginCollapseInfo}; use multicol::MulticolFlow; @@ -203,22 +203,21 @@ pub trait Flow: fmt::Debug + Sync + Send + 'static { /// Like `assign_block_size`, but is recurses explicitly into descendants. /// Fit as much content as possible within `available_block_size`. - /// If that’s not all of it, - /// return an indication of where in the tree to break and start the next fragment. - /// - /// FIXME: replace `()` in the return value with something meaningful. + /// If that’s not all of it, truncate the contents of `self` + /// and return a new flow similar to `self` with the rest of the content. /// /// The default is to make a flow "atomic": it can not be fragmented. - fn fragment<'a>(&mut self, ctx: &'a LayoutContext<'a>, _available_block_size: Au) - -> Option<()> { - fn recursive_assign_block_size<'a, F: ?Sized + Flow> - (flow: &mut F, ctx: &'a LayoutContext<'a>) { + fn fragment(&mut self, + layout_context: &LayoutContext, + _fragmentation_context: Option) + -> Option { + fn recursive_assign_block_size(flow: &mut F, ctx: &LayoutContext) { for child in mut_base(flow).children.iter_mut() { recursive_assign_block_size(child, ctx) } flow.assign_block_size(ctx); } - recursive_assign_block_size(self, ctx); + recursive_assign_block_size(self, layout_context); None } @@ -538,6 +537,7 @@ pub enum FlowClass { TableCaption, TableCell, Multicol, + MulticolColumn, Flex, } @@ -799,6 +799,7 @@ pub type AbsoluteDescendantOffsetIter<'a> = Zip, Iter /// Information needed to compute absolute (i.e. viewport-relative) flow positions (not to be /// confused with absolutely-positioned flows) that is computed during block-size assignment. +#[derive(Copy, Clone)] pub struct EarlyAbsolutePositionInfo { /// The size of the containing block for relatively-positioned descendants. pub relative_containing_block_size: LogicalSize, @@ -836,6 +837,12 @@ impl LateAbsolutePositionInfo { } } +#[derive(Copy, Clone, Debug)] +pub struct FragmentationContext { + pub available_block_size: Au, + pub this_fragment_is_empty: bool, +} + /// Data common to all flows. pub struct BaseFlow { pub restyle_damage: RestyleDamage, @@ -1092,6 +1099,23 @@ impl BaseFlow { } } + /// Return a new BaseFlow like this one but with the given children list + pub fn clone_with_children(&self, children: FlowList) -> BaseFlow { + BaseFlow { + children: children, + restyle_damage: self.restyle_damage | REPAINT | REFLOW_OUT_OF_FLOW | REFLOW, + parallel: FlowParallelInfo::new(), + display_list_building_result: None, + + floats: self.floats.clone(), + abs_descendants: self.abs_descendants.clone(), + absolute_cb: self.absolute_cb.clone(), + clip: self.clip.clone(), + + ..*self + } + } + pub fn child_iter(&mut self) -> MutFlowListIterator { self.children.iter_mut() } @@ -1454,6 +1478,7 @@ impl MutableOwnedFlowUtils for FlowRef { /// invalidation and use-after-free. /// /// FIXME(pcwalton): I think this would be better with a borrow flag instead of `unsafe`. +#[derive(Clone)] pub struct ContainingBlockLink { /// The pointer up to the containing block. link: Option, diff --git a/components/layout/flow_list.rs b/components/layout/flow_list.rs index 94045f21d3d..37044008f7f 100644 --- a/components/layout/flow_list.rs +++ b/components/layout/flow_list.rs @@ -29,6 +29,17 @@ impl FlowList { self.flows.push_back(new_tail); } + /// Add an element first in the list + /// + /// O(1) + pub fn push_front(&mut self, new_head: FlowRef) { + self.flows.push_front(new_head); + } + + pub fn pop_front(&mut self) -> Option { + self.flows.pop_front() + } + /// Create an empty list #[inline] pub fn new() -> FlowList { @@ -64,6 +75,13 @@ impl FlowList { pub fn len(&self) -> usize { self.flows.len() } + + #[inline] + pub fn split_off(&mut self, i: usize) -> Self { + FlowList { + flows: self.flows.split_off(i) + } + } } impl<'a> Iterator for FlowListIterator<'a> { diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs index 004e8ce0543..9fa563ce20d 100644 --- a/components/layout/fragment.rs +++ b/components/layout/fragment.rs @@ -161,6 +161,8 @@ pub enum SpecificFragmentInfo { TableColumn(TableColumnFragmentInfo), TableRow, TableWrapper, + Multicol, + MulticolColumn, UnscannedText(UnscannedTextFragmentInfo), } @@ -178,6 +180,8 @@ impl SpecificFragmentInfo { SpecificFragmentInfo::TableColumn(_) | SpecificFragmentInfo::TableRow | SpecificFragmentInfo::TableWrapper | + SpecificFragmentInfo::Multicol | + SpecificFragmentInfo::MulticolColumn | SpecificFragmentInfo::UnscannedText(_) | SpecificFragmentInfo::Generic => return RestyleDamage::empty(), SpecificFragmentInfo::InlineAbsoluteHypothetical(ref info) => &info.flow_ref, @@ -206,6 +210,8 @@ impl SpecificFragmentInfo { SpecificFragmentInfo::TableColumn(_) => "SpecificFragmentInfo::TableColumn", SpecificFragmentInfo::TableRow => "SpecificFragmentInfo::TableRow", SpecificFragmentInfo::TableWrapper => "SpecificFragmentInfo::TableWrapper", + SpecificFragmentInfo::Multicol => "SpecificFragmentInfo::Multicol", + SpecificFragmentInfo::MulticolColumn => "SpecificFragmentInfo::MulticolColumn", SpecificFragmentInfo::UnscannedText(_) => "SpecificFragmentInfo::UnscannedText", } } @@ -912,7 +918,8 @@ impl Fragment { SpecificFragmentInfo::GeneratedContent(_) | SpecificFragmentInfo::Iframe(_) | SpecificFragmentInfo::Image(_) | - SpecificFragmentInfo::InlineAbsolute(_) => { + SpecificFragmentInfo::InlineAbsolute(_) | + SpecificFragmentInfo::Multicol => { QuantitiesIncludedInIntrinsicInlineSizes::all() } SpecificFragmentInfo::Table | SpecificFragmentInfo::TableCell => { @@ -948,7 +955,8 @@ impl Fragment { SpecificFragmentInfo::TableColumn(_) | SpecificFragmentInfo::UnscannedText(_) | SpecificFragmentInfo::InlineAbsoluteHypothetical(_) | - SpecificFragmentInfo::InlineBlock(_) => { + SpecificFragmentInfo::InlineBlock(_) | + SpecificFragmentInfo::MulticolColumn => { QuantitiesIncludedInIntrinsicInlineSizes::empty() } } @@ -1308,6 +1316,8 @@ impl Fragment { SpecificFragmentInfo::TableColumn(_) | SpecificFragmentInfo::TableRow | SpecificFragmentInfo::TableWrapper | + SpecificFragmentInfo::Multicol | + SpecificFragmentInfo::MulticolColumn | SpecificFragmentInfo::InlineAbsoluteHypothetical(_) => {} SpecificFragmentInfo::InlineBlock(ref info) => { let block_flow = info.flow_ref.as_block(); @@ -1409,6 +1419,8 @@ impl Fragment { SpecificFragmentInfo::TableCell | SpecificFragmentInfo::TableRow | SpecificFragmentInfo::TableWrapper | + SpecificFragmentInfo::Multicol | + SpecificFragmentInfo::MulticolColumn | SpecificFragmentInfo::InlineBlock(_) | SpecificFragmentInfo::InlineAbsoluteHypothetical(_) | SpecificFragmentInfo::InlineAbsolute(_) => Au(0), @@ -1667,7 +1679,9 @@ impl Fragment { SpecificFragmentInfo::Table | SpecificFragmentInfo::TableCell | SpecificFragmentInfo::TableRow | - SpecificFragmentInfo::TableWrapper => return, + SpecificFragmentInfo::TableWrapper | + SpecificFragmentInfo::Multicol | + SpecificFragmentInfo::MulticolColumn => return, SpecificFragmentInfo::TableColumn(_) => { panic!("Table column fragments do not have inline size") } @@ -1759,7 +1773,9 @@ impl Fragment { SpecificFragmentInfo::Table | SpecificFragmentInfo::TableCell | SpecificFragmentInfo::TableRow | - SpecificFragmentInfo::TableWrapper => return, + SpecificFragmentInfo::TableWrapper | + SpecificFragmentInfo::Multicol | + SpecificFragmentInfo::MulticolColumn => return, SpecificFragmentInfo::TableColumn(_) => { panic!("Table column fragments do not have block size") } @@ -1977,6 +1993,7 @@ impl Fragment { SpecificFragmentInfo::InlineBlock(_) | SpecificFragmentInfo::InlineAbsoluteHypothetical(_) | SpecificFragmentInfo::InlineAbsolute(_) | + SpecificFragmentInfo::MulticolColumn | SpecificFragmentInfo::TableWrapper => false, SpecificFragmentInfo::Canvas(_) | SpecificFragmentInfo::Generic | @@ -1988,6 +2005,7 @@ impl Fragment { SpecificFragmentInfo::TableCell | SpecificFragmentInfo::TableColumn(_) | SpecificFragmentInfo::TableRow | + SpecificFragmentInfo::Multicol | SpecificFragmentInfo::UnscannedText(_) => true, } } diff --git a/components/layout/model.rs b/components/layout/model.rs index f49ac51ad21..b32a9c48bf9 100644 --- a/components/layout/model.rs +++ b/components/layout/model.rs @@ -272,7 +272,7 @@ pub enum MarginCollapseState { } /// Intrinsic inline-sizes, which consist of minimum and preferred. -#[derive(RustcEncodable)] +#[derive(RustcEncodable, Copy, Clone)] pub struct IntrinsicISizes { /// The *minimum inline-size* of the content. pub minimum_inline_size: Au, diff --git a/components/layout/multicol.rs b/components/layout/multicol.rs index 3ac17412a76..196a65715d6 100644 --- a/components/layout/multicol.rs +++ b/components/layout/multicol.rs @@ -11,23 +11,43 @@ use block::BlockFlow; use context::LayoutContext; use euclid::{Point2D, Rect}; use floats::FloatKind; -use flow::{Flow, FlowClass, OpaqueFlow, mut_base}; +use flow::{Flow, FlowClass, OpaqueFlow, mut_base, FragmentationContext}; +use flow_ref::{self, FlowRef}; use fragment::{Fragment, FragmentBorderBoxIterator}; use std::cmp::{min, max}; use std::fmt; use std::sync::Arc; +use style::context::StyleContext; use style::properties::ComputedValues; +use style::values::computed::{LengthOrPercentageOrAuto, LengthOrPercentageOrNone}; use util::logical_geometry::LogicalSize; use util::print_tree::PrintTree; pub struct MulticolFlow { pub block_flow: BlockFlow, + + /// Length between the inline-start edge of a column and that of the next. + /// That is, the used column-width + used column-gap. + pub column_pitch: Au, +} + +pub struct MulticolColumnFlow { + pub block_flow: BlockFlow, } impl MulticolFlow { pub fn from_fragment(fragment: Fragment, float_kind: Option) -> MulticolFlow { MulticolFlow { - block_flow: BlockFlow::from_fragment(fragment, float_kind) + block_flow: BlockFlow::from_fragment(fragment, float_kind), + column_pitch: Au(0), + } + } +} + +impl MulticolColumnFlow { + pub fn from_fragment(fragment: Fragment) -> MulticolColumnFlow { + MulticolColumnFlow { + block_flow: BlockFlow::from_fragment(fragment, None), } } } @@ -37,10 +57,6 @@ impl Flow for MulticolFlow { FlowClass::Multicol } - fn as_mut_multicol(&mut self) -> &mut MulticolFlow { - self - } - fn as_mut_block(&mut self) -> &mut BlockFlow { &mut self.block_flow } @@ -49,6 +65,10 @@ impl Flow for MulticolFlow { &self.block_flow } + fn as_mut_multicol(&mut self) -> &mut MulticolFlow { + self + } + fn bubble_inline_sizes(&mut self) { // FIXME(SimonSapin) http://dev.w3.org/csswg/css-sizing/#multicol-intrinsic self.block_flow.bubble_inline_sizes(); @@ -90,6 +110,7 @@ impl Flow for MulticolFlow { } column_width = max(Au(0), (content_inline_size + column_gap) / column_count - column_gap); + self.column_pitch = column_width + column_gap; } self.block_flow.fragment.border_box.size.inline = content_inline_size + padding_and_borders; @@ -101,15 +122,51 @@ impl Flow for MulticolFlow { fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) { debug!("assign_block_size: assigning block_size for multicol"); - let available_block_size = Au(0); - for child in mut_base(self).children.iter_mut() { - child.fragment(ctx, available_block_size); - } + + let fragmentation_context = Some(FragmentationContext { + this_fragment_is_empty: true, + available_block_size: { + let style = &self.block_flow.fragment.style; + if let LengthOrPercentageOrAuto::Length(length) = style.content_block_size() { + length + } else if let LengthOrPercentageOrNone::Length(length) = style.max_block_size() { + length + } else { + // FIXME: do column balancing instead + // FIXME: (until column balancing) substract margins/borders/padding + LogicalSize::from_physical( + self.block_flow.base.writing_mode, + ctx.shared_context().viewport_size, + ).block + } + } + }); + + // Before layout, everything is in a single "column" + assert!(self.block_flow.base.children.len() == 1); + let mut column = self.block_flow.base.children.pop_front().unwrap(); + + // Pretend there is no children for this: self.block_flow.assign_block_size(ctx); + + loop { + let remaining = flow_ref::deref_mut(&mut column).fragment(ctx, fragmentation_context); + self.block_flow.base.children.push_back(column); + column = match remaining { + Some(remaining) => remaining, + None => break + }; + } } fn compute_absolute_position(&mut self, layout_context: &LayoutContext) { - self.block_flow.compute_absolute_position(layout_context) + self.block_flow.compute_absolute_position(layout_context); + let pitch = LogicalSize::new(self.block_flow.base.writing_mode, self.column_pitch, Au(0)); + let pitch = pitch.to_physical(self.block_flow.base.writing_mode); + for (i, child) in self.block_flow.base.children.iter_mut().enumerate() { + let point = &mut mut_base(child).stacking_relative_position; + *point = *point + pitch * i as i32; + } } fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) { @@ -121,8 +178,8 @@ impl Flow for MulticolFlow { } fn build_display_list(&mut self, layout_context: &LayoutContext) { - debug!("build_display_list_multicol: same process as block flow"); - self.block_flow.build_display_list(layout_context) + debug!("build_display_list_multicol"); + self.block_flow.build_display_list(layout_context); } fn repair_style(&mut self, new_style: &Arc) { @@ -141,11 +198,89 @@ impl Flow for MulticolFlow { iterator: &mut FragmentBorderBoxIterator, level: i32, stacking_context_position: &Point2D) { - self.block_flow.iterate_through_fragment_border_boxes(iterator, level, stacking_context_position) + self.block_flow.iterate_through_fragment_border_boxes(iterator, level, stacking_context_position); } fn mutate_fragments(&mut self, mutator: &mut FnMut(&mut Fragment)) { - self.block_flow.mutate_fragments(mutator) + self.block_flow.mutate_fragments(mutator); + } + + fn print_extra_flow_children(&self, print_tree: &mut PrintTree) { + self.block_flow.print_extra_flow_children(print_tree); + } +} + +impl Flow for MulticolColumnFlow { + fn class(&self) -> FlowClass { + FlowClass::MulticolColumn + } + + fn as_mut_block(&mut self) -> &mut BlockFlow { + &mut self.block_flow + } + + fn as_block(&self) -> &BlockFlow { + &self.block_flow + } + + fn bubble_inline_sizes(&mut self) { + self.block_flow.bubble_inline_sizes(); + } + + fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) { + debug!("assign_inline_sizes({}): assigning inline_size for flow", "multicol column"); + self.block_flow.assign_inline_sizes(layout_context); + } + + fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) { + debug!("assign_block_size: assigning block_size for multicol column"); + self.block_flow.assign_block_size(ctx); + } + + fn fragment(&mut self, layout_context: &LayoutContext, + fragmentation_context: Option) + -> Option { + Flow::fragment(&mut self.block_flow, layout_context, fragmentation_context) + } + + fn compute_absolute_position(&mut self, layout_context: &LayoutContext) { + self.block_flow.compute_absolute_position(layout_context) + } + + fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) { + self.block_flow.update_late_computed_inline_position_if_necessary(inline_position) + } + + fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) { + self.block_flow.update_late_computed_block_position_if_necessary(block_position) + } + + fn build_display_list(&mut self, layout_context: &LayoutContext) { + debug!("build_display_list_multicol column"); + self.block_flow.build_display_list(layout_context); + } + + fn repair_style(&mut self, new_style: &Arc) { + self.block_flow.repair_style(new_style) + } + + fn compute_overflow(&self) -> Rect { + self.block_flow.compute_overflow() + } + + fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize { + self.block_flow.generated_containing_block_size(flow) + } + + fn iterate_through_fragment_border_boxes(&self, + iterator: &mut FragmentBorderBoxIterator, + level: i32, + stacking_context_position: &Point2D) { + self.block_flow.iterate_through_fragment_border_boxes(iterator, level, stacking_context_position); + } + + fn mutate_fragments(&mut self, mutator: &mut FnMut(&mut Fragment)) { + self.block_flow.mutate_fragments(mutator); } fn print_extra_flow_children(&self, print_tree: &mut PrintTree) { @@ -158,3 +293,9 @@ impl fmt::Debug for MulticolFlow { write!(f, "MulticolFlow: {:?}", self.block_flow) } } + +impl fmt::Debug for MulticolColumnFlow { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "MulticolColumnFlow: {:?}", self.block_flow) + } +} diff --git a/components/layout/table_cell.rs b/components/layout/table_cell.rs index ad6d2ca1804..cfd66e26bef 100644 --- a/components/layout/table_cell.rs +++ b/components/layout/table_cell.rs @@ -69,9 +69,11 @@ impl TableCellFlow { /// methods. #[inline(always)] fn assign_block_size_table_cell_base<'a>(&mut self, layout_context: &'a LayoutContext<'a>) { - self.block_flow.assign_block_size_block_base( + let remaining = self.block_flow.assign_block_size_block_base( layout_context, - MarginsMayCollapseFlag::MarginsMayNotCollapse) + None, + MarginsMayCollapseFlag::MarginsMayNotCollapse); + debug_assert!(remaining.is_none()); } } diff --git a/components/layout/table_wrapper.rs b/components/layout/table_wrapper.rs index b41ee8ed119..02cc1b768fb 100644 --- a/components/layout/table_wrapper.rs +++ b/components/layout/table_wrapper.rs @@ -408,9 +408,11 @@ impl Flow for TableWrapperFlow { fn assign_block_size<'a>(&mut self, layout_context: &'a LayoutContext<'a>) { debug!("assign_block_size: assigning block_size for table_wrapper"); - self.block_flow - .assign_block_size_block_base(layout_context, - MarginsMayCollapseFlag::MarginsMayNotCollapse); + let remaining = self.block_flow.assign_block_size_block_base( + layout_context, + None, + MarginsMayCollapseFlag::MarginsMayNotCollapse); + debug_assert!(remaining.is_none()); } fn compute_absolute_position(&mut self, layout_context: &LayoutContext) { From f766e9425944272547e17593d0e551287546f7bc Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Tue, 12 Jan 2016 15:17:13 +0100 Subject: [PATCH 7/8] Remove unused `fixed_descendants: AbsoluteDescendants` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … in `construct.rs` for multicol and table wrappers. --- components/layout/construct.rs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/components/layout/construct.rs b/components/layout/construct.rs index 36c5742e053..39bdbfa5093 100644 --- a/components/layout/construct.rs +++ b/components/layout/construct.rs @@ -1093,7 +1093,6 @@ impl<'a, 'ln, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode<'ln>> let construction_result = self.build_flow_for_block_like(column_flow, node); let mut abs_descendants = AbsoluteDescendants::new(); - let mut fixed_descendants = AbsoluteDescendants::new(); if let ConstructionResult::Flow(column_flow, column_abs_descendants) = construction_result { flow.add_new_child(column_flow); @@ -1109,13 +1108,9 @@ impl<'a, 'ln, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode<'ln>> abs_descendants = AbsoluteDescendants::new(); - let is_fixed_positioned = flow.as_block().is_fixed(); let is_absolutely_positioned = flow::base(&*flow).flags.contains(IS_ABSOLUTELY_POSITIONED); - if is_fixed_positioned { - // Send itself along with the other fixed descendants. - fixed_descendants.push(flow.clone()); - } else if is_absolutely_positioned { + if is_absolutely_positioned { // This is now the only absolute flow in the subtree which hasn't yet // reached its containing block. abs_descendants.push(flow.clone()); @@ -1140,7 +1135,6 @@ impl<'a, 'ln, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode<'ln>> let construction_result = self.build_flow_for_block_like(table_flow, node); let mut abs_descendants = AbsoluteDescendants::new(); - let mut fixed_descendants = AbsoluteDescendants::new(); // The order of the caption and the table are not necessarily the same order as in the DOM // tree. All caption blocks are placed before or after the table flow, depending on the @@ -1168,13 +1162,9 @@ impl<'a, 'ln, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode<'ln>> abs_descendants = AbsoluteDescendants::new(); - let is_fixed_positioned = wrapper_flow.as_block().is_fixed(); let is_absolutely_positioned = flow::base(&*wrapper_flow).flags.contains(IS_ABSOLUTELY_POSITIONED); - if is_fixed_positioned { - // Send itself along with the other fixed descendants. - fixed_descendants.push(wrapper_flow.clone()); - } else if is_absolutely_positioned { + if is_absolutely_positioned { // This is now the only absolute flow in the subtree which hasn't yet // reached its containing block. abs_descendants.push(wrapper_flow.clone()); From 47d918ca35f8ae8ce4d24430e3520a06272f35f9 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Thu, 14 Jan 2016 00:29:55 +0100 Subject: [PATCH 8/8] Document new return value of BlockFlow::assign_block_size_block_base --- components/layout/block.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/components/layout/block.rs b/components/layout/block.rs index 2ec619607a3..3774184fd6e 100644 --- a/components/layout/block.rs +++ b/components/layout/block.rs @@ -751,6 +751,14 @@ impl BlockFlow { /// For absolute flows, we store the calculated content block-size for the flow. We defer the /// calculation of the other values until a later traversal. /// + /// When `fragmentation_context` is given (not `None`), this should fit as much of the content + /// as possible within the available block size. + /// If there is more content (that doesn’t fit), this flow is *fragmented* + /// with the extra content moved to another fragment (a flow like this one) which is returrned. + /// See `Flow::fragment`. + /// + /// The return value is always `None` when `fragmentation_context` is `None`. + /// /// `inline(always)` because this is only ever called by in-order or non-in-order top-level /// methods. #[inline(always)]