diff options
Diffstat (limited to 'components/layout')
30 files changed, 13912 insertions, 0 deletions
diff --git a/components/layout/Cargo.toml b/components/layout/Cargo.toml new file mode 100644 index 00000000000..9d8e8abbdaa --- /dev/null +++ b/components/layout/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "layout" +version = "0.0.1" +authors = ["The Servo Project Developers"] + +[lib] +name = "layout" +path = "lib.rs" + +[dependencies.gfx] +path = "../gfx" + +[dependencies.script] +path = "../script" + +[dependencies.layout_traits] +path = "../layout_traits" + +[dependencies.script_traits] +path = "../script_traits" + +[dependencies.style] +path = "../style" + +[dependencies.macros] +path = "../macros" + +[dependencies.net] +path = "../net" + +[dependencies.util] +path = "../util" + +[dependencies.geom] +git = "https://github.com/servo/rust-geom" + +[dependencies.url] +git = "https://github.com/servo/rust-url" diff --git a/components/layout/block.rs b/components/layout/block.rs new file mode 100644 index 00000000000..b84e0da50f7 --- /dev/null +++ b/components/layout/block.rs @@ -0,0 +1,2428 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! CSS block formatting contexts. +//! +//! Terminology Note: +//! As per the CSS Spec, the term 'absolute positioning' here refers to +//! elements with position = 'absolute' or 'fixed'. +//! The term 'positioned element' refers to elements with position = +//! 'relative', 'absolute', or 'fixed'. +//! +//! CB: Containing Block of the current flow. + +#![deny(unsafe_block)] + +use construct::FlowConstructor; +use context::LayoutContext; +use floats::{ClearBoth, ClearLeft, ClearRight, FloatKind, Floats, PlacementInfo}; +use flow::{BaseFlow, BlockFlowClass, FlowClass, Flow, ImmutableFlowUtils}; +use flow::{MutableFlowUtils, PreorderFlowTraversal, PostorderFlowTraversal, mut_base}; +use flow; +use fragment::{Fragment, ImageFragment, ScannedTextFragment}; +use layout_debug; +use model::{Auto, IntrinsicISizes, MarginCollapseInfo, MarginsCollapse}; +use model::{MarginsCollapseThrough, MaybeAuto, NoCollapsibleMargins, Specified, specified}; +use model::{specified_or_none}; +use wrapper::ThreadSafeLayoutNode; +use style::ComputedValues; +use style::computed_values::{clear, position}; + +use collections::dlist::DList; +use geom::{Size2D, Point2D, Rect}; +use gfx::color; +use gfx::display_list::{BackgroundAndBorderLevel, BlockLevel, ContentStackingLevel, DisplayList}; +use gfx::display_list::{FloatStackingLevel, PositionedDescendantStackingLevel}; +use gfx::display_list::{RootOfStackingContextLevel}; +use gfx::render_task::RenderLayer; +use servo_msg::compositor_msg::{FixedPosition, LayerId, Scrollable}; +use servo_util::geometry::Au; +use servo_util::geometry; +use servo_util::logical_geometry::WritingMode; +use servo_util::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize}; +use std::fmt; +use std::mem; +use style::computed_values::{LPA_Auto, LPA_Length, LPA_Percentage, LPN_Length, LPN_None}; +use style::computed_values::{LPN_Percentage, LP_Length, LP_Percentage}; +use style::computed_values::{display, float, overflow}; +use sync::Arc; + +/// Information specific to floated blocks. +#[deriving(Encodable)] +pub struct FloatedBlockInfo { + pub containing_inline_size: Au, + + /// Offset relative to where the parent tried to position this flow + pub rel_pos: LogicalPoint<Au>, + + /// Index into the fragment list for inline floats + pub index: Option<uint>, + + /// Left or right? + pub float_kind: FloatKind, +} + +impl FloatedBlockInfo { + pub fn new(float_kind: FloatKind, writing_mode: WritingMode) -> FloatedBlockInfo { + FloatedBlockInfo { + containing_inline_size: Au(0), + rel_pos: LogicalPoint::new(writing_mode, Au(0), Au(0)), + index: None, + float_kind: float_kind, + } + } +} + +/// The solutions for the block-size-and-margins constraint equation. +struct BSizeConstraintSolution { + block_start: Au, + _block_end: Au, + block_size: Au, + margin_block_start: Au, + margin_block_end: Au +} + +impl BSizeConstraintSolution { + fn new(block_start: Au, block_end: Au, block_size: Au, margin_block_start: Au, margin_block_end: Au) + -> BSizeConstraintSolution { + BSizeConstraintSolution { + block_start: block_start, + _block_end: block_end, + block_size: block_size, + margin_block_start: margin_block_start, + margin_block_end: margin_block_end, + } + } + + /// Solve the vertical constraint equation for absolute non-replaced elements. + /// + /// CSS Section 10.6.4 + /// Constraint equation: + /// block-start + block-end + block-size + margin-block-start + margin-block-end + /// = absolute containing block block-size - (vertical padding and border) + /// [aka available_block-size] + /// + /// Return the solution for the equation. + fn solve_vertical_constraints_abs_nonreplaced(block_size: MaybeAuto, + block_start_margin: MaybeAuto, + block_end_margin: MaybeAuto, + block_start: MaybeAuto, + block_end: MaybeAuto, + content_block_size: Au, + available_block_size: Au, + static_b_offset: Au) + -> BSizeConstraintSolution { + // Distance from the block-start edge of the Absolute Containing Block to the + // block-start margin edge of a hypothetical box that would have been the + // first box of the element. + let static_position_block_start = static_b_offset; + + let (block_start, block_end, block_size, margin_block_start, margin_block_end) = match (block_start, block_end, block_size) { + (Auto, Auto, Auto) => { + let margin_block_start = block_start_margin.specified_or_zero(); + let margin_block_end = block_end_margin.specified_or_zero(); + let block_start = static_position_block_start; + // Now it is the same situation as block-start Specified and block-end + // and block-size Auto. + + let block_size = content_block_size; + let sum = block_start + block_size + margin_block_start + margin_block_end; + (block_start, available_block_size - sum, block_size, margin_block_start, margin_block_end) + } + (Specified(block_start), Specified(block_end), Specified(block_size)) => { + match (block_start_margin, block_end_margin) { + (Auto, Auto) => { + let total_margin_val = available_block_size - block_start - block_end - block_size; + (block_start, block_end, block_size, + total_margin_val.scale_by(0.5), + total_margin_val.scale_by(0.5)) + } + (Specified(margin_block_start), Auto) => { + let sum = block_start + block_end + block_size + margin_block_start; + (block_start, block_end, block_size, margin_block_start, available_block_size - sum) + } + (Auto, Specified(margin_block_end)) => { + let sum = block_start + block_end + block_size + margin_block_end; + (block_start, block_end, block_size, available_block_size - sum, margin_block_end) + } + (Specified(margin_block_start), Specified(margin_block_end)) => { + // Values are over-constrained. Ignore value for 'block-end'. + let sum = block_start + block_size + margin_block_start + margin_block_end; + (block_start, available_block_size - sum, block_size, margin_block_start, margin_block_end) + } + } + } + + // For the rest of the cases, auto values for margin are set to 0 + + // If only one is Auto, solve for it + (Auto, Specified(block_end), Specified(block_size)) => { + let margin_block_start = block_start_margin.specified_or_zero(); + let margin_block_end = block_end_margin.specified_or_zero(); + let sum = block_end + block_size + margin_block_start + margin_block_end; + (available_block_size - sum, block_end, block_size, margin_block_start, margin_block_end) + } + (Specified(block_start), Auto, Specified(block_size)) => { + let margin_block_start = block_start_margin.specified_or_zero(); + let margin_block_end = block_end_margin.specified_or_zero(); + let sum = block_start + block_size + margin_block_start + margin_block_end; + (block_start, available_block_size - sum, block_size, margin_block_start, margin_block_end) + } + (Specified(block_start), Specified(block_end), Auto) => { + let margin_block_start = block_start_margin.specified_or_zero(); + let margin_block_end = block_end_margin.specified_or_zero(); + let sum = block_start + block_end + margin_block_start + margin_block_end; + (block_start, block_end, available_block_size - sum, margin_block_start, margin_block_end) + } + + // If block-size is auto, then block-size is content block-size. Solve for the + // non-auto value. + (Specified(block_start), Auto, Auto) => { + let margin_block_start = block_start_margin.specified_or_zero(); + let margin_block_end = block_end_margin.specified_or_zero(); + let block_size = content_block_size; + let sum = block_start + block_size + margin_block_start + margin_block_end; + (block_start, available_block_size - sum, block_size, margin_block_start, margin_block_end) + } + (Auto, Specified(block_end), Auto) => { + let margin_block_start = block_start_margin.specified_or_zero(); + let margin_block_end = block_end_margin.specified_or_zero(); + let block_size = content_block_size; + let sum = block_end + block_size + margin_block_start + margin_block_end; + (available_block_size - sum, block_end, block_size, margin_block_start, margin_block_end) + } + + (Auto, Auto, Specified(block_size)) => { + let margin_block_start = block_start_margin.specified_or_zero(); + let margin_block_end = block_end_margin.specified_or_zero(); + let block_start = static_position_block_start; + let sum = block_start + block_size + margin_block_start + margin_block_end; + (block_start, available_block_size - sum, block_size, margin_block_start, margin_block_end) + } + }; + BSizeConstraintSolution::new(block_start, block_end, block_size, margin_block_start, margin_block_end) + } + + /// Solve the vertical constraint equation for absolute replaced elements. + /// + /// Assumption: The used value for block-size has already been calculated. + /// + /// CSS Section 10.6.5 + /// Constraint equation: + /// block-start + block-end + block-size + margin-block-start + margin-block-end + /// = absolute containing block block-size - (vertical padding and border) + /// [aka available_block-size] + /// + /// Return the solution for the equation. + fn solve_vertical_constraints_abs_replaced(block_size: Au, + block_start_margin: MaybeAuto, + block_end_margin: MaybeAuto, + block_start: MaybeAuto, + block_end: MaybeAuto, + _: Au, + available_block_size: Au, + static_b_offset: Au) + -> BSizeConstraintSolution { + // Distance from the block-start edge of the Absolute Containing Block to the + // block-start margin edge of a hypothetical box that would have been the + // first box of the element. + let static_position_block_start = static_b_offset; + + let (block_start, block_end, block_size, margin_block_start, margin_block_end) = match (block_start, block_end) { + (Auto, Auto) => { + let margin_block_start = block_start_margin.specified_or_zero(); + let margin_block_end = block_end_margin.specified_or_zero(); + let block_start = static_position_block_start; + let sum = block_start + block_size + margin_block_start + margin_block_end; + (block_start, available_block_size - sum, block_size, margin_block_start, margin_block_end) + } + (Specified(block_start), Specified(block_end)) => { + match (block_start_margin, block_end_margin) { + (Auto, Auto) => { + let total_margin_val = available_block_size - block_start - block_end - block_size; + (block_start, block_end, block_size, + total_margin_val.scale_by(0.5), + total_margin_val.scale_by(0.5)) + } + (Specified(margin_block_start), Auto) => { + let sum = block_start + block_end + block_size + margin_block_start; + (block_start, block_end, block_size, margin_block_start, available_block_size - sum) + } + (Auto, Specified(margin_block_end)) => { + let sum = block_start + block_end + block_size + margin_block_end; + (block_start, block_end, block_size, available_block_size - sum, margin_block_end) + } + (Specified(margin_block_start), Specified(margin_block_end)) => { + // Values are over-constrained. Ignore value for 'block-end'. + let sum = block_start + block_size + margin_block_start + margin_block_end; + (block_start, available_block_size - sum, block_size, margin_block_start, margin_block_end) + } + } + } + + // If only one is Auto, solve for it + (Auto, Specified(block_end)) => { + let margin_block_start = block_start_margin.specified_or_zero(); + let margin_block_end = block_end_margin.specified_or_zero(); + let sum = block_end + block_size + margin_block_start + margin_block_end; + (available_block_size - sum, block_end, block_size, margin_block_start, margin_block_end) + } + (Specified(block_start), Auto) => { + let margin_block_start = block_start_margin.specified_or_zero(); + let margin_block_end = block_end_margin.specified_or_zero(); + let sum = block_start + block_size + margin_block_start + margin_block_end; + (block_start, available_block_size - sum, block_size, margin_block_start, margin_block_end) + } + }; + BSizeConstraintSolution::new(block_start, block_end, block_size, margin_block_start, margin_block_end) + } +} + +/// Performs block-size calculations potentially multiple times, taking +/// (assuming an horizontal writing mode) `height`, `min-height`, and `max-height` +/// into account. After each call to `next()`, the caller must call `.try()` with the +/// current calculated value of `height`. +/// +/// See CSS 2.1 § 10.7. +struct CandidateBSizeIterator { + block_size: MaybeAuto, + max_block_size: Option<Au>, + min_block_size: Au, + candidate_value: Au, + status: CandidateBSizeIteratorStatus, +} + +impl CandidateBSizeIterator { + /// Creates a new candidate block-size iterator. `block_container_block-size` is `None` if the block-size + /// of the block container has not been determined yet. It will always be `Some` in the case of + /// absolutely-positioned containing blocks. + pub fn new(style: &ComputedValues, block_container_block_size: Option<Au>) + -> CandidateBSizeIterator { + // Per CSS 2.1 § 10.7, (assuming an horizontal writing mode,) + // percentages in `min-height` and `max-height` refer to the height of + // the containing block. + // If that is not determined yet by the time we need to resolve + // `min-height` and `max-height`, percentage values are ignored. + + let block_size = match (style.content_block_size(), block_container_block_size) { + (LPA_Percentage(percent), Some(block_container_block_size)) => { + Specified(block_container_block_size.scale_by(percent)) + } + (LPA_Percentage(_), None) | (LPA_Auto, _) => Auto, + (LPA_Length(length), _) => Specified(length), + }; + let max_block_size = match (style.max_block_size(), block_container_block_size) { + (LPN_Percentage(percent), Some(block_container_block_size)) => { + Some(block_container_block_size.scale_by(percent)) + } + (LPN_Percentage(_), None) | (LPN_None, _) => None, + (LPN_Length(length), _) => Some(length), + }; + let min_block_size = match (style.min_block_size(), block_container_block_size) { + (LP_Percentage(percent), Some(block_container_block_size)) => { + block_container_block_size.scale_by(percent) + } + (LP_Percentage(_), None) => Au(0), + (LP_Length(length), _) => length, + }; + + CandidateBSizeIterator { + block_size: block_size, + max_block_size: max_block_size, + min_block_size: min_block_size, + candidate_value: Au(0), + status: InitialCandidateBSizeStatus, + } + } +} + +impl Iterator<MaybeAuto> for CandidateBSizeIterator { + fn next(&mut self) -> Option<MaybeAuto> { + self.status = match self.status { + InitialCandidateBSizeStatus => TryingBSizeCandidateBSizeStatus, + TryingBSizeCandidateBSizeStatus => { + match self.max_block_size { + Some(max_block_size) if self.candidate_value > max_block_size => { + TryingMaxCandidateBSizeStatus + } + _ if self.candidate_value < self.min_block_size => TryingMinCandidateBSizeStatus, + _ => FoundCandidateBSizeStatus, + } + } + TryingMaxCandidateBSizeStatus => { + if self.candidate_value < self.min_block_size { + TryingMinCandidateBSizeStatus + } else { + FoundCandidateBSizeStatus + } + } + TryingMinCandidateBSizeStatus | FoundCandidateBSizeStatus => { + FoundCandidateBSizeStatus + } + }; + + match self.status { + TryingBSizeCandidateBSizeStatus => Some(self.block_size), + TryingMaxCandidateBSizeStatus => { + Some(Specified(self.max_block_size.unwrap())) + } + TryingMinCandidateBSizeStatus => { + Some(Specified(self.min_block_size)) + } + FoundCandidateBSizeStatus => None, + InitialCandidateBSizeStatus => fail!(), + } + } +} + +enum CandidateBSizeIteratorStatus { + InitialCandidateBSizeStatus, + TryingBSizeCandidateBSizeStatus, + TryingMaxCandidateBSizeStatus, + TryingMinCandidateBSizeStatus, + FoundCandidateBSizeStatus, +} + +// A helper function used in block-size calculation. +fn translate_including_floats(cur_b: &mut Au, delta: Au, floats: &mut Floats) { + *cur_b = *cur_b + delta; + let writing_mode = floats.writing_mode; + floats.translate(LogicalSize::new(writing_mode, Au(0), -delta)); +} + +/// The real assign-block-sizes traversal for flows with position 'absolute'. +/// +/// This is a traversal of an Absolute Flow tree. +/// - Relatively positioned flows and the Root flow start new Absolute flow trees. +/// - The kids of a flow in this tree will be the flows for which it is the +/// absolute Containing Block. +/// - Thus, leaf nodes and inner non-root nodes are all Absolute Flows. +/// +/// A Flow tree can have several Absolute Flow trees (depending on the number +/// of relatively positioned flows it has). +/// +/// Note that flows with position 'fixed' just form a flat list as they all +/// have the Root flow as their CB. +struct AbsoluteAssignBSizesTraversal<'a>(&'a LayoutContext<'a>); + +impl<'a> PreorderFlowTraversal for AbsoluteAssignBSizesTraversal<'a> { + #[inline] + fn process(&mut self, flow: &mut Flow) -> bool { + let block_flow = flow.as_block(); + + // The root of the absolute flow tree is definitely not absolutely + // positioned. Nothing to process here. + if block_flow.is_root_of_absolute_flow_tree() { + return true; + } + + + let AbsoluteAssignBSizesTraversal(ref ctx) = *self; + block_flow.calculate_abs_block_size_and_margins(*ctx); + true + } +} + +/// The store-overflow traversal particular to absolute flows. +/// +/// Propagate overflow up the Absolute flow tree and update overflow up to and +/// not including the root of the Absolute flow tree. +/// After that, it is up to the normal store-overflow traversal to propagate +/// it further up. +struct AbsoluteStoreOverflowTraversal<'a>{ + layout_context: &'a LayoutContext<'a>, +} + +impl<'a> PostorderFlowTraversal for AbsoluteStoreOverflowTraversal<'a> { + #[inline] + fn process(&mut self, flow: &mut Flow) -> bool { + // This will be taken care of by the normal store-overflow traversal. + if flow.is_root_of_absolute_flow_tree() { + return true; + } + + flow.store_overflow(self.layout_context); + true + } +} + +enum BlockType { + BlockReplacedType, + BlockNonReplacedType, + AbsoluteReplacedType, + AbsoluteNonReplacedType, + FloatReplacedType, + FloatNonReplacedType, +} + +#[deriving(Clone, PartialEq)] +pub enum MarginsMayCollapseFlag { + MarginsMayCollapse, + MarginsMayNotCollapse, +} + +#[deriving(PartialEq)] +enum FormattingContextType { + NonformattingContext, + BlockFormattingContext, + OtherFormattingContext, +} + +// Propagates the `layers_needed_for_descendants` flag appropriately from a child. This is called +// as part of block-size assignment. +// +// If any fixed descendants of kids are present, this kid needs a layer. +// +// FIXME(#2006, pcwalton): This is too layer-happy. Like WebKit, we shouldn't do this unless +// the positioned descendants are actually on top of the fixed kids. +// +// TODO(#1244, #2007, pcwalton): Do this for CSS transforms and opacity too, at least if they're +// animating. +fn propagate_layer_flag_from_child(layers_needed_for_descendants: &mut bool, kid: &mut Flow) { + if kid.is_absolute_containing_block() { + let kid_base = flow::mut_base(kid); + if kid_base.flags.needs_layer() { + *layers_needed_for_descendants = true + } + } else { + let kid_base = flow::mut_base(kid); + if kid_base.flags.layers_needed_for_descendants() { + *layers_needed_for_descendants = true + } + } +} + +// A block formatting context. +#[deriving(Encodable)] +pub struct BlockFlow { + /// Data common to all flows. + pub base: BaseFlow, + + /// The associated fragment. + pub fragment: Fragment, + + /// TODO: is_root should be a bit field to conserve memory. + /// Whether this block flow is the root flow. + pub is_root: bool, + + /// Static y offset of an absolute flow from its CB. + pub static_b_offset: Au, + + /// The inline-size of the last float prior to this block. This is used to speculatively lay out + /// block formatting contexts. + previous_float_inline_size: Option<Au>, + + /// Additional floating flow members. + pub float: Option<Box<FloatedBlockInfo>> +} + +impl BlockFlow { + pub fn from_node(constructor: &mut FlowConstructor, node: &ThreadSafeLayoutNode) -> BlockFlow { + BlockFlow { + base: BaseFlow::new((*node).clone()), + fragment: Fragment::new(constructor, node), + is_root: false, + static_b_offset: Au::new(0), + previous_float_inline_size: None, + float: None + } + } + + pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode, fragment: Fragment) -> BlockFlow { + BlockFlow { + base: BaseFlow::new((*node).clone()), + fragment: fragment, + is_root: false, + static_b_offset: Au::new(0), + previous_float_inline_size: None, + float: None + } + } + + pub fn float_from_node(constructor: &mut FlowConstructor, + node: &ThreadSafeLayoutNode, + float_kind: FloatKind) + -> BlockFlow { + let base = BaseFlow::new((*node).clone()); + BlockFlow { + fragment: Fragment::new(constructor, node), + is_root: false, + static_b_offset: Au::new(0), + previous_float_inline_size: None, + float: Some(box FloatedBlockInfo::new(float_kind, base.writing_mode)), + base: base, + } + } + + /// Return the type of this block. + /// + /// This determines the algorithm used to calculate inline-size, block-size, and the + /// relevant margins for this Block. + fn block_type(&self) -> BlockType { + if self.is_absolutely_positioned() { + if self.is_replaced_content() { + AbsoluteReplacedType + } else { + AbsoluteNonReplacedType + } + } else if self.is_float() { + if self.is_replaced_content() { + FloatReplacedType + } else { + FloatNonReplacedType + } + } else { + if self.is_replaced_content() { + BlockReplacedType + } else { + BlockNonReplacedType + } + } + } + + /// Compute the used value of inline-size for this Block. + fn compute_used_inline_size(&mut self, ctx: &LayoutContext, containing_block_inline_size: Au) { + let block_type = self.block_type(); + match block_type { + AbsoluteReplacedType => { + let inline_size_computer = AbsoluteReplaced; + inline_size_computer.compute_used_inline_size(self, ctx, containing_block_inline_size); + } + AbsoluteNonReplacedType => { + let inline_size_computer = AbsoluteNonReplaced; + inline_size_computer.compute_used_inline_size(self, ctx, containing_block_inline_size); + } + FloatReplacedType => { + let inline_size_computer = FloatReplaced; + inline_size_computer.compute_used_inline_size(self, ctx, containing_block_inline_size); + } + FloatNonReplacedType => { + let inline_size_computer = FloatNonReplaced; + inline_size_computer.compute_used_inline_size(self, ctx, containing_block_inline_size); + } + BlockReplacedType => { + let inline_size_computer = BlockReplaced; + inline_size_computer.compute_used_inline_size(self, ctx, containing_block_inline_size); + } + BlockNonReplacedType => { + let inline_size_computer = BlockNonReplaced; + inline_size_computer.compute_used_inline_size(self, ctx, containing_block_inline_size); + } + } + } + + /// Return this flow's fragment. + pub fn fragment<'a>(&'a mut self) -> &'a mut Fragment { + &mut self.fragment + } + + /// Return the static x offset from the appropriate Containing Block for this flow. + pub fn static_i_offset(&self) -> Au { + if self.is_fixed() { + self.base.fixed_static_i_offset + } else { + self.base.absolute_static_i_offset + } + } + + /// Return the size of the Containing Block for this flow. + /// + /// Right now, this only gets the Containing Block size for absolutely + /// positioned elements. + /// Note: Assume this is called in a top-down traversal, so it is ok to + /// reference the CB. + #[inline] + pub fn containing_block_size(&mut self, viewport_size: Size2D<Au>) -> LogicalSize<Au> { + assert!(self.is_absolutely_positioned()); + if self.is_fixed() { + // Initial containing block is the CB for the root + LogicalSize::from_physical(self.base.writing_mode, viewport_size) + } else { + self.base.absolute_cb.generated_containing_block_rect().size + } + } + + /// Traverse the Absolute flow tree in preorder. + /// + /// Traverse all your direct absolute descendants, who will then traverse + /// their direct absolute descendants. + /// Also, set the static y offsets for each descendant (using the value + /// which was bubbled up during normal assign-block-size). + /// + /// Return true if the traversal is to continue or false to stop. + fn traverse_preorder_absolute_flows<T:PreorderFlowTraversal>(&mut self, + traversal: &mut T) + -> bool { + let flow = self as &mut Flow; + if traversal.should_prune(flow) { + return true + } + + if !traversal.process(flow) { + return false + } + + let cb_block_start_edge_offset = flow.generated_containing_block_rect().start.b; + let mut descendant_offset_iter = mut_base(flow).abs_descendants.iter_with_offset(); + // Pass in the respective static y offset for each descendant. + for (ref mut descendant_link, ref y_offset) in descendant_offset_iter { + let block = descendant_link.as_block(); + // The stored y_offset is wrt to the flow box. + // Translate it to the CB (which is the padding box). + block.static_b_offset = **y_offset - cb_block_start_edge_offset; + if !block.traverse_preorder_absolute_flows(traversal) { + return false + } + } + + true + } + + /// Traverse the Absolute flow tree in postorder. + /// + /// Return true if the traversal is to continue or false to stop. + fn traverse_postorder_absolute_flows<T:PostorderFlowTraversal>(&mut self, + traversal: &mut T) + -> bool { + let flow = self as &mut Flow; + if traversal.should_prune(flow) { + return true + } + + for descendant_link in mut_base(flow).abs_descendants.iter() { + let block = descendant_link.as_block(); + if !block.traverse_postorder_absolute_flows(traversal) { + return false + } + } + + traversal.process(flow) + } + + /// Return true if this has a replaced fragment. + /// + /// The only two types of replaced fragments currently are text fragments + /// and image fragments. + fn is_replaced_content(&self) -> bool { + match self.fragment.specific { + ScannedTextFragment(_) | ImageFragment(_) => true, + _ => false, + } + } + + /// Return shrink-to-fit inline-size. + /// + /// This is where we use the preferred inline-sizes and minimum inline-sizes + /// calculated in the bubble-inline-sizes traversal. + fn get_shrink_to_fit_inline_size(&self, available_inline_size: Au) -> Au { + geometry::min(self.base.intrinsic_inline_sizes.preferred_inline_size, + geometry::max(self.base.intrinsic_inline_sizes.minimum_inline_size, available_inline_size)) + } + + /// Collect and update static y-offsets bubbled up by kids. + /// + /// This would essentially give us offsets of all absolutely positioned + /// direct descendants and all fixed descendants, in tree order. + /// + /// Assume that this is called in a bottom-up traversal (specifically, the + /// assign-block-size traversal). So, kids have their flow origin already set. + /// In the case of absolute flow kids, they have their hypothetical box + /// position already set. + fn collect_static_b_offsets_from_kids(&mut self) { + let mut abs_descendant_y_offsets = Vec::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() || kid_block.is_absolutely_positioned() { + // It won't contribute any offsets for descendants because it + // would be the CB for them. + gives_abs_offsets = false; + // Give the offset for the current absolute flow alone. + abs_descendant_y_offsets.push(kid_block.get_hypothetical_block_start_edge()); + } else if kid_block.is_positioned() { + // It won't contribute any offsets because it would be the CB + // for the descendants. + gives_abs_offsets = false; + } + } + + if gives_abs_offsets { + let kid_base = flow::mut_base(kid); + // Avoid copying the offset vector. + let offsets = mem::replace(&mut kid_base.abs_descendants.static_b_offsets, Vec::new()); + // Consume all the static y-offsets bubbled up by kid. + for y_offset in offsets.move_iter() { + // The offsets are wrt the kid flow box. Translate them to current flow. + abs_descendant_y_offsets.push(y_offset + kid_base.position.start.b); + } + } + } + self.base.abs_descendants.static_b_offsets = abs_descendant_y_offsets; + } + + /// If this is the root flow, shifts all kids down and adjusts our size to account for + /// root flow margins, which should never be collapsed according to CSS § 8.3.1. + /// + /// TODO(#2017, pcwalton): This is somewhat inefficient (traverses kids twice); can we do + /// better? + fn adjust_fragments_for_collapsed_margins_if_root(&mut self) { + if !self.is_root() { + return + } + + let (block_start_margin_value, block_end_margin_value) = match self.base.collapsible_margins { + MarginsCollapseThrough(_) => fail!("Margins unexpectedly collapsed through root flow."), + MarginsCollapse(block_start_margin, block_end_margin) => { + (block_start_margin.collapse(), block_end_margin.collapse()) + } + NoCollapsibleMargins(block_start, block_end) => (block_start, block_end), + }; + + // Shift all kids down (or up, if margins are negative) if necessary. + if block_start_margin_value != Au(0) { + for kid in self.base.child_iter() { + let kid_base = flow::mut_base(kid); + kid_base.position.start.b = kid_base.position.start.b + block_start_margin_value + } + } + + self.base.position.size.block = self.base.position.size.block + block_start_margin_value + + block_end_margin_value; + self.fragment.border_box.size.block = self.fragment.border_box.size.block + block_start_margin_value + + block_end_margin_value; + } + + /// Assign block-size for current flow. + /// + /// * Collapse margins for flow's children and set in-flow child flows' y-coordinates now that + /// we know their block-sizes. + /// * Calculate and set the block-size of the current flow. + /// * Calculate block-size, 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 block-size 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_block_size_block_base<'a>(&mut self, + layout_context: &'a LayoutContext<'a>, + margins_may_collapse: MarginsMayCollapseFlag) { + let _scope = layout_debug_scope!("assign_block_size_block_base {:s}", self.base.debug_id()); + + // Our current border-box position. + let mut cur_b = Au(0); + + // Absolute positioning establishes a block formatting context. Don't propagate floats + // in or out. (But do propagate them between kids.) + if self.is_absolutely_positioned() { + self.base.floats = Floats::new(self.fragment.style.writing_mode); + } + if margins_may_collapse != MarginsMayCollapse { + self.base.floats = Floats::new(self.fragment.style.writing_mode); + } + + let mut margin_collapse_info = MarginCollapseInfo::new(); + self.base.floats.translate(LogicalSize::new( + self.fragment.style.writing_mode, -self.fragment.inline_start_offset(), Au(0))); + + // The sum of our block-start border and block-start padding. + let block_start_offset = self.fragment.border_padding.block_start; + translate_including_floats(&mut cur_b, block_start_offset, &mut self.base.floats); + + let can_collapse_block_start_margin_with_kids = + margins_may_collapse == MarginsMayCollapse && + !self.is_absolutely_positioned() && + self.fragment.border_padding.block_start == Au(0); + margin_collapse_info.initialize_block_start_margin(&self.fragment, + can_collapse_block_start_margin_with_kids); + + // 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 mut layers_needed_for_descendants = false; + for kid in self.base.child_iter() { + if kid.is_absolutely_positioned() { + // Assume that the *hypothetical box* for an absolute flow starts immediately after + // the block-end border edge of the previous flow. + flow::mut_base(kid).position.start.b = cur_b; + kid.assign_block_size_for_inorder_child_if_necessary(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. + continue + } + + // 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(); + if kid.is_float() { + // FIXME(pcwalton): Using `position.start.b` to mean the float ceiling is a + // bit of a hack. + flow::mut_base(kid).position.start.b = + margin_collapse_info.current_float_ceiling(); + propagate_layer_flag_from_child(&mut layers_needed_for_descendants, kid); + + let kid_was_impacted_by_floats = + kid.assign_block_size_for_inorder_child_if_necessary(layout_context); + assert!(kid_was_impacted_by_floats); // As it was a float itself... + + let kid_base = flow::mut_base(kid); + kid_base.position.start.b = cur_b; + floats = kid_base.floats.clone(); + continue + } + + + // If we have clearance, assume there are no floats in. + // + // FIXME(#2008, 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.) + if kid.float_clearance() != clear::none { + flow::mut_base(kid).floats = Floats::new(self.fragment.style.writing_mode) + } + + // Lay the child out if this was an in-order traversal. + let kid_was_impacted_by_floats = + kid.assign_block_size_for_inorder_child_if_necessary(layout_context); + + // Mark flows for layerization if necessary to handle painting order correctly. + propagate_layer_flag_from_child(&mut layers_needed_for_descendants, kid); + + // Handle any (possibly collapsed) top margin. + let delta = margin_collapse_info.advance_block_start_margin( + &flow::base(kid).collapsible_margins); + translate_including_floats(&mut cur_b, delta, &mut floats); + + // Clear past the floats that came in, if necessary. + let clearance = match kid.float_clearance() { + clear::none => Au(0), + clear::left => floats.clearance(ClearLeft), + clear::right => floats.clearance(ClearRight), + clear::both => floats.clearance(ClearBoth), + }; + cur_b = cur_b + clearance; + + // At this point, `cur_b` is at the border edge of the child. + flow::mut_base(kid).position.start.b = cur_b; + + // Now pull out the child's outgoing floats. We didn't do this immediately after the + // `assign_block-size_for_inorder_child_if_necessary` call because clearance on a block + // operates on the floats that come *in*, not the floats that go *out*. + if kid_was_impacted_by_floats { + floats = flow::mut_base(kid).floats.clone() + } + + // 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. + let kid_base = flow::mut_base(kid); + cur_b = cur_b + kid_base.position.size.block; + + // Handle any (possibly collapsed) block-end margin. + let delta = margin_collapse_info.advance_block_end_margin(&kid_base.collapsible_margins); + translate_including_floats(&mut cur_b, delta, &mut floats); + } + + // Mark ourselves for layerization if that will be necessary to paint in the proper order + // (CSS 2.1, Appendix E). + self.base.flags.set_layers_needed_for_descendants(layers_needed_for_descendants); + + // Collect various offsets needed by absolutely positioned descendants. + self.collect_static_b_offsets_from_kids(); + + // Add in our block-end margin and compute our collapsible margins. + let can_collapse_block_end_margin_with_kids = + margins_may_collapse == MarginsMayCollapse && + !self.is_absolutely_positioned() && + self.fragment.border_padding.block_end == Au(0); + let (collapsible_margins, delta) = + margin_collapse_info.finish_and_compute_collapsible_margins( + &self.fragment, + can_collapse_block_end_margin_with_kids); + self.base.collapsible_margins = collapsible_margins; + translate_including_floats(&mut cur_b, delta, &mut floats); + + // FIXME(#2003, 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 block_size = cur_b - block_start_offset; + if self.is_root() { + let screen_size = LogicalSize::from_physical( + self.fragment.style.writing_mode, layout_context.shared.screen_size); + block_size = Au::max(screen_size.block, block_size) + } + + if self.is_absolutely_positioned() { + // The content block-size includes all the floats per CSS 2.1 § 10.6.7. The easiest way to + // handle this is to just treat this as clearance. + block_size = block_size + floats.clearance(ClearBoth); + + // Fixed position layers get layers. + if self.is_fixed() { + self.base.flags.set_needs_layer(true) + } + + // Store the content block-size for use in calculating the absolute flow's dimensions + // later. + self.fragment.border_box.size.block = block_size; + return + } + + let mut candidate_block_size_iterator = CandidateBSizeIterator::new(self.fragment.style(), + None); + for candidate_block_size in candidate_block_size_iterator { + candidate_block_size_iterator.candidate_value = match candidate_block_size { + Auto => block_size, + Specified(value) => value + } + } + + // Adjust `cur_b` as necessary to account for the explicitly-specified block-size. + block_size = candidate_block_size_iterator.candidate_value; + let delta = block_size - (cur_b - block_start_offset); + translate_including_floats(&mut cur_b, delta, &mut floats); + + // Compute content block-size and noncontent block-size. + let block_end_offset = self.fragment.border_padding.block_end; + translate_including_floats(&mut cur_b, block_end_offset, &mut floats); + + // Now that `cur_b` is at the block-end of the border box, compute the final border box + // position. + self.fragment.border_box.size.block = cur_b; + self.fragment.border_box.start.b = Au(0); + self.base.position.size.block = cur_b; + + self.base.floats = floats.clone(); + self.adjust_fragments_for_collapsed_margins_if_root(); + + if self.is_root_of_absolute_flow_tree() { + // Assign block-sizes for all flows in this Absolute flow tree. + // This is preorder because the block-size of an absolute flow may depend on + // the block-size of its CB, which may also be an absolute flow. + self.traverse_preorder_absolute_flows(&mut AbsoluteAssignBSizesTraversal( + layout_context)); + // Store overflow for all absolute descendants. + self.traverse_postorder_absolute_flows(&mut AbsoluteStoreOverflowTraversal { + layout_context: layout_context, + }); + } + } + + /// Add placement information about current float flow for use by the parent. + /// + /// Also, use information given by parent about other floats to find out our relative position. + /// + /// This does not give any information about any float descendants because they do not affect + /// elements outside of the subtree rooted at this float. + /// + /// This function is called on a kid flow by a parent. Therefore, `assign_block-size_float` was + /// already called on this kid flow by the traversal function. So, the values used are + /// well-defined. + pub fn place_float(&mut self) { + let block_size = self.fragment.border_box.size.block; + let clearance = match self.fragment.clear() { + None => Au(0), + Some(clear) => self.base.floats.clearance(clear), + }; + + let margin_block_size = self.fragment.margin.block_start_end(); + let info = PlacementInfo { + size: LogicalSize::new( + self.fragment.style.writing_mode, + self.base.position.size.inline + self.fragment.margin.inline_start_end() + + self.fragment.border_padding.inline_start_end(), + block_size + margin_block_size), + ceiling: clearance + self.base.position.start.b, + max_inline_size: self.float.get_ref().containing_inline_size, + kind: self.float.get_ref().float_kind, + }; + + // Place the float and return the `Floats` back to the parent flow. + // After, grab the position and use that to set our position. + self.base.floats.add_float(&info); + + self.float.get_mut_ref().rel_pos = self.base.floats.last_float_pos().unwrap(); + } + + /// Assign block-size for current flow. + /// + /// + Set in-flow child flows' y-coordinates now that we know their + /// block-sizes. This _doesn't_ do any margin collapsing for its children. + /// + Calculate block-size and y-coordinate for the flow's box. Ideally, this + /// should be calculated using CSS Section 10.6.7 + /// + /// It does not calculate the block-size of the flow itself. + pub fn assign_block_size_float<'a>(&mut self, ctx: &'a LayoutContext<'a>) { + let _scope = layout_debug_scope!("assign_block_size_float {:s}", self.base.debug_id()); + + let mut floats = Floats::new(self.fragment.style.writing_mode); + for kid in self.base.child_iter() { + flow::mut_base(kid).floats = floats.clone(); + kid.assign_block_size_for_inorder_child_if_necessary(ctx); + floats = flow::mut_base(kid).floats.clone(); + } + + let block_start_offset = self.fragment.margin.block_start + self.fragment.border_padding.block_start; + let mut cur_b = block_start_offset; + + // cur_b is now at the block-start content edge + + for kid in self.base.child_iter() { + let child_base = flow::mut_base(kid); + child_base.position.start.b = cur_b; + // cur_b is now at the block-end margin edge of kid + cur_b = cur_b + child_base.position.size.block; + } + + // Intrinsic height should include floating descendants with a margin + // below the element's bottom edge (see CSS Section 10.6.7). + let content_block_size = geometry::max( + cur_b - block_start_offset, + floats.clearance(ClearBoth)); + + // Floats establish a block formatting context, so we discard the output floats here. + drop(floats); + + // The associated fragment has the border box of this flow. + self.fragment.border_box.start.b = self.fragment.margin.block_start; + + // Calculate content block-size, taking `min-block-size` and `max-block-size` into account. + let mut candidate_block_size_iterator = CandidateBSizeIterator::new(self.fragment.style(), None); + for candidate_block_size in candidate_block_size_iterator { + candidate_block_size_iterator.candidate_value = match candidate_block_size { + Auto => content_block_size, + Specified(value) => value, + } + } + + let content_block_size = candidate_block_size_iterator.candidate_value; + let noncontent_block_size = self.fragment.border_padding.block_start_end(); + debug!("assign_block_size_float -- block_size: {}", content_block_size + noncontent_block_size); + self.fragment.border_box.size.block = content_block_size + noncontent_block_size; + } + + fn build_display_list_block_common(&mut self, + layout_context: &LayoutContext, + offset: LogicalPoint<Au>, + background_border_level: BackgroundAndBorderLevel) { + let rel_offset = + self.fragment.relative_position(&self.base + .absolute_position_info + .relative_containing_block_size); + + // FIXME(#2795): Get the real container size + let container_size = Size2D::zero(); + + // Add the box that starts the block context. + let mut display_list = DisplayList::new(); + let mut accumulator = self.fragment.build_display_list( + &mut display_list, + layout_context, + self.base.abs_position + (offset + rel_offset).to_physical( + self.base.writing_mode, container_size), + background_border_level); + + let mut child_layers = DList::new(); + for kid in self.base.child_iter() { + if kid.is_absolutely_positioned() { + // All absolute flows will be handled by their containing block. + continue + } + + accumulator.push_child(&mut display_list, kid); + child_layers.append(mem::replace(&mut flow::mut_base(kid).layers, DList::new())) + } + + // Process absolute descendant links. + for abs_descendant_link in self.base.abs_descendants.iter() { + // TODO(pradeep): Send in our absolute position directly. + accumulator.push_child(&mut display_list, abs_descendant_link); + child_layers.append(mem::replace(&mut flow::mut_base(abs_descendant_link).layers, + DList::new())); + } + + accumulator.finish(&mut *self, display_list); + self.base.layers = child_layers + } + + /// Add display items for current block. + /// + /// Set the absolute position for children after doing any offsetting for + /// position: relative. + pub fn build_display_list_block(&mut self, layout_context: &LayoutContext) { + if self.is_float() { + // TODO(#2009, pcwalton): This is a pseudo-stacking context. We need to merge `z-index: + // auto` kids into the parent stacking context, when that is supported. + self.build_display_list_float(layout_context) + } else if self.is_absolutely_positioned() { + self.build_display_list_abs(layout_context) + } else { + let writing_mode = self.base.writing_mode; + self.build_display_list_block_common( + layout_context, LogicalPoint::zero(writing_mode), BlockLevel) + } + } + + pub fn build_display_list_float(&mut self, layout_context: &LayoutContext) { + let float_offset = self.float.get_ref().rel_pos; + self.build_display_list_block_common(layout_context, + float_offset, + RootOfStackingContextLevel); + self.base.display_list = mem::replace(&mut self.base.display_list, + DisplayList::new()).flatten(FloatStackingLevel) + } + + /// Calculate and set the block-size, offsets, etc. for absolutely positioned flow. + /// + /// The layout for its in-flow children has been done during normal layout. + /// This is just the calculation of: + /// + block-size for the flow + /// + y-coordinate of the flow wrt its Containing Block. + /// + block-size, vertical margins, and y-coordinate for the flow's box. + fn calculate_abs_block_size_and_margins(&mut self, ctx: &LayoutContext) { + let containing_block_block_size = self.containing_block_size(ctx.shared.screen_size).block; + let static_b_offset = self.static_b_offset; + + // This is the stored content block-size value from assign-block-size + let content_block_size = self.fragment.content_box().size.block; + + let mut solution = None; + { + // Non-auto margin-block-start and margin-block-end values have already been + // calculated during assign-inline-size. + let margin = self.fragment.style().logical_margin(); + let margin_block_start = match margin.block_start { + LPA_Auto => Auto, + _ => Specified(self.fragment.margin.block_start) + }; + let margin_block_end = match margin.block_end { + LPA_Auto => Auto, + _ => Specified(self.fragment.margin.block_end) + }; + + let block_start; + let block_end; + { + let position = self.fragment.style().logical_position(); + block_start = MaybeAuto::from_style(position.block_start, containing_block_block_size); + block_end = MaybeAuto::from_style(position.block_end, containing_block_block_size); + } + + let available_block_size = containing_block_block_size - self.fragment.border_padding.block_start_end(); + if self.is_replaced_content() { + // Calculate used value of block-size just like we do for inline replaced elements. + // TODO: Pass in the containing block block-size when Fragment's + // assign-block-size can handle it correctly. + self.fragment.assign_replaced_block_size_if_necessary(); + // TODO: Right now, this content block-size value includes the + // margin because of erroneous block-size calculation in fragment. + // Check this when that has been fixed. + let block_size_used_val = self.fragment.border_box.size.block; + solution = Some(BSizeConstraintSolution::solve_vertical_constraints_abs_replaced( + block_size_used_val, + margin_block_start, + margin_block_end, + block_start, + block_end, + content_block_size, + available_block_size, + static_b_offset)); + } else { + let style = self.fragment.style(); + let mut candidate_block_size_iterator = + CandidateBSizeIterator::new(style, Some(containing_block_block_size)); + + for block_size_used_val in candidate_block_size_iterator { + solution = + Some(BSizeConstraintSolution::solve_vertical_constraints_abs_nonreplaced( + block_size_used_val, + margin_block_start, + margin_block_end, + block_start, + block_end, + content_block_size, + available_block_size, + static_b_offset)); + + candidate_block_size_iterator.candidate_value = solution.unwrap().block_size + } + } + } + + let solution = solution.unwrap(); + self.fragment.margin.block_start = solution.margin_block_start; + self.fragment.margin.block_end = solution.margin_block_end; + self.fragment.border_box.start.b = Au(0); + self.fragment.border_box.size.block = solution.block_size + self.fragment.border_padding.block_start_end(); + + self.base.position.start.b = solution.block_start + self.fragment.margin.block_start; + self.base.position.size.block = solution.block_size + self.fragment.border_padding.block_start_end(); + } + + /// Add display items for Absolutely Positioned flow. + fn build_display_list_abs(&mut self, layout_context: &LayoutContext) { + let writing_mode = self.base.writing_mode; + self.build_display_list_block_common(layout_context, + LogicalPoint::zero(writing_mode), + RootOfStackingContextLevel); + + if !self.base.absolute_position_info.layers_needed_for_positioned_flows && + !self.base.flags.needs_layer() { + // We didn't need a layer. + // + // TODO(#781, pcwalton): `z-index`. + self.base.display_list = + mem::replace(&mut self.base.display_list, + DisplayList::new()).flatten(PositionedDescendantStackingLevel(0)); + return + } + + // If we got here, then we need a new layer. + let layer_rect = self.base.position.union(&self.base.overflow); + let size = Size2D(layer_rect.size.inline.to_nearest_px() as uint, + layer_rect.size.block.to_nearest_px() as uint); + let origin = Point2D(layer_rect.start.i.to_nearest_px() as uint, + layer_rect.start.b.to_nearest_px() as uint); + let scroll_policy = if self.is_fixed() { + FixedPosition + } else { + Scrollable + }; + let display_list = mem::replace(&mut self.base.display_list, DisplayList::new()); + let new_layer = RenderLayer { + id: self.layer_id(0), + display_list: Arc::new(display_list.flatten(ContentStackingLevel)), + position: Rect(origin, size), + background_color: color::rgba(1.0, 1.0, 1.0, 0.0), + scroll_policy: scroll_policy, + }; + self.base.layers.push(new_layer) + } + + /// Return the block-start outer edge of the hypothetical box for an absolute flow. + /// + /// This is wrt its parent flow box. + /// + /// During normal layout assign-block-size, the absolute flow's position is + /// roughly set to its static position (the position it would have had in + /// the normal flow). + fn get_hypothetical_block_start_edge(&self) -> Au { + self.base.position.start.b + } + + /// Assigns the computed inline-start content edge and inline-size to all the children of this block flow. + /// Also computes whether each child will be impacted by floats. + /// + /// `#[inline(always)]` because this is called only from block or table inline-size assignment and + /// the code for block layout is significantly simpler. + #[inline(always)] + pub fn propagate_assigned_inline_size_to_children(&mut self, + inline_start_content_edge: Au, + content_inline_size: Au, + opt_col_inline_sizes: Option<Vec<Au>>) { + // Keep track of whether floats could impact each child. + let mut inline_start_floats_impact_child = self.base.flags.impacted_by_left_floats(); + let mut inline_end_floats_impact_child = self.base.flags.impacted_by_right_floats(); + + let absolute_static_i_offset = if self.is_positioned() { + // This flow is the containing block. The static X offset will be the inline-start padding + // edge. + self.fragment.border_padding.inline_start + - self.fragment.style().logical_border_width().inline_start + } else { + // For kids, the inline-start margin edge will be at our inline-start content edge. The current static + // offset is at our inline-start margin edge. So move in to the inline-start content edge. + self.base.absolute_static_i_offset + inline_start_content_edge + }; + + let fixed_static_i_offset = self.base.fixed_static_i_offset + inline_start_content_edge; + let flags = self.base.flags.clone(); + + // This value is used only for table cells. + let mut inline_start_margin_edge = inline_start_content_edge; + + // The inline-size of the last float, if there was one. This is used for estimating the inline-sizes of + // block formatting contexts. (We estimate that the inline-size of any block formatting context + // that we see will be based on the inline-size of the containing block as well as the last float + // seen before it.) + let mut last_float_inline_size = None; + + for (i, kid) in self.base.child_iter().enumerate() { + if kid.is_block_flow() { + let kid_block = kid.as_block(); + kid_block.base.absolute_static_i_offset = absolute_static_i_offset; + kid_block.base.fixed_static_i_offset = fixed_static_i_offset; + + if kid_block.is_float() { + last_float_inline_size = Some(kid_block.base.intrinsic_inline_sizes.preferred_inline_size) + } else { + kid_block.previous_float_inline_size = last_float_inline_size + } + } + + // The inline-start margin edge of the child flow is at our inline-start content edge, and its inline-size + // is our content inline-size. + flow::mut_base(kid).position.start.i = inline_start_content_edge; + flow::mut_base(kid).position.size.inline = content_inline_size; + + // Determine float impaction. + match kid.float_clearance() { + clear::none => {} + clear::left => inline_start_floats_impact_child = false, + clear::right => inline_end_floats_impact_child = false, + clear::both => { + inline_start_floats_impact_child = false; + inline_end_floats_impact_child = false; + } + } + { + let kid_base = flow::mut_base(kid); + inline_start_floats_impact_child = inline_start_floats_impact_child || + kid_base.flags.has_left_floated_descendants(); + inline_end_floats_impact_child = inline_end_floats_impact_child || + kid_base.flags.has_right_floated_descendants(); + kid_base.flags.set_impacted_by_left_floats(inline_start_floats_impact_child); + kid_base.flags.set_impacted_by_right_floats(inline_end_floats_impact_child); + } + + // Handle tables. + match opt_col_inline_sizes { + Some(ref col_inline_sizes) => { + propagate_column_inline_sizes_to_child(kid, + i, + content_inline_size, + col_inline_sizes.as_slice(), + &mut inline_start_margin_edge) + } + None => {} + } + + // Per CSS 2.1 § 16.3.1, text alignment propagates to all children in flow. + // + // TODO(#2018, pcwalton): Do this in the cascade instead. + flow::mut_base(kid).flags.propagate_text_alignment_from_parent(flags.clone()) + } + } + + /// Determines the type of formatting context this is. See the definition of + /// `FormattingContextType`. + fn formatting_context_type(&self) -> FormattingContextType { + let style = self.fragment.style(); + if style.get_box().float != float::none { + return OtherFormattingContext + } + match style.get_box().display { + display::table_cell | display::table_caption | display::inline_block => { + OtherFormattingContext + } + _ if style.get_box().position == position::static_ && + style.get_box().overflow != overflow::visible => { + BlockFormattingContext + } + _ => NonformattingContext, + } + } +} + +impl Flow for BlockFlow { + fn class(&self) -> FlowClass { + BlockFlowClass + } + + fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow { + self + } + + fn as_immutable_block<'a>(&'a self) -> &'a BlockFlow { + self + } + + /// Returns the direction that this flow clears floats in, if any. + fn float_clearance(&self) -> clear::T { + self.fragment.style().get_box().clear + } + + /// 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. + /// + /// TODO(pcwalton): Inline blocks. + fn bubble_inline_sizes(&mut self, _: &LayoutContext) { + let _scope = layout_debug_scope!("bubble_inline_sizes {:s}", self.base.debug_id()); + + let mut flags = self.base.flags; + flags.set_has_left_floated_descendants(false); + flags.set_has_right_floated_descendants(false); + + // If this block has a fixed width, just use that for the minimum + // and preferred width, rather than bubbling up children inline + // width. + let fixed_width = match self.fragment.style().get_box().width { + LPA_Length(_) => true, + _ => false, + }; + + // Find the maximum inline-size from children. + let mut intrinsic_inline_sizes = IntrinsicISizes::new(); + for child_ctx in self.base.child_iter() { + assert!(child_ctx.is_block_flow() || + child_ctx.is_inline_flow() || + child_ctx.is_table_kind()); + + let child_base = flow::mut_base(child_ctx); + + if !fixed_width { + intrinsic_inline_sizes.minimum_inline_size = + geometry::max(intrinsic_inline_sizes.minimum_inline_size, + child_base.intrinsic_inline_sizes.total_minimum_inline_size()); + intrinsic_inline_sizes.preferred_inline_size = + geometry::max(intrinsic_inline_sizes.preferred_inline_size, + child_base.intrinsic_inline_sizes.total_preferred_inline_size()); + } + + flags.union_floated_descendants_flags(child_base.flags); + } + + let fragment_intrinsic_inline_sizes = self.fragment.intrinsic_inline_sizes(); + intrinsic_inline_sizes.minimum_inline_size = geometry::max(intrinsic_inline_sizes.minimum_inline_size, + fragment_intrinsic_inline_sizes.minimum_inline_size); + intrinsic_inline_sizes.preferred_inline_size = geometry::max(intrinsic_inline_sizes.preferred_inline_size, + fragment_intrinsic_inline_sizes.preferred_inline_size); + intrinsic_inline_sizes.surround_inline_size = fragment_intrinsic_inline_sizes.surround_inline_size; + self.base.intrinsic_inline_sizes = intrinsic_inline_sizes; + + match self.fragment.style().get_box().float { + float::none => {} + float::left => flags.set_has_left_floated_descendants(true), + float::right => flags.set_has_right_floated_descendants(true), + } + self.base.flags = flags + } + + /// 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 {:s}", self.base.debug_id()); + + debug!("assign_inline_sizes({}): assigning inline_size for flow", + if self.is_float() { + "float" + } else { + "block" + }); + + if self.is_root() { + debug!("Setting root position"); + self.base.position.start = LogicalPoint::zero(self.base.writing_mode); + self.base.position.size.inline = LogicalSize::from_physical( + self.base.writing_mode, layout_context.shared.screen_size).inline; + self.base.floats = Floats::new(self.base.writing_mode); + + // The root element is never impacted by floats. + self.base.flags.set_impacted_by_left_floats(false); + self.base.flags.set_impacted_by_right_floats(false); + } + + // Our inline-size was set to the inline-size of the containing block by the flow's parent. Now compute + // the real value. + let containing_block_inline_size = self.base.position.size.inline; + self.compute_used_inline_size(layout_context, containing_block_inline_size); + if self.is_float() { + self.float.get_mut_ref().containing_inline_size = containing_block_inline_size; + } + + // Formatting contexts are never impacted by floats. + match self.formatting_context_type() { + NonformattingContext => {} + BlockFormattingContext => { + self.base.flags.set_impacted_by_left_floats(false); + self.base.flags.set_impacted_by_right_floats(false); + + // We can't actually compute the inline-size of this block now, because floats might + // affect it. Speculate that its inline-size is equal to the inline-size computed above minus + // the inline-size of the previous float. + match self.previous_float_inline_size { + None => {} + Some(previous_float_inline_size) => { + self.fragment.border_box.size.inline = + self.fragment.border_box.size.inline - previous_float_inline_size + } + } + } + OtherFormattingContext => { + self.base.flags.set_impacted_by_left_floats(false); + self.base.flags.set_impacted_by_right_floats(false); + } + } + + // 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; + let padding_and_borders = self.fragment.border_padding.inline_start_end(); + let content_inline_size = self.fragment.border_box.size.inline - padding_and_borders; + + if self.is_float() { + self.base.position.size.inline = content_inline_size; + } + + self.propagate_assigned_inline_size_to_children(inline_start_content_edge, content_inline_size, None); + } + + /// Assigns block-sizes in-order; or, if this is a float, places the float. The default + /// implementation simply assigns block-sizes if this flow is impacted by floats. Returns true if + /// this child was impacted by floats or false otherwise. + /// + /// This is called on child flows by the parent. Hence, we can assume that `assign_block-size` has + /// already been called on the child (because of the bottom-up traversal). + fn assign_block_size_for_inorder_child_if_necessary<'a>(&mut self, layout_context: &'a LayoutContext<'a>) + -> bool { + if self.is_float() { + self.place_float(); + return true + } + + let impacted = self.base.flags.impacted_by_floats(); + if impacted { + self.assign_block_size(layout_context); + } + impacted + } + + fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) { + + if self.is_replaced_content() { + // Assign block-size for fragment if it is an image fragment. + self.fragment.assign_replaced_block_size_if_necessary(); + } else if self.is_float() { + debug!("assign_block_size_float: assigning block_size for float"); + self.assign_block_size_float(ctx); + } else if self.is_root() { + // Root element margins should never be collapsed according to CSS § 8.3.1. + debug!("assign_block_size: assigning block_size for root flow"); + self.assign_block_size_block_base(ctx, MarginsMayNotCollapse); + } else { + debug!("assign_block_size: assigning block_size for block"); + self.assign_block_size_block_base(ctx, MarginsMayCollapse); + } + } + + fn compute_absolute_position(&mut self) { + // FIXME(#2795): Get the real container size + let container_size = Size2D::zero(); + + if self.is_absolutely_positioned() { + let position_start = self.base.position.start.to_physical( + self.base.writing_mode, container_size); + self.base + .absolute_position_info + .absolute_containing_block_position = if self.is_fixed() { + // The viewport is initially at (0, 0). + position_start + } else { + // Absolute position of the containing block + position of absolute flow w/r/t the + // containing block. + self.base.absolute_position_info.absolute_containing_block_position + + position_start + }; + + // Set the absolute position, which will be passed down later as part + // of containing block details for absolute descendants. + self.base.abs_position = + self.base.absolute_position_info.absolute_containing_block_position; + } + + // 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, is only established if we are positioned. + let relative_offset = + self.fragment.relative_position(&self.base + .absolute_position_info + .relative_containing_block_size); + if self.is_positioned() { + self.base.absolute_position_info.absolute_containing_block_position = + self.base.abs_position + + (self.generated_containing_block_rect().start + + relative_offset).to_physical(self.base.writing_mode, container_size) + } + + let float_offset = if self.is_float() { + self.float.get_ref().rel_pos + } else { + LogicalPoint::zero(self.base.writing_mode) + }; + + // Compute absolute position info for children. + let mut absolute_position_info = self.base.absolute_position_info; + absolute_position_info.relative_containing_block_size = self.fragment.content_box().size; + absolute_position_info.layers_needed_for_positioned_flows = + self.base.flags.layers_needed_for_descendants(); + + // Process children. + let this_position = self.base.abs_position; + let writing_mode = self.base.writing_mode; + for kid in self.base.child_iter() { + if !kid.is_absolutely_positioned() { + let kid_base = flow::mut_base(kid); + kid_base.abs_position = this_position + ( + kid_base.position.start + .add_point(&float_offset) + + relative_offset).to_physical(writing_mode, container_size); + kid_base.absolute_position_info = absolute_position_info + } + } + + // Process absolute descendant links. + for absolute_descendant in self.base.abs_descendants.iter() { + flow::mut_base(absolute_descendant).absolute_position_info = absolute_position_info + } + } + + fn mark_as_root(&mut self) { + self.is_root = true + } + + /// Return true if store overflow is delayed for this flow. + /// + /// Currently happens only for absolutely positioned flows. + fn is_store_overflow_delayed(&mut self) -> bool { + self.is_absolutely_positioned() + } + + fn is_root(&self) -> bool { + self.is_root + } + + fn is_float(&self) -> bool { + self.float.is_some() + } + + /// The 'position' property of this flow. + fn positioning(&self) -> position::T { + self.fragment.style.get_box().position + } + + /// Return true if this is the root of an Absolute flow tree. + /// + /// It has to be either relatively positioned or the Root flow. + fn is_root_of_absolute_flow_tree(&self) -> bool { + self.is_relatively_positioned() || self.is_root() + } + + /// Return the dimensions of the containing block generated by this flow for absolutely- + /// positioned descendants. For block flows, this is the padding box. + fn generated_containing_block_rect(&self) -> LogicalRect<Au> { + self.fragment.border_box - self.fragment.style().logical_border_width() + } + + fn layer_id(&self, fragment_index: uint) -> LayerId { + // FIXME(#2010, pcwalton): This is a hack and is totally bogus in the presence of pseudo- + // elements. But until we have incremental reflow we can't do better--we recreate the flow + // for every DOM node so otherwise we nuke layers on every reflow. + LayerId(self.fragment.node.id(), fragment_index) + } + + fn is_absolute_containing_block(&self) -> bool { + self.is_positioned() + } +} + +impl fmt::Show for BlockFlow { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.is_float() { + write!(f, "FloatFlow: {}", self.fragment) + } else if self.is_root() { + write!(f, "RootFlow: {}", self.fragment) + } else { + write!(f, "BlockFlow: {}", self.fragment) + } + } +} + +/// The inputs for the inline-sizes-and-margins constraint equation. +pub struct ISizeConstraintInput { + pub computed_inline_size: MaybeAuto, + pub inline_start_margin: MaybeAuto, + pub inline_end_margin: MaybeAuto, + pub inline_start: MaybeAuto, + pub inline_end: MaybeAuto, + pub available_inline_size: Au, + pub static_i_offset: Au, +} + +impl ISizeConstraintInput { + pub fn new(computed_inline_size: MaybeAuto, + inline_start_margin: MaybeAuto, + inline_end_margin: MaybeAuto, + inline_start: MaybeAuto, + inline_end: MaybeAuto, + available_inline_size: Au, + static_i_offset: Au) + -> ISizeConstraintInput { + ISizeConstraintInput { + computed_inline_size: computed_inline_size, + inline_start_margin: inline_start_margin, + inline_end_margin: inline_end_margin, + inline_start: inline_start, + inline_end: inline_end, + available_inline_size: available_inline_size, + static_i_offset: static_i_offset, + } + } +} + +/// The solutions for the inline-size-and-margins constraint equation. +pub struct ISizeConstraintSolution { + pub inline_start: Au, + pub inline_end: Au, + pub inline_size: Au, + pub margin_inline_start: Au, + pub margin_inline_end: Au +} + +impl ISizeConstraintSolution { + pub fn new(inline_size: Au, margin_inline_start: Au, margin_inline_end: Au) -> ISizeConstraintSolution { + ISizeConstraintSolution { + inline_start: Au(0), + inline_end: Au(0), + inline_size: inline_size, + margin_inline_start: margin_inline_start, + margin_inline_end: margin_inline_end, + } + } + + fn for_absolute_flow(inline_start: Au, + inline_end: Au, + inline_size: Au, + margin_inline_start: Au, + margin_inline_end: Au) + -> ISizeConstraintSolution { + ISizeConstraintSolution { + inline_start: inline_start, + inline_end: inline_end, + inline_size: inline_size, + margin_inline_start: margin_inline_start, + margin_inline_end: margin_inline_end, + } + } +} + +// Trait to encapsulate the ISize and Margin calculation. +// +// CSS Section 10.3 +pub trait ISizeAndMarginsComputer { + /// Compute the inputs for the ISize constraint equation. + /// + /// This is called only once to compute the initial inputs. For + /// calculation involving min-inline-size and max-inline-size, we don't need to + /// recompute these. + fn compute_inline_size_constraint_inputs(&self, + block: &mut BlockFlow, + parent_flow_inline_size: Au, + ctx: &LayoutContext) + -> ISizeConstraintInput { + let containing_block_inline_size = self.containing_block_inline_size(block, parent_flow_inline_size, ctx); + let computed_inline_size = self.initial_computed_inline_size(block, parent_flow_inline_size, ctx); + + block.fragment.compute_border_padding_margins(containing_block_inline_size); + + let style = block.fragment.style(); + + // The text alignment of a block flow is the text alignment of its box's style. + block.base.flags.set_text_align(style.get_inheritedtext().text_align); + + let margin = style.logical_margin(); + let position = style.logical_position(); + + let available_inline_size = containing_block_inline_size - block.fragment.border_padding.inline_start_end(); + return ISizeConstraintInput::new( + computed_inline_size, + MaybeAuto::from_style(margin.inline_start, containing_block_inline_size), + MaybeAuto::from_style(margin.inline_end, containing_block_inline_size), + MaybeAuto::from_style(position.inline_start, containing_block_inline_size), + MaybeAuto::from_style(position.inline_end, containing_block_inline_size), + available_inline_size, + block.static_i_offset()); + } + + /// Set the used values for inline-size and margins got from the relevant constraint equation. + /// + /// This is called only once. + /// + /// Set: + /// + used values for content inline-size, inline-start margin, and inline-end margin for this flow's box. + /// + x-coordinate of this flow's box. + /// + x-coordinate of the flow wrt its Containing Block (if this is an absolute flow). + fn set_inline_size_constraint_solutions(&self, + block: &mut BlockFlow, + solution: ISizeConstraintSolution) { + let inline_size; + { + let fragment = block.fragment(); + fragment.margin.inline_start = solution.margin_inline_start; + fragment.margin.inline_end = solution.margin_inline_end; + + // The associated fragment has the border box of this flow. + // Left border edge. + fragment.border_box.start.i = fragment.margin.inline_start; + // Border box inline-size. + inline_size = solution.inline_size + fragment.border_padding.inline_start_end(); + fragment.border_box.size.inline = inline_size; + } + + // We also resize the block itself, to ensure that overflow is not calculated + // as the inline-size of our parent. We might be smaller and we might be larger if we + // overflow. + let flow = flow::mut_base(block); + flow.position.size.inline = inline_size; + } + + /// Set the x coordinate of the given flow if it is absolutely positioned. + fn set_flow_x_coord_if_necessary(&self, _: &mut BlockFlow, _: ISizeConstraintSolution) {} + + /// Solve the inline-size and margins constraints for this block flow. + fn solve_inline_size_constraints(&self, + block: &mut BlockFlow, + input: &ISizeConstraintInput) + -> ISizeConstraintSolution; + + fn initial_computed_inline_size(&self, + block: &mut BlockFlow, + parent_flow_inline_size: Au, + ctx: &LayoutContext) + -> MaybeAuto { + MaybeAuto::from_style(block.fragment().style().content_inline_size(), + self.containing_block_inline_size(block, parent_flow_inline_size, ctx)) + } + + fn containing_block_inline_size(&self, + _: &mut BlockFlow, + parent_flow_inline_size: Au, + _: &LayoutContext) + -> Au { + parent_flow_inline_size + } + + /// Compute the used value of inline-size, taking care of min-inline-size and max-inline-size. + /// + /// CSS Section 10.4: Minimum and Maximum inline-sizes + fn compute_used_inline_size(&self, + block: &mut BlockFlow, + ctx: &LayoutContext, + parent_flow_inline_size: Au) { + let mut input = self.compute_inline_size_constraint_inputs(block, parent_flow_inline_size, ctx); + + let containing_block_inline_size = self.containing_block_inline_size(block, parent_flow_inline_size, ctx); + + let mut solution = self.solve_inline_size_constraints(block, &input); + + // If the tentative used inline-size is greater than 'max-inline-size', inline-size should be recalculated, + // but this time using the computed value of 'max-inline-size' as the computed value for 'inline-size'. + match specified_or_none(block.fragment().style().max_inline_size(), containing_block_inline_size) { + Some(max_inline_size) if max_inline_size < solution.inline_size => { + input.computed_inline_size = Specified(max_inline_size); + solution = self.solve_inline_size_constraints(block, &input); + } + _ => {} + } + + // If the resulting inline-size is smaller than 'min-inline-size', inline-size should be recalculated, + // but this time using the value of 'min-inline-size' as the computed value for 'inline-size'. + let computed_min_inline_size = specified(block.fragment().style().min_inline_size(), + containing_block_inline_size); + if computed_min_inline_size > solution.inline_size { + input.computed_inline_size = Specified(computed_min_inline_size); + solution = self.solve_inline_size_constraints(block, &input); + } + + self.set_inline_size_constraint_solutions(block, solution); + self.set_flow_x_coord_if_necessary(block, solution); + } + + /// Computes inline-start and inline-end margins and inline-size. + /// + /// This is used by both replaced and non-replaced Blocks. + /// + /// CSS 2.1 Section 10.3.3. + /// Constraint Equation: margin-inline-start + margin-inline-end + inline-size = available_inline-size + /// where available_inline-size = CB inline-size - (horizontal border + padding) + fn solve_block_inline_size_constraints(&self, + _: &mut BlockFlow, + input: &ISizeConstraintInput) + -> ISizeConstraintSolution { + let (computed_inline_size, inline_start_margin, inline_end_margin, available_inline_size) = (input.computed_inline_size, + input.inline_start_margin, + input.inline_end_margin, + input.available_inline_size); + + // If inline-size is not 'auto', and inline-size + margins > available_inline-size, all + // 'auto' margins are treated as 0. + let (inline_start_margin, inline_end_margin) = match computed_inline_size { + Auto => (inline_start_margin, inline_end_margin), + Specified(inline_size) => { + let inline_start = inline_start_margin.specified_or_zero(); + let inline_end = inline_end_margin.specified_or_zero(); + + if (inline_start + inline_end + inline_size) > available_inline_size { + (Specified(inline_start), Specified(inline_end)) + } else { + (inline_start_margin, inline_end_margin) + } + } + }; + + // Invariant: inline-start_margin + inline-size + inline-end_margin == available_inline-size + let (inline_start_margin, inline_size, inline_end_margin) = match (inline_start_margin, computed_inline_size, inline_end_margin) { + // If all have a computed value other than 'auto', the system is + // over-constrained so we discard the end margin. + (Specified(margin_start), Specified(inline_size), Specified(_margin_end)) => + (margin_start, inline_size, available_inline_size - (margin_start + inline_size)), + + // If exactly one value is 'auto', solve for it + (Auto, Specified(inline_size), Specified(margin_end)) => + (available_inline_size - (inline_size + margin_end), inline_size, margin_end), + (Specified(margin_start), Auto, Specified(margin_end)) => + (margin_start, available_inline_size - (margin_start + margin_end), margin_end), + (Specified(margin_start), Specified(inline_size), Auto) => + (margin_start, inline_size, available_inline_size - (margin_start + inline_size)), + + // If inline-size is set to 'auto', any other 'auto' value becomes '0', + // and inline-size is solved for + (Auto, Auto, Specified(margin_end)) => + (Au::new(0), available_inline_size - margin_end, margin_end), + (Specified(margin_start), Auto, Auto) => + (margin_start, available_inline_size - margin_start, Au::new(0)), + (Auto, Auto, Auto) => + (Au::new(0), available_inline_size, Au::new(0)), + + // If inline-start and inline-end margins are auto, they become equal + (Auto, Specified(inline_size), Auto) => { + let margin = (available_inline_size - inline_size).scale_by(0.5); + (margin, inline_size, margin) + } + }; + ISizeConstraintSolution::new(inline_size, inline_start_margin, inline_end_margin) + } +} + +/// The different types of Blocks. +/// +/// They mainly differ in the way inline-size and block-sizes and margins are calculated +/// for them. +struct AbsoluteNonReplaced; +struct AbsoluteReplaced; +struct BlockNonReplaced; +struct BlockReplaced; +struct FloatNonReplaced; +struct FloatReplaced; + +impl ISizeAndMarginsComputer for AbsoluteNonReplaced { + /// Solve the horizontal constraint equation for absolute non-replaced elements. + /// + /// CSS Section 10.3.7 + /// Constraint equation: + /// inline-start + inline-end + inline-size + margin-inline-start + margin-inline-end + /// = absolute containing block inline-size - (horizontal padding and border) + /// [aka available_inline-size] + /// + /// Return the solution for the equation. + fn solve_inline_size_constraints(&self, + block: &mut BlockFlow, + input: &ISizeConstraintInput) + -> ISizeConstraintSolution { + let &ISizeConstraintInput { + computed_inline_size, + inline_start_margin, + inline_end_margin, + inline_start, + inline_end, + available_inline_size, + static_i_offset, + .. + } = input; + + // TODO: Check for direction of parent flow (NOT Containing Block) + // when right-to-left is implemented. + // Assume direction is 'ltr' for now + + // Distance from the inline-start edge of the Absolute Containing Block to the + // inline-start margin edge of a hypothetical box that would have been the + // first box of the element. + let static_position_inline_start = static_i_offset; + + let (inline_start, inline_end, inline_size, margin_inline_start, margin_inline_end) = match (inline_start, inline_end, computed_inline_size) { + (Auto, Auto, Auto) => { + let margin_start = inline_start_margin.specified_or_zero(); + let margin_end = inline_end_margin.specified_or_zero(); + let inline_start = static_position_inline_start; + // Now it is the same situation as inline-start Specified and inline-end + // and inline-size Auto. + + // Set inline-end to zero to calculate inline-size + let inline_size = block.get_shrink_to_fit_inline_size( + available_inline_size - (inline_start + margin_start + margin_end)); + let sum = inline_start + inline_size + margin_start + margin_end; + (inline_start, available_inline_size - sum, inline_size, margin_start, margin_end) + } + (Specified(inline_start), Specified(inline_end), Specified(inline_size)) => { + match (inline_start_margin, inline_end_margin) { + (Auto, Auto) => { + let total_margin_val = available_inline_size - inline_start - inline_end - inline_size; + if total_margin_val < Au(0) { + // margin-inline-start becomes 0 because direction is 'ltr'. + // TODO: Handle 'rtl' when it is implemented. + (inline_start, inline_end, inline_size, Au(0), total_margin_val) + } else { + // Equal margins + (inline_start, inline_end, inline_size, + total_margin_val.scale_by(0.5), + total_margin_val.scale_by(0.5)) + } + } + (Specified(margin_start), Auto) => { + let sum = inline_start + inline_end + inline_size + margin_start; + (inline_start, inline_end, inline_size, margin_start, available_inline_size - sum) + } + (Auto, Specified(margin_end)) => { + let sum = inline_start + inline_end + inline_size + margin_end; + (inline_start, inline_end, inline_size, available_inline_size - sum, margin_end) + } + (Specified(margin_start), Specified(margin_end)) => { + // Values are over-constrained. + // Ignore value for 'inline-end' cos direction is 'ltr'. + // TODO: Handle 'rtl' when it is implemented. + let sum = inline_start + inline_size + margin_start + margin_end; + (inline_start, available_inline_size - sum, inline_size, margin_start, margin_end) + } + } + } + // For the rest of the cases, auto values for margin are set to 0 + + // If only one is Auto, solve for it + (Auto, Specified(inline_end), Specified(inline_size)) => { + let margin_start = inline_start_margin.specified_or_zero(); + let margin_end = inline_end_margin.specified_or_zero(); + let sum = inline_end + inline_size + margin_start + margin_end; + (available_inline_size - sum, inline_end, inline_size, margin_start, margin_end) + } + (Specified(inline_start), Auto, Specified(inline_size)) => { + let margin_start = inline_start_margin.specified_or_zero(); + let margin_end = inline_end_margin.specified_or_zero(); + let sum = inline_start + inline_size + margin_start + margin_end; + (inline_start, available_inline_size - sum, inline_size, margin_start, margin_end) + } + (Specified(inline_start), Specified(inline_end), Auto) => { + let margin_start = inline_start_margin.specified_or_zero(); + let margin_end = inline_end_margin.specified_or_zero(); + let sum = inline_start + inline_end + margin_start + margin_end; + (inline_start, inline_end, available_inline_size - sum, margin_start, margin_end) + } + + // If inline-size is auto, then inline-size is shrink-to-fit. Solve for the + // non-auto value. + (Specified(inline_start), Auto, Auto) => { + let margin_start = inline_start_margin.specified_or_zero(); + let margin_end = inline_end_margin.specified_or_zero(); + // Set inline-end to zero to calculate inline-size + let inline_size = block.get_shrink_to_fit_inline_size( + available_inline_size - (inline_start + margin_start + margin_end)); + let sum = inline_start + inline_size + margin_start + margin_end; + (inline_start, available_inline_size - sum, inline_size, margin_start, margin_end) + } + (Auto, Specified(inline_end), Auto) => { + let margin_start = inline_start_margin.specified_or_zero(); + let margin_end = inline_end_margin.specified_or_zero(); + // Set inline-start to zero to calculate inline-size + let inline_size = block.get_shrink_to_fit_inline_size( + available_inline_size - (inline_end + margin_start + margin_end)); + let sum = inline_end + inline_size + margin_start + margin_end; + (available_inline_size - sum, inline_end, inline_size, margin_start, margin_end) + } + + (Auto, Auto, Specified(inline_size)) => { + let margin_start = inline_start_margin.specified_or_zero(); + let margin_end = inline_end_margin.specified_or_zero(); + // Setting 'inline-start' to static position because direction is 'ltr'. + // TODO: Handle 'rtl' when it is implemented. + let inline_start = static_position_inline_start; + let sum = inline_start + inline_size + margin_start + margin_end; + (inline_start, available_inline_size - sum, inline_size, margin_start, margin_end) + } + }; + ISizeConstraintSolution::for_absolute_flow(inline_start, inline_end, inline_size, margin_inline_start, margin_inline_end) + } + + fn containing_block_inline_size(&self, block: &mut BlockFlow, _: Au, ctx: &LayoutContext) -> Au { + block.containing_block_size(ctx.shared.screen_size).inline + } + + fn set_flow_x_coord_if_necessary(&self, + block: &mut BlockFlow, + solution: ISizeConstraintSolution) { + // Set the x-coordinate of the absolute flow wrt to its containing block. + block.base.position.start.i = solution.inline_start; + } +} + +impl ISizeAndMarginsComputer for AbsoluteReplaced { + /// Solve the horizontal constraint equation for absolute replaced elements. + /// + /// `static_i_offset`: total offset of current flow's hypothetical + /// position (static position) from its actual Containing Block. + /// + /// CSS Section 10.3.8 + /// Constraint equation: + /// inline-start + inline-end + inline-size + margin-inline-start + margin-inline-end + /// = absolute containing block inline-size - (horizontal padding and border) + /// [aka available_inline-size] + /// + /// Return the solution for the equation. + fn solve_inline_size_constraints(&self, _: &mut BlockFlow, input: &ISizeConstraintInput) + -> ISizeConstraintSolution { + let &ISizeConstraintInput { + computed_inline_size, + inline_start_margin, + inline_end_margin, + inline_start, + inline_end, + available_inline_size, + static_i_offset, + .. + } = input; + // TODO: Check for direction of static-position Containing Block (aka + // parent flow, _not_ the actual Containing Block) when right-to-left + // is implemented + // Assume direction is 'ltr' for now + // TODO: Handle all the cases for 'rtl' direction. + + let inline_size = match computed_inline_size { + Specified(w) => w, + _ => fail!("{} {}", + "The used value for inline_size for absolute replaced flow", + "should have already been calculated by now.") + }; + + // Distance from the inline-start edge of the Absolute Containing Block to the + // inline-start margin edge of a hypothetical box that would have been the + // first box of the element. + let static_position_inline_start = static_i_offset; + + let (inline_start, inline_end, inline_size, margin_inline_start, margin_inline_end) = match (inline_start, inline_end) { + (Auto, Auto) => { + let inline_start = static_position_inline_start; + let margin_start = inline_start_margin.specified_or_zero(); + let margin_end = inline_end_margin.specified_or_zero(); + let sum = inline_start + inline_size + margin_start + margin_end; + (inline_start, available_inline_size - sum, inline_size, margin_start, margin_end) + } + // If only one is Auto, solve for it + (Auto, Specified(inline_end)) => { + let margin_start = inline_start_margin.specified_or_zero(); + let margin_end = inline_end_margin.specified_or_zero(); + let sum = inline_end + inline_size + margin_start + margin_end; + (available_inline_size - sum, inline_end, inline_size, margin_start, margin_end) + } + (Specified(inline_start), Auto) => { + let margin_start = inline_start_margin.specified_or_zero(); + let margin_end = inline_end_margin.specified_or_zero(); + let sum = inline_start + inline_size + margin_start + margin_end; + (inline_start, available_inline_size - sum, inline_size, margin_start, margin_end) + } + (Specified(inline_start), Specified(inline_end)) => { + match (inline_start_margin, inline_end_margin) { + (Auto, Auto) => { + let total_margin_val = available_inline_size - inline_start - inline_end - inline_size; + if total_margin_val < Au(0) { + // margin-inline-start becomes 0 because direction is 'ltr'. + (inline_start, inline_end, inline_size, Au(0), total_margin_val) + } else { + // Equal margins + (inline_start, inline_end, inline_size, + total_margin_val.scale_by(0.5), + total_margin_val.scale_by(0.5)) + } + } + (Specified(margin_start), Auto) => { + let sum = inline_start + inline_end + inline_size + margin_start; + (inline_start, inline_end, inline_size, margin_start, available_inline_size - sum) + } + (Auto, Specified(margin_end)) => { + let sum = inline_start + inline_end + inline_size + margin_end; + (inline_start, inline_end, inline_size, available_inline_size - sum, margin_end) + } + (Specified(margin_start), Specified(margin_end)) => { + // Values are over-constrained. + // Ignore value for 'inline-end' cos direction is 'ltr'. + let sum = inline_start + inline_size + margin_start + margin_end; + (inline_start, available_inline_size - sum, inline_size, margin_start, margin_end) + } + } + } + }; + ISizeConstraintSolution::for_absolute_flow(inline_start, inline_end, inline_size, margin_inline_start, margin_inline_end) + } + + /// Calculate used value of inline-size just like we do for inline replaced elements. + fn initial_computed_inline_size(&self, + block: &mut BlockFlow, + _: Au, + ctx: &LayoutContext) + -> MaybeAuto { + let containing_block_inline_size = block.containing_block_size(ctx.shared.screen_size).inline; + let fragment = block.fragment(); + fragment.assign_replaced_inline_size_if_necessary(containing_block_inline_size); + // For replaced absolute flow, the rest of the constraint solving will + // take inline-size to be specified as the value computed here. + Specified(fragment.content_inline_size()) + } + + fn containing_block_inline_size(&self, block: &mut BlockFlow, _: Au, ctx: &LayoutContext) -> Au { + block.containing_block_size(ctx.shared.screen_size).inline + } + + fn set_flow_x_coord_if_necessary(&self, block: &mut BlockFlow, solution: ISizeConstraintSolution) { + // Set the x-coordinate of the absolute flow wrt to its containing block. + block.base.position.start.i = solution.inline_start; + } +} + +impl ISizeAndMarginsComputer for BlockNonReplaced { + /// Compute inline-start and inline-end margins and inline-size. + fn solve_inline_size_constraints(&self, + block: &mut BlockFlow, + input: &ISizeConstraintInput) + -> ISizeConstraintSolution { + self.solve_block_inline_size_constraints(block, input) + } +} + +impl ISizeAndMarginsComputer for BlockReplaced { + /// Compute inline-start and inline-end margins and inline-size. + /// + /// ISize has already been calculated. We now calculate the margins just + /// like for non-replaced blocks. + fn solve_inline_size_constraints(&self, + block: &mut BlockFlow, + input: &ISizeConstraintInput) + -> ISizeConstraintSolution { + match input.computed_inline_size { + Specified(_) => {}, + Auto => fail!("BlockReplaced: inline_size should have been computed by now") + }; + self.solve_block_inline_size_constraints(block, input) + } + + /// Calculate used value of inline-size just like we do for inline replaced elements. + fn initial_computed_inline_size(&self, + block: &mut BlockFlow, + parent_flow_inline_size: Au, + _: &LayoutContext) + -> MaybeAuto { + let fragment = block.fragment(); + fragment.assign_replaced_inline_size_if_necessary(parent_flow_inline_size); + // For replaced block flow, the rest of the constraint solving will + // take inline-size to be specified as the value computed here. + Specified(fragment.content_inline_size()) + } + +} + +impl ISizeAndMarginsComputer for FloatNonReplaced { + /// CSS Section 10.3.5 + /// + /// If inline-size is computed as 'auto', the used value is the 'shrink-to-fit' inline-size. + fn solve_inline_size_constraints(&self, + block: &mut BlockFlow, + input: &ISizeConstraintInput) + -> ISizeConstraintSolution { + let (computed_inline_size, inline_start_margin, inline_end_margin, available_inline_size) = (input.computed_inline_size, + input.inline_start_margin, + input.inline_end_margin, + input.available_inline_size); + let margin_inline_start = inline_start_margin.specified_or_zero(); + let margin_inline_end = inline_end_margin.specified_or_zero(); + let available_inline_size_float = available_inline_size - margin_inline_start - margin_inline_end; + let shrink_to_fit = block.get_shrink_to_fit_inline_size(available_inline_size_float); + let inline_size = computed_inline_size.specified_or_default(shrink_to_fit); + debug!("assign_inline_sizes_float -- inline_size: {}", inline_size); + ISizeConstraintSolution::new(inline_size, margin_inline_start, margin_inline_end) + } +} + +impl ISizeAndMarginsComputer for FloatReplaced { + /// CSS Section 10.3.5 + /// + /// If inline-size is computed as 'auto', the used value is the 'shrink-to-fit' inline-size. + fn solve_inline_size_constraints(&self, _: &mut BlockFlow, input: &ISizeConstraintInput) + -> ISizeConstraintSolution { + let (computed_inline_size, inline_start_margin, inline_end_margin) = (input.computed_inline_size, + input.inline_start_margin, + input.inline_end_margin); + let margin_inline_start = inline_start_margin.specified_or_zero(); + let margin_inline_end = inline_end_margin.specified_or_zero(); + let inline_size = match computed_inline_size { + Specified(w) => w, + Auto => fail!("FloatReplaced: inline_size should have been computed by now") + }; + debug!("assign_inline_sizes_float -- inline_size: {}", inline_size); + ISizeConstraintSolution::new(inline_size, margin_inline_start, margin_inline_end) + } + + /// Calculate used value of inline-size just like we do for inline replaced elements. + fn initial_computed_inline_size(&self, + block: &mut BlockFlow, + parent_flow_inline_size: Au, + _: &LayoutContext) + -> MaybeAuto { + let fragment = block.fragment(); + fragment.assign_replaced_inline_size_if_necessary(parent_flow_inline_size); + // For replaced block flow, the rest of the constraint solving will + // take inline-size to be specified as the value computed here. + Specified(fragment.content_inline_size()) + } +} + +fn propagate_column_inline_sizes_to_child(kid: &mut Flow, + child_index: uint, + content_inline_size: Au, + column_inline_sizes: &[Au], + inline_start_margin_edge: &mut Au) { + // If kid is table_rowgroup or table_row, the column inline-sizes info should be copied from its + // parent. + // + // FIXME(pcwalton): This seems inefficient. Reference count it instead? + let inline_size = if kid.is_table() || kid.is_table_rowgroup() || kid.is_table_row() { + *kid.col_inline_sizes() = column_inline_sizes.iter().map(|&x| x).collect(); + + // ISize of kid flow is our content inline-size. + content_inline_size + } else if kid.is_table_cell() { + // If kid is table_cell, the x offset and inline-size for each cell should be + // calculated from parent's column inline-sizes info. + *inline_start_margin_edge = if child_index == 0 { + Au(0) + } else { + *inline_start_margin_edge + column_inline_sizes[child_index - 1] + }; + + column_inline_sizes[child_index] + } else { + // ISize of kid flow is our content inline-size. + content_inline_size + }; + + let kid_base = flow::mut_base(kid); + kid_base.position.start.i = *inline_start_margin_edge; + kid_base.position.size.inline = inline_size; +} + diff --git a/components/layout/construct.rs b/components/layout/construct.rs new file mode 100644 index 00000000000..0f832bacfb8 --- /dev/null +++ b/components/layout/construct.rs @@ -0,0 +1,1049 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Creates flows and fragments from a DOM tree via a bottom-up, incremental traversal of the DOM. +//! +//! Each step of the traversal considers the node and existing flow, if there is one. If a node is +//! not dirty and an existing flow exists, then the traversal reuses that flow. Otherwise, it +//! proceeds to construct either a flow or a `ConstructionItem`. A construction item is a piece of +//! intermediate data that goes with a DOM node and hasn't found its "home" yet-maybe it's a box, +//! maybe it's an absolute or fixed position thing that hasn't found its containing block yet. +//! Construction items bubble up the tree from children to parents until they find their homes. +//! +//! TODO(pcwalton): There is no incremental reflow yet. This scheme requires that nodes either have +//! weak references to flows or that there be some mechanism to efficiently (O(1) time) "blow +//! apart" a flow tree and have the flows migrate "home" to their respective DOM nodes while we +//! perform flow tree construction. The precise mechanism for this will take some experimentation +//! to get right. + +#![deny(unsafe_block)] + +use css::node_style::StyledNode; +use block::BlockFlow; +use context::LayoutContext; +use floats::FloatKind; +use flow::{Flow, ImmutableFlowUtils, MutableOwnedFlowUtils}; +use flow::{Descendants, AbsDescendants}; +use flow; +use flow_ref::FlowRef; +use fragment::{Fragment, GenericFragment, IframeFragment, IframeFragmentInfo}; +use fragment::{ImageFragment, ImageFragmentInfo, SpecificFragmentInfo, TableFragment}; +use fragment::{TableCellFragment, TableColumnFragment, TableColumnFragmentInfo}; +use fragment::{TableRowFragment, TableWrapperFragment, UnscannedTextFragment}; +use fragment::{UnscannedTextFragmentInfo}; +use inline::{InlineFragments, InlineFlow}; +use parallel; +use table_wrapper::TableWrapperFlow; +use table::TableFlow; +use table_caption::TableCaptionFlow; +use table_colgroup::TableColGroupFlow; +use table_rowgroup::TableRowGroupFlow; +use table_row::TableRowFlow; +use table_cell::TableCellFlow; +use text::TextRunScanner; +use util::{LayoutDataAccess, OpaqueNodeMethods}; +use wrapper::{PostorderNodeMutTraversal, TLayoutNode, ThreadSafeLayoutNode}; +use wrapper::{Before, BeforeBlock, After, AfterBlock, Normal}; + +use gfx::display_list::OpaqueNode; +use script::dom::element::{HTMLIFrameElementTypeId, HTMLImageElementTypeId}; +use script::dom::element::{HTMLObjectElementTypeId}; +use script::dom::element::{HTMLTableColElementTypeId, HTMLTableDataCellElementTypeId}; +use script::dom::element::{HTMLTableElementTypeId, HTMLTableHeaderCellElementTypeId}; +use script::dom::element::{HTMLTableRowElementTypeId, HTMLTableSectionElementTypeId}; +use script::dom::node::{CommentNodeTypeId, DoctypeNodeTypeId, DocumentFragmentNodeTypeId}; +use script::dom::node::{DocumentNodeTypeId, ElementNodeTypeId, ProcessingInstructionNodeTypeId}; +use script::dom::node::{TextNodeTypeId}; +use script::dom::htmlobjectelement::is_image_data; +use servo_util::namespace; +use std::mem; +use std::sync::atomics::Relaxed; +use style::ComputedValues; +use style::computed_values::{display, position, float}; +use sync::Arc; +use url::Url; + +/// The results of flow construction for a DOM node. +pub enum ConstructionResult { + /// This node contributes nothing at all (`display: none`). Alternately, this is what newly + /// created nodes have their `ConstructionResult` set to. + NoConstructionResult, + + /// This node contributed a flow at the proper position in the tree. + /// Nothing more needs to be done for this node. It has bubbled up fixed + /// and absolute descendant flows that have a CB above it. + FlowConstructionResult(FlowRef, AbsDescendants), + + /// This node contributed some object or objects that will be needed to construct a proper flow + /// later up the tree, but these objects have not yet found their home. + ConstructionItemConstructionResult(ConstructionItem), +} + +/// Represents the output of flow construction for a DOM node that has not yet resulted in a +/// complete flow. Construction items bubble up the tree until they find a `Flow` to be +/// attached to. +pub enum ConstructionItem { + /// Inline fragments and associated {ib} splits that have not yet found flows. + InlineFragmentsConstructionItem(InlineFragmentsConstructionResult), + /// Potentially ignorable whitespace. + WhitespaceConstructionItem(OpaqueNode, Arc<ComputedValues>), + /// TableColumn Fragment + TableColumnFragmentConstructionItem(Fragment), +} + +/// Represents inline fragments and {ib} splits that are bubbling up from an inline. +pub struct InlineFragmentsConstructionResult { + /// Any {ib} splits that we're bubbling up. + pub splits: Vec<InlineBlockSplit>, + + /// Any fragments that succeed the {ib} splits. + pub fragments: InlineFragments, + + /// Any absolute descendants that we're bubbling up. + pub abs_descendants: AbsDescendants, +} + +/// Represents an {ib} split that has not yet found the containing block that it belongs to. This +/// is somewhat tricky. An example may be helpful. For this DOM fragment: +/// +/// <span> +/// A +/// <div>B</div> +/// C +/// </span> +/// +/// The resulting `ConstructionItem` for the outer `span` will be: +/// +/// InlineFragmentsConstructionItem(Some(~[ +/// InlineBlockSplit { +/// predecessor_fragments: ~[ +/// A +/// ], +/// block: ~BlockFlow { +/// B +/// }, +/// }),~[ +/// C +/// ]) +pub struct InlineBlockSplit { + /// The inline fragments that precede the flow. + pub predecessors: InlineFragments, + + /// The flow that caused this {ib} split. + pub flow: FlowRef, +} + +/// Holds inline fragments that we're gathering for children of an inline node. +struct InlineFragmentsAccumulator { + /// The list of fragments. + fragments: InlineFragments, + + /// Whether we've created a range to enclose all the fragments. This will be Some() if the outer node + /// is an inline and None otherwise. + enclosing_style: Option<Arc<ComputedValues>>, +} + +impl InlineFragmentsAccumulator { + fn new() -> InlineFragmentsAccumulator { + InlineFragmentsAccumulator { + fragments: InlineFragments::new(), + enclosing_style: None, + } + } + + fn from_inline_node(node: &ThreadSafeLayoutNode) -> InlineFragmentsAccumulator { + let fragments = InlineFragments::new(); + InlineFragmentsAccumulator { + fragments: fragments, + enclosing_style: Some(node.style().clone()), + } + } + + fn finish(self) -> InlineFragments { + let InlineFragmentsAccumulator { + fragments: mut fragments, + enclosing_style + } = self; + + match enclosing_style { + Some(enclosing_style) => { + for frag in fragments.fragments.mut_iter() { + frag.add_inline_context_style(enclosing_style.clone()); + } + } + None => {} + } + fragments + } +} + +enum WhitespaceStrippingMode { + NoWhitespaceStripping, + StripWhitespaceFromStart, + StripWhitespaceFromEnd, +} + +/// An object that knows how to create flows. +pub struct FlowConstructor<'a, 'b> { + /// The layout context. + pub layout_context: &'b LayoutContext<'b>, +} + +impl<'a, 'b> FlowConstructor<'a, 'b> { + /// Creates a new flow constructor. + pub fn new<'b>(layout_context: &'b LayoutContext) + -> FlowConstructor<'a, 'b> { + FlowConstructor { + layout_context: layout_context, + } + } + + /// Builds the `ImageFragmentInfo` for the given image. This is out of line to guide inlining. + fn build_fragment_info_for_image(&mut self, node: &ThreadSafeLayoutNode, url: Option<Url>) + -> SpecificFragmentInfo { + match url { + None => GenericFragment, + Some(url) => { + // FIXME(pcwalton): The fact that image fragments store the cache within them makes + // little sense to me. + ImageFragment(ImageFragmentInfo::new(node, url, self.layout_context.shared.image_cache.clone())) + } + } + } + + /// Builds specific `Fragment` info for the given node. + pub fn build_specific_fragment_info_for_node(&mut self, node: &ThreadSafeLayoutNode) + -> SpecificFragmentInfo { + match node.type_id() { + Some(ElementNodeTypeId(HTMLImageElementTypeId)) => { + self.build_fragment_info_for_image(node, node.image_url()) + } + Some(ElementNodeTypeId(HTMLIFrameElementTypeId)) => { + IframeFragment(IframeFragmentInfo::new(node)) + } + Some(ElementNodeTypeId(HTMLObjectElementTypeId)) => { + let data = node.get_object_data(); + self.build_fragment_info_for_image(node, data) + } + Some(ElementNodeTypeId(HTMLTableElementTypeId)) => TableWrapperFragment, + Some(ElementNodeTypeId(HTMLTableColElementTypeId)) => { + TableColumnFragment(TableColumnFragmentInfo::new(node)) + } + Some(ElementNodeTypeId(HTMLTableDataCellElementTypeId)) | + Some(ElementNodeTypeId(HTMLTableHeaderCellElementTypeId)) => TableCellFragment, + Some(ElementNodeTypeId(HTMLTableRowElementTypeId)) | + Some(ElementNodeTypeId(HTMLTableSectionElementTypeId)) => TableRowFragment, + None | Some(TextNodeTypeId) => UnscannedTextFragment(UnscannedTextFragmentInfo::new(node)), + _ => GenericFragment, + } + } + + /// Creates an inline flow from a set of inline fragments, then adds it as a child of the given flow + /// or pushes it onto the given flow list. + /// + /// `#[inline(always)]` because this is performance critical and LLVM will not inline it + /// otherwise. + #[inline(always)] + fn flush_inline_fragments_to_flow_or_list(&mut self, + fragment_accumulator: InlineFragmentsAccumulator, + flow: &mut FlowRef, + flow_list: &mut Vec<FlowRef>, + whitespace_stripping: WhitespaceStrippingMode, + node: &ThreadSafeLayoutNode) { + let mut fragments = fragment_accumulator.finish(); + if fragments.is_empty() { return }; + + match whitespace_stripping { + NoWhitespaceStripping => {} + StripWhitespaceFromStart => { + fragments.strip_ignorable_whitespace_from_start(); + if fragments.is_empty() { return }; + } + StripWhitespaceFromEnd => { + fragments.strip_ignorable_whitespace_from_end(); + if fragments.is_empty() { return }; + } + } + + let mut inline_flow = box InlineFlow::from_fragments((*node).clone(), fragments); + let (ascent, descent) = inline_flow.compute_minimum_ascent_and_descent(self.layout_context.font_context(), &**node.style()); + inline_flow.minimum_block_size_above_baseline = ascent; + inline_flow.minimum_depth_below_baseline = descent; + let mut inline_flow = inline_flow as Box<Flow>; + TextRunScanner::new().scan_for_runs(self.layout_context.font_context(), inline_flow); + let mut inline_flow = FlowRef::new(inline_flow); + inline_flow.finish(self.layout_context); + + if flow.get().need_anonymous_flow(inline_flow.get()) { + flow_list.push(inline_flow) + } else { + flow.add_new_child(inline_flow) + } + } + + fn build_block_flow_using_children_construction_result(&mut self, + flow: &mut FlowRef, + consecutive_siblings: &mut Vec<FlowRef>, + node: &ThreadSafeLayoutNode, + kid: ThreadSafeLayoutNode, + inline_fragment_accumulator: + &mut InlineFragmentsAccumulator, + abs_descendants: &mut Descendants, + first_fragment: &mut bool) { + match kid.swap_out_construction_result() { + NoConstructionResult => {} + FlowConstructionResult(kid_flow, kid_abs_descendants) => { + // If kid_flow is TableCaptionFlow, kid_flow should be added under + // TableWrapperFlow. + if flow.get().is_table() && kid_flow.get().is_table_caption() { + kid.set_flow_construction_result(FlowConstructionResult( + kid_flow, + Descendants::new())) + } else if flow.get().need_anonymous_flow(kid_flow.get()) { + consecutive_siblings.push(kid_flow) + } else { + // Flush any inline fragments that we were gathering up. This allows us to handle + // {ib} splits. + debug!("flushing {} inline box(es) to flow A", + inline_fragment_accumulator.fragments.len()); + self.flush_inline_fragments_to_flow_or_list( + mem::replace(inline_fragment_accumulator, InlineFragmentsAccumulator::new()), + flow, + consecutive_siblings, + StripWhitespaceFromStart, + node); + if !consecutive_siblings.is_empty() { + let consecutive_siblings = mem::replace(consecutive_siblings, vec!()); + self.generate_anonymous_missing_child(consecutive_siblings, + flow, + node); + } + flow.add_new_child(kid_flow); + } + abs_descendants.push_descendants(kid_abs_descendants); + } + ConstructionItemConstructionResult(InlineFragmentsConstructionItem( + InlineFragmentsConstructionResult { + splits: splits, + fragments: successor_fragments, + abs_descendants: kid_abs_descendants, + })) => { + // Add any {ib} splits. + for split in splits.move_iter() { + // Pull apart the {ib} split object and push its predecessor fragments + // onto the list. + let InlineBlockSplit { + predecessors: predecessors, + flow: kid_flow + } = split; + inline_fragment_accumulator.fragments.push_all(predecessors); + + // If this is the first fragment in flow, then strip ignorable + // whitespace per CSS 2.1 § 9.2.1.1. + let whitespace_stripping = if *first_fragment { + *first_fragment = false; + StripWhitespaceFromStart + } else { + NoWhitespaceStripping + }; + + // Flush any inline fragments that we were gathering up. + debug!("flushing {} inline box(es) to flow A", + inline_fragment_accumulator.fragments.len()); + self.flush_inline_fragments_to_flow_or_list( + mem::replace(inline_fragment_accumulator, + InlineFragmentsAccumulator::new()), + flow, + consecutive_siblings, + whitespace_stripping, + node); + + // Push the flow generated by the {ib} split onto our list of + // flows. + if flow.get().need_anonymous_flow(kid_flow.get()) { + consecutive_siblings.push(kid_flow) + } else { + flow.add_new_child(kid_flow) + } + } + + // Add the fragments to the list we're maintaining. + inline_fragment_accumulator.fragments.push_all(successor_fragments); + abs_descendants.push_descendants(kid_abs_descendants); + } + ConstructionItemConstructionResult(WhitespaceConstructionItem(whitespace_node, whitespace_style)) => { + // Add whitespace results. They will be stripped out later on when + // between block elements, and retained when between inline elements. + let fragment_info = UnscannedTextFragment(UnscannedTextFragmentInfo::from_text(" ".to_string())); + let mut fragment = Fragment::from_opaque_node_and_style(whitespace_node, + whitespace_style.clone(), + fragment_info); + inline_fragment_accumulator.fragments.push(&mut fragment, whitespace_style); + } + ConstructionItemConstructionResult(TableColumnFragmentConstructionItem(_)) => { + // TODO: Implement anonymous table objects for missing parents + // CSS 2.1 § 17.2.1, step 3-2 + } + } + } + + /// Build block flow for current node using information from children nodes. + /// + /// Consume results from children and combine them, handling {ib} splits. + /// Block flows and inline flows thus created will become the children of + /// this block flow. + /// Also, deal with the absolute and fixed descendants bubbled up by + /// children nodes. + fn build_flow_using_children(&mut self, mut flow: FlowRef, node: &ThreadSafeLayoutNode) + -> ConstructionResult { + // Gather up fragments for the inline flows we might need to create. + let mut inline_fragment_accumulator = InlineFragmentsAccumulator::new(); + let mut consecutive_siblings = vec!(); + let mut first_fragment = true; + + // List of absolute descendants, in tree order. + let mut abs_descendants = Descendants::new(); + for kid in node.children() { + if kid.get_pseudo_element_type() != Normal { + self.process(&kid); + } + + self.build_block_flow_using_children_construction_result(&mut flow, + &mut consecutive_siblings, + node, + kid, + &mut inline_fragment_accumulator, + &mut abs_descendants, + &mut first_fragment); + } + + // Perform a final flush of any inline fragments that we were gathering up to handle {ib} + // splits, after stripping ignorable whitespace. + self.flush_inline_fragments_to_flow_or_list(inline_fragment_accumulator, + &mut flow, + &mut consecutive_siblings, + StripWhitespaceFromEnd, + node); + if !consecutive_siblings.is_empty() { + self.generate_anonymous_missing_child(consecutive_siblings, &mut flow, node); + } + + // The flow is done. + flow.finish(self.layout_context); + let is_positioned = flow.get_mut().as_block().is_positioned(); + let is_fixed_positioned = flow.get_mut().as_block().is_fixed(); + let is_absolutely_positioned = flow.get_mut().as_block().is_absolutely_positioned(); + if is_positioned { + // This is the CB for all the absolute descendants. + flow.set_abs_descendants(abs_descendants); + + abs_descendants = Descendants::new(); + + if is_fixed_positioned || is_absolutely_positioned { + // This is now the only absolute flow in the subtree which hasn't yet + // reached its CB. + abs_descendants.push(flow.clone()); + } + } + FlowConstructionResult(flow, abs_descendants) + } + + /// Builds a flow for a node with `display: block`. This yields a `BlockFlow` with possibly + /// other `BlockFlow`s or `InlineFlow`s underneath it, depending on whether {ib} splits needed + /// to happen. + fn build_flow_for_block(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult { + let flow = box BlockFlow::from_node(self, node) as Box<Flow>; + self.build_flow_using_children(FlowRef::new(flow), node) + } + + /// Builds the flow for a node with `float: {left|right}`. This yields a float `BlockFlow` with + /// a `BlockFlow` underneath it. + fn build_flow_for_floated_block(&mut self, node: &ThreadSafeLayoutNode, float_kind: FloatKind) + -> ConstructionResult { + let flow = box BlockFlow::float_from_node(self, node, float_kind) as Box<Flow>; + self.build_flow_using_children(FlowRef::new(flow), node) + } + + /// Concatenates the fragments of kids, adding in our own borders/padding/margins if necessary. + /// Returns the `InlineFragmentsConstructionResult`, if any. There will be no + /// `InlineFragmentsConstructionResult` if this node consisted entirely of ignorable whitespace. + fn build_fragments_for_nonreplaced_inline_content(&mut self, node: &ThreadSafeLayoutNode) + -> ConstructionResult { + let mut opt_inline_block_splits: Vec<InlineBlockSplit> = Vec::new(); + let mut fragment_accumulator = InlineFragmentsAccumulator::from_inline_node(node); + let mut abs_descendants = Descendants::new(); + + // Concatenate all the fragments of our kids, creating {ib} splits as necessary. + for kid in node.children() { + if kid.get_pseudo_element_type() != Normal { + self.process(&kid); + } + match kid.swap_out_construction_result() { + NoConstructionResult => {} + FlowConstructionResult(flow, kid_abs_descendants) => { + // {ib} split. Flush the accumulator to our new split and make a new + // accumulator to hold any subsequent fragments we come across. + let split = InlineBlockSplit { + predecessors: + mem::replace(&mut fragment_accumulator, + InlineFragmentsAccumulator::from_inline_node(node)).finish(), + flow: flow, + }; + opt_inline_block_splits.push(split); + abs_descendants.push_descendants(kid_abs_descendants); + } + ConstructionItemConstructionResult(InlineFragmentsConstructionItem( + InlineFragmentsConstructionResult { + splits: splits, + fragments: successors, + abs_descendants: kid_abs_descendants, + })) => { + + // Bubble up {ib} splits. + for split in splits.move_iter() { + let InlineBlockSplit { + predecessors: predecessors, + flow: kid_flow + } = split; + fragment_accumulator.fragments.push_all(predecessors); + + let split = InlineBlockSplit { + predecessors: + mem::replace(&mut fragment_accumulator, + InlineFragmentsAccumulator::from_inline_node(node)) + .finish(), + flow: kid_flow, + }; + opt_inline_block_splits.push(split) + } + + // Push residual fragments. + fragment_accumulator.fragments.push_all(successors); + abs_descendants.push_descendants(kid_abs_descendants); + } + ConstructionItemConstructionResult(WhitespaceConstructionItem(whitespace_node, + whitespace_style)) + => { + // Instantiate the whitespace fragment. + let fragment_info = UnscannedTextFragment(UnscannedTextFragmentInfo::from_text(" ".to_string())); + let mut fragment = Fragment::from_opaque_node_and_style(whitespace_node, + whitespace_style.clone(), + fragment_info); + fragment_accumulator.fragments.push(&mut fragment, whitespace_style) + } + ConstructionItemConstructionResult(TableColumnFragmentConstructionItem(_)) => { + // TODO: Implement anonymous table objects for missing parents + // CSS 2.1 § 17.2.1, step 3-2 + } + } + } + + // Finally, make a new construction result. + if opt_inline_block_splits.len() > 0 || fragment_accumulator.fragments.len() > 0 + || abs_descendants.len() > 0 { + let construction_item = InlineFragmentsConstructionItem(InlineFragmentsConstructionResult { + splits: opt_inline_block_splits, + fragments: fragment_accumulator.finish(), + abs_descendants: abs_descendants, + }); + ConstructionItemConstructionResult(construction_item) + } else { + NoConstructionResult + } + } + + /// Creates an `InlineFragmentsConstructionResult` for replaced content. Replaced content doesn't + /// render its children, so this just nukes a child's fragments and creates a `Fragment`. + fn build_fragments_for_replaced_inline_content(&mut self, node: &ThreadSafeLayoutNode) + -> ConstructionResult { + for kid in node.children() { + kid.set_flow_construction_result(NoConstructionResult) + } + + // If this node is ignorable whitespace, bail out now. + // + // FIXME(#2001, pcwalton): Don't do this if there's padding or borders. + if node.is_ignorable_whitespace() { + let opaque_node = OpaqueNodeMethods::from_thread_safe_layout_node(node); + return ConstructionItemConstructionResult(WhitespaceConstructionItem( + opaque_node, + node.style().clone())) + } + + let mut fragments = InlineFragments::new(); + fragments.push(&mut Fragment::new(self, node), node.style().clone()); + + let construction_item = InlineFragmentsConstructionItem(InlineFragmentsConstructionResult { + splits: Vec::new(), + fragments: fragments, + abs_descendants: Descendants::new(), + }); + ConstructionItemConstructionResult(construction_item) + } + + /// Builds one or more fragments for a node with `display: inline`. This yields an + /// `InlineFragmentsConstructionResult`. + fn build_fragments_for_inline(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult { + // Is this node replaced content? + if !node.is_replaced_content() { + // Go to a path that concatenates our kids' fragments. + self.build_fragments_for_nonreplaced_inline_content(node) + } else { + // Otherwise, just nuke our kids' fragments, create our fragment if any, and be done + // with it. + self.build_fragments_for_replaced_inline_content(node) + } + } + + /// TableCaptionFlow is populated underneath TableWrapperFlow + fn place_table_caption_under_table_wrapper(&mut self, + table_wrapper_flow: &mut FlowRef, + node: &ThreadSafeLayoutNode) { + for kid in node.children() { + match kid.swap_out_construction_result() { + NoConstructionResult | ConstructionItemConstructionResult(_) => {} + FlowConstructionResult(kid_flow, _) => { + // Only kid flows with table-caption are matched here. + assert!(kid_flow.get().is_table_caption()); + table_wrapper_flow.add_new_child(kid_flow); + } + } + } + } + + /// Generates an anonymous table flow according to CSS 2.1 § 17.2.1, step 2. + /// If necessary, generate recursively another anonymous table flow. + fn generate_anonymous_missing_child(&mut self, + child_flows: Vec<FlowRef>, + flow: &mut FlowRef, + node: &ThreadSafeLayoutNode) { + let mut anonymous_flow = flow.get().generate_missing_child_flow(node); + let mut consecutive_siblings = vec!(); + for kid_flow in child_flows.move_iter() { + if anonymous_flow.get().need_anonymous_flow(kid_flow.get()) { + consecutive_siblings.push(kid_flow); + continue; + } + if !consecutive_siblings.is_empty() { + self.generate_anonymous_missing_child(consecutive_siblings, + &mut anonymous_flow, + node); + consecutive_siblings = vec!(); + } + anonymous_flow.add_new_child(kid_flow); + } + if !consecutive_siblings.is_empty() { + self.generate_anonymous_missing_child(consecutive_siblings, &mut anonymous_flow, node); + } + // The flow is done. + anonymous_flow.finish(self.layout_context); + flow.add_new_child(anonymous_flow); + } + + /// 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: &ThreadSafeLayoutNode) -> ConstructionResult { + let fragment = Fragment::new_from_specific_info(node, TableWrapperFragment); + let wrapper_flow = box TableWrapperFlow::from_node_and_fragment(node, fragment); + let mut wrapper_flow = FlowRef::new(wrapper_flow as Box<Flow>); + + let table_fragment = Fragment::new_from_specific_info(node, TableFragment); + let table_flow = box TableFlow::from_node_and_fragment(node, table_fragment); + let table_flow = FlowRef::new(table_flow as Box<Flow>); + + // We first populate the TableFlow with other flows than TableCaptionFlow. + // We then populate the TableWrapperFlow with TableCaptionFlow, and attach + // the TableFlow to the TableWrapperFlow + let construction_result = self.build_flow_using_children(table_flow, node); + self.place_table_caption_under_table_wrapper(&mut wrapper_flow, node); + + let mut abs_descendants = Descendants::new(); + let mut fixed_descendants = Descendants::new(); + + // NOTE: The order of captions and table are not the same order as in the DOM tree. + // All caption blocks are placed before the table flow + match construction_result { + FlowConstructionResult(table_flow, table_abs_descendants) => { + wrapper_flow.add_new_child(table_flow); + abs_descendants.push_descendants(table_abs_descendants); + } + _ => {} + } + + // The flow is done. + wrapper_flow.finish(self.layout_context); + let is_positioned = wrapper_flow.get_mut().as_block().is_positioned(); + let is_fixed_positioned = wrapper_flow.get_mut().as_block().is_fixed(); + let is_absolutely_positioned = wrapper_flow.get_mut() + .as_block() + .is_absolutely_positioned(); + if is_positioned { + // This is the CB for all the absolute descendants. + wrapper_flow.set_abs_descendants(abs_descendants); + + abs_descendants = Descendants::new(); + + if is_fixed_positioned { + // Send itself along with the other fixed descendants. + fixed_descendants.push(wrapper_flow.clone()); + } else if is_absolutely_positioned { + // This is now the only absolute flow in the subtree which hasn't yet + // reached its CB. + abs_descendants.push(wrapper_flow.clone()); + } + } + FlowConstructionResult(wrapper_flow, abs_descendants) + } + + /// Builds a flow for a node with `display: table-caption`. This yields a `TableCaptionFlow` + /// with possibly other `BlockFlow`s or `InlineFlow`s underneath it. + fn build_flow_for_table_caption(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult { + let flow = box TableCaptionFlow::from_node(self, node) as Box<Flow>; + self.build_flow_using_children(FlowRef::new(flow), node) + } + + /// Builds a flow for a node with `display: table-row-group`. This yields a `TableRowGroupFlow` + /// with possibly other `TableRowFlow`s underneath it. + fn build_flow_for_table_rowgroup(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult { + let fragment = Fragment::new_from_specific_info(node, TableRowFragment); + let flow = box TableRowGroupFlow::from_node_and_fragment(node, fragment); + let flow = flow as Box<Flow>; + self.build_flow_using_children(FlowRef::new(flow), node) + } + + /// Builds a flow for a node with `display: table-row`. This yields a `TableRowFlow` with + /// possibly other `TableCellFlow`s underneath it. + fn build_flow_for_table_row(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult { + let fragment = Fragment::new_from_specific_info(node, TableRowFragment); + let flow = box TableRowFlow::from_node_and_fragment(node, fragment) as Box<Flow>; + self.build_flow_using_children(FlowRef::new(flow), node) + } + + /// Builds a flow for a node with `display: table-cell`. This yields a `TableCellFlow` with + /// possibly other `BlockFlow`s or `InlineFlow`s underneath it. + fn build_flow_for_table_cell(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult { + let fragment = Fragment::new_from_specific_info(node, TableCellFragment); + let flow = box TableCellFlow::from_node_and_fragment(node, fragment) as Box<Flow>; + self.build_flow_using_children(FlowRef::new(flow), node) + } + + /// Creates a fragment for a node with `display: table-column`. + fn build_fragments_for_table_column(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult { + // CSS 2.1 § 17.2.1. Treat all child fragments of a `table-column` as `display: none`. + for kid in node.children() { + kid.set_flow_construction_result(NoConstructionResult) + } + + let specific = TableColumnFragment(TableColumnFragmentInfo::new(node)); + let construction_item = TableColumnFragmentConstructionItem( + Fragment::new_from_specific_info(node, specific) + ); + ConstructionItemConstructionResult(construction_item) + } + + /// Builds a flow for a node with `display: table-column-group`. + /// This yields a `TableColGroupFlow`. + fn build_flow_for_table_colgroup(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult { + let fragment = Fragment::new_from_specific_info(node, + TableColumnFragment(TableColumnFragmentInfo::new(node))); + let mut col_fragments = vec!(); + for kid in node.children() { + // CSS 2.1 § 17.2.1. Treat all non-column child fragments of `table-column-group` + // as `display: none`. + match kid.swap_out_construction_result() { + ConstructionItemConstructionResult(TableColumnFragmentConstructionItem(fragment)) => { + col_fragments.push(fragment); + } + _ => {} + } + } + if col_fragments.is_empty() { + debug!("add TableColumnFragment for empty colgroup"); + let specific = TableColumnFragment(TableColumnFragmentInfo::new(node)); + col_fragments.push(Fragment::new_from_specific_info(node, specific)); + } + let flow = box TableColGroupFlow::from_node_and_fragments(node, fragment, col_fragments); + let mut flow = FlowRef::new(flow as Box<Flow>); + flow.finish(self.layout_context); + + FlowConstructionResult(flow, Descendants::new()) + } +} + +impl<'a, 'b> PostorderNodeMutTraversal for FlowConstructor<'a, 'b> { + // Construct Flow based on 'display', 'position', and 'float' values. + // + // CSS 2.1 Section 9.7 + // + // TODO: This should actually consult the table in that section to get the + // final computed value for 'display'. + // + // `#[inline(always)]` because this is always called from the traversal function and for some + // reason LLVM's inlining heuristics go awry here. + #[inline(always)] + fn process(&mut self, node: &ThreadSafeLayoutNode) -> bool { + // Get the `display` property for this node, and determine whether this node is floated. + let (display, float, positioning) = match node.type_id() { + None => { + // Pseudo-element. + let style = node.style(); + (display::inline, style.get_box().float, style.get_box().position) + } + Some(ElementNodeTypeId(_)) => { + let style = node.style(); + (style.get_box().display, style.get_box().float, style.get_box().position) + } + Some(TextNodeTypeId) => (display::inline, float::none, position::static_), + Some(CommentNodeTypeId) | + Some(DoctypeNodeTypeId) | + Some(DocumentFragmentNodeTypeId) | + Some(DocumentNodeTypeId) | + Some(ProcessingInstructionNodeTypeId) => { + (display::none, float::none, position::static_) + } + }; + + debug!("building flow for node: {:?} {:?}", display, float); + + // Switch on display and floatedness. + match (display, float, positioning) { + // `display: none` contributes no flow construction result. Nuke the flow construction + // results of children. + (display::none, _, _) => { + for child in node.children() { + drop(child.swap_out_construction_result()) + } + } + + // Table items contribute table flow construction results. + (display::table, _, _) => { + let construction_result = self.build_flow_for_table_wrapper(node); + node.set_flow_construction_result(construction_result) + } + + // Absolutely positioned elements will have computed value of + // `float` as 'none' and `display` as per the table. + // Only match here for block items. If an item is absolutely + // positioned, but inline we shouldn't try to construct a block + // flow here - instead, let it match the inline case + // below. + (display::block, _, position::absolute) | (_, _, position::fixed) => { + node.set_flow_construction_result(self.build_flow_for_block(node)) + } + + // Inline items contribute inline fragment construction results. + (display::inline, float::none, _) => { + let construction_result = self.build_fragments_for_inline(node); + node.set_flow_construction_result(construction_result) + } + + // Table items contribute table flow construction results. + (display::table_caption, _, _) => { + let construction_result = self.build_flow_for_table_caption(node); + node.set_flow_construction_result(construction_result) + } + + // Table items contribute table flow construction results. + (display::table_column_group, _, _) => { + let construction_result = self.build_flow_for_table_colgroup(node); + node.set_flow_construction_result(construction_result) + } + + // Table items contribute table flow construction results. + (display::table_column, _, _) => { + let construction_result = self.build_fragments_for_table_column(node); + node.set_flow_construction_result(construction_result) + } + + // Table items contribute table flow construction results. + (display::table_row_group, _, _) | (display::table_header_group, _, _) | + (display::table_footer_group, _, _) => { + let construction_result = self.build_flow_for_table_rowgroup(node); + node.set_flow_construction_result(construction_result) + } + + // Table items contribute table flow construction results. + (display::table_row, _, _) => { + let construction_result = self.build_flow_for_table_row(node); + node.set_flow_construction_result(construction_result) + } + + // Table items contribute table flow construction results. + (display::table_cell, _, _) => { + let construction_result = self.build_flow_for_table_cell(node); + node.set_flow_construction_result(construction_result) + } + + // Block flows that are not floated contribute block flow construction results. + // + // TODO(pcwalton): Make this only trigger for blocks and handle the other `display` + // properties separately. + + (_, float::none, _) => { + node.set_flow_construction_result(self.build_flow_for_block(node)) + } + + // Floated flows contribute float flow construction results. + (_, float_value, _) => { + let float_kind = FloatKind::from_property(float_value); + node.set_flow_construction_result( + self.build_flow_for_floated_block(node, float_kind)) + } + } + + true + } +} + +/// A utility trait with some useful methods for node queries. +trait NodeUtils { + /// Returns true if this node doesn't render its kids and false otherwise. + fn is_replaced_content(&self) -> bool; + + /// Sets the construction result of a flow. + fn set_flow_construction_result(&self, result: ConstructionResult); + + /// Replaces the flow construction result in a node with `NoConstructionResult` and returns the + /// old value. + fn swap_out_construction_result(&self) -> ConstructionResult; +} + +impl<'ln> NodeUtils for ThreadSafeLayoutNode<'ln> { + fn is_replaced_content(&self) -> bool { + match self.type_id() { + Some(TextNodeTypeId) | + Some(ProcessingInstructionNodeTypeId) | + Some(CommentNodeTypeId) | + Some(DoctypeNodeTypeId) | + Some(DocumentFragmentNodeTypeId) | + Some(DocumentNodeTypeId) | + None | + Some(ElementNodeTypeId(HTMLImageElementTypeId)) => true, + Some(ElementNodeTypeId(HTMLObjectElementTypeId)) => self.has_object_data(), + Some(ElementNodeTypeId(_)) => false, + } + } + + #[inline(always)] + fn set_flow_construction_result(&self, result: ConstructionResult) { + let mut layout_data_ref = self.mutate_layout_data(); + match &mut *layout_data_ref { + &Some(ref mut layout_data) =>{ + match self.get_pseudo_element_type() { + Before | BeforeBlock => { + layout_data.data.before_flow_construction_result = result + }, + After | AfterBlock => { + layout_data.data.after_flow_construction_result = result + }, + Normal => layout_data.data.flow_construction_result = result, + } + }, + &None => fail!("no layout data"), + } + } + + #[inline(always)] + fn swap_out_construction_result(&self) -> ConstructionResult { + let mut layout_data_ref = self.mutate_layout_data(); + match &mut *layout_data_ref { + &Some(ref mut layout_data) => { + match self.get_pseudo_element_type() { + Before | BeforeBlock => { + mem::replace(&mut layout_data.data.before_flow_construction_result, + NoConstructionResult) + } + After | AfterBlock => { + mem::replace(&mut layout_data.data.after_flow_construction_result, + NoConstructionResult) + } + Normal => { + mem::replace(&mut layout_data.data.flow_construction_result, + NoConstructionResult) + } + } + } + &None => fail!("no layout data"), + } + } +} + +/// Methods for interacting with HTMLObjectElement nodes +trait ObjectElement { + /// Returns None if this node is not matching attributes. + fn get_type_and_data(&self) -> (Option<&'static str>, Option<&'static str>); + + /// Returns true if this node has object data that is correct uri. + fn has_object_data(&self) -> bool; + + /// Returns the "data" attribute value parsed as a URL + fn get_object_data(&self) -> Option<Url>; +} + +impl<'ln> ObjectElement for ThreadSafeLayoutNode<'ln> { + fn get_type_and_data(&self) -> (Option<&'static str>, Option<&'static str>) { + let elem = self.as_element(); + (elem.get_attr(&namespace::Null, "type"), elem.get_attr(&namespace::Null, "data")) + } + + fn has_object_data(&self) -> bool { + match self.get_type_and_data() { + (None, Some(uri)) => is_image_data(uri), + _ => false + } + } + + fn get_object_data(&self) -> Option<Url> { + match self.get_type_and_data() { + (None, Some(uri)) if is_image_data(uri) => Url::parse(uri).ok(), + _ => None + } + } +} + +pub trait FlowConstructionUtils { + /// Adds a new flow as a child of this flow. Removes the flow from the given leaf set if + /// it's present. + fn add_new_child(&mut self, new_child: FlowRef); + + /// Finishes a flow. Once a flow is finished, no more child flows or boxes may be added to it. + /// This will normally run the bubble-inline-sizes (minimum and preferred -- i.e. intrinsic -- inline-size) + /// calculation, unless the global `bubble_inline-sizes_separately` flag is on. + /// + /// All flows must be finished at some point, or they will not have their intrinsic inline-sizes + /// properly computed. (This is not, however, a memory safety problem.) + fn finish(&mut self, context: &LayoutContext); +} + +impl FlowConstructionUtils for FlowRef { + /// Adds a new flow as a child of this flow. Fails if this flow is marked as a leaf. + /// + /// This must not be public because only the layout constructor can do this. + fn add_new_child(&mut self, mut new_child: FlowRef) { + { + let kid_base = flow::mut_base(new_child.get_mut()); + kid_base.parallel.parent = parallel::mut_owned_flow_to_unsafe_flow(self); + } + + let base = flow::mut_base(self.get_mut()); + base.children.push_back(new_child); + let _ = base.parallel.children_count.fetch_add(1, Relaxed); + let _ = base.parallel.children_and_absolute_descendant_count.fetch_add(1, Relaxed); + } + + /// Finishes a flow. Once a flow is finished, no more child flows or fragments may be added to + /// it. This will normally run the bubble-inline-sizes (minimum and preferred -- i.e. intrinsic -- + /// inline-size) calculation, unless the global `bubble_inline-sizes_separately` flag is on. + /// + /// All flows must be finished at some point, or they will not have their intrinsic inline-sizes + /// properly computed. (This is not, however, a memory safety problem.) + /// + /// This must not be public because only the layout constructor can do this. + fn finish(&mut self, context: &LayoutContext) { + if !context.shared.opts.bubble_inline_sizes_separately { + self.get_mut().bubble_inline_sizes(context) + } + } +} + diff --git a/components/layout/context.rs b/components/layout/context.rs new file mode 100644 index 00000000000..936314dcb63 --- /dev/null +++ b/components/layout/context.rs @@ -0,0 +1,123 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Data needed by the layout task. + +use css::matching::{ApplicableDeclarationsCache, StyleSharingCandidateCache}; + +use geom::{Rect, Size2D}; +use gfx::display_list::OpaqueNode; +use gfx::font_context::FontContext; +use gfx::font_cache_task::FontCacheTask; +use script::layout_interface::LayoutChan; +use servo_msg::constellation_msg::ConstellationChan; +use servo_net::local_image_cache::LocalImageCache; +use servo_util::geometry::Au; +use servo_util::opts::Opts; +use sync::{Arc, Mutex}; +use std::mem; +use style::Stylist; +use url::Url; + +struct LocalLayoutContext { + font_context: FontContext, + applicable_declarations_cache: ApplicableDeclarationsCache, + style_sharing_candidate_cache: StyleSharingCandidateCache, +} + +local_data_key!(local_context_key: *mut LocalLayoutContext) + +fn create_or_get_local_context(shared_layout_context: &SharedLayoutContext) -> *mut LocalLayoutContext { + let maybe_context = local_context_key.get(); + + let context = match maybe_context { + None => { + let context = box LocalLayoutContext { + font_context: FontContext::new(shared_layout_context.font_cache_task.clone()), + applicable_declarations_cache: ApplicableDeclarationsCache::new(), + style_sharing_candidate_cache: StyleSharingCandidateCache::new(), + }; + local_context_key.replace(Some(unsafe { mem::transmute(context) })); + local_context_key.get().unwrap() + }, + Some(context) => context + }; + + *context +} + +pub struct SharedLayoutContext { + /// The local image cache. + pub image_cache: Arc<Mutex<LocalImageCache>>, + + /// The current screen size. + pub screen_size: Size2D<Au>, + + /// A channel up to the constellation. + pub constellation_chan: ConstellationChan, + + /// A channel up to the layout task. + pub layout_chan: LayoutChan, + + /// Interface to the font cache task. + pub font_cache_task: FontCacheTask, + + /// The CSS selector stylist. + /// + /// FIXME(#2604): Make this no longer an unsafe pointer once we have fast `RWArc`s. + pub stylist: *const Stylist, + + /// The root node at which we're starting the layout. + pub reflow_root: OpaqueNode, + + /// The URL. + pub url: Url, + + /// The command line options. + pub opts: Opts, + + /// The dirty rectangle, used during display list building. + pub dirty: Rect<Au>, +} + +pub struct LayoutContext<'a> { + pub shared: &'a SharedLayoutContext, + cached_local_layout_context: *mut LocalLayoutContext, +} + +impl<'a> LayoutContext<'a> { + pub fn new(shared_layout_context: &'a SharedLayoutContext) -> LayoutContext<'a> { + + let local_context = create_or_get_local_context(shared_layout_context); + + LayoutContext { + shared: shared_layout_context, + cached_local_layout_context: local_context, + } + } + + #[inline(always)] + pub fn font_context<'a>(&'a self) -> &'a mut FontContext { + unsafe { + let cached_context = &*self.cached_local_layout_context; + mem::transmute(&cached_context.font_context) + } + } + + #[inline(always)] + pub fn applicable_declarations_cache<'a>(&'a self) -> &'a mut ApplicableDeclarationsCache { + unsafe { + let cached_context = &*self.cached_local_layout_context; + mem::transmute(&cached_context.applicable_declarations_cache) + } + } + + #[inline(always)] + pub fn style_sharing_candidate_cache<'a>(&'a self) -> &'a mut StyleSharingCandidateCache { + unsafe { + let cached_context = &*self.cached_local_layout_context; + mem::transmute(&cached_context.style_sharing_candidate_cache) + } + } +} diff --git a/components/layout/css/matching.rs b/components/layout/css/matching.rs new file mode 100644 index 00000000000..c1d766c32ca --- /dev/null +++ b/components/layout/css/matching.rs @@ -0,0 +1,558 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// High-level interface to CSS selector matching. + +use css::node_style::StyledNode; +use construct::FlowConstructor; +use context::LayoutContext; +use extra::LayoutAuxMethods; +use util::{LayoutDataAccess, LayoutDataWrapper}; +use wrapper::{LayoutElement, LayoutNode, PostorderNodeMutTraversal, ThreadSafeLayoutNode}; + +use servo_util::atom::Atom; +use servo_util::cache::{Cache, LRUCache, SimpleHashCache}; +use servo_util::namespace::Null; +use servo_util::smallvec::{SmallVec, SmallVec16}; +use servo_util::str::DOMString; +use std::mem; +use std::hash::{Hash, sip}; +use std::slice::Items; +use style::{After, Before, ComputedValues, DeclarationBlock, Stylist, TElement, TNode, cascade}; +use sync::Arc; + +pub struct ApplicableDeclarations { + pub normal: SmallVec16<DeclarationBlock>, + pub before: Vec<DeclarationBlock>, + pub after: Vec<DeclarationBlock>, + + /// Whether the `normal` declarations are shareable with other nodes. + pub normal_shareable: bool, +} + +impl ApplicableDeclarations { + pub fn new() -> ApplicableDeclarations { + ApplicableDeclarations { + normal: SmallVec16::new(), + before: Vec::new(), + after: Vec::new(), + normal_shareable: false, + } + } + + pub fn clear(&mut self) { + self.normal = SmallVec16::new(); + self.before = Vec::new(); + self.after = Vec::new(); + self.normal_shareable = false; + } +} + +#[deriving(Clone)] +pub struct ApplicableDeclarationsCacheEntry { + pub declarations: Vec<DeclarationBlock>, +} + +impl ApplicableDeclarationsCacheEntry { + fn new(slice: &[DeclarationBlock]) -> ApplicableDeclarationsCacheEntry { + let mut entry_declarations = Vec::new(); + for declarations in slice.iter() { + entry_declarations.push(declarations.clone()); + } + ApplicableDeclarationsCacheEntry { + declarations: entry_declarations, + } + } +} + +impl PartialEq for ApplicableDeclarationsCacheEntry { + fn eq(&self, other: &ApplicableDeclarationsCacheEntry) -> bool { + let this_as_query = ApplicableDeclarationsCacheQuery::new(self.declarations.as_slice()); + this_as_query.equiv(other) + } +} + +impl Hash for ApplicableDeclarationsCacheEntry { + fn hash(&self, state: &mut sip::SipState) { + let tmp = ApplicableDeclarationsCacheQuery::new(self.declarations.as_slice()); + tmp.hash(state); + } +} + +struct ApplicableDeclarationsCacheQuery<'a> { + declarations: &'a [DeclarationBlock], +} + +impl<'a> ApplicableDeclarationsCacheQuery<'a> { + fn new(declarations: &'a [DeclarationBlock]) -> ApplicableDeclarationsCacheQuery<'a> { + ApplicableDeclarationsCacheQuery { + declarations: declarations, + } + } +} + +// Workaround for lack of `ptr_eq` on Arcs... +#[inline] +fn arc_ptr_eq<T>(a: &Arc<T>, b: &Arc<T>) -> bool { + unsafe { + let a: uint = mem::transmute_copy(a); + let b: uint = mem::transmute_copy(b); + a == b + } +} + +impl<'a> Equiv<ApplicableDeclarationsCacheEntry> for ApplicableDeclarationsCacheQuery<'a> { + fn equiv(&self, other: &ApplicableDeclarationsCacheEntry) -> bool { + if self.declarations.len() != other.declarations.len() { + return false + } + for (this, other) in self.declarations.iter().zip(other.declarations.iter()) { + if !arc_ptr_eq(&this.declarations, &other.declarations) { + return false + } + } + return true + } +} + + +impl<'a> Hash for ApplicableDeclarationsCacheQuery<'a> { + fn hash(&self, state: &mut sip::SipState) { + for declaration in self.declarations.iter() { + let ptr: uint = unsafe { + mem::transmute_copy(declaration) + }; + ptr.hash(state); + } + } +} + +static APPLICABLE_DECLARATIONS_CACHE_SIZE: uint = 32; + +pub struct ApplicableDeclarationsCache { + cache: SimpleHashCache<ApplicableDeclarationsCacheEntry,Arc<ComputedValues>>, +} + +impl ApplicableDeclarationsCache { + pub fn new() -> ApplicableDeclarationsCache { + ApplicableDeclarationsCache { + cache: SimpleHashCache::new(APPLICABLE_DECLARATIONS_CACHE_SIZE), + } + } + + fn find(&self, declarations: &[DeclarationBlock]) -> Option<Arc<ComputedValues>> { + match self.cache.find_equiv(&ApplicableDeclarationsCacheQuery::new(declarations)) { + None => None, + Some(ref values) => Some((*values).clone()), + } + } + + fn insert(&mut self, declarations: &[DeclarationBlock], style: Arc<ComputedValues>) { + self.cache.insert(ApplicableDeclarationsCacheEntry::new(declarations), style) + } +} + +/// An LRU cache of the last few nodes seen, so that we can aggressively try to reuse their styles. +pub struct StyleSharingCandidateCache { + cache: LRUCache<StyleSharingCandidate,()>, +} + +#[deriving(Clone)] +pub struct StyleSharingCandidate { + pub style: Arc<ComputedValues>, + pub parent_style: Arc<ComputedValues>, + pub local_name: Atom, + pub class: Option<DOMString>, +} + +impl PartialEq for StyleSharingCandidate { + fn eq(&self, other: &StyleSharingCandidate) -> bool { + arc_ptr_eq(&self.style, &other.style) && + arc_ptr_eq(&self.parent_style, &other.parent_style) && + self.local_name == other.local_name && + self.class == other.class + } +} + +impl StyleSharingCandidate { + /// Attempts to create a style sharing candidate from this node. Returns + /// the style sharing candidate or `None` if this node is ineligible for + /// style sharing. + fn new(node: &LayoutNode) -> Option<StyleSharingCandidate> { + let parent_node = match node.parent_node() { + None => return None, + Some(parent_node) => parent_node, + }; + if !parent_node.is_element() { + return None + } + + let style = unsafe { + match *node.borrow_layout_data_unchecked() { + None => return None, + Some(ref layout_data_ref) => { + match layout_data_ref.shared_data.style { + None => return None, + Some(ref data) => (*data).clone(), + } + } + } + }; + let parent_style = unsafe { + match *parent_node.borrow_layout_data_unchecked() { + None => return None, + Some(ref parent_layout_data_ref) => { + match parent_layout_data_ref.shared_data.style { + None => return None, + Some(ref data) => (*data).clone(), + } + } + } + }; + + let mut style = Some(style); + let mut parent_style = Some(parent_style); + let element = node.as_element(); + if element.style_attribute().is_some() { + return None + } + + Some(StyleSharingCandidate { + style: style.take_unwrap(), + parent_style: parent_style.take_unwrap(), + local_name: element.get_local_name().clone(), + class: element.get_attr(&Null, "class") + .map(|string| string.to_string()), + }) + } + + fn can_share_style_with(&self, element: &LayoutElement) -> bool { + if *element.get_local_name() != self.local_name { + return false + } + match (&self.class, element.get_attr(&Null, "class")) { + (&None, Some(_)) | (&Some(_), None) => return false, + (&Some(ref this_class), Some(element_class)) if element_class != this_class.as_slice() => { + return false + } + (&Some(_), Some(_)) | (&None, None) => {} + } + true + } +} + +static STYLE_SHARING_CANDIDATE_CACHE_SIZE: uint = 40; + +impl StyleSharingCandidateCache { + pub fn new() -> StyleSharingCandidateCache { + StyleSharingCandidateCache { + cache: LRUCache::new(STYLE_SHARING_CANDIDATE_CACHE_SIZE), + } + } + + pub fn iter<'a>(&'a self) -> Items<'a,(StyleSharingCandidate,())> { + self.cache.iter() + } + + pub fn insert_if_possible(&mut self, node: &LayoutNode) { + match StyleSharingCandidate::new(node) { + None => {} + Some(candidate) => self.cache.insert(candidate, ()) + } + } + + pub fn touch(&mut self, index: uint) { + self.cache.touch(index) + } +} + +/// The results of attempting to share a style. +pub enum StyleSharingResult<'ln> { + /// We didn't find anybody to share the style with. The boolean indicates whether the style + /// is shareable at all. + CannotShare(bool), + /// The node's style can be shared. The integer specifies the index in the LRU cache that was + /// hit. + StyleWasShared(uint), +} + +pub trait MatchMethods { + /// Performs aux initialization, selector matching, cascading, and flow construction + /// sequentially. + fn recalc_style_for_subtree(&self, + stylist: &Stylist, + layout_context: &LayoutContext, + applicable_declarations: &mut ApplicableDeclarations, + parent: Option<LayoutNode>); + + fn match_node(&self, + stylist: &Stylist, + applicable_declarations: &mut ApplicableDeclarations, + shareable: &mut bool); + + /// Attempts to share a style with another node. This method is unsafe because it depends on + /// the `style_sharing_candidate_cache` having only live nodes in it, and we have no way to + /// guarantee that at the type system level yet. + unsafe fn share_style_if_possible(&self, + style_sharing_candidate_cache: + &mut StyleSharingCandidateCache, + parent: Option<LayoutNode>) + -> StyleSharingResult; + + unsafe fn cascade_node(&self, + parent: Option<LayoutNode>, + applicable_declarations: &ApplicableDeclarations, + applicable_declarations_cache: &mut ApplicableDeclarationsCache); +} + +trait PrivateMatchMethods { + fn cascade_node_pseudo_element(&self, + parent_style: Option<&Arc<ComputedValues>>, + applicable_declarations: &[DeclarationBlock], + style: &mut Option<Arc<ComputedValues>>, + applicable_declarations_cache: &mut + ApplicableDeclarationsCache, + shareable: bool); + + fn share_style_with_candidate_if_possible(&self, + parent_node: Option<LayoutNode>, + candidate: &StyleSharingCandidate) + -> Option<Arc<ComputedValues>>; +} + +impl<'ln> PrivateMatchMethods for LayoutNode<'ln> { + fn cascade_node_pseudo_element(&self, + parent_style: Option<&Arc<ComputedValues>>, + applicable_declarations: &[DeclarationBlock], + style: &mut Option<Arc<ComputedValues>>, + applicable_declarations_cache: &mut + ApplicableDeclarationsCache, + shareable: bool) { + let this_style; + let cacheable; + match parent_style { + Some(ref parent_style) => { + let cache_entry = applicable_declarations_cache.find(applicable_declarations); + let cached_computed_values = match cache_entry { + None => None, + Some(ref style) => Some(&**style), + }; + let (the_style, is_cacheable) = cascade(applicable_declarations, + shareable, + Some(&***parent_style), + cached_computed_values); + cacheable = is_cacheable; + this_style = Arc::new(the_style); + } + None => { + let (the_style, is_cacheable) = cascade(applicable_declarations, + shareable, + None, + None); + cacheable = is_cacheable; + this_style = Arc::new(the_style); + } + }; + + // Cache the resolved style if it was cacheable. + if cacheable { + applicable_declarations_cache.insert(applicable_declarations, this_style.clone()); + } + + *style = Some(this_style); + } + + + fn share_style_with_candidate_if_possible(&self, + parent_node: Option<LayoutNode>, + candidate: &StyleSharingCandidate) + -> Option<Arc<ComputedValues>> { + assert!(self.is_element()); + + let parent_node = match parent_node { + Some(ref parent_node) if parent_node.is_element() => parent_node, + Some(_) | None => return None, + }; + + let parent_layout_data: &Option<LayoutDataWrapper> = unsafe { + mem::transmute(parent_node.borrow_layout_data_unchecked()) + }; + match parent_layout_data { + &Some(ref parent_layout_data_ref) => { + // Check parent style. + let parent_style = parent_layout_data_ref.shared_data.style.as_ref().unwrap(); + if !arc_ptr_eq(parent_style, &candidate.parent_style) { + return None + } + + // Check tag names, classes, etc. + if !candidate.can_share_style_with(&self.as_element()) { + return None + } + + return Some(candidate.style.clone()) + } + _ => {} + } + + None + } +} + +impl<'ln> MatchMethods for LayoutNode<'ln> { + fn match_node(&self, + stylist: &Stylist, + applicable_declarations: &mut ApplicableDeclarations, + shareable: &mut bool) { + let style_attribute = self.as_element().style_attribute().as_ref(); + + applicable_declarations.normal_shareable = + stylist.push_applicable_declarations(self, + style_attribute, + None, + &mut applicable_declarations.normal); + stylist.push_applicable_declarations(self, + None, + Some(Before), + &mut applicable_declarations.before); + stylist.push_applicable_declarations(self, + None, + Some(After), + &mut applicable_declarations.after); + + *shareable = applicable_declarations.normal_shareable + } + + unsafe fn share_style_if_possible(&self, + style_sharing_candidate_cache: + &mut StyleSharingCandidateCache, + parent: Option<LayoutNode>) + -> StyleSharingResult { + if !self.is_element() { + return CannotShare(false) + } + let ok = { + let element = self.as_element(); + element.style_attribute().is_none() && element.get_attr(&Null, "id").is_none() + }; + if !ok { + return CannotShare(false) + } + + for (i, &(ref candidate, ())) in style_sharing_candidate_cache.iter().enumerate() { + match self.share_style_with_candidate_if_possible(parent.clone(), candidate) { + Some(shared_style) => { + // Yay, cache hit. Share the style. + let mut layout_data_ref = self.mutate_layout_data(); + layout_data_ref.get_mut_ref().shared_data.style = Some(shared_style); + return StyleWasShared(i) + } + None => {} + } + } + + CannotShare(true) + } + + fn recalc_style_for_subtree(&self, + stylist: &Stylist, + layout_context: &LayoutContext, + applicable_declarations: &mut ApplicableDeclarations, + parent: Option<LayoutNode>) { + self.initialize_layout_data(layout_context.shared.layout_chan.clone()); + + // First, check to see whether we can share a style with someone. + let sharing_result = unsafe { + self.share_style_if_possible(layout_context.style_sharing_candidate_cache(), parent.clone()) + }; + + // Otherwise, match and cascade selectors. + match sharing_result { + CannotShare(mut shareable) => { + if self.is_element() { + self.match_node(stylist, applicable_declarations, &mut shareable) + } + + unsafe { + self.cascade_node(parent, + applicable_declarations, + layout_context.applicable_declarations_cache()) + } + + applicable_declarations.clear(); + + // Add ourselves to the LRU cache. + if shareable { + layout_context.style_sharing_candidate_cache().insert_if_possible(self) + } + } + StyleWasShared(index) => layout_context.style_sharing_candidate_cache().touch(index), + } + + for kid in self.children() { + kid.recalc_style_for_subtree(stylist, + layout_context, + applicable_declarations, + Some(self.clone())) + } + + // Construct flows. + let layout_node = ThreadSafeLayoutNode::new(self); + let mut flow_constructor = FlowConstructor::new(layout_context); + flow_constructor.process(&layout_node); + } + + unsafe fn cascade_node(&self, + parent: Option<LayoutNode>, + applicable_declarations: &ApplicableDeclarations, + applicable_declarations_cache: &mut ApplicableDeclarationsCache) { + // Get our parent's style. This must be unsafe so that we don't touch the parent's + // borrow flags. + // + // FIXME(pcwalton): Isolate this unsafety into the `wrapper` module to allow + // enforced safe, race-free access to the parent style. + let parent_style = match parent { + None => None, + Some(parent_node) => { + let parent_layout_data = parent_node.borrow_layout_data_unchecked(); + match *parent_layout_data { + None => fail!("no parent data?!"), + Some(ref parent_layout_data) => { + match parent_layout_data.shared_data.style { + None => fail!("parent hasn't been styled yet?!"), + Some(ref style) => Some(style), + } + } + } + } + }; + + let mut layout_data_ref = self.mutate_layout_data(); + match &mut *layout_data_ref { + &None => fail!("no layout data"), + &Some(ref mut layout_data) => { + self.cascade_node_pseudo_element(parent_style, + applicable_declarations.normal.as_slice(), + &mut layout_data.shared_data.style, + applicable_declarations_cache, + applicable_declarations.normal_shareable); + if applicable_declarations.before.len() > 0 { + self.cascade_node_pseudo_element(Some(layout_data.shared_data.style.get_ref()), + applicable_declarations.before.as_slice(), + &mut layout_data.data.before_style, + applicable_declarations_cache, + false); + } + if applicable_declarations.after.len() > 0 { + self.cascade_node_pseudo_element(Some(layout_data.shared_data.style.get_ref()), + applicable_declarations.after.as_slice(), + &mut layout_data.data.after_style, + applicable_declarations_cache, + false); + } + } + } + } +} + diff --git a/components/layout/css/node_style.rs b/components/layout/css/node_style.rs new file mode 100644 index 00000000000..e201b0050aa --- /dev/null +++ b/components/layout/css/node_style.rs @@ -0,0 +1,30 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Style retrieval from DOM elements. + +use css::node_util::NodeUtil; +use incremental::RestyleDamage; +use wrapper::ThreadSafeLayoutNode; + +use style::ComputedValues; +use sync::Arc; + +/// Node mixin providing `style` method that returns a `NodeStyle` +pub trait StyledNode { + fn style<'a>(&'a self) -> &'a Arc<ComputedValues>; + fn restyle_damage(&self) -> RestyleDamage; +} + +impl<'ln> StyledNode for ThreadSafeLayoutNode<'ln> { + #[inline] + fn style<'a>(&'a self) -> &'a Arc<ComputedValues> { + self.get_css_select_results() + } + + fn restyle_damage(&self) -> RestyleDamage { + self.get_restyle_damage() + } +} + diff --git a/components/layout/css/node_util.rs b/components/layout/css/node_util.rs new file mode 100644 index 00000000000..150995428ea --- /dev/null +++ b/components/layout/css/node_util.rs @@ -0,0 +1,90 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use incremental::RestyleDamage; +use util::LayoutDataAccess; +use wrapper::{TLayoutNode, ThreadSafeLayoutNode}; +use wrapper::{After, AfterBlock, Before, BeforeBlock, Normal}; +use std::mem; +use style::ComputedValues; +use sync::Arc; + +pub trait NodeUtil { + fn get_css_select_results<'a>(&'a self) -> &'a Arc<ComputedValues>; + fn have_css_select_results(&self) -> bool; + + fn get_restyle_damage(&self) -> RestyleDamage; + fn set_restyle_damage(&self, damage: RestyleDamage); +} + +impl<'ln> NodeUtil for ThreadSafeLayoutNode<'ln> { + /// Returns the style results for the given node. If CSS selector + /// matching has not yet been performed, fails. + #[inline] + fn get_css_select_results<'a>(&'a self) -> &'a Arc<ComputedValues> { + unsafe { + let layout_data_ref = self.borrow_layout_data(); + match self.get_pseudo_element_type() { + Before | BeforeBlock => { + mem::transmute(layout_data_ref.as_ref() + .unwrap() + .data + .before_style + .as_ref() + .unwrap()) + } + After | AfterBlock => { + mem::transmute(layout_data_ref.as_ref() + .unwrap() + .data + .after_style + .as_ref() + .unwrap()) + } + Normal => { + mem::transmute(layout_data_ref.as_ref() + .unwrap() + .shared_data + .style + .as_ref() + .unwrap()) + } + } + } + } + + /// Does this node have a computed style yet? + fn have_css_select_results(&self) -> bool { + let layout_data_ref = self.borrow_layout_data(); + layout_data_ref.get_ref().shared_data.style.is_some() + } + + /// Get the description of how to account for recent style changes. + /// This is a simple bitfield and fine to copy by value. + fn get_restyle_damage(&self) -> RestyleDamage { + // For DOM elements, if we haven't computed damage yet, assume the worst. + // Other nodes don't have styles. + let default = if self.node_is_element() { + RestyleDamage::all() + } else { + RestyleDamage::empty() + }; + + let layout_data_ref = self.borrow_layout_data(); + layout_data_ref + .get_ref() + .data + .restyle_damage + .unwrap_or(default) + } + + /// Set the restyle damage field. + fn set_restyle_damage(&self, damage: RestyleDamage) { + let mut layout_data_ref = self.mutate_layout_data(); + match &mut *layout_data_ref { + &Some(ref mut layout_data) => layout_data.data.restyle_damage = Some(damage), + _ => fail!("no layout data for this node"), + } + } +} diff --git a/components/layout/extra.rs b/components/layout/extra.rs new file mode 100644 index 00000000000..7b731185272 --- /dev/null +++ b/components/layout/extra.rs @@ -0,0 +1,44 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Code for managing the layout data in the DOM. + +use util::{PrivateLayoutData, LayoutDataAccess, LayoutDataWrapper}; +use wrapper::LayoutNode; +use script::dom::node::SharedLayoutData; +use script::layout_interface::LayoutChan; + +/// Functionality useful for querying the layout-specific data on DOM nodes. +pub trait LayoutAuxMethods { + fn initialize_layout_data(&self, chan: LayoutChan); + fn initialize_style_for_subtree(&self, chan: LayoutChan); +} + +impl<'ln> LayoutAuxMethods for LayoutNode<'ln> { + /// Resets layout data and styles for the node. + /// + /// FIXME(pcwalton): Do this as part of fragment building instead of in a traversal. + fn initialize_layout_data(&self, chan: LayoutChan) { + let mut layout_data_ref = self.mutate_layout_data(); + match *layout_data_ref { + None => { + *layout_data_ref = Some(LayoutDataWrapper { + chan: Some(chan), + shared_data: SharedLayoutData { style: None }, + data: box PrivateLayoutData::new(), + }); + } + Some(_) => {} + } + } + + /// Resets layout data and styles for a Node tree. + /// + /// FIXME(pcwalton): Do this as part of fragment building instead of in a traversal. + fn initialize_style_for_subtree(&self, chan: LayoutChan) { + for n in self.traverse_preorder() { + n.initialize_layout_data(chan.clone()); + } + } +} diff --git a/components/layout/floats.rs b/components/layout/floats.rs new file mode 100644 index 00000000000..94017e6b3f7 --- /dev/null +++ b/components/layout/floats.rs @@ -0,0 +1,439 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use servo_util::geometry::{Au, max, min}; +use servo_util::logical_geometry::WritingMode; +use servo_util::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize}; +use std::i32; +use std::fmt; +use style::computed_values::float; +use sync::Arc; + +/// The kind of float: left or right. +#[deriving(Clone, Encodable)] +pub enum FloatKind { + FloatLeft, + FloatRight +} + +impl FloatKind { + pub fn from_property(property: float::T) -> FloatKind { + match property { + float::none => fail!("can't create a float type from an unfloated property"), + float::left => FloatLeft, + float::right => FloatRight, + } + } +} + +/// The kind of clearance: left, right, or both. +pub enum ClearType { + ClearLeft, + ClearRight, + ClearBoth, +} + +/// Information about a single float. +#[deriving(Clone)] +struct Float { + /// The boundaries of this float. + bounds: LogicalRect<Au>, + /// The kind of float: left or right. + kind: FloatKind, +} + +impl fmt::Show for Float { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "bounds={} kind={:?}", self.bounds, self.kind) + } +} + +/// Information about the floats next to a flow. +/// +/// FIXME(pcwalton): When we have fast `MutexArc`s, try removing `#[deriving(Clone)]` and wrap in a +/// mutex. +#[deriving(Clone)] +struct FloatList { + /// Information about each of the floats here. + floats: Vec<Float>, + /// Cached copy of the maximum block-start offset of the float. + max_block_start: Au, +} + +impl FloatList { + fn new() -> FloatList { + FloatList { + floats: vec!(), + max_block_start: Au(0), + } + } +} + +impl fmt::Show for FloatList { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "max_block_start={} floats={:?}", self.max_block_start, self.floats) + } +} + +/// Wraps a `FloatList` to avoid allocation in the common case of no floats. +/// +/// FIXME(pcwalton): When we have fast `MutexArc`s, try removing `CowArc` and use a mutex instead. +#[deriving(Clone)] +struct FloatListRef { + list: Option<Arc<FloatList>>, +} + +impl FloatListRef { + fn new() -> FloatListRef { + FloatListRef { + list: None, + } + } + + /// Returns true if the list is allocated and false otherwise. If false, there are guaranteed + /// not to be any floats. + fn is_present(&self) -> bool { + self.list.is_some() + } + + #[inline] + fn get<'a>(&'a self) -> Option<&'a FloatList> { + match self.list { + None => None, + Some(ref list) => Some(&**list), + } + } + + #[allow(experimental)] + #[inline] + fn get_mut<'a>(&'a mut self) -> &'a mut FloatList { + if self.list.is_none() { + self.list = Some(Arc::new(FloatList::new())) + } + self.list.as_mut().unwrap().make_unique() + } +} + +/// All the information necessary to place a float. +pub struct PlacementInfo { + /// The dimensions of the float. + pub size: LogicalSize<Au>, + /// The minimum block-start of the float, as determined by earlier elements. + pub ceiling: Au, + /// The maximum inline-end position of the float, generally determined by the containing block. + pub max_inline_size: Au, + /// The kind of float. + pub kind: FloatKind +} + +impl fmt::Show for PlacementInfo { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "size={} ceiling={} max_inline_size={} kind={:?}", self.size, self.ceiling, self.max_inline_size, self.kind) + } +} + +fn range_intersect(block_start_1: Au, block_end_1: Au, block_start_2: Au, block_end_2: Au) -> (Au, Au) { + (max(block_start_1, block_start_2), min(block_end_1, block_end_2)) +} + +/// Encapsulates information about floats. This is optimized to avoid allocation if there are +/// no floats, and to avoid copying when translating the list of floats downward. +#[deriving(Clone)] +pub struct Floats { + /// The list of floats. + list: FloatListRef, + /// The offset of the flow relative to the first float. + offset: LogicalSize<Au>, + pub writing_mode: WritingMode, +} + +impl fmt::Show for Floats { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.list.get() { + None => { + write!(f, "[empty]") + } + Some(list) => { + write!(f, "offset={} floats={}", self.offset, list) + } + } + } +} + +impl Floats { + /// Creates a new `Floats` object. + pub fn new(writing_mode: WritingMode) -> Floats { + Floats { + list: FloatListRef::new(), + offset: LogicalSize::zero(writing_mode), + writing_mode: writing_mode, + } + } + + /// Adjusts the recorded offset of the flow relative to the first float. + pub fn translate(&mut self, delta: LogicalSize<Au>) { + self.offset = self.offset + delta + } + + /// Returns the position of the last float in flow coordinates. + pub fn last_float_pos(&self) -> Option<LogicalPoint<Au>> { + match self.list.get() { + None => None, + Some(list) => { + match list.floats.last() { + None => None, + Some(float) => Some(float.bounds.start + self.offset), + } + } + } + } + + /// Returns a rectangle that encloses the region from block-start to block-start + block-size, with inline-size small + /// enough that it doesn't collide with any floats. max_x is the x-coordinate beyond which + /// floats have no effect. (Generally this is the containing block inline-size.) + pub fn available_rect(&self, block_start: Au, block_size: Au, max_x: Au) -> Option<LogicalRect<Au>> { + let list = match self.list.get() { + None => return None, + Some(list) => list, + }; + + let block_start = block_start - self.offset.block; + + debug!("available_rect: trying to find space at {}", block_start); + + // Relevant dimensions for the inline-end-most inline-start float + let mut max_inline_start = Au(0) - self.offset.inline; + let mut l_block_start = None; + let mut l_block_end = None; + // Relevant dimensions for the inline-start-most inline-end float + let mut min_inline_end = max_x - self.offset.inline; + let mut r_block_start = None; + let mut r_block_end = None; + + // Find the float collisions for the given vertical range. + for float in list.floats.iter() { + debug!("available_rect: Checking for collision against float"); + let float_pos = float.bounds.start; + let float_size = float.bounds.size; + + debug!("float_pos: {}, float_size: {}", float_pos, float_size); + match float.kind { + FloatLeft if float_pos.i + float_size.inline > max_inline_start && + float_pos.b + float_size.block > block_start && float_pos.b < block_start + block_size => { + max_inline_start = float_pos.i + float_size.inline; + + l_block_start = Some(float_pos.b); + l_block_end = Some(float_pos.b + float_size.block); + + debug!("available_rect: collision with inline_start float: new max_inline_start is {}", + max_inline_start); + } + FloatRight if float_pos.i < min_inline_end && + float_pos.b + float_size.block > block_start && float_pos.b < block_start + block_size => { + min_inline_end = float_pos.i; + + r_block_start = Some(float_pos.b); + r_block_end = Some(float_pos.b + float_size.block); + debug!("available_rect: collision with inline_end float: new min_inline_end is {}", + min_inline_end); + } + FloatLeft | FloatRight => {} + } + } + + // Extend the vertical range of the rectangle to the closest floats. + // If there are floats on both sides, take the intersection of the + // two areas. Also make sure we never return a block-start smaller than the + // given upper bound. + let (block_start, block_end) = match (r_block_start, r_block_end, l_block_start, l_block_end) { + (Some(r_block_start), Some(r_block_end), Some(l_block_start), Some(l_block_end)) => + range_intersect(max(block_start, r_block_start), r_block_end, max(block_start, l_block_start), l_block_end), + + (None, None, Some(l_block_start), Some(l_block_end)) => (max(block_start, l_block_start), l_block_end), + (Some(r_block_start), Some(r_block_end), None, None) => (max(block_start, r_block_start), r_block_end), + (None, None, None, None) => return None, + _ => fail!("Reached unreachable state when computing float area") + }; + + // FIXME(eatkinson): This assertion is too strong and fails in some cases. It is OK to + // return negative inline-sizes since we check against that inline-end away, but we should still + // undersrtand why they occur and add a stronger assertion here. + // assert!(max_inline-start < min_inline-end); + + assert!(block_start <= block_end, "Float position error"); + + Some(LogicalRect::new( + self.writing_mode, max_inline_start + self.offset.inline, block_start + self.offset.block, + min_inline_end - max_inline_start, block_end - block_start + )) + } + + /// Adds a new float to the list. + pub fn add_float(&mut self, info: &PlacementInfo) { + let new_info; + { + let list = self.list.get_mut(); + new_info = PlacementInfo { + size: info.size, + ceiling: max(info.ceiling, list.max_block_start + self.offset.block), + max_inline_size: info.max_inline_size, + kind: info.kind + } + } + + debug!("add_float: added float with info {:?}", new_info); + + let new_float = Float { + bounds: LogicalRect::from_point_size( + self.writing_mode, + self.place_between_floats(&new_info).start - self.offset, + info.size, + ), + kind: info.kind + }; + + let list = self.list.get_mut(); + list.floats.push(new_float); + list.max_block_start = max(list.max_block_start, new_float.bounds.start.b); + } + + /// Given the block-start 3 sides of the rectangle, finds the largest block-size that will result in the + /// rectangle not colliding with any floats. Returns None if that block-size is infinite. + fn max_block_size_for_bounds(&self, inline_start: Au, block_start: Au, inline_size: Au) -> Option<Au> { + let list = match self.list.get() { + None => return None, + Some(list) => list, + }; + + let block_start = block_start - self.offset.block; + let inline_start = inline_start - self.offset.inline; + let mut max_block_size = None; + + for float in list.floats.iter() { + if float.bounds.start.b + float.bounds.size.block > block_start && + float.bounds.start.i + float.bounds.size.inline > inline_start && + float.bounds.start.i < inline_start + inline_size { + let new_y = float.bounds.start.b; + max_block_size = Some(min(max_block_size.unwrap_or(new_y), new_y)); + } + } + + max_block_size.map(|h| h + self.offset.block) + } + + /// Given placement information, finds the closest place a fragment can be positioned without + /// colliding with any floats. + pub fn place_between_floats(&self, info: &PlacementInfo) -> LogicalRect<Au> { + debug!("place_between_floats: Placing object with {}", info.size); + + // If no floats, use this fast path. + if !self.list.is_present() { + match info.kind { + FloatLeft => { + return LogicalRect::new( + self.writing_mode, + Au(0), + info.ceiling, + info.max_inline_size, + Au(i32::MAX)) + } + FloatRight => { + return LogicalRect::new( + self.writing_mode, + info.max_inline_size - info.size.inline, + info.ceiling, + info.max_inline_size, + Au(i32::MAX)) + } + } + } + + // Can't go any higher than previous floats or previous elements in the document. + let mut float_b = info.ceiling; + loop { + let maybe_location = self.available_rect(float_b, info.size.block, info.max_inline_size); + debug!("place_float: Got available rect: {:?} for y-pos: {}", maybe_location, float_b); + match maybe_location { + // If there are no floats blocking us, return the current location + // TODO(eatkinson): integrate with overflow + None => { + return match info.kind { + FloatLeft => { + LogicalRect::new( + self.writing_mode, + Au(0), + float_b, + info.max_inline_size, + Au(i32::MAX)) + } + FloatRight => { + LogicalRect::new( + self.writing_mode, + info.max_inline_size - info.size.inline, + float_b, + info.max_inline_size, + Au(i32::MAX)) + } + } + } + Some(rect) => { + assert!(rect.start.b + rect.size.block != float_b, + "Non-terminating float placement"); + + // Place here if there is enough room + if rect.size.inline >= info.size.inline { + let block_size = self.max_block_size_for_bounds(rect.start.i, + rect.start.b, + rect.size.inline); + let block_size = block_size.unwrap_or(Au(i32::MAX)); + return match info.kind { + FloatLeft => { + LogicalRect::new( + self.writing_mode, + rect.start.i, + float_b, + rect.size.inline, + block_size) + } + FloatRight => { + LogicalRect::new( + self.writing_mode, + rect.start.i + rect.size.inline - info.size.inline, + float_b, + rect.size.inline, + block_size) + } + } + } + + // Try to place at the next-lowest location. + // Need to be careful of fencepost errors. + float_b = rect.start.b + rect.size.block; + } + } + } + } + + pub fn clearance(&self, clear: ClearType) -> Au { + let list = match self.list.get() { + None => return Au(0), + Some(list) => list, + }; + + let mut clearance = Au(0); + for float in list.floats.iter() { + match (clear, float.kind) { + (ClearLeft, FloatLeft) | + (ClearRight, FloatRight) | + (ClearBoth, _) => { + let b = self.offset.block + float.bounds.start.b + float.bounds.size.block; + clearance = max(clearance, b); + } + _ => {} + } + } + clearance + } +} + diff --git a/components/layout/flow.rs b/components/layout/flow.rs new file mode 100644 index 00000000000..2759ebbe74b --- /dev/null +++ b/components/layout/flow.rs @@ -0,0 +1,1138 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Servo's experimental layout system builds a tree of `Flow` and `Fragment` objects and solves +//! layout constraints to obtain positions and display attributes of tree nodes. Positions are +//! computed in several tree traversals driven by the fundamental data dependencies required by +/// inline and block layout. +/// +/// Flows are interior nodes in the layout tree and correspond closely to *flow contexts* in the +/// CSS specification. Flows are responsible for positioning their child flow contexts and fragments. +/// Flows have purpose-specific fields, such as auxiliary line structs, out-of-flow child +/// lists, and so on. +/// +/// Currently, the important types of flows are: +/// +/// * `BlockFlow`: A flow that establishes a block context. It has several child flows, each of +/// which are positioned according to block formatting context rules (CSS block boxes). Block +/// flows also contain a single box to represent their rendered borders, padding, etc. +/// The BlockFlow at the root of the tree has special behavior: it stretches to the boundaries of +/// the viewport. +/// +/// * `InlineFlow`: A flow that establishes an inline context. It has a flat list of child +/// fragments/flows that are subject to inline layout and line breaking and structs to represent +/// line breaks and mapping to CSS boxes, for the purpose of handling `getClientRects()` and +/// similar methods. + +use css::node_style::StyledNode; +use block::BlockFlow; +use context::LayoutContext; +use floats::Floats; +use flow_list::{FlowList, Link, FlowListIterator, MutFlowListIterator}; +use flow_ref::FlowRef; +use fragment::{Fragment, TableRowFragment, TableCellFragment}; +use incremental::RestyleDamage; +use inline::InlineFlow; +use model::{CollapsibleMargins, IntrinsicISizes, MarginCollapseInfo}; +use parallel::FlowParallelInfo; +use table_wrapper::TableWrapperFlow; +use table::TableFlow; +use table_colgroup::TableColGroupFlow; +use table_rowgroup::TableRowGroupFlow; +use table_row::TableRowFlow; +use table_caption::TableCaptionFlow; +use table_cell::TableCellFlow; +use wrapper::ThreadSafeLayoutNode; + +use collections::dlist::DList; +use geom::Point2D; +use gfx::display_list::DisplayList; +use gfx::render_task::RenderLayer; +use serialize::{Encoder, Encodable}; +use servo_msg::compositor_msg::LayerId; +use servo_util::geometry::Au; +use servo_util::logical_geometry::WritingMode; +use servo_util::logical_geometry::{LogicalRect, LogicalSize}; +use std::mem; +use std::num::Zero; +use std::fmt; +use std::iter::Zip; +use std::raw; +use std::sync::atomics::{AtomicUint, Relaxed, SeqCst}; +use std::slice::MutItems; +use style::computed_values::{clear, position, text_align}; + +/// Virtual methods that make up a float context. +/// +/// Note that virtual methods have a cost; we should not overuse them in Servo. Consider adding +/// methods to `ImmutableFlowUtils` or `MutableFlowUtils` before adding more methods here. +pub trait Flow: fmt::Show + ToString + Share { + // RTTI + // + // TODO(pcwalton): Use Rust's RTTI, once that works. + + /// Returns the class of flow that this is. + fn class(&self) -> FlowClass; + + /// If this is a block flow, returns the underlying object, borrowed immutably. Fails + /// otherwise. + fn as_immutable_block<'a>(&'a self) -> &'a BlockFlow { + fail!("called as_immutable_block() on a non-block flow") + } + + /// If this is a block flow, returns the underlying object. Fails otherwise. + fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow { + debug!("called as_block() on a flow of type {}", self.class()); + fail!("called as_block() on a non-block flow") + } + + /// If this is an inline flow, returns the underlying object, borrowed immutably. Fails + /// otherwise. + fn as_immutable_inline<'a>(&'a self) -> &'a InlineFlow { + fail!("called as_immutable_inline() on a non-inline flow") + } + + /// If this is an inline flow, returns the underlying object. Fails otherwise. + fn as_inline<'a>(&'a mut self) -> &'a mut InlineFlow { + fail!("called as_inline() on a non-inline flow") + } + + /// If this is a table wrapper flow, returns the underlying object. Fails otherwise. + fn as_table_wrapper<'a>(&'a mut self) -> &'a mut TableWrapperFlow { + fail!("called as_table_wrapper() on a non-tablewrapper flow") + } + + /// If this is a table flow, returns the underlying object. Fails otherwise. + fn as_table<'a>(&'a mut self) -> &'a mut TableFlow { + fail!("called as_table() on a non-table flow") + } + + /// If this is a table colgroup flow, returns the underlying object. Fails otherwise. + fn as_table_colgroup<'a>(&'a mut self) -> &'a mut TableColGroupFlow { + fail!("called as_table_colgroup() on a non-tablecolgroup flow") + } + + /// If this is a table rowgroup flow, returns the underlying object. Fails otherwise. + fn as_table_rowgroup<'a>(&'a mut self) -> &'a mut TableRowGroupFlow { + fail!("called as_table_rowgroup() on a non-tablerowgroup flow") + } + + /// If this is a table row flow, returns the underlying object. Fails otherwise. + fn as_table_row<'a>(&'a mut self) -> &'a mut TableRowFlow { + fail!("called as_table_row() on a non-tablerow flow") + } + + /// If this is a table cell flow, returns the underlying object. Fails otherwise. + fn as_table_caption<'a>(&'a mut self) -> &'a mut TableCaptionFlow { + fail!("called as_table_caption() on a non-tablecaption flow") + } + + /// If this is a table cell flow, returns the underlying object. Fails otherwise. + fn as_table_cell<'a>(&'a mut self) -> &'a mut TableCellFlow { + fail!("called as_table_cell() on a non-tablecell flow") + } + + /// If this is a table row or table rowgroup or table flow, returns column inline-sizes. + /// Fails otherwise. + fn col_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<Au> { + fail!("called col_inline_sizes() on an other flow than table-row/table-rowgroup/table") + } + + /// If this is a table row flow or table rowgroup flow or table flow, returns column min inline-sizes. + /// Fails otherwise. + fn col_min_inline_sizes<'a>(&'a self) -> &'a Vec<Au> { + fail!("called col_min_inline_sizes() on an other flow than table-row/table-rowgroup/table") + } + + /// If this is a table row flow or table rowgroup flow or table flow, returns column min inline-sizes. + /// Fails otherwise. + fn col_pref_inline_sizes<'a>(&'a self) -> &'a Vec<Au> { + fail!("called col_pref_inline_sizes() on an other flow than table-row/table-rowgroup/table") + } + + // Main methods + + /// 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 boxes it is responsible for flowing. + fn bubble_inline_sizes(&mut self, _ctx: &LayoutContext) { + fail!("bubble_inline_sizes not yet implemented") + } + + /// Pass 2 of reflow: computes inline-size. + fn assign_inline_sizes(&mut self, _ctx: &LayoutContext) { + fail!("assign_inline_sizes not yet implemented") + } + + /// Pass 3a of reflow: computes block-size. + fn assign_block_size<'a>(&mut self, _ctx: &'a LayoutContext<'a>) { + fail!("assign_block_size not yet implemented") + } + + /// Assigns block-sizes in-order; or, if this is a float, places the float. The default + /// implementation simply assigns block-sizes if this flow is impacted by floats. Returns true if + /// this child was impacted by floats or false otherwise. + fn assign_block_size_for_inorder_child_if_necessary<'a>(&mut self, layout_context: &'a LayoutContext<'a>) + -> bool { + let impacted = base(&*self).flags.impacted_by_floats(); + if impacted { + self.assign_block_size(layout_context); + } + impacted + } + + /// Phase 4 of reflow: computes absolute positions. + fn compute_absolute_position(&mut self) { + // The default implementation is a no-op. + } + + /// Returns the direction that this flow clears floats in, if any. + fn float_clearance(&self) -> clear::T { + clear::none + } + + /// Returns true if this float is a block formatting context and false otherwise. The default + /// implementation returns false. + fn is_block_formatting_context(&self, _only_impactable_by_floats: bool) -> bool { + false + } + + fn compute_collapsible_block_start_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. + fn mark_as_root(&mut self) {} + + // Note that the following functions are mostly called using static method + // dispatch, so it's ok to have them in this trait. Plus, they have + // different behaviour for different types of Flow, so they can't go into + // the Immutable / Mutable Flow Utils traits without additional casts. + + /// Return true if store overflow is delayed for this flow. + /// + /// Currently happens only for absolutely positioned flows. + fn is_store_overflow_delayed(&mut self) -> bool { + false + } + + fn is_root(&self) -> bool { + false + } + + fn is_float(&self) -> bool { + false + } + + /// The 'position' property of this flow. + fn positioning(&self) -> position::T { + position::static_ + } + + /// Return true if this flow has position 'fixed'. + fn is_fixed(&self) -> bool { + self.positioning() == position::fixed + } + + fn is_positioned(&self) -> bool { + self.is_relatively_positioned() || self.is_absolutely_positioned() + } + + fn is_relatively_positioned(&self) -> bool { + self.positioning() == position::relative + } + + fn is_absolutely_positioned(&self) -> bool { + self.positioning() == position::absolute || self.is_fixed() + } + + /// Return true if this is the root of an Absolute flow tree. + fn is_root_of_absolute_flow_tree(&self) -> bool { + false + } + + /// Returns true if this is an absolute containing block. + fn is_absolute_containing_block(&self) -> bool { + false + } + + /// Return the dimensions of the containing block generated by this flow for absolutely- + /// positioned descendants. For block flows, this is the padding box. + fn generated_containing_block_rect(&self) -> LogicalRect<Au> { + fail!("generated_containing_block_position not yet implemented for this flow") + } + + /// Returns a layer ID for the given fragment. + fn layer_id(&self, fragment_id: uint) -> LayerId { + unsafe { + let pointer: uint = mem::transmute(self); + LayerId(pointer, fragment_id) + } + } +} + +impl<'a, E, S: Encoder<E>> Encodable<S, E> for &'a Flow { + fn encode(&self, e: &mut S) -> Result<(), E> { + e.emit_struct("flow", 0, |e| { + try!(e.emit_struct_field("class", 0, |e| self.class().encode(e))) + e.emit_struct_field("data", 1, |e| { + match self.class() { + BlockFlowClass => self.as_immutable_block().encode(e), + InlineFlowClass => self.as_immutable_inline().encode(e), + _ => { Ok(()) } // TODO: Support tables + } + }) + }) + } +} + +// Base access + +#[inline(always)] +pub fn base<'a>(this: &'a Flow) -> &'a BaseFlow { + unsafe { + let obj = mem::transmute::<&'a Flow, raw::TraitObject>(this); + mem::transmute::<*mut (), &'a BaseFlow>(obj.data) + } +} + +/// Iterates over the children of this immutable flow. +pub fn imm_child_iter<'a>(flow: &'a Flow) -> FlowListIterator<'a> { + base(flow).children.iter() +} + +#[inline(always)] +pub fn mut_base<'a>(this: &'a mut Flow) -> &'a mut BaseFlow { + unsafe { + let obj = mem::transmute::<&'a mut Flow, raw::TraitObject>(this); + mem::transmute::<*mut (), &'a mut BaseFlow>(obj.data) + } +} + +/// Iterates over the children of this flow. +pub fn child_iter<'a>(flow: &'a mut Flow) -> MutFlowListIterator<'a> { + mut_base(flow).children.mut_iter() +} + +pub trait ImmutableFlowUtils { + // Convenience functions + + /// Returns true if this flow is a block or a float flow. + fn is_block_like(self) -> bool; + + /// Returns true if this flow is a table flow. + fn is_table(self) -> bool; + + /// Returns true if this flow is a table caption flow. + fn is_table_caption(self) -> bool; + + /// Returns true if this flow is a proper table child. + fn is_proper_table_child(self) -> bool; + + /// Returns true if this flow is a table row flow. + fn is_table_row(self) -> bool; + + /// Returns true if this flow is a table cell flow. + fn is_table_cell(self) -> bool; + + /// Returns true if this flow is a table colgroup flow. + fn is_table_colgroup(self) -> bool; + + /// Returns true if this flow is a table rowgroup flow. + fn is_table_rowgroup(self) -> bool; + + /// Returns true if this flow is one of table-related flows. + fn is_table_kind(self) -> bool; + + /// Returns true if anonymous flow is needed between this flow and child flow. + fn need_anonymous_flow(self, child: &Flow) -> bool; + + /// Generates missing child flow of this flow. + fn generate_missing_child_flow(self, node: &ThreadSafeLayoutNode) -> FlowRef; + + /// Returns true if this flow has no children. + fn is_leaf(self) -> bool; + + /// Returns the number of children that this flow possesses. + fn child_count(self) -> uint; + + /// Return true if this flow is a Block Container. + fn is_block_container(self) -> bool; + + /// Returns true if this flow is a block flow. + fn is_block_flow(self) -> bool; + + /// Returns true if this flow is an inline flow. + fn is_inline_flow(self) -> bool; + + /// Dumps the flow tree for debugging. + fn dump(self); + + /// Dumps the flow tree for debugging, with a prefix to indicate that we're at the given level. + fn dump_with_level(self, level: uint); +} + +pub trait MutableFlowUtils { + // Traversals + + /// Traverses the tree in preorder. + fn traverse_preorder<T:PreorderFlowTraversal>(self, traversal: &mut T) -> bool; + + /// Traverses the tree in postorder. + fn traverse_postorder<T:PostorderFlowTraversal>(self, traversal: &mut T) -> bool; + + // Mutators + + /// Computes the overflow region for this flow. + fn store_overflow(self, _: &LayoutContext); + + /// Builds the display lists for this flow. + fn build_display_list(self, layout_context: &LayoutContext); +} + +pub trait MutableOwnedFlowUtils { + /// Set absolute descendants for this flow. + /// + /// Set this flow as the Containing Block for all the absolute descendants. + fn set_abs_descendants(&mut self, abs_descendants: AbsDescendants); +} + +#[deriving(Encodable, PartialEq, Show)] +pub enum FlowClass { + BlockFlowClass, + InlineFlowClass, + TableWrapperFlowClass, + TableFlowClass, + TableColGroupFlowClass, + TableRowGroupFlowClass, + TableRowFlowClass, + TableCaptionFlowClass, + TableCellFlowClass, +} + +/// A top-down traversal. +pub trait PreorderFlowTraversal { + /// The operation to perform. Return true to continue or false to stop. + fn process(&mut self, flow: &mut Flow) -> bool; + + /// Returns true if this node should be pruned. If this returns true, we skip the operation + /// entirely and do not process any descendant nodes. This is called *before* child nodes are + /// visited. The default implementation never prunes any nodes. + fn should_prune(&mut self, _flow: &mut Flow) -> bool { + false + } +} + +/// A bottom-up traversal, with a optional in-order pass. +pub trait PostorderFlowTraversal { + /// The operation to perform. Return true to continue or false to stop. + fn process(&mut self, flow: &mut Flow) -> bool; + + /// Returns false if this node must be processed in-order. If this returns false, we skip the + /// operation for this node, but continue processing the ancestors. This is called *after* + /// child nodes are visited. + fn should_process(&mut self, _flow: &mut Flow) -> bool { + true + } + + /// Returns true if this node should be pruned. If this returns true, we skip the operation + /// entirely and do not process any descendant nodes. This is called *before* child nodes are + /// visited. The default implementation never prunes any nodes. + fn should_prune(&mut self, _flow: &mut Flow) -> bool { + false + } +} + +/// Flags used in flows, tightly packed to save space. +#[deriving(Clone, Encodable)] +pub struct FlowFlags(pub u8); + +/// The bitmask of flags that represent the `has_left_floated_descendants` and +/// `has_right_floated_descendants` fields. +/// +/// NB: If you update this field, you must update the bitfields below. +static HAS_FLOATED_DESCENDANTS_BITMASK: u8 = 0b0000_0011; + +// Whether this flow has descendants that float left in the same block formatting context. +bitfield!(FlowFlags, has_left_floated_descendants, set_has_left_floated_descendants, 0b0000_0001) + +// Whether this flow has descendants that float right in the same block formatting context. +bitfield!(FlowFlags, has_right_floated_descendants, set_has_right_floated_descendants, 0b0000_0010) + +// Whether this flow is impacted by floats to the left in the same block formatting context (i.e. +// its block-size depends on some prior flows with `float: left`). +bitfield!(FlowFlags, impacted_by_left_floats, set_impacted_by_left_floats, 0b0000_0100) + +// Whether this flow is impacted by floats to the right in the same block formatting context (i.e. +// its block-size depends on some prior flows with `float: right`). +bitfield!(FlowFlags, impacted_by_right_floats, set_impacted_by_right_floats, 0b0000_1000) + +/// The bitmask of flags that represent the text alignment field. +/// +/// NB: If you update this field, you must update the bitfields below. +static TEXT_ALIGN_BITMASK: u8 = 0b0011_0000; + +/// The number of bits we must shift off to handle the text alignment field. +/// +/// NB: If you update this field, you must update the bitfields below. +static TEXT_ALIGN_SHIFT: u8 = 4; + +// Whether this flow contains a flow that has its own layer within the same absolute containing +// block. +bitfield!(FlowFlags, + layers_needed_for_descendants, + set_layers_needed_for_descendants, + 0b0100_0000) + +// Whether this flow must have its own layer. Even if this flag is not set, it might get its own +// layer if it's deemed to be likely to overlap flows with their own layer. +bitfield!(FlowFlags, needs_layer, set_needs_layer, 0b1000_0000) + +impl FlowFlags { + /// Creates a new set of flow flags. + pub fn new() -> FlowFlags { + FlowFlags(0) + } + + /// Propagates text alignment flags from an appropriate parent flow per CSS 2.1. + /// + /// FIXME(#2265, pcwalton): It would be cleaner and faster to make this a derived CSS property + /// `-servo-text-align-in-effect`. + pub fn propagate_text_alignment_from_parent(&mut self, parent_flags: FlowFlags) { + self.set_text_align_override(parent_flags); + } + + #[inline] + pub fn text_align(self) -> text_align::T { + let FlowFlags(ff) = self; + FromPrimitive::from_u8((ff & TEXT_ALIGN_BITMASK) >> TEXT_ALIGN_SHIFT as uint).unwrap() + } + + #[inline] + pub fn set_text_align(&mut self, value: text_align::T) { + let FlowFlags(ff) = *self; + *self = FlowFlags((ff & !TEXT_ALIGN_BITMASK) | ((value as u8) << TEXT_ALIGN_SHIFT as uint)) + } + + #[inline] + pub fn set_text_align_override(&mut self, parent: FlowFlags) { + let FlowFlags(ff) = *self; + let FlowFlags(pff) = parent; + *self = FlowFlags(ff | (pff & TEXT_ALIGN_BITMASK)) + } + + #[inline] + pub fn union_floated_descendants_flags(&mut self, other: FlowFlags) { + let FlowFlags(my_flags) = *self; + let FlowFlags(other_flags) = other; + *self = FlowFlags(my_flags | (other_flags & HAS_FLOATED_DESCENDANTS_BITMASK)) + } + + #[inline] + pub fn impacted_by_floats(&self) -> bool { + self.impacted_by_left_floats() || self.impacted_by_right_floats() + } +} + +/// The Descendants of a flow. +/// +/// Also, details about their position wrt this flow. +pub struct Descendants { + /// Links to every descendant. This must be private because it is unsafe to leak `FlowRef`s to + /// layout. + descendant_links: Vec<FlowRef>, + + /// Static y offsets of all descendants from the start of this flow box. + pub static_b_offsets: Vec<Au>, +} + +impl Descendants { + pub fn new() -> Descendants { + Descendants { + descendant_links: Vec::new(), + static_b_offsets: Vec::new(), + } + } + + pub fn len(&self) -> uint { + self.descendant_links.len() + } + + pub fn push(&mut self, given_descendant: FlowRef) { + self.descendant_links.push(given_descendant); + } + + /// Push the given descendants on to the existing descendants. + /// + /// Ignore any static y offsets, because they are None before layout. + pub fn push_descendants(&mut self, given_descendants: Descendants) { + for elem in given_descendants.descendant_links.move_iter() { + self.descendant_links.push(elem); + } + } + + /// Return an iterator over the descendant flows. + pub fn iter<'a>(&'a mut self) -> DescendantIter<'a> { + DescendantIter { + iter: self.descendant_links.mut_slice_from(0).mut_iter(), + } + } + + /// Return an iterator over (descendant, static y offset). + pub fn iter_with_offset<'a>(&'a mut self) -> DescendantOffsetIter<'a> { + let descendant_iter = DescendantIter { + iter: self.descendant_links.mut_slice_from(0).mut_iter(), + }; + descendant_iter.zip(self.static_b_offsets.mut_slice_from(0).mut_iter()) + } +} + +pub type AbsDescendants = Descendants; + +pub struct DescendantIter<'a> { + iter: MutItems<'a, FlowRef>, +} + +impl<'a> Iterator<&'a mut Flow> for DescendantIter<'a> { + fn next(&mut self) -> Option<&'a mut Flow> { + match self.iter.next() { + None => None, + Some(ref mut flow) => { + unsafe { + let result: &'a mut Flow = mem::transmute(flow.get_mut()); + Some(result) + } + } + } + } +} + +pub type DescendantOffsetIter<'a> = Zip<DescendantIter<'a>, MutItems<'a, Au>>; + +/// Information needed to compute absolute (i.e. viewport-relative) flow positions (not to be +/// confused with absolutely-positioned flows). +#[deriving(Encodable)] +pub struct AbsolutePositionInfo { + /// The size of the containing block for relatively-positioned descendants. + pub relative_containing_block_size: LogicalSize<Au>, + /// The position of the absolute containing block. + pub absolute_containing_block_position: Point2D<Au>, + /// Whether the absolute containing block forces positioned descendants to be layerized. + /// + /// FIXME(pcwalton): Move into `FlowFlags`. + pub layers_needed_for_positioned_flows: bool, +} + +impl AbsolutePositionInfo { + pub fn new(writing_mode: WritingMode) -> AbsolutePositionInfo { + // FIXME(pcwalton): The initial relative containing block-size should be equal to the size + // of the root layer. + AbsolutePositionInfo { + relative_containing_block_size: LogicalSize::zero(writing_mode), + absolute_containing_block_position: Zero::zero(), + layers_needed_for_positioned_flows: false, + } + } +} + +/// Data common to all flows. +pub struct BaseFlow { + /// NB: Must be the first element. + /// + /// The necessity of this will disappear once we have dynamically-sized types. + ref_count: AtomicUint, + + pub restyle_damage: RestyleDamage, + + /// The children of this flow. + pub children: FlowList, + pub next_sibling: Link, + pub prev_sibling: Link, + + /* layout computations */ + // TODO: min/pref and position are used during disjoint phases of + // layout; maybe combine into a single enum to save space. + pub intrinsic_inline_sizes: IntrinsicISizes, + + /// 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*. + pub position: LogicalRect<Au>, + + /// The amount of overflow of this flow, relative to the containing block. Must include all the + /// pixels of all the display list items for correct invalidation. + pub overflow: LogicalRect<Au>, + + /// Data used during parallel traversals. + /// + /// TODO(pcwalton): Group with other transient data to save space. + pub parallel: FlowParallelInfo, + + /// The floats next to this flow. + pub floats: Floats, + + /// The collapsible margins for this flow, if any. + pub collapsible_margins: CollapsibleMargins, + + /// The position of this flow in page coordinates, computed during display list construction. + pub abs_position: Point2D<Au>, + + /// Details about descendants with position 'absolute' or 'fixed' for which we are the + /// containing block. This is in tree order. This includes any direct children. + pub abs_descendants: AbsDescendants, + + /// Offset wrt the nearest positioned ancestor - aka the Containing Block + /// for any absolutely positioned elements. + pub absolute_static_i_offset: Au, + + /// Offset wrt the Initial Containing Block. + pub fixed_static_i_offset: Au, + + /// Reference to the Containing Block, if this flow is absolutely positioned. + pub absolute_cb: ContainingBlockLink, + + /// Information needed to compute absolute (i.e. viewport-relative) flow positions (not to be + /// confused with absolutely-positioned flows). + /// + /// FIXME(pcwalton): Merge with `absolute_static_i_offset` and `fixed_static_i_offset` above? + pub absolute_position_info: AbsolutePositionInfo, + + /// The unflattened display items for this flow. + pub display_list: DisplayList, + + /// Any layers that we're bubbling up, in a linked list. + pub layers: DList<RenderLayer>, + + /// Various flags for flows, tightly packed to save space. + pub flags: FlowFlags, + + pub writing_mode: WritingMode, +} + +impl<E, S: Encoder<E>> Encodable<S, E> for BaseFlow { + fn encode(&self, e: &mut S) -> Result<(), E> { + e.emit_struct("base", 0, |e| { + try!(e.emit_struct_field("id", 0, |e| self.debug_id().encode(e))) + try!(e.emit_struct_field("abs_position", 1, |e| self.abs_position.encode(e))) + try!(e.emit_struct_field("intrinsic_inline_sizes", 2, |e| self.intrinsic_inline_sizes.encode(e))) + try!(e.emit_struct_field("position", 3, |e| self.position.encode(e))) + e.emit_struct_field("children", 4, |e| { + e.emit_seq(self.children.len(), |e| { + for (i, c) in self.children.iter().enumerate() { + try!(e.emit_seq_elt(i, |e| c.encode(e))) + } + Ok(()) + }) + + }) + }) + } +} + +#[unsafe_destructor] +impl Drop for BaseFlow { + fn drop(&mut self) { + if self.ref_count.load(SeqCst) != 0 { + fail!("Flow destroyed before its ref count hit zero—this is unsafe!") + } + } +} + +impl BaseFlow { + #[inline] + pub fn new(node: ThreadSafeLayoutNode) -> BaseFlow { + let writing_mode = node.style().writing_mode; + BaseFlow { + ref_count: AtomicUint::new(1), + + restyle_damage: node.restyle_damage(), + + children: FlowList::new(), + next_sibling: None, + prev_sibling: None, + + intrinsic_inline_sizes: IntrinsicISizes::new(), + position: LogicalRect::zero(writing_mode), + overflow: LogicalRect::zero(writing_mode), + + parallel: FlowParallelInfo::new(), + + floats: Floats::new(writing_mode), + collapsible_margins: CollapsibleMargins::new(), + abs_position: Zero::zero(), + abs_descendants: Descendants::new(), + absolute_static_i_offset: Au::new(0), + fixed_static_i_offset: Au::new(0), + absolute_cb: ContainingBlockLink::new(), + display_list: DisplayList::new(), + layers: DList::new(), + absolute_position_info: AbsolutePositionInfo::new(writing_mode), + + flags: FlowFlags::new(), + writing_mode: writing_mode, + } + } + + pub fn child_iter<'a>(&'a mut self) -> MutFlowListIterator<'a> { + self.children.mut_iter() + } + + pub unsafe fn ref_count<'a>(&'a self) -> &'a AtomicUint { + &self.ref_count + } + + pub fn debug_id(&self) -> String { + format!("{:p}", self as *const _) + } +} + +impl<'a> ImmutableFlowUtils for &'a Flow { + /// Returns true if this flow is a block or a float flow. + fn is_block_like(self) -> bool { + match self.class() { + BlockFlowClass => true, + _ => false, + } + } + + /// Returns true if this flow is a proper table child. + /// 'Proper table child' is defined as table-row flow, table-rowgroup flow, + /// table-column-group flow, or table-caption flow. + fn is_proper_table_child(self) -> bool { + match self.class() { + TableRowFlowClass | TableRowGroupFlowClass | + TableColGroupFlowClass | TableCaptionFlowClass => true, + _ => false, + } + } + + /// Returns true if this flow is a table row flow. + fn is_table_row(self) -> bool { + match self.class() { + TableRowFlowClass => true, + _ => false, + } + } + + /// Returns true if this flow is a table cell flow. + fn is_table_cell(self) -> bool { + match self.class() { + TableCellFlowClass => true, + _ => false, + } + } + + /// Returns true if this flow is a table colgroup flow. + fn is_table_colgroup(self) -> bool { + match self.class() { + TableColGroupFlowClass => true, + _ => false, + } + } + + /// Returns true if this flow is a table flow. + fn is_table(self) -> bool { + match self.class() { + TableFlowClass => true, + _ => false, + } + } + + /// Returns true if this flow is a table caption flow. + fn is_table_caption(self) -> bool { + match self.class() { + TableCaptionFlowClass => true, + _ => false, + } + } + + /// Returns true if this flow is a table rowgroup flow. + fn is_table_rowgroup(self) -> bool { + match self.class() { + TableRowGroupFlowClass => true, + _ => false, + } + } + + /// Returns true if this flow is one of table-related flows. + fn is_table_kind(self) -> bool { + match self.class() { + TableWrapperFlowClass | TableFlowClass | + TableColGroupFlowClass | TableRowGroupFlowClass | + TableRowFlowClass | TableCaptionFlowClass | TableCellFlowClass => true, + _ => false, + } + } + + /// Returns true if anonymous flow is needed between this flow and child flow. + /// Spec: http://www.w3.org/TR/CSS21/tables.html#anonymous-boxes + fn need_anonymous_flow(self, child: &Flow) -> bool { + match self.class() { + TableFlowClass => !child.is_proper_table_child(), + TableRowGroupFlowClass => !child.is_table_row(), + TableRowFlowClass => !child.is_table_cell(), + _ => false + } + } + + /// Generates missing child flow of this flow. + fn generate_missing_child_flow(self, node: &ThreadSafeLayoutNode) -> FlowRef { + let flow = match self.class() { + TableFlowClass | TableRowGroupFlowClass => { + let fragment = Fragment::new_anonymous_table_fragment(node, TableRowFragment); + box TableRowFlow::from_node_and_fragment(node, fragment) as Box<Flow> + }, + TableRowFlowClass => { + let fragment = Fragment::new_anonymous_table_fragment(node, TableCellFragment); + box TableCellFlow::from_node_and_fragment(node, fragment) as Box<Flow> + }, + _ => { + fail!("no need to generate a missing child") + } + }; + FlowRef::new(flow) + } + + /// Returns true if this flow has no children. + fn is_leaf(self) -> bool { + base(self).children.len() == 0 + } + + /// Returns the number of children that this flow possesses. + fn child_count(self) -> uint { + base(self).children.len() + } + + /// Return true if this flow is a Block Container. + /// + /// Except for table fragments and replaced elements, block-level fragments (`BlockFlow`) are + /// also block container fragments. + /// Non-replaced inline blocks and non-replaced table cells are also block + /// containers. + fn is_block_container(self) -> bool { + match self.class() { + // TODO: Change this when inline-blocks are supported. + BlockFlowClass | TableCaptionFlowClass | TableCellFlowClass => { + // FIXME: Actually check the type of the node + self.child_count() != 0 + } + _ => false, + } + } + + /// Returns true if this flow is a block flow. + fn is_block_flow(self) -> bool { + match self.class() { + BlockFlowClass => true, + _ => false, + } + } + + /// Returns true if this flow is an inline flow. + fn is_inline_flow(self) -> bool { + match self.class() { + InlineFlowClass => true, + _ => false, + } + } + + /// Dumps the flow tree for debugging. + fn dump(self) { + self.dump_with_level(0) + } + + /// Dumps the flow tree for debugging, with a prefix to indicate that we're at the given level. + fn dump_with_level(self, level: uint) { + let mut indent = String::new(); + for _ in range(0, level) { + indent.push_str("| ") + } + debug!("{}+ {}", indent, self.to_string()); + for kid in imm_child_iter(self) { + kid.dump_with_level(level + 1) + } + } +} + +impl<'a> MutableFlowUtils for &'a mut Flow { + /// Traverses the tree in preorder. + fn traverse_preorder<T:PreorderFlowTraversal>(self, traversal: &mut T) -> bool { + if traversal.should_prune(self) { + return true + } + + if !traversal.process(self) { + return false + } + + for kid in child_iter(self) { + if !kid.traverse_preorder(traversal) { + return false + } + } + + true + } + + /// Traverses the tree in postorder. + fn traverse_postorder<T:PostorderFlowTraversal>(self, traversal: &mut T) -> bool { + if traversal.should_prune(self) { + return true + } + + for kid in child_iter(self) { + if !kid.traverse_postorder(traversal) { + return false + } + } + + if !traversal.should_process(self) { + return true + } + + traversal.process(self) + } + + /// Calculate and set overflow for current flow. + /// + /// CSS Section 11.1 + /// This is the union of rectangles of the flows for which we define the + /// Containing Block. + /// + /// Assumption: This is called in a bottom-up traversal, so kids' overflows have + /// already been set. + /// Assumption: Absolute descendants have had their overflow calculated. + fn store_overflow(self, _: &LayoutContext) { + let my_position = mut_base(self).position; + let mut overflow = my_position; + + if self.is_block_container() { + for kid in child_iter(self) { + if kid.is_store_overflow_delayed() { + // Absolute flows will be handled by their CB. If we are + // their CB, they will show up in `abs_descendants`. + continue; + } + let mut kid_overflow = base(kid).overflow; + kid_overflow = kid_overflow.translate(&my_position.start); + overflow = overflow.union(&kid_overflow) + } + + // FIXME(#2004, pcwalton): This is wrong for `position: fixed`. + for descendant_link in mut_base(self).abs_descendants.iter() { + let mut kid_overflow = base(descendant_link).overflow; + kid_overflow = kid_overflow.translate(&my_position.start); + overflow = overflow.union(&kid_overflow) + } + } + mut_base(self).overflow = overflow; + } + + /// Push display items for current flow and its descendants onto the appropriate display lists + /// of the given stacking context. + /// + /// Arguments: + /// + /// * `builder`: The display list builder, which contains information used during the entire + /// display list building pass. + /// + /// * `info`: Per-flow display list building information. + fn build_display_list(self, layout_context: &LayoutContext) { + debug!("Flow: building display list"); + match self.class() { + BlockFlowClass => self.as_block().build_display_list_block(layout_context), + InlineFlowClass => self.as_inline().build_display_list_inline(layout_context), + TableWrapperFlowClass => { + self.as_table_wrapper().build_display_list_table_wrapper(layout_context) + } + TableFlowClass => self.as_table().build_display_list_table(layout_context), + TableRowGroupFlowClass => { + self.as_table_rowgroup().build_display_list_table_rowgroup(layout_context) + } + TableRowFlowClass => self.as_table_row().build_display_list_table_row(layout_context), + TableCaptionFlowClass => { + self.as_table_caption().build_display_list_table_caption(layout_context) + } + TableCellFlowClass => { + self.as_table_cell().build_display_list_table_cell(layout_context) + } + TableColGroupFlowClass => { + // Nothing to do here, as column groups don't render. + } + } + } +} + +impl MutableOwnedFlowUtils for FlowRef { + /// Set absolute descendants for this flow. + /// + /// Set yourself as the Containing Block for all the absolute descendants. + /// + /// This is called during flow construction, so nothing else can be accessing the descendant + /// flows. This is enforced by the fact that we have a mutable `FlowRef`, which only flow + /// construction is allowed to possess. + fn set_abs_descendants(&mut self, abs_descendants: AbsDescendants) { + let this = self.clone(); + + let block = self.get_mut().as_block(); + block.base.abs_descendants = abs_descendants; + block.base + .parallel + .children_and_absolute_descendant_count + .fetch_add(block.base.abs_descendants.len() as int, Relaxed); + + for descendant_link in block.base.abs_descendants.iter() { + let base = mut_base(descendant_link); + base.absolute_cb.set(this.clone()); + } + } +} + +/// A link to a flow's containing block. +/// +/// This cannot safely be a `Flow` pointer because this is a pointer *up* the tree, not *down* the +/// tree. A pointer up the tree is unsafe during layout because it can be used to access a node +/// with an immutable reference while that same node is being laid out, causing possible iterator +/// invalidation and use-after-free. +/// +/// FIXME(pcwalton): I think this would be better with a borrow flag instead of `unsafe`. +pub struct ContainingBlockLink { + /// The pointer up to the containing block. + link: Option<FlowRef>, +} + +impl ContainingBlockLink { + fn new() -> ContainingBlockLink { + ContainingBlockLink { + link: None, + } + } + + fn set(&mut self, link: FlowRef) { + self.link = Some(link) + } + + pub unsafe fn get<'a>(&'a mut self) -> &'a mut Option<FlowRef> { + &mut self.link + } + + #[inline] + pub fn generated_containing_block_rect(&mut self) -> LogicalRect<Au> { + match self.link { + None => fail!("haven't done it"), + Some(ref mut link) => link.get_mut().generated_containing_block_rect(), + } + } +} + diff --git a/components/layout/flow_list.rs b/components/layout/flow_list.rs new file mode 100644 index 00000000000..4277326a624 --- /dev/null +++ b/components/layout/flow_list.rs @@ -0,0 +1,296 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! A variant of `DList` specialized to store `Flow`s without an extra +//! indirection. + +use flow::{Flow, base, mut_base}; +use flow_ref::FlowRef; + +use std::kinds::marker::ContravariantLifetime; +use std::mem; +use std::ptr; +use std::raw; + +pub type Link = Option<FlowRef>; + + +#[allow(raw_pointer_deriving)] +pub struct Rawlink<'a> { + object: raw::TraitObject, + marker: ContravariantLifetime<'a>, +} + +/// Doubly-linked list of Flows. +/// +/// The forward links are strong references. +/// The backward links are weak references. +pub struct FlowList { + length: uint, + list_head: Link, + list_tail: Link, +} + +/// Double-ended FlowList iterator +pub struct FlowListIterator<'a> { + head: &'a Link, + nelem: uint, +} + +/// Double-ended mutable FlowList iterator +pub struct MutFlowListIterator<'a> { + head: Rawlink<'a>, + nelem: uint, +} + +impl<'a> Rawlink<'a> { + /// Like Option::None for Rawlink + pub fn none() -> Rawlink<'static> { + Rawlink { + object: raw::TraitObject { + vtable: ptr::mut_null(), + data: ptr::mut_null(), + }, + marker: ContravariantLifetime, + } + } + + /// Like Option::Some for Rawlink + pub fn some(n: &Flow) -> Rawlink { + unsafe { + Rawlink { + object: mem::transmute::<&Flow, raw::TraitObject>(n), + marker: ContravariantLifetime, + } + } + } + + pub unsafe fn resolve_mut(&self) -> Option<&'a mut Flow> { + if self.object.data.is_null() { + None + } else { + Some(mem::transmute_copy::<raw::TraitObject, &mut Flow>(&self.object)) + } + } +} + +/// Set the .prev field on `next`, then return `Some(next)` +unsafe fn link_with_prev(mut next: FlowRef, prev: Option<FlowRef>) -> Link { + mut_base(next.get_mut()).prev_sibling = prev; + Some(next) +} + +impl Collection for FlowList { + /// O(1) + #[inline] + fn is_empty(&self) -> bool { + self.list_head.is_none() + } + /// O(1) + #[inline] + fn len(&self) -> uint { + self.length + } +} + +// This doesn't quite fit the Deque trait because of the need to switch between +// &Flow and ~Flow. +impl FlowList { + /// Provide a reference to the front element, or None if the list is empty + #[inline] + pub fn front<'a>(&'a self) -> Option<&'a Flow> { + self.list_head.as_ref().map(|head| head.get()) + } + + /// Provide a mutable reference to the front element, or None if the list is empty + #[inline] + pub unsafe fn front_mut<'a>(&'a mut self) -> Option<&'a mut Flow> { + self.list_head.as_mut().map(|head| head.get_mut()) + } + + /// Provide a reference to the back element, or None if the list is empty + #[inline] + pub fn back<'a>(&'a self) -> Option<&'a Flow> { + match self.list_tail { + None => None, + Some(ref list_tail) => Some(list_tail.get()) + } + } + + /// Provide a mutable reference to the back element, or None if the list is empty + #[inline] + pub unsafe fn back_mut<'a>(&'a mut self) -> Option<&'a mut Flow> { + // Can't use map() due to error: + // lifetime of `tail` is too short to guarantee its contents can be safely reborrowed + match self.list_tail { + None => None, + Some(ref mut tail) => { + let x: &mut Flow = tail.get_mut(); + Some(mem::transmute_copy(&x)) + } + } + } + + /// Add an element first in the list + /// + /// O(1) + pub fn push_front(&mut self, mut new_head: FlowRef) { + unsafe { + match self.list_head { + None => { + self.list_tail = Some(new_head.clone()); + self.list_head = link_with_prev(new_head, None); + } + Some(ref mut head) => { + mut_base(new_head.get_mut()).prev_sibling = None; + mut_base(head.get_mut()).prev_sibling = Some(new_head.clone()); + mem::swap(head, &mut new_head); + mut_base(head.get_mut()).next_sibling = Some(new_head); + } + } + self.length += 1; + } + } + + /// Remove the first element and return it, or None if the list is empty + /// + /// O(1) + pub fn pop_front(&mut self) -> Option<FlowRef> { + self.list_head.take().map(|mut front_node| { + self.length -= 1; + unsafe { + match mut_base(front_node.get_mut()).next_sibling.take() { + Some(node) => self.list_head = link_with_prev(node, None), + None => self.list_tail = None, + } + } + front_node + }) + } + + /// Add an element last in the list + /// + /// O(1) + pub fn push_back(&mut self, new_tail: FlowRef) { + if self.list_tail.is_none() { + return self.push_front(new_tail); + } + + let old_tail = self.list_tail.clone(); + self.list_tail = Some(new_tail.clone()); + let mut tail = (*old_tail.as_ref().unwrap()).clone(); + let tail_clone = Some(tail.clone()); + unsafe { + mut_base(tail.get_mut()).next_sibling = link_with_prev(new_tail, tail_clone); + } + self.length += 1; + } + + /// Create an empty list + #[inline] + pub fn new() -> FlowList { + FlowList { + list_head: None, + list_tail: None, + length: 0, + } + } + + /// Provide a forward iterator + #[inline] + pub fn iter<'a>(&'a self) -> FlowListIterator<'a> { + FlowListIterator { + nelem: self.len(), + head: &self.list_head, + } + } + + /// Provide a forward iterator with mutable references + #[inline] + pub fn mut_iter<'a>(&'a mut self) -> MutFlowListIterator<'a> { + let len = self.len(); + let head_raw = match self.list_head { + Some(ref mut h) => Rawlink::some(h.get()), + None => Rawlink::none(), + }; + MutFlowListIterator { + nelem: len, + head: head_raw, + } + } +} + +#[unsafe_destructor] +impl Drop for FlowList { + fn drop(&mut self) { + // Dissolve the list in backwards direction + // Just dropping the list_head can lead to stack exhaustion + // when length is >> 1_000_000 + let mut tail = mem::replace(&mut self.list_tail, None); + loop { + let new_tail = match tail { + None => break, + Some(ref mut prev) => { + let prev_base = mut_base(prev.get_mut()); + prev_base.next_sibling.take(); + prev_base.prev_sibling.clone() + } + }; + tail = new_tail + } + self.length = 0; + self.list_head = None; + } +} + +impl<'a> Iterator<&'a Flow> for FlowListIterator<'a> { + #[inline] + fn next(&mut self) -> Option<&'a Flow> { + if self.nelem == 0 { + return None; + } + self.head.as_ref().map(|head| { + let head_base = base(head.get()); + self.nelem -= 1; + self.head = &head_base.next_sibling; + let ret: &Flow = head.get(); + ret + }) + } + + #[inline] + fn size_hint(&self) -> (uint, Option<uint>) { + (self.nelem, Some(self.nelem)) + } +} + +impl<'a> Iterator<&'a mut Flow> for MutFlowListIterator<'a> { + #[inline] + fn next(&mut self) -> Option<&'a mut Flow> { + if self.nelem == 0 { + return None; + } + unsafe { + self.head.resolve_mut().map(|next| { + self.nelem -= 1; + self.head = match mut_base(next).next_sibling { + Some(ref mut node) => { + let x: &mut Flow = node.get_mut(); + // NOTE: transmute needed here to break the link + // between x and next so that it is no longer + // borrowed. + mem::transmute(Rawlink::some(x)) + } + None => Rawlink::none(), + }; + next + }) + } + } + + #[inline] + fn size_hint(&self) -> (uint, Option<uint>) { + (self.nelem, Some(self.nelem)) + } +} diff --git a/components/layout/flow_ref.rs b/components/layout/flow_ref.rs new file mode 100644 index 00000000000..d90d9ac4cc0 --- /dev/null +++ b/components/layout/flow_ref.rs @@ -0,0 +1,84 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/// Reference-counted pointers to flows. +/// +/// Eventually, with dynamically sized types in Rust, much of this code will be superfluous. + +use flow::Flow; +use flow; + +use std::mem; +use std::ptr; +use std::raw; +use std::sync::atomics::SeqCst; + +#[unsafe_no_drop_flag] +pub struct FlowRef { + object: raw::TraitObject, +} + +impl FlowRef { + pub fn new(mut flow: Box<Flow>) -> FlowRef { + unsafe { + let result = { + let flow_ref: &mut Flow = flow; + let object = mem::transmute::<&mut Flow, raw::TraitObject>(flow_ref); + FlowRef { object: object } + }; + mem::forget(flow); + result + } + } + + pub fn get<'a>(&'a self) -> &'a Flow { + unsafe { + mem::transmute_copy::<raw::TraitObject, &'a Flow>(&self.object) + } + } + + pub fn get_mut<'a>(&'a mut self) -> &'a mut Flow { + unsafe { + mem::transmute_copy::<raw::TraitObject, &'a mut Flow>(&self.object) + } + } +} + +impl Drop for FlowRef { + fn drop(&mut self) { + unsafe { + if self.object.vtable.is_null() { + return + } + if flow::base(self.get()).ref_count().fetch_sub(1, SeqCst) > 1 { + return + } + let flow_ref: FlowRef = mem::replace(self, FlowRef { + object: raw::TraitObject { + vtable: ptr::mut_null(), + data: ptr::mut_null(), + } + }); + drop(mem::transmute::<raw::TraitObject, Box<Flow>>(flow_ref.object)); + mem::forget(flow_ref); + self.object.vtable = ptr::mut_null(); + self.object.data = ptr::mut_null(); + } + } +} + +impl Clone for FlowRef { + fn clone(&self) -> FlowRef { + unsafe { + drop(flow::base(self.get()).ref_count().fetch_add(1, SeqCst)); + FlowRef { + object: raw::TraitObject { + vtable: self.object.vtable, + data: self.object.data, + } + } + } + } +} + diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs new file mode 100644 index 00000000000..191283603b9 --- /dev/null +++ b/components/layout/fragment.rs @@ -0,0 +1,1597 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! The `Fragment` type, which represents the leaves of the layout tree. + +#![deny(unsafe_block)] + +use css::node_style::StyledNode; +use construct::FlowConstructor; +use context::LayoutContext; +use floats::{ClearBoth, ClearLeft, ClearRight, ClearType}; +use flow::Flow; +use flow; +use inline::{InlineFragmentContext, InlineMetrics}; +use layout_debug; +use model::{Auto, IntrinsicISizes, MaybeAuto, Specified, specified}; +use model; +use text; +use util::{OpaqueNodeMethods, ToGfxColor}; +use wrapper::{TLayoutNode, ThreadSafeLayoutNode}; + +use geom::{Point2D, Rect, Size2D, SideOffsets2D}; +use geom::approxeq::ApproxEq; +use gfx::color::rgb; +use gfx::display_list::{BackgroundAndBorderLevel, BaseDisplayItem, BorderDisplayItem}; +use gfx::display_list::{BorderDisplayItemClass, ClipDisplayItem, ClipDisplayItemClass}; +use gfx::display_list::{ContentStackingLevel, DisplayItem, DisplayList, ImageDisplayItem}; +use gfx::display_list::{ImageDisplayItemClass, LineDisplayItem}; +use gfx::display_list::{LineDisplayItemClass, OpaqueNode, PseudoDisplayItemClass}; +use gfx::display_list::{SolidColorDisplayItem, SolidColorDisplayItemClass, StackingLevel}; +use gfx::display_list::{TextDisplayItem, TextDisplayItemClass}; +use gfx::display_list::{Upright, SidewaysLeft, SidewaysRight}; +use gfx::font::FontStyle; +use gfx::text::glyph::CharIndex; +use gfx::text::text_run::TextRun; +use serialize::{Encodable, Encoder}; +use servo_msg::constellation_msg::{ConstellationChan, FrameRectMsg, PipelineId, SubpageId}; +use servo_net::image::holder::ImageHolder; +use servo_net::local_image_cache::LocalImageCache; +use servo_util::geometry::Au; +use servo_util::geometry; +use servo_util::logical_geometry::{LogicalRect, LogicalSize, LogicalMargin}; +use servo_util::range::*; +use servo_util::namespace; +use servo_util::smallvec::SmallVec; +use servo_util::str::is_whitespace; +use std::fmt; +use std::from_str::FromStr; +use std::mem; +use std::num::Zero; +use style::{ComputedValues, TElement, TNode, cascade_anonymous, RGBA}; +use style::computed_values::{LengthOrPercentageOrAuto, overflow, LPA_Auto, background_attachment}; +use style::computed_values::{background_repeat, border_style, clear, position, text_align}; +use style::computed_values::{text_decoration, vertical_align, visibility, white_space}; +use sync::{Arc, Mutex}; +use url::Url; + +/// Fragments (`struct Fragment`) are the leaves of the layout tree. They cannot position themselves. In +/// general, fragments do not have a simple correspondence with CSS fragments in the specification: +/// +/// * Several fragments may correspond to the same CSS box or DOM node. For example, a CSS text box +/// broken across two lines is represented by two fragments. +/// +/// * Some CSS fragments are not created at all, such as some anonymous block fragments induced by inline +/// fragments with block-level sibling fragments. In that case, Servo uses an `InlineFlow` with +/// `BlockFlow` siblings; the `InlineFlow` is block-level, but not a block container. It is +/// positioned as if it were a block fragment, but its children are positioned according to inline +/// flow. +/// +/// A `GenericFragment` is an empty fragment that contributes only borders, margins, padding, and +/// backgrounds. It is analogous to a CSS nonreplaced content box. +/// +/// A fragment's type influences how its styles are interpreted during layout. For example, replaced +/// content such as images are resized differently from tables, text, or other content. Different +/// types of fragments may also contain custom data; for example, text fragments contain text. +/// +/// FIXME(#2260, pcwalton): This can be slimmed down some. +#[deriving(Clone)] +pub struct Fragment { + /// An opaque reference to the DOM node that this `Fragment` originates from. + pub node: OpaqueNode, + + /// The CSS style of this fragment. + pub style: Arc<ComputedValues>, + + /// The position of this fragment relative to its owning flow. + /// The size includes padding and border, but not margin. + pub border_box: LogicalRect<Au>, + + /// The sum of border and padding; i.e. the distance from the edge of the border box to the + /// content edge of the fragment. + pub border_padding: LogicalMargin<Au>, + + /// The margin of the content box. + pub margin: LogicalMargin<Au>, + + /// Info specific to the kind of fragment. Keep this enum small. + pub specific: SpecificFragmentInfo, + + /// New-line chracter(\n)'s positions(relative, not absolute) + /// + /// FIXME(#2260, pcwalton): This is very inefficient; remove. + pub new_line_pos: Vec<CharIndex>, + + /// Holds the style context information for fragments + /// that are part of an inline formatting context. + pub inline_context: Option<InlineFragmentContext>, + + /// A debug ID that is consistent for the life of + /// this fragment (via transform etc). + pub debug_id: uint, +} + +impl<E, S: Encoder<E>> Encodable<S, E> for Fragment { + fn encode(&self, e: &mut S) -> Result<(), E> { + e.emit_struct("fragment", 0, |e| { + try!(e.emit_struct_field("id", 0, |e| self.debug_id().encode(e))) + try!(e.emit_struct_field("border_box", 1, |e| self.border_box.encode(e))) + e.emit_struct_field("margin", 2, |e| self.margin.encode(e)) + }) + } +} + +/// Info specific to the kind of fragment. Keep this enum small. +#[deriving(Clone)] +pub enum SpecificFragmentInfo { + GenericFragment, + ImageFragment(ImageFragmentInfo), + IframeFragment(IframeFragmentInfo), + ScannedTextFragment(ScannedTextFragmentInfo), + TableFragment, + TableCellFragment, + TableColumnFragment(TableColumnFragmentInfo), + TableRowFragment, + TableWrapperFragment, + UnscannedTextFragment(UnscannedTextFragmentInfo), +} + +/// A fragment that represents a replaced content image and its accompanying borders, shadows, etc. +#[deriving(Clone)] +pub struct ImageFragmentInfo { + /// The image held within this fragment. + pub image: ImageHolder, + pub computed_inline_size: Option<Au>, + pub computed_block_size: Option<Au>, + pub dom_inline_size: Option<Au>, + pub dom_block_size: Option<Au>, + pub writing_mode_is_vertical: bool, +} + +impl ImageFragmentInfo { + /// Creates a new image fragment from the given URL and local image cache. + /// + /// FIXME(pcwalton): The fact that image fragments store the cache in the fragment makes little sense to + /// me. + pub fn new(node: &ThreadSafeLayoutNode, + image_url: Url, + local_image_cache: Arc<Mutex<LocalImageCache>>) + -> ImageFragmentInfo { + fn convert_length(node: &ThreadSafeLayoutNode, name: &str) -> Option<Au> { + let element = node.as_element(); + element.get_attr(&namespace::Null, name).and_then(|string| { + let n: Option<int> = FromStr::from_str(string); + n + }).and_then(|pixels| Some(Au::from_px(pixels))) + } + + let is_vertical = node.style().writing_mode.is_vertical(); + let dom_width = convert_length(node, "width"); + let dom_height = convert_length(node, "height"); + ImageFragmentInfo { + image: ImageHolder::new(image_url, local_image_cache), + computed_inline_size: None, + computed_block_size: None, + dom_inline_size: if is_vertical { dom_height } else { dom_width }, + dom_block_size: if is_vertical { dom_width } else { dom_height }, + writing_mode_is_vertical: is_vertical, + } + } + + /// Returns the calculated inline-size of the image, accounting for the inline-size attribute. + pub fn computed_inline_size(&self) -> Au { + self.computed_inline_size.expect("image inline_size is not computed yet!") + } + + /// Returns the calculated block-size of the image, accounting for the block-size attribute. + pub fn computed_block_size(&self) -> Au { + self.computed_block_size.expect("image block_size is not computed yet!") + } + + /// Returns the original inline-size of the image. + pub fn image_inline_size(&mut self) -> Au { + let size = self.image.get_size().unwrap_or(Size2D::zero()); + Au::from_px(if self.writing_mode_is_vertical { size.height } else { size.width }) + } + + /// Returns the original block-size of the image. + pub fn image_block_size(&mut self) -> Au { + let size = self.image.get_size().unwrap_or(Size2D::zero()); + Au::from_px(if self.writing_mode_is_vertical { size.width } else { size.height }) + } + + // Return used value for inline-size or block-size. + // + // `dom_length`: inline-size or block-size as specified in the `img` tag. + // `style_length`: inline-size as given in the CSS + pub fn style_length(style_length: LengthOrPercentageOrAuto, + dom_length: Option<Au>, + container_inline_size: Au) -> MaybeAuto { + match (MaybeAuto::from_style(style_length,container_inline_size),dom_length) { + (Specified(length),_) => { + Specified(length) + }, + (Auto,Some(length)) => { + Specified(length) + }, + (Auto,None) => { + Auto + } + } + } +} + +/// A fragment that represents an inline frame (iframe). This stores the pipeline ID so that the size +/// of this iframe can be communicated via the constellation to the iframe's own layout task. +#[deriving(Clone)] +pub struct IframeFragmentInfo { + /// The pipeline ID of this iframe. + pub pipeline_id: PipelineId, + /// The subpage ID of this iframe. + pub subpage_id: SubpageId, +} + +impl IframeFragmentInfo { + /// Creates the information specific to an iframe fragment. + pub fn new(node: &ThreadSafeLayoutNode) -> IframeFragmentInfo { + let (pipeline_id, subpage_id) = node.iframe_pipeline_and_subpage_ids(); + IframeFragmentInfo { + pipeline_id: pipeline_id, + subpage_id: subpage_id, + } + } +} + +/// A scanned text fragment represents a single run of text with a distinct style. A `TextFragment` +/// may be split into two or more fragments across line breaks. Several `TextFragment`s may +/// correspond to a single DOM text node. Split text fragments are implemented by referring to +/// subsets of a single `TextRun` object. +#[deriving(Clone)] +pub struct ScannedTextFragmentInfo { + /// The text run that this represents. + pub run: Arc<Box<TextRun>>, + + /// The range within the above text run that this represents. + pub range: Range<CharIndex>, +} + +impl ScannedTextFragmentInfo { + /// Creates the information specific to a scanned text fragment from a range and a text run. + pub fn new(run: Arc<Box<TextRun>>, range: Range<CharIndex>) -> ScannedTextFragmentInfo { + ScannedTextFragmentInfo { + run: run, + range: range, + } + } +} + +#[deriving(Show)] +pub struct SplitInfo { + // TODO(bjz): this should only need to be a single character index, but both values are + // currently needed for splitting in the `inline::try_append_*` functions. + pub range: Range<CharIndex>, + pub inline_size: Au, +} + +impl SplitInfo { + fn new(range: Range<CharIndex>, info: &ScannedTextFragmentInfo) -> SplitInfo { + SplitInfo { + range: range, + inline_size: info.run.advance_for_range(&range), + } + } +} + +/// Data for an unscanned text fragment. Unscanned text fragments are the results of flow construction that +/// have not yet had their inline-size determined. +#[deriving(Clone)] +pub struct UnscannedTextFragmentInfo { + /// The text inside the fragment. + pub text: String, +} + +impl UnscannedTextFragmentInfo { + /// Creates a new instance of `UnscannedTextFragmentInfo` from the given DOM node. + pub fn new(node: &ThreadSafeLayoutNode) -> UnscannedTextFragmentInfo { + // FIXME(pcwalton): Don't copy text; atomically reference count it instead. + UnscannedTextFragmentInfo { + text: node.text(), + } + } + + /// Creates a new instance of `UnscannedTextFragmentInfo` from the given text. + #[inline] + pub fn from_text(text: String) -> UnscannedTextFragmentInfo { + UnscannedTextFragmentInfo { + text: text, + } + } +} + +/// A fragment that represents a table column. +#[deriving(Clone)] +pub struct TableColumnFragmentInfo { + /// the number of columns a <col> element should span + pub span: Option<int>, +} + +impl TableColumnFragmentInfo { + /// Create the information specific to an table column fragment. + pub fn new(node: &ThreadSafeLayoutNode) -> TableColumnFragmentInfo { + let span = { + let element = node.as_element(); + element.get_attr(&namespace::Null, "span").and_then(|string| { + let n: Option<int> = FromStr::from_str(string); + n + }) + }; + TableColumnFragmentInfo { + span: span, + } + } +} + +impl Fragment { + /// Constructs a new `Fragment` instance for the given node. + /// + /// Arguments: + /// + /// * `constructor`: The flow constructor. + /// + /// * `node`: The node to create a fragment for. + pub fn new(constructor: &mut FlowConstructor, node: &ThreadSafeLayoutNode) -> Fragment { + let style = node.style().clone(); + let writing_mode = style.writing_mode; + Fragment { + node: OpaqueNodeMethods::from_thread_safe_layout_node(node), + style: style, + border_box: LogicalRect::zero(writing_mode), + border_padding: LogicalMargin::zero(writing_mode), + margin: LogicalMargin::zero(writing_mode), + specific: constructor.build_specific_fragment_info_for_node(node), + new_line_pos: vec!(), + inline_context: None, + debug_id: layout_debug::generate_unique_debug_id(), + } + } + + /// Constructs a new `Fragment` instance from a specific info. + pub fn new_from_specific_info(node: &ThreadSafeLayoutNode, specific: SpecificFragmentInfo) -> Fragment { + let style = node.style().clone(); + let writing_mode = style.writing_mode; + Fragment { + node: OpaqueNodeMethods::from_thread_safe_layout_node(node), + style: style, + border_box: LogicalRect::zero(writing_mode), + border_padding: LogicalMargin::zero(writing_mode), + margin: LogicalMargin::zero(writing_mode), + specific: specific, + new_line_pos: vec!(), + inline_context: None, + debug_id: layout_debug::generate_unique_debug_id(), + } + } + + /// Constructs a new `Fragment` instance for an anonymous table object. + pub fn new_anonymous_table_fragment(node: &ThreadSafeLayoutNode, specific: SpecificFragmentInfo) -> Fragment { + // CSS 2.1 § 17.2.1 This is for non-inherited properties on anonymous table fragments + // example: + // + // <div style="display: table"> + // Foo + // </div> + // + // Anonymous table fragments, TableRowFragment and TableCellFragment, are generated around `Foo`, but it shouldn't inherit the border. + + let node_style = cascade_anonymous(&**node.style()); + let writing_mode = node_style.writing_mode; + Fragment { + node: OpaqueNodeMethods::from_thread_safe_layout_node(node), + style: Arc::new(node_style), + border_box: LogicalRect::zero(writing_mode), + border_padding: LogicalMargin::zero(writing_mode), + margin: LogicalMargin::zero(writing_mode), + specific: specific, + new_line_pos: vec!(), + inline_context: None, + debug_id: layout_debug::generate_unique_debug_id(), + } + } + + /// Constructs a new `Fragment` instance from an opaque node. + pub fn from_opaque_node_and_style(node: OpaqueNode, + style: Arc<ComputedValues>, + specific: SpecificFragmentInfo) + -> Fragment { + let writing_mode = style.writing_mode; + Fragment { + node: node, + style: style, + border_box: LogicalRect::zero(writing_mode), + border_padding: LogicalMargin::zero(writing_mode), + margin: LogicalMargin::zero(writing_mode), + specific: specific, + new_line_pos: vec!(), + inline_context: None, + debug_id: layout_debug::generate_unique_debug_id(), + } + } + + /// Returns a debug ID of this fragment. This ID should not be considered stable across multiple + /// layouts or fragment manipulations. + pub fn debug_id(&self) -> uint { + self.debug_id + } + + /// Transforms this fragment into another fragment of the given type, with the given size, preserving all + /// the other data. + pub fn transform(&self, size: LogicalSize<Au>, specific: SpecificFragmentInfo) -> Fragment { + Fragment { + node: self.node, + style: self.style.clone(), + border_box: LogicalRect::from_point_size( + self.style.writing_mode, self.border_box.start, size), + border_padding: self.border_padding, + margin: self.margin, + specific: specific, + new_line_pos: self.new_line_pos.clone(), + inline_context: self.inline_context.clone(), + debug_id: self.debug_id, + } + } + + /// Adds a style to the inline context for this fragment. If the inline + /// context doesn't exist yet, it will be created. + pub fn add_inline_context_style(&mut self, style: Arc<ComputedValues>) { + if self.inline_context.is_none() { + self.inline_context = Some(InlineFragmentContext::new()); + } + self.inline_context.get_mut_ref().styles.push(style.clone()); + } + + /// Uses the style only to estimate the intrinsic inline-sizes. These may be modified for text or + /// replaced elements. + fn style_specified_intrinsic_inline_size(&self) -> IntrinsicISizes { + let (use_margins, use_padding) = match self.specific { + GenericFragment | IframeFragment(_) | ImageFragment(_) => (true, true), + TableFragment | TableCellFragment => (false, true), + TableWrapperFragment => (true, false), + TableRowFragment => (false, false), + ScannedTextFragment(_) | TableColumnFragment(_) | UnscannedTextFragment(_) => { + // Styles are irrelevant for these kinds of fragments. + return IntrinsicISizes::new() + } + }; + + let style = self.style(); + let inline_size = MaybeAuto::from_style(style.content_inline_size(), Au::new(0)).specified_or_zero(); + + let margin = style.logical_margin(); + let (margin_inline_start, margin_inline_end) = if use_margins { + (MaybeAuto::from_style(margin.inline_start, Au(0)).specified_or_zero(), + MaybeAuto::from_style(margin.inline_end, Au(0)).specified_or_zero()) + } else { + (Au(0), Au(0)) + }; + + let padding = style.logical_padding(); + let (padding_inline_start, padding_inline_end) = if use_padding { + (model::specified(padding.inline_start, Au(0)), + model::specified(padding.inline_end, Au(0))) + } else { + (Au(0), Au(0)) + }; + + // FIXME(#2261, pcwalton): This won't work well for inlines: is this OK? + let border = self.border_width(); + let surround_inline_size = margin_inline_start + margin_inline_end + padding_inline_start + padding_inline_end + + border.inline_start_end(); + + IntrinsicISizes { + minimum_inline_size: inline_size, + preferred_inline_size: inline_size, + surround_inline_size: surround_inline_size, + } + } + + pub fn calculate_line_height(&self, layout_context: &LayoutContext) -> Au { + let font_style = text::computed_style_to_font_style(&*self.style); + let font_metrics = text::font_metrics_for_style(layout_context.font_context(), &font_style); + text::line_height_from_style(&*self.style, &font_metrics) + } + + /// Returns the sum of the inline-sizes of all the borders of this fragment. This is private because + /// it should only be called during intrinsic inline-size computation or computation of + /// `border_padding`. Other consumers of this information should simply consult that field. + #[inline] + fn border_width(&self) -> LogicalMargin<Au> { + match self.inline_context { + None => self.style().logical_border_width(), + Some(ref inline_fragment_context) => { + let zero = LogicalMargin::zero(self.style.writing_mode); + inline_fragment_context.styles.iter().fold(zero, |acc, style| acc + style.logical_border_width()) + } + } + } + + /// Computes the border, padding, and vertical margins from the containing block inline-size and the + /// style. After this call, the `border_padding` and the vertical direction of the `margin` + /// field will be correct. + pub fn compute_border_padding_margins(&mut self, + containing_block_inline_size: Au) { + // Compute vertical margins. Note that this value will be ignored by layout if the style + // specifies `auto`. + match self.specific { + TableFragment | TableCellFragment | TableRowFragment | TableColumnFragment(_) => { + self.margin.block_start = Au(0); + self.margin.block_end = Au(0) + } + _ => { + // NB: Percentages are relative to containing block inline-size (not block-size) per CSS 2.1. + let margin = self.style().logical_margin(); + self.margin.block_start = MaybeAuto::from_style(margin.block_start, containing_block_inline_size) + .specified_or_zero(); + self.margin.block_end = MaybeAuto::from_style(margin.block_end, containing_block_inline_size) + .specified_or_zero() + } + } + + // Compute border. + let border = self.border_width(); + + // Compute padding. + let padding = match self.specific { + TableColumnFragment(_) | TableRowFragment | + TableWrapperFragment => LogicalMargin::zero(self.style.writing_mode), + _ => { + match self.inline_context { + None => model::padding_from_style(self.style(), containing_block_inline_size), + Some(ref inline_fragment_context) => { + let zero = LogicalMargin::zero(self.style.writing_mode); + inline_fragment_context.styles.iter() + .fold(zero, |acc, style| acc + model::padding_from_style(&**style, Au(0))) + } + } + } + }; + + self.border_padding = border + padding + } + + // Return offset from original position because of `position: relative`. + pub fn relative_position(&self, + containing_block_size: &LogicalSize<Au>) + -> LogicalSize<Au> { + fn from_style(style: &ComputedValues, container_size: &LogicalSize<Au>) + -> LogicalSize<Au> { + let offsets = style.logical_position(); + let offset_i = if offsets.inline_start != LPA_Auto { + MaybeAuto::from_style(offsets.inline_start, container_size.inline).specified_or_zero() + } else { + -MaybeAuto::from_style(offsets.inline_end, container_size.inline).specified_or_zero() + }; + let offset_b = if offsets.block_start != LPA_Auto { + MaybeAuto::from_style(offsets.block_start, container_size.inline).specified_or_zero() + } else { + -MaybeAuto::from_style(offsets.block_end, container_size.inline).specified_or_zero() + }; + LogicalSize::new(style.writing_mode, offset_i, offset_b) + } + + // Go over the ancestor fragments and add all relative offsets (if any). + let mut rel_pos = LogicalSize::zero(self.style.writing_mode); + match self.inline_context { + None => { + if self.style().get_box().position == position::relative { + rel_pos = rel_pos + from_style(self.style(), containing_block_size); + } + } + Some(ref inline_fragment_context) => { + for style in inline_fragment_context.styles.iter() { + if style.get_box().position == position::relative { + rel_pos = rel_pos + from_style(&**style, containing_block_size); + } + } + }, + } + rel_pos + } + + /// Always inline for SCCP. + /// + /// FIXME(pcwalton): Just replace with the clear type from the style module for speed? + #[inline(always)] + pub fn clear(&self) -> Option<ClearType> { + let style = self.style(); + match style.get_box().clear { + clear::none => None, + clear::left => Some(ClearLeft), + clear::right => Some(ClearRight), + clear::both => Some(ClearBoth), + } + } + + /// Converts this fragment's computed style to a font style used for rendering. + pub fn font_style(&self) -> FontStyle { + text::computed_style_to_font_style(self.style()) + } + + #[inline(always)] + pub fn style<'a>(&'a self) -> &'a ComputedValues { + &*self.style + } + + /// Returns the text alignment of the computed style of the nearest ancestor-or-self `Element` + /// node. + pub fn text_align(&self) -> text_align::T { + self.style().get_inheritedtext().text_align + } + + pub fn vertical_align(&self) -> vertical_align::T { + self.style().get_box().vertical_align + } + + pub fn white_space(&self) -> white_space::T { + self.style().get_inheritedtext().white_space + } + + /// Returns the text decoration of this fragment, according to the style of the nearest ancestor + /// element. + /// + /// NB: This may not be the actual text decoration, because of the override rules specified in + /// CSS 2.1 § 16.3.1. Unfortunately, computing this properly doesn't really fit into Servo's + /// model. Therefore, this is a best lower bound approximation, but the end result may actually + /// have the various decoration flags turned on afterward. + pub fn text_decoration(&self) -> text_decoration::T { + self.style().get_text().text_decoration + } + + /// Returns the inline-start offset from margin edge to content edge. + /// + /// FIXME(#2262, pcwalton): I think this method is pretty bogus, because it won't work for + /// inlines. + pub fn inline_start_offset(&self) -> Au { + match self.specific { + TableWrapperFragment => self.margin.inline_start, + TableFragment | TableCellFragment | TableRowFragment => self.border_padding.inline_start, + TableColumnFragment(_) => Au(0), + _ => self.margin.inline_start + self.border_padding.inline_start, + } + } + + /// Returns true if this element can be split. This is true for text fragments. + pub fn can_split(&self) -> bool { + match self.specific { + ScannedTextFragment(..) => true, + _ => false, + } + } + + /// Adds the display items necessary to paint the background of this fragment to the display + /// list if necessary. + pub fn build_display_list_for_background_if_applicable(&self, + style: &ComputedValues, + list: &mut DisplayList, + layout_context: &LayoutContext, + level: StackingLevel, + absolute_bounds: &Rect<Au>) { + // FIXME: This causes a lot of background colors to be displayed when they are clearly not + // needed. We could use display list optimization to clean this up, but it still seems + // inefficient. What we really want is something like "nearest ancestor element that + // doesn't have a fragment". + let background_color = style.resolve_color(style.get_background().background_color); + if !background_color.alpha.approx_eq(&0.0) { + let display_item = box SolidColorDisplayItem { + base: BaseDisplayItem::new(*absolute_bounds, self.node, level), + color: background_color.to_gfx_color(), + }; + + list.push(SolidColorDisplayItemClass(display_item)) + } + + // The background image is painted on top of the background color. + // Implements background image, per spec: + // http://www.w3.org/TR/CSS21/colors.html#background + let background = style.get_background(); + let image_url = match background.background_image { + None => return, + Some(ref image_url) => image_url, + }; + + let mut holder = ImageHolder::new(image_url.clone(), layout_context.shared.image_cache.clone()); + let image = match holder.get_image() { + None => { + // No image data at all? Do nothing. + // + // TODO: Add some kind of placeholder background image. + debug!("(building display list) no background image :("); + return + } + Some(image) => image, + }; + debug!("(building display list) building background image"); + + // Adjust bounds for `background-position` and `background-attachment`. + let mut bounds = *absolute_bounds; + let horizontal_position = model::specified(background.background_position.horizontal, + bounds.size.width); + let vertical_position = model::specified(background.background_position.vertical, + bounds.size.height); + + let clip_display_item; + match background.background_attachment { + background_attachment::scroll => { + clip_display_item = None; + bounds.origin.x = bounds.origin.x + horizontal_position; + bounds.origin.y = bounds.origin.y + vertical_position; + bounds.size.width = bounds.size.width - horizontal_position; + bounds.size.height = bounds.size.height - vertical_position; + } + background_attachment::fixed => { + clip_display_item = Some(box ClipDisplayItem { + base: BaseDisplayItem::new(bounds, self.node, level), + children: DisplayList::new(), + }); + + bounds = Rect { + origin: Point2D(horizontal_position, vertical_position), + size: Size2D(bounds.origin.x + bounds.size.width, + bounds.origin.y + bounds.size.height), + } + } + } + + // Adjust sizes for `background-repeat`. + match background.background_repeat { + background_repeat::no_repeat => { + bounds.size.width = Au::from_px(image.width as int); + bounds.size.height = Au::from_px(image.height as int) + } + background_repeat::repeat_x => { + bounds.size.height = Au::from_px(image.height as int) + } + background_repeat::repeat_y => { + bounds.size.width = Au::from_px(image.width as int) + } + background_repeat::repeat => {} + }; + + // Create the image display item. + let image_display_item = ImageDisplayItemClass(box ImageDisplayItem { + base: BaseDisplayItem::new(bounds, self.node, level), + image: image.clone(), + stretch_size: Size2D(Au::from_px(image.width as int), + Au::from_px(image.height as int)), + }); + + match clip_display_item { + None => list.push(image_display_item), + Some(mut clip_display_item) => { + clip_display_item.children.push(image_display_item); + list.push(ClipDisplayItemClass(clip_display_item)) + } + } + } + + /// Adds the display items necessary to paint the borders of this fragment to a display list if + /// necessary. + pub fn build_display_list_for_borders_if_applicable(&self, + style: &ComputedValues, + list: &mut DisplayList, + abs_bounds: &Rect<Au>, + level: StackingLevel) { + let border = style.logical_border_width(); + if border.is_zero() { + return + } + + let top_color = style.resolve_color(style.get_border().border_top_color); + let right_color = style.resolve_color(style.get_border().border_right_color); + let bottom_color = style.resolve_color(style.get_border().border_bottom_color); + let left_color = style.resolve_color(style.get_border().border_left_color); + + // Append the border to the display list. + let border_display_item = box BorderDisplayItem { + base: BaseDisplayItem::new(*abs_bounds, self.node, level), + border: border.to_physical(style.writing_mode), + color: SideOffsets2D::new(top_color.to_gfx_color(), + right_color.to_gfx_color(), + bottom_color.to_gfx_color(), + left_color.to_gfx_color()), + style: SideOffsets2D::new(style.get_border().border_top_style, + style.get_border().border_right_style, + style.get_border().border_bottom_style, + style.get_border().border_left_style) + }; + + list.push(BorderDisplayItemClass(border_display_item)) + } + + fn build_debug_borders_around_text_fragments(&self, + display_list: &mut DisplayList, + flow_origin: Point2D<Au>, + text_fragment: &ScannedTextFragmentInfo) { + // FIXME(#2795): Get the real container size + let container_size = Size2D::zero(); + // Fragment position wrt to the owning flow. + let fragment_bounds = self.border_box.to_physical(self.style.writing_mode, container_size); + let absolute_fragment_bounds = Rect( + fragment_bounds.origin + flow_origin, + fragment_bounds.size); + + // Compute the text fragment bounds and draw a border surrounding them. + let border_display_item = box BorderDisplayItem { + base: BaseDisplayItem::new(absolute_fragment_bounds, self.node, ContentStackingLevel), + border: SideOffsets2D::new_all_same(Au::from_px(1)), + color: SideOffsets2D::new_all_same(rgb(0, 0, 200)), + style: SideOffsets2D::new_all_same(border_style::solid) + }; + display_list.push(BorderDisplayItemClass(border_display_item)); + + // Draw a rectangle representing the baselines. + let ascent = text_fragment.run.ascent(); + let mut baseline = self.border_box.clone(); + baseline.start.b = baseline.start.b + ascent; + baseline.size.block = Au(0); + let mut baseline = baseline.to_physical(self.style.writing_mode, container_size); + baseline.origin = baseline.origin + flow_origin; + + let line_display_item = box LineDisplayItem { + base: BaseDisplayItem::new(baseline, self.node, ContentStackingLevel), + color: rgb(0, 200, 0), + style: border_style::dashed, + }; + display_list.push(LineDisplayItemClass(line_display_item)); + } + + fn build_debug_borders_around_fragment(&self, + display_list: &mut DisplayList, + flow_origin: Point2D<Au>) { + // FIXME(#2795): Get the real container size + let container_size = Size2D::zero(); + // Fragment position wrt to the owning flow. + let fragment_bounds = self.border_box.to_physical(self.style.writing_mode, container_size); + let absolute_fragment_bounds = Rect( + fragment_bounds.origin + flow_origin, + fragment_bounds.size); + + // This prints a debug border around the border of this fragment. + let border_display_item = box BorderDisplayItem { + base: BaseDisplayItem::new(absolute_fragment_bounds, self.node, ContentStackingLevel), + border: SideOffsets2D::new_all_same(Au::from_px(1)), + color: SideOffsets2D::new_all_same(rgb(0, 0, 200)), + style: SideOffsets2D::new_all_same(border_style::solid) + }; + display_list.push(BorderDisplayItemClass(border_display_item)) + } + + /// Adds the display items for this fragment to the given stacking context. + /// + /// Arguments: + /// + /// * `display_list`: The unflattened display list to add display items to. + /// * `layout_context`: The layout context. + /// * `dirty`: The dirty rectangle in the coordinate system of the owning flow. + /// * `flow_origin`: Position of the origin of the owning flow wrt the display list root flow. + pub fn build_display_list(&self, + display_list: &mut DisplayList, + layout_context: &LayoutContext, + flow_origin: Point2D<Au>, + background_and_border_level: BackgroundAndBorderLevel) + -> ChildDisplayListAccumulator { + // FIXME(#2795): Get the real container size + let container_size = Size2D::zero(); + let rect_to_absolute = |logical_rect: LogicalRect<Au>| { + let physical_rect = logical_rect.to_physical(self.style.writing_mode, container_size); + Rect(physical_rect.origin + flow_origin, physical_rect.size) + }; + // Fragment position wrt to the owning flow. + let absolute_fragment_bounds = rect_to_absolute(self.border_box); + debug!("Fragment::build_display_list at rel={}, abs={}: {}", + self.border_box, + absolute_fragment_bounds, + self); + debug!("Fragment::build_display_list: dirty={}, flow_origin={}", + layout_context.shared.dirty, + flow_origin); + + let mut accumulator = ChildDisplayListAccumulator::new(self.style(), + absolute_fragment_bounds, + self.node, + ContentStackingLevel); + if self.style().get_inheritedbox().visibility != visibility::visible { + return accumulator + } + + if !absolute_fragment_bounds.intersects(&layout_context.shared.dirty) { + debug!("Fragment::build_display_list: Did not intersect..."); + return accumulator + } + + debug!("Fragment::build_display_list: intersected. Adding display item..."); + + { + let level = + StackingLevel::from_background_and_border_level(background_and_border_level); + + // Add a pseudo-display item for content box queries. This is a very bogus thing to do. + let base_display_item = box BaseDisplayItem::new(absolute_fragment_bounds, self.node, level); + display_list.push(PseudoDisplayItemClass(base_display_item)); + + // Add the background to the list, if applicable. + match self.inline_context { + Some(ref inline_context) => { + for style in inline_context.styles.iter().rev() { + self.build_display_list_for_background_if_applicable(&**style, + display_list, + layout_context, + level, + &absolute_fragment_bounds); + } + } + None => { + self.build_display_list_for_background_if_applicable(&*self.style, + display_list, + layout_context, + level, + &absolute_fragment_bounds); + } + } + + // Add a border, if applicable. + // + // TODO: Outlines. + match self.inline_context { + Some(ref inline_context) => { + for style in inline_context.styles.iter().rev() { + self.build_display_list_for_borders_if_applicable(&**style, + display_list, + &absolute_fragment_bounds, + level); + } + } + None => { + self.build_display_list_for_borders_if_applicable(&*self.style, + display_list, + &absolute_fragment_bounds, + level); + } + } + } + + let content_box = self.content_box(); + let absolute_content_box = rect_to_absolute(content_box); + + // Add a clip, if applicable. + match self.specific { + UnscannedTextFragment(_) => fail!("Shouldn't see unscanned fragments here."), + TableColumnFragment(_) => fail!("Shouldn't see table column fragments here."), + ScannedTextFragment(ref text_fragment) => { + // Create the text display item. + let orientation = if self.style.writing_mode.is_vertical() { + if self.style.writing_mode.is_sideways_left() { + SidewaysLeft + } else { + SidewaysRight + } + } else { + Upright + }; + + let metrics = &text_fragment.run.font_metrics; + let baseline_origin ={ + let mut tmp = content_box.start; + tmp.b = tmp.b + metrics.ascent; + tmp.to_physical(self.style.writing_mode, container_size) + flow_origin + }; + + let text_display_item = box TextDisplayItem { + base: BaseDisplayItem::new( + absolute_content_box, self.node, ContentStackingLevel), + text_run: text_fragment.run.clone(), + range: text_fragment.range, + text_color: self.style().get_color().color.to_gfx_color(), + orientation: orientation, + baseline_origin: baseline_origin, + }; + accumulator.push(display_list, TextDisplayItemClass(text_display_item)); + + + // Create display items for text decoration + { + let line = |maybe_color: Option<RGBA>, rect: || -> LogicalRect<Au>| { + match maybe_color { + None => {}, + Some(color) => { + accumulator.push(display_list, SolidColorDisplayItemClass( + box SolidColorDisplayItem { + base: BaseDisplayItem::new( + rect_to_absolute(rect()), + self.node, ContentStackingLevel), + color: color.to_gfx_color(), + } + )); + } + } + }; + + let text_decorations = + self.style().get_inheritedtext()._servo_text_decorations_in_effect; + line(text_decorations.underline, || { + let mut rect = content_box.clone(); + rect.start.b = rect.start.b + metrics.ascent - metrics.underline_offset; + rect.size.block = metrics.underline_size; + rect + }); + + line(text_decorations.overline, || { + let mut rect = content_box.clone(); + rect.size.block = metrics.underline_size; + rect + }); + + line(text_decorations.line_through, || { + let mut rect = content_box.clone(); + rect.start.b = rect.start.b + metrics.ascent - metrics.strikeout_offset; + rect.size.block = metrics.strikeout_size; + rect + }); + } + + // Draw debug frames for text bounds. + // + // FIXME(#2263, pcwalton): This is a bit of an abuse of the logging infrastructure. + // We should have a real `SERVO_DEBUG` system. + debug!("{:?}", self.build_debug_borders_around_text_fragments(display_list, + flow_origin, + text_fragment)) + }, + GenericFragment | IframeFragment(..) | TableFragment | TableCellFragment | TableRowFragment | + TableWrapperFragment => { + // FIXME(pcwalton): This is a bit of an abuse of the logging infrastructure. We + // should have a real `SERVO_DEBUG` system. + debug!("{:?}", self.build_debug_borders_around_fragment(display_list, flow_origin)) + }, + ImageFragment(_) => { + match self.specific { + ImageFragment(ref image_fragment) => { + let image_ref = &image_fragment.image; + match image_ref.get_image_if_present() { + Some(image) => { + debug!("(building display list) building image fragment"); + + // Place the image into the display list. + let image_display_item = box ImageDisplayItem { + base: BaseDisplayItem::new(absolute_content_box, + self.node, + ContentStackingLevel), + image: image.clone(), + stretch_size: absolute_content_box.size, + }; + accumulator.push(display_list, + ImageDisplayItemClass(image_display_item)) + } + None => { + // No image data at all? Do nothing. + // + // TODO: Add some kind of placeholder image. + debug!("(building display list) no image :("); + } + } + } + _ => fail!("shouldn't get here"), + } + + // FIXME(pcwalton): This is a bit of an abuse of the logging + // infrastructure. We should have a real `SERVO_DEBUG` system. + debug!("{:?}", self.build_debug_borders_around_fragment(display_list, flow_origin)) + } + } + + // If this is an iframe, then send its position and size up to the constellation. + // + // FIXME(pcwalton): Doing this during display list construction seems potentially + // problematic if iframes are outside the area we're computing the display list for, since + // they won't be able to reflow at all until the user scrolls to them. Perhaps we should + // separate this into two parts: first we should send the size only to the constellation + // once that's computed during assign-block-sizes, and second we should should send the origin + // to the constellation here during display list construction. This should work because + // layout for the iframe only needs to know size, and origin is only relevant if the + // iframe is actually going to be displayed. + match self.specific { + IframeFragment(ref iframe_fragment) => { + self.finalize_position_and_size_of_iframe(iframe_fragment, flow_origin, layout_context) + } + _ => {} + } + + accumulator + } + + /// Returns the intrinsic inline-sizes of this fragment. + pub fn intrinsic_inline_sizes(&mut self) + -> IntrinsicISizes { + let mut result = self.style_specified_intrinsic_inline_size(); + + match self.specific { + GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment | TableColumnFragment(_) | TableRowFragment | + TableWrapperFragment => {} + ImageFragment(ref mut image_fragment_info) => { + let image_inline_size = image_fragment_info.image_inline_size(); + result.minimum_inline_size = geometry::max(result.minimum_inline_size, image_inline_size); + result.preferred_inline_size = geometry::max(result.preferred_inline_size, image_inline_size); + } + ScannedTextFragment(ref text_fragment_info) => { + let range = &text_fragment_info.range; + let min_line_inline_size = text_fragment_info.run.min_width_for_range(range); + + // See http://dev.w3.org/csswg/css-sizing/#max-content-inline-size. + // TODO: Account for soft wrap opportunities. + let max_line_inline_size = text_fragment_info.run.metrics_for_range(range).advance_width; + + result.minimum_inline_size = geometry::max(result.minimum_inline_size, min_line_inline_size); + result.preferred_inline_size = geometry::max(result.preferred_inline_size, max_line_inline_size); + } + UnscannedTextFragment(..) => fail!("Unscanned text fragments should have been scanned by now!"), + } + + // Take borders and padding for parent inline fragments into account, if necessary. + match self.inline_context { + None => {} + Some(ref context) => { + for style in context.styles.iter() { + let border_width = style.logical_border_width().inline_start_end(); + let padding_inline_size = model::padding_from_style(&**style, Au(0)).inline_start_end(); + result.minimum_inline_size = result.minimum_inline_size + border_width + padding_inline_size; + result.preferred_inline_size = result.preferred_inline_size + border_width + padding_inline_size; + } + } + } + + result + } + + + /// TODO: What exactly does this function return? Why is it Au(0) for GenericFragment? + pub fn content_inline_size(&self) -> Au { + match self.specific { + GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment | TableRowFragment | + TableWrapperFragment => Au(0), + ImageFragment(ref image_fragment_info) => { + image_fragment_info.computed_inline_size() + } + ScannedTextFragment(ref text_fragment_info) => { + let (range, run) = (&text_fragment_info.range, &text_fragment_info.run); + let text_bounds = run.metrics_for_range(range).bounding_box; + text_bounds.size.width + } + TableColumnFragment(_) => fail!("Table column fragments do not have inline_size"), + UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"), + } + } + + /// Returns, and computes, the block-size of this fragment. + pub fn content_block_size(&self, layout_context: &LayoutContext) -> Au { + match self.specific { + GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment | TableRowFragment | + TableWrapperFragment => Au(0), + ImageFragment(ref image_fragment_info) => { + image_fragment_info.computed_block_size() + } + ScannedTextFragment(_) => { + // Compute the block-size based on the line-block-size and font size. + self.calculate_line_height(layout_context) + } + TableColumnFragment(_) => fail!("Table column fragments do not have block_size"), + UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"), + } + } + + /// Returns the dimensions of the content box. + /// + /// This is marked `#[inline]` because it is frequently called when only one or two of the + /// values are needed and that will save computation. + #[inline] + pub fn content_box(&self) -> LogicalRect<Au> { + self.border_box - self.border_padding + } + + /// Find the split of a fragment that includes a new-line character. + /// + /// A return value of `None` indicates that the fragment is not splittable. + /// Otherwise the split information is returned. The right information is + /// optional due to the possibility of it being whitespace. + // + // TODO(bjz): The text run should be removed in the future, but it is currently needed for + // the current method of fragment splitting in the `inline::try_append_*` functions. + pub fn find_split_info_by_new_line(&self) + -> Option<(SplitInfo, Option<SplitInfo>, Arc<Box<TextRun>> /* TODO(bjz): remove */)> { + match self.specific { + GenericFragment | IframeFragment(_) | ImageFragment(_) | TableFragment | TableCellFragment | + TableRowFragment | TableWrapperFragment => None, + TableColumnFragment(_) => fail!("Table column fragments do not need to split"), + UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"), + ScannedTextFragment(ref text_fragment_info) => { + let mut new_line_pos = self.new_line_pos.clone(); + let cur_new_line_pos = new_line_pos.remove(0).unwrap(); + + let inline_start_range = Range::new(text_fragment_info.range.begin(), cur_new_line_pos); + let inline_end_range = Range::new(text_fragment_info.range.begin() + cur_new_line_pos + CharIndex(1), + text_fragment_info.range.length() - (cur_new_line_pos + CharIndex(1))); + + // Left fragment is for inline-start text of first founded new-line character. + let inline_start_fragment = SplitInfo::new(inline_start_range, text_fragment_info); + + // Right fragment is for inline-end text of first founded new-line character. + let inline_end_fragment = if inline_end_range.length() > CharIndex(0) { + Some(SplitInfo::new(inline_end_range, text_fragment_info)) + } else { + None + }; + + Some((inline_start_fragment, inline_end_fragment, text_fragment_info.run.clone())) + } + } + } + + /// Attempts to find the split positions of a text fragment so that its inline-size is + /// no more than `max_inline-size`. + /// + /// A return value of `None` indicates that the fragment could not be split. + /// Otherwise the information pertaining to the split is returned. The inline-start + /// and inline-end split information are both optional due to the possibility of + /// them being whitespace. + // + // TODO(bjz): The text run should be removed in the future, but it is currently needed for + // the current method of fragment splitting in the `inline::try_append_*` functions. + pub fn find_split_info_for_inline_size(&self, start: CharIndex, max_inline_size: Au, starts_line: bool) + -> Option<(Option<SplitInfo>, Option<SplitInfo>, Arc<Box<TextRun>> /* TODO(bjz): remove */)> { + match self.specific { + GenericFragment | IframeFragment(_) | ImageFragment(_) | TableFragment | TableCellFragment | + TableRowFragment | TableWrapperFragment => None, + TableColumnFragment(_) => fail!("Table column fragments do not have inline_size"), + UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"), + ScannedTextFragment(ref text_fragment_info) => { + let mut pieces_processed_count: uint = 0; + let mut remaining_inline_size: Au = max_inline_size; + let mut inline_start_range = Range::new(text_fragment_info.range.begin() + start, CharIndex(0)); + let mut inline_end_range: Option<Range<CharIndex>> = None; + + debug!("split_to_inline_size: splitting text fragment (strlen={}, range={}, avail_inline_size={})", + text_fragment_info.run.text.len(), + text_fragment_info.range, + max_inline_size); + + for (glyphs, offset, slice_range) in text_fragment_info.run.iter_slices_for_range( + &text_fragment_info.range) { + debug!("split_to_inline_size: considering slice (offset={}, range={}, \ + remain_inline_size={})", + offset, + slice_range, + remaining_inline_size); + + let metrics = text_fragment_info.run.metrics_for_slice(glyphs, &slice_range); + let advance = metrics.advance_width; + + let should_continue; + if advance <= remaining_inline_size { + should_continue = true; + + if starts_line && pieces_processed_count == 0 && glyphs.is_whitespace() { + debug!("split_to_inline_size: case=skipping leading trimmable whitespace"); + inline_start_range.shift_by(slice_range.length()); + } else { + debug!("split_to_inline_size: case=enlarging span"); + remaining_inline_size = remaining_inline_size - advance; + inline_start_range.extend_by(slice_range.length()); + } + } else { + // The advance is more than the remaining inline-size. + should_continue = false; + let slice_begin = offset + slice_range.begin(); + let slice_end = offset + slice_range.end(); + + if glyphs.is_whitespace() { + // If there are still things after the trimmable whitespace, create the + // inline-end chunk. + if slice_end < text_fragment_info.range.end() { + debug!("split_to_inline_size: case=skipping trimmable trailing \ + whitespace, then split remainder"); + let inline_end_range_end = text_fragment_info.range.end() - slice_end; + inline_end_range = Some(Range::new(slice_end, inline_end_range_end)); + } else { + debug!("split_to_inline_size: case=skipping trimmable trailing \ + whitespace"); + } + } else if slice_begin < text_fragment_info.range.end() { + // There are still some things inline-start over at the end of the line. Create + // the inline-end chunk. + let inline_end_range_end = text_fragment_info.range.end() - slice_begin; + inline_end_range = Some(Range::new(slice_begin, inline_end_range_end)); + debug!("split_to_inline_size: case=splitting remainder with inline_end range={:?}", + inline_end_range); + } + } + + pieces_processed_count += 1; + + if !should_continue { + break + } + } + + let inline_start_is_some = inline_start_range.length() > CharIndex(0); + + if (pieces_processed_count == 1 || !inline_start_is_some) && !starts_line { + None + } else { + let inline_start = if inline_start_is_some { + Some(SplitInfo::new(inline_start_range, text_fragment_info)) + } else { + None + }; + let inline_end = inline_end_range.map(|inline_end_range| SplitInfo::new(inline_end_range, text_fragment_info)); + + Some((inline_start, inline_end, text_fragment_info.run.clone())) + } + } + } + } + + /// Returns true if this fragment is an unscanned text fragment that consists entirely of whitespace. + pub fn is_whitespace_only(&self) -> bool { + match self.specific { + UnscannedTextFragment(ref text_fragment_info) => is_whitespace(text_fragment_info.text.as_slice()), + _ => false, + } + } + + /// Assigns replaced inline-size, padding, and margins for this fragment only if it is replaced + /// content per CSS 2.1 § 10.3.2. + pub fn assign_replaced_inline_size_if_necessary(&mut self, + container_inline_size: Au) { + match self.specific { + GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment | TableRowFragment | + TableWrapperFragment => return, + TableColumnFragment(_) => fail!("Table column fragments do not have inline_size"), + UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"), + ImageFragment(_) | ScannedTextFragment(_) => {} + }; + + self.compute_border_padding_margins(container_inline_size); + + let style_inline_size = self.style().content_inline_size(); + let style_block_size = self.style().content_block_size(); + let noncontent_inline_size = self.border_padding.inline_start_end(); + + match self.specific { + ScannedTextFragment(_) => { + // Scanned text fragments will have already had their content inline-sizes assigned by this + // point. + self.border_box.size.inline = self.border_box.size.inline + noncontent_inline_size + } + ImageFragment(ref mut image_fragment_info) => { + // TODO(ksh8281): compute border,margin + let inline_size = ImageFragmentInfo::style_length(style_inline_size, + image_fragment_info.dom_inline_size, + container_inline_size); + let block_size = ImageFragmentInfo::style_length(style_block_size, + image_fragment_info.dom_block_size, + Au(0)); + + let inline_size = match (inline_size,block_size) { + (Auto, Auto) => image_fragment_info.image_inline_size(), + (Auto,Specified(h)) => { + let scale = image_fragment_info. + image_block_size().to_f32().unwrap() / h.to_f32().unwrap(); + Au::new((image_fragment_info.image_inline_size().to_f32().unwrap() / scale) as i32) + }, + (Specified(w), _) => w, + }; + + self.border_box.size.inline = inline_size + noncontent_inline_size; + image_fragment_info.computed_inline_size = Some(inline_size); + } + _ => fail!("this case should have been handled above"), + } + } + + /// Assign block-size for this fragment if it is replaced content. The inline-size must have been assigned + /// first. + /// + /// Ideally, this should follow CSS 2.1 § 10.6.2. + pub fn assign_replaced_block_size_if_necessary(&mut self) { + match self.specific { + GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment | TableRowFragment | + TableWrapperFragment => return, + TableColumnFragment(_) => fail!("Table column fragments do not have block_size"), + UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"), + ImageFragment(_) | ScannedTextFragment(_) => {} + } + + let style_inline_size = self.style().content_inline_size(); + let style_block_size = self.style().content_block_size(); + let noncontent_block_size = self.border_padding.block_start_end(); + + match self.specific { + ImageFragment(ref mut image_fragment_info) => { + // TODO(ksh8281): compute border,margin,padding + let inline_size = image_fragment_info.computed_inline_size(); + // FIXME(ksh8281): we shouldn't assign block-size this way + // we don't know about size of parent's block-size + let block_size = ImageFragmentInfo::style_length(style_block_size, + image_fragment_info.dom_block_size, + Au(0)); + + let block_size = match (style_inline_size, image_fragment_info.dom_inline_size, block_size) { + (LPA_Auto, None, Auto) => { + image_fragment_info.image_block_size() + }, + (_,_,Auto) => { + let scale = image_fragment_info.image_inline_size().to_f32().unwrap() + / inline_size.to_f32().unwrap(); + Au::new((image_fragment_info.image_block_size().to_f32().unwrap() / scale) as i32) + }, + (_,_,Specified(h)) => { + h + } + }; + + image_fragment_info.computed_block_size = Some(block_size); + self.border_box.size.block = block_size + noncontent_block_size + } + ScannedTextFragment(_) => { + // Scanned text fragments' content block-sizes are calculated by the text run scanner + // during flow construction. + self.border_box.size.block = self.border_box.size.block + noncontent_block_size + } + _ => fail!("should have been handled above"), + } + } + + /// Calculates block-size above baseline, depth below baseline, and ascent for this fragment when + /// used in an inline formatting context. See CSS 2.1 § 10.8.1. + pub fn inline_metrics(&self, layout_context: &LayoutContext) -> InlineMetrics { + match self.specific { + ImageFragment(ref image_fragment_info) => { + let computed_block_size = image_fragment_info.computed_block_size(); + InlineMetrics { + block_size_above_baseline: computed_block_size + self.border_padding.block_start_end(), + depth_below_baseline: Au(0), + ascent: computed_block_size + self.border_padding.block_end, + } + } + ScannedTextFragment(ref text_fragment) => { + // See CSS 2.1 § 10.8.1. + let line_height = self.calculate_line_height(layout_context); + InlineMetrics::from_font_metrics(&text_fragment.run.font_metrics, line_height) + } + _ => { + InlineMetrics { + block_size_above_baseline: self.border_box.size.block, + depth_below_baseline: Au(0), + ascent: self.border_box.size.block, + } + } + } + } + + /// Returns true if this fragment can merge with another adjacent fragment or false otherwise. + pub fn can_merge_with_fragment(&self, other: &Fragment) -> bool { + match (&self.specific, &other.specific) { + (&UnscannedTextFragment(_), &UnscannedTextFragment(_)) => { + // FIXME: Should probably use a whitelist of styles that can safely differ (#3165) + self.font_style() == other.font_style() && + self.text_decoration() == other.text_decoration() && + self.white_space() == other.white_space() + } + _ => false, + } + } + + /// Sends the size and position of this iframe fragment to the constellation. This is out of + /// line to guide inlining. + #[inline(never)] + fn finalize_position_and_size_of_iframe(&self, + iframe_fragment: &IframeFragmentInfo, + offset: Point2D<Au>, + layout_context: &LayoutContext) { + let mbp = (self.margin + self.border_padding).to_physical(self.style.writing_mode); + let content_size = self.content_box().size.to_physical(self.style.writing_mode); + + let left = offset.x + mbp.left; + let top = offset.y + mbp.top; + let width = content_size.width; + let height = content_size.height; + let origin = Point2D(geometry::to_frac_px(left) as f32, geometry::to_frac_px(top) as f32); + let size = Size2D(geometry::to_frac_px(width) as f32, geometry::to_frac_px(height) as f32); + let rect = Rect(origin, size); + + debug!("finalizing position and size of iframe for {:?},{:?}", + iframe_fragment.pipeline_id, + iframe_fragment.subpage_id); + let msg = FrameRectMsg(iframe_fragment.pipeline_id, iframe_fragment.subpage_id, rect); + let ConstellationChan(ref chan) = layout_context.shared.constellation_chan; + chan.send(msg) + } +} + +impl fmt::Show for Fragment { + /// Outputs a debugging string describing this fragment. + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + try!(write!(f, "({} ", + match self.specific { + GenericFragment => "GenericFragment", + IframeFragment(_) => "IframeFragment", + ImageFragment(_) => "ImageFragment", + ScannedTextFragment(_) => "ScannedTextFragment", + TableFragment => "TableFragment", + TableCellFragment => "TableCellFragment", + TableColumnFragment(_) => "TableColumnFragment", + TableRowFragment => "TableRowFragment", + TableWrapperFragment => "TableWrapperFragment", + UnscannedTextFragment(_) => "UnscannedTextFragment", + })); + try!(write!(f, "bp {}", self.border_padding)); + try!(write!(f, " ")); + try!(write!(f, "m {}", self.margin)); + write!(f, ")") + } +} + +/// An object that accumulates display lists of child flows, applying a clipping rect if necessary. +pub struct ChildDisplayListAccumulator { + clip_display_item: Option<Box<ClipDisplayItem>>, +} + +impl ChildDisplayListAccumulator { + /// Creates a `ChildDisplayListAccumulator` from the `overflow` property in the given style. + fn new(style: &ComputedValues, bounds: Rect<Au>, node: OpaqueNode, level: StackingLevel) + -> ChildDisplayListAccumulator { + ChildDisplayListAccumulator { + clip_display_item: match style.get_box().overflow { + overflow::hidden | overflow::auto | overflow::scroll => { + Some(box ClipDisplayItem { + base: BaseDisplayItem::new(bounds, node, level), + children: DisplayList::new(), + }) + }, + overflow::visible => None, + } + } + } + + /// Pushes the given display item onto this display list. + pub fn push(&mut self, parent_display_list: &mut DisplayList, item: DisplayItem) { + match self.clip_display_item { + None => parent_display_list.push(item), + Some(ref mut clip_display_item) => clip_display_item.children.push(item), + } + } + + /// Pushes the display items from the given child onto this display list. + pub fn push_child(&mut self, parent_display_list: &mut DisplayList, child: &mut Flow) { + let kid_display_list = mem::replace(&mut flow::mut_base(child).display_list, + DisplayList::new()); + match self.clip_display_item { + None => parent_display_list.push_all_move(kid_display_list), + Some(ref mut clip_display_item) => { + clip_display_item.children.push_all_move(kid_display_list) + } + } + } + + /// Consumes this accumulator and pushes the clipping item, if any, onto the display list + /// associated with the given flow, along with the items in the given display list. + pub fn finish(self, parent: &mut Flow, mut display_list: DisplayList) { + let ChildDisplayListAccumulator { + clip_display_item + } = self; + match clip_display_item { + None => {} + Some(clip_display_item) => display_list.push(ClipDisplayItemClass(clip_display_item)), + } + flow::mut_base(parent).display_list = display_list + } +} diff --git a/components/layout/incremental.rs b/components/layout/incremental.rs new file mode 100644 index 00000000000..d04c068b6aa --- /dev/null +++ b/components/layout/incremental.rs @@ -0,0 +1,78 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use style::ComputedValues; + +bitflags! { + #[doc = "Individual layout actions that may be necessary after restyling."] + flags RestyleDamage: int { + #[doc = "Repaint the node itself."] + #[doc = "Currently unused; need to decide how this propagates."] + static Repaint = 0x01, + + #[doc = "Recompute intrinsic inline_sizes (minimum and preferred)."] + #[doc = "Propagates down the flow tree because the computation is"] + #[doc = "bottom-up."] + static BubbleISizes = 0x02, + + #[doc = "Recompute actual inline_sizes and block_sizes."] + #[doc = "Propagates up the flow tree because the computation is"] + #[doc = "top-down."] + static Reflow = 0x04 + } +} + +impl RestyleDamage { + /// Elements of self which should also get set on any ancestor flow. + pub fn propagate_up(self) -> RestyleDamage { + self & Reflow + } + + /// Elements of self which should also get set on any child flows. + pub fn propagate_down(self) -> RestyleDamage { + self & BubbleISizes + } +} + +// NB: We need the braces inside the RHS due to Rust #8012. This particular +// version of this macro might be safe anyway, but we want to avoid silent +// breakage on modifications. +macro_rules! add_if_not_equal( + ($old:ident, $new:ident, $damage:ident, + [ $($effect:ident),* ], [ $($style_struct_getter:ident.$name:ident),* ]) => ({ + if $( ($old.$style_struct_getter().$name != $new.$style_struct_getter().$name) )||* { + $damage.insert($($effect)|*); + } + }) +) + +pub fn compute_damage(old: &ComputedValues, new: &ComputedValues) -> RestyleDamage { + let mut damage = RestyleDamage::empty(); + + // This checks every CSS property, as enumerated in + // impl<'self> CssComputedStyle<'self> + // in src/support/netsurfcss/rust-netsurfcss/netsurfcss.rc. + + // FIXME: We can short-circuit more of this. + + add_if_not_equal!(old, new, damage, [ Repaint ], + [ get_color.color, get_background.background_color, + get_border.border_top_color, get_border.border_right_color, + get_border.border_bottom_color, get_border.border_left_color ]); + + add_if_not_equal!(old, new, damage, [ Repaint, BubbleISizes, Reflow ], + [ get_border.border_top_width, get_border.border_right_width, + get_border.border_bottom_width, get_border.border_left_width, + get_margin.margin_top, get_margin.margin_right, + get_margin.margin_bottom, get_margin.margin_left, + get_padding.padding_top, get_padding.padding_right, + get_padding.padding_bottom, get_padding.padding_left, + get_box.position, get_box.width, get_box.height, get_box.float, get_box.display, + get_font.font_family, get_font.font_size, get_font.font_style, get_font.font_weight, + get_inheritedtext.text_align, get_text.text_decoration, get_inheritedbox.line_height ]); + + // FIXME: test somehow that we checked every CSS property + + damage +} diff --git a/components/layout/inline.rs b/components/layout/inline.rs new file mode 100644 index 00000000000..6c22b25746e --- /dev/null +++ b/components/layout/inline.rs @@ -0,0 +1,1170 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#![deny(unsafe_block)] + +use css::node_style::StyledNode; +use context::LayoutContext; +use floats::{FloatLeft, Floats, PlacementInfo}; +use flow::{BaseFlow, FlowClass, Flow, InlineFlowClass}; +use flow; +use fragment::{Fragment, ScannedTextFragment, ScannedTextFragmentInfo, SplitInfo}; +use layout_debug; +use model::IntrinsicISizes; +use text; +use wrapper::ThreadSafeLayoutNode; + +use collections::{Deque, RingBuf}; +use geom::Rect; +use gfx::display_list::ContentLevel; +use gfx::font::FontMetrics; +use gfx::font_context::FontContext; +use gfx::text::glyph::CharIndex; +use servo_util::geometry::Au; +use servo_util::geometry; +use servo_util::logical_geometry::{LogicalRect, LogicalSize}; +use servo_util::range; +use servo_util::range::{EachIndex, Range, RangeIndex, IntRangeIndex}; +use std::fmt; +use std::mem; +use std::num; +use std::u16; +use style::computed_values::{text_align, vertical_align, white_space}; +use style::ComputedValues; +use sync::Arc; + +/// `Line`s are represented as offsets into the child list, rather than +/// as an object that "owns" fragments. Choosing a different set of line +/// breaks requires a new list of offsets, and possibly some splitting and +/// merging of TextFragments. +/// +/// A similar list will keep track of the mapping between CSS fragments and +/// the corresponding fragments in the inline flow. +/// +/// After line breaks are determined, render fragments in the inline flow may +/// overlap visually. For example, in the case of nested inline CSS fragments, +/// outer inlines must be at least as large as the inner inlines, for +/// purposes of drawing noninherited things like backgrounds, borders, +/// outlines. +/// +/// N.B. roc has an alternative design where the list instead consists of +/// things like "start outer fragment, text, start inner fragment, text, end inner +/// fragment, text, end outer fragment, text". This seems a little complicated to +/// serve as the starting point, but the current design doesn't make it +/// hard to try out that alternative. +/// +/// Line fragments also contain some metadata used during line breaking. The +/// green zone is the area that the line can expand to before it collides +/// with a float or a horizontal wall of the containing block. The block-start +/// inline-start corner of the green zone is the same as that of the line, but +/// the green zone can be taller and wider than the line itself. +#[deriving(Encodable)] +pub struct Line { + /// A range of line indices that describe line breaks. + /// + /// For example, consider the following HTML and rendered element with + /// linebreaks: + /// + /// ~~~html + /// <span>I <span>like truffles, <img></span> yes I do.</span> + /// ~~~ + /// + /// ~~~text + /// +------------+ + /// | I like | + /// | truffles, | + /// | +----+ | + /// | | | | + /// | +----+ yes | + /// | I do. | + /// +------------+ + /// ~~~ + /// + /// The ranges that describe these lines would be: + /// + /// | [0.0, 1.4) | [1.5, 2.0) | [2.0, 3.4) | [3.4, 4.0) | + /// |------------|-------------|-------------|------------| + /// | 'I like' | 'truffles,' | '<img> yes' | 'I do.' | + pub range: Range<LineIndices>, + /// The bounds are the exact position and extents of the line with respect + /// to the parent box. + /// + /// For example, for the HTML below... + /// + /// ~~~html + /// <div><span>I <span>like truffles, <img></span></div> + /// ~~~ + /// + /// ...the bounds would be: + /// + /// ~~~text + /// +-----------------------------------------------------------+ + /// | ^ | + /// | | | + /// | origin.y | + /// | | | + /// | v | + /// |< - origin.x ->+ - - - - - - - - +---------+---- | + /// | | | | ^ | + /// | | | <img> | size.block-size | + /// | I like truffles, | | v | + /// | + - - - - - - - - +---------+---- | + /// | | | | + /// | |<------ size.inline-size ------->| | + /// | | + /// | | + /// +-----------------------------------------------------------+ + /// ~~~ + pub bounds: LogicalRect<Au>, + /// The green zone is the greatest extent from wich a line can extend to + /// before it collides with a float. + /// + /// ~~~text + /// +-----------------------+ + /// |::::::::::::::::: | + /// |:::::::::::::::::FFFFFF| + /// |============:::::FFFFFF| + /// |:::::::::::::::::FFFFFF| + /// |:::::::::::::::::FFFFFF| + /// |::::::::::::::::: | + /// | FFFFFFFFF | + /// | FFFFFFFFF | + /// | FFFFFFFFF | + /// | | + /// +-----------------------+ + /// + /// === line + /// ::: green zone + /// FFF float + /// ~~~ + pub green_zone: LogicalSize<Au> +} + +int_range_index! { + #[deriving(Encodable)] + #[doc = "The index of a fragment in a flattened vector of DOM elements."] + struct FragmentIndex(int) +} + +/// A line index consists of two indices: a fragment index that refers to the +/// index of a DOM fragment within a flattened inline element; and a glyph index +/// where the 0th glyph refers to the first glyph of that fragment. +#[deriving(Clone, Encodable, PartialEq, PartialOrd, Eq, Ord, Zero)] +pub struct LineIndices { + /// The index of a fragment into the flattened vector of DOM elements. + /// + /// For example, given the HTML below: + /// + /// ~~~html + /// <span>I <span>like truffles, <img></span> yes I do.</span> + /// ~~~ + /// + /// The fragments would be indexed as follows: + /// + /// | 0 | 1 | 2 | 3 | + /// |------|------------------|---------|--------------| + /// | 'I ' | 'like truffles,' | `<img>` | ' yes I do.' | + pub fragment_index: FragmentIndex, + /// The index of a character in a DOM fragment. Continuous runs of whitespace + /// are treated as single characters. Non-breakable DOM fragments such as + /// images are treated as having a range length of `1`. + /// + /// For example, given the HTML below: + /// + /// ~~~html + /// <span>I <span>like truffles, <img></span> yes I do.</span> + /// ~~~ + /// + /// The characters would be indexed as follows: + /// + /// | 0 | 1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | + /// |---|---|---|---|---|---|---|---|---|---|---|---|----|----|----|----|----| + /// | I | | l | i | k | e | | t | r | u | f | f | l | e | s | , | | + /// + /// | 0 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | + /// |---------|---|---|---|---|---|---|---|---|---|---| + /// | `<img>` | | y | e | s | | I | | d | o | . | + pub char_index: CharIndex, +} + +impl RangeIndex for LineIndices {} + +impl Add<LineIndices, LineIndices> for LineIndices { + fn add(&self, other: &LineIndices) -> LineIndices { + // TODO: use debug_assert! after rustc upgrade + if cfg!(not(ndebug)) { + assert!(other.fragment_index == num::zero() || other.char_index == num::zero(), + "Attempted to add {} to {}. Both the fragment_index and \ + char_index of the RHS are non-zero. This probably was a \ + mistake!", self, other); + } + LineIndices { + fragment_index: self.fragment_index + other.fragment_index, + char_index: self.char_index + other.char_index, + } + } +} + +impl Sub<LineIndices, LineIndices> for LineIndices { + fn sub(&self, other: &LineIndices) -> LineIndices { + // TODO: use debug_assert! after rustc upgrade + if cfg!(not(ndebug)) { + assert!(other.fragment_index == num::zero() || other.char_index == num::zero(), + "Attempted to subtract {} from {}. Both the fragment_index \ + and char_index of the RHS are non-zero. This probably was \ + a mistake!", self, other); + } + LineIndices { + fragment_index: self.fragment_index - other.fragment_index, + char_index: self.char_index - other.char_index, + } + } +} + +impl Neg<LineIndices> for LineIndices { + fn neg(&self) -> LineIndices { + // TODO: use debug_assert! after rustc upgrade + if cfg!(not(ndebug)) { + assert!(self.fragment_index == num::zero() || self.char_index == num::zero(), + "Attempted to negate {}. Both the fragment_index and \ + char_index are non-zero. This probably was a mistake!", + self); + } + LineIndices { + fragment_index: -self.fragment_index, + char_index: -self.char_index, + } + } +} + +impl fmt::Show for LineIndices { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}.{}", self.fragment_index, self.char_index) + } +} + +pub fn each_fragment_index(range: &Range<LineIndices>) -> EachIndex<int, FragmentIndex> { + range::each_index(range.begin().fragment_index, range.end().fragment_index) +} + +pub fn each_char_index(range: &Range<LineIndices>) -> EachIndex<int, CharIndex> { + range::each_index(range.begin().char_index, range.end().char_index) +} + +struct LineBreaker { + pub floats: Floats, + pub new_fragments: Vec<Fragment>, + pub work_list: RingBuf<Fragment>, + pub pending_line: Line, + pub lines: Vec<Line>, + pub cur_b: Au, // Current position on the block direction +} + +impl LineBreaker { + pub fn new(float_ctx: Floats) -> LineBreaker { + LineBreaker { + new_fragments: Vec::new(), + work_list: RingBuf::new(), + pending_line: Line { + range: Range::empty(), + bounds: LogicalRect::zero(float_ctx.writing_mode), + green_zone: LogicalSize::zero(float_ctx.writing_mode) + }, + floats: float_ctx, + lines: Vec::new(), + cur_b: Au::new(0) + } + } + + pub fn floats(&mut self) -> Floats { + self.floats.clone() + } + + fn reset_scanner(&mut self) { + debug!("Resetting LineBreaker's state for flow."); + self.lines = Vec::new(); + self.new_fragments = Vec::new(); + self.cur_b = Au(0); + self.reset_line(); + } + + fn reset_line(&mut self) { + self.pending_line.range.reset(num::zero(), num::zero()); + self.pending_line.bounds = LogicalRect::new( + self.floats.writing_mode, Au::new(0), self.cur_b, Au::new(0), Au::new(0)); + self.pending_line.green_zone = LogicalSize::zero(self.floats.writing_mode) + } + + pub fn scan_for_lines(&mut self, flow: &mut InlineFlow, layout_context: &LayoutContext) { + self.reset_scanner(); + + let mut old_fragments = mem::replace(&mut flow.fragments, InlineFragments::new()); + + { // Enter a new scope so that old_fragment_iter's borrow is released + let mut old_fragment_iter = old_fragments.fragments.iter(); + loop { + // acquire the next fragment to lay out from work list or fragment list + let cur_fragment = if self.work_list.is_empty() { + match old_fragment_iter.next() { + None => break, + Some(fragment) => { + debug!("LineBreaker: Working with fragment from flow: b{}", + fragment.debug_id()); + (*fragment).clone() + } + } + } else { + let fragment = self.work_list.pop_front().unwrap(); + debug!("LineBreaker: Working with fragment from work list: b{}", + fragment.debug_id()); + fragment + }; + + let fragment_was_appended = match cur_fragment.white_space() { + white_space::normal => self.try_append_to_line(cur_fragment, flow, layout_context), + white_space::pre => self.try_append_to_line_by_new_line(cur_fragment), + }; + + if !fragment_was_appended { + debug!("LineBreaker: Fragment wasn't appended, because line {:u} was full.", + self.lines.len()); + self.flush_current_line(); + } else { + debug!("LineBreaker: appended a fragment to line {:u}", self.lines.len()); + } + } + + if self.pending_line.range.length() > num::zero() { + debug!("LineBreaker: Partially full line {:u} inline_start at end of scanning.", + self.lines.len()); + self.flush_current_line(); + } + } + + old_fragments.fragments = mem::replace(&mut self.new_fragments, vec![]); + flow.fragments = old_fragments; + flow.lines = mem::replace(&mut self.lines, Vec::new()); + } + + fn flush_current_line(&mut self) { + debug!("LineBreaker: Flushing line {:u}: {:?}", + self.lines.len(), self.pending_line); + + // clear line and add line mapping + debug!("LineBreaker: Saving information for flushed line {:u}.", self.lines.len()); + self.lines.push(self.pending_line); + self.cur_b = self.pending_line.bounds.start.b + self.pending_line.bounds.size.block; + self.reset_line(); + } + + // FIXME(eatkinson): this assumes that the tallest fragment in the line determines the line block-size + // This might not be the case with some weird text fonts. + fn new_block_size_for_line(&self, new_fragment: &Fragment, layout_context: &LayoutContext) -> Au { + let fragment_block_size = new_fragment.content_block_size(layout_context); + if fragment_block_size > self.pending_line.bounds.size.block { + fragment_block_size + } else { + self.pending_line.bounds.size.block + } + } + + /// Computes the position of a line that has only the provided fragment. Returns the bounding + /// rect of the line's green zone (whose origin coincides with the line's origin) and the actual + /// inline-size of the first fragment after splitting. + fn initial_line_placement(&self, first_fragment: &Fragment, ceiling: Au, flow: &InlineFlow) + -> (LogicalRect<Au>, Au) { + debug!("LineBreaker: Trying to place first fragment of line {}", self.lines.len()); + + let first_fragment_size = first_fragment.border_box.size; + let splittable = first_fragment.can_split(); + debug!("LineBreaker: fragment size: {}, splittable: {}", first_fragment_size, splittable); + + // Initally, pretend a splittable fragment has 0 inline-size. + // We will move it later if it has nonzero inline-size + // and that causes problems. + let placement_inline_size = if splittable { + Au::new(0) + } else { + first_fragment_size.inline + }; + + let info = PlacementInfo { + size: LogicalSize::new( + self.floats.writing_mode, placement_inline_size, first_fragment_size.block), + ceiling: ceiling, + max_inline_size: flow.base.position.size.inline, + kind: FloatLeft, + }; + + let line_bounds = self.floats.place_between_floats(&info); + + debug!("LineBreaker: found position for line: {} using placement_info: {:?}", + line_bounds, + info); + + // Simple case: if the fragment fits, then we can stop here + if line_bounds.size.inline > first_fragment_size.inline { + debug!("LineBreaker: case=fragment fits"); + return (line_bounds, first_fragment_size.inline); + } + + // If not, but we can't split the fragment, then we'll place + // the line here and it will overflow. + if !splittable { + debug!("LineBreaker: case=line doesn't fit, but is unsplittable"); + return (line_bounds, first_fragment_size.inline); + } + + debug!("LineBreaker: used to call split_to_inline_size here"); + return (line_bounds, first_fragment_size.inline); + } + + /// Performs float collision avoidance. This is called when adding a fragment is going to increase + /// the block-size, and because of that we will collide with some floats. + /// + /// We have two options here: + /// 1) Move the entire line so that it doesn't collide any more. + /// 2) Break the line and put the new fragment on the next line. + /// + /// The problem with option 1 is that we might move the line and then wind up breaking anyway, + /// which violates the standard. + /// But option 2 is going to look weird sometimes. + /// + /// So we'll try to move the line whenever we can, but break if we have to. + /// + /// Returns false if and only if we should break the line. + fn avoid_floats(&mut self, + in_fragment: Fragment, + flow: &InlineFlow, + new_block_size: Au, + line_is_empty: bool) + -> bool { + debug!("LineBreaker: entering float collision avoider!"); + + // First predict where the next line is going to be. + let this_line_y = self.pending_line.bounds.start.b; + let (next_line, first_fragment_inline_size) = self.initial_line_placement(&in_fragment, this_line_y, flow); + let next_green_zone = next_line.size; + + let new_inline_size = self.pending_line.bounds.size.inline + first_fragment_inline_size; + + // Now, see if everything can fit at the new location. + if next_green_zone.inline >= new_inline_size && next_green_zone.block >= new_block_size { + debug!("LineBreaker: case=adding fragment collides vertically with floats: moving line"); + + self.pending_line.bounds.start = next_line.start; + self.pending_line.green_zone = next_green_zone; + + assert!(!line_is_empty, "Non-terminating line breaking"); + self.work_list.push_front(in_fragment); + return true + } + + debug!("LineBreaker: case=adding fragment collides vertically with floats: breaking line"); + self.work_list.push_front(in_fragment); + false + } + + fn try_append_to_line_by_new_line(&mut self, in_fragment: Fragment) -> bool { + if in_fragment.new_line_pos.len() == 0 { + debug!("LineBreaker: Did not find a new-line character, so pushing the fragment to \ + the line without splitting."); + self.push_fragment_to_line(in_fragment); + true + } else { + debug!("LineBreaker: Found a new-line character, so splitting theline."); + + let (inline_start, inline_end, run) = in_fragment.find_split_info_by_new_line() + .expect("LineBreaker: This split case makes no sense!"); + let writing_mode = self.floats.writing_mode; + + // TODO(bjz): Remove fragment splitting + let split_fragment = |split: SplitInfo| { + let info = ScannedTextFragmentInfo::new(run.clone(), split.range); + let specific = ScannedTextFragment(info); + let size = LogicalSize::new( + writing_mode, split.inline_size, in_fragment.border_box.size.block); + in_fragment.transform(size, specific) + }; + + debug!("LineBreaker: Pushing the fragment to the inline_start of the new-line character \ + to the line."); + let mut inline_start = split_fragment(inline_start); + inline_start.new_line_pos = vec![]; + self.push_fragment_to_line(inline_start); + + for inline_end in inline_end.move_iter() { + debug!("LineBreaker: Deferring the fragment to the inline_end of the new-line \ + character to the line."); + let mut inline_end = split_fragment(inline_end); + inline_end.new_line_pos = in_fragment.new_line_pos.clone(); + self.work_list.push_front(inline_end); + } + false + } + } + + /// Tries to append the given fragment to the line, splitting it if necessary. Returns false only if + /// we should break the line. + fn try_append_to_line(&mut self, in_fragment: Fragment, flow: &InlineFlow, layout_context: &LayoutContext) -> bool { + let line_is_empty = self.pending_line.range.length() == num::zero(); + if line_is_empty { + let (line_bounds, _) = self.initial_line_placement(&in_fragment, self.cur_b, flow); + self.pending_line.bounds.start = line_bounds.start; + self.pending_line.green_zone = line_bounds.size; + } + + debug!("LineBreaker: Trying to append fragment to line {:u} (fragment size: {}, green zone: \ + {}): {}", + self.lines.len(), + in_fragment.border_box.size, + self.pending_line.green_zone, + in_fragment); + + let green_zone = self.pending_line.green_zone; + + // NB: At this point, if `green_zone.inline-size < self.pending_line.bounds.size.inline-size` or + // `green_zone.block-size < self.pending_line.bounds.size.block-size`, then we committed a line + // that overlaps with floats. + + let new_block_size = self.new_block_size_for_line(&in_fragment, layout_context); + if new_block_size > green_zone.block { + // Uh-oh. Float collision imminent. Enter the float collision avoider + return self.avoid_floats(in_fragment, flow, new_block_size, line_is_empty) + } + + // If we're not going to overflow the green zone vertically, we might still do so + // horizontally. We'll try to place the whole fragment on this line and break somewhere if it + // doesn't fit. + + let new_inline_size = self.pending_line.bounds.size.inline + in_fragment.border_box.size.inline; + if new_inline_size <= green_zone.inline { + debug!("LineBreaker: case=fragment fits without splitting"); + self.push_fragment_to_line(in_fragment); + return true + } + + if !in_fragment.can_split() { + // TODO(eatkinson, issue #224): Signal that horizontal overflow happened? + if line_is_empty { + debug!("LineBreaker: case=fragment can't split and line {:u} is empty, so \ + overflowing.", + self.lines.len()); + self.push_fragment_to_line(in_fragment); + return true + } + } + + let available_inline_size = green_zone.inline - self.pending_line.bounds.size.inline; + let split = in_fragment.find_split_info_for_inline_size(CharIndex(0), available_inline_size, line_is_empty); + match split.map(|(inline_start, inline_end, run)| { + // TODO(bjz): Remove fragment splitting + let split_fragment = |split: SplitInfo| { + let info = ScannedTextFragmentInfo::new(run.clone(), split.range); + let specific = ScannedTextFragment(info); + let size = LogicalSize::new( + self.floats.writing_mode, split.inline_size, in_fragment.border_box.size.block); + in_fragment.transform(size, specific) + }; + + (inline_start.map(|x| { debug!("LineBreaker: Left split {}", x); split_fragment(x) }), + inline_end.map(|x| { debug!("LineBreaker: Right split {}", x); split_fragment(x) })) + }) { + None => { + debug!("LineBreaker: Tried to split unsplittable render fragment! Deferring to next \ + line. {}", in_fragment); + self.work_list.push_front(in_fragment); + false + }, + Some((Some(inline_start_fragment), Some(inline_end_fragment))) => { + debug!("LineBreaker: Line break found! Pushing inline_start fragment to line and deferring \ + inline_end fragment to next line."); + self.push_fragment_to_line(inline_start_fragment); + self.work_list.push_front(inline_end_fragment); + true + }, + Some((Some(inline_start_fragment), None)) => { + debug!("LineBreaker: Pushing inline_start fragment to line."); + self.push_fragment_to_line(inline_start_fragment); + true + }, + Some((None, Some(inline_end_fragment))) => { + debug!("LineBreaker: Pushing inline_end fragment to line."); + self.push_fragment_to_line(inline_end_fragment); + true + }, + Some((None, None)) => { + error!("LineBreaker: This split case makes no sense!"); + true + }, + } + } + + // An unconditional push + fn push_fragment_to_line(&mut self, fragment: Fragment) { + debug!("LineBreaker: Pushing fragment {} to line {:u}", fragment.debug_id(), self.lines.len()); + + if self.pending_line.range.length() == num::zero() { + assert!(self.new_fragments.len() <= (u16::MAX as uint)); + self.pending_line.range.reset( + LineIndices { + fragment_index: FragmentIndex(self.new_fragments.len() as int), + char_index: CharIndex(0) /* unused for now */, + }, + num::zero() + ); + } + self.pending_line.range.extend_by(LineIndices { + fragment_index: FragmentIndex(1), + char_index: CharIndex(0) /* unused for now */ , + }); + self.pending_line.bounds.size.inline = self.pending_line.bounds.size.inline + + fragment.border_box.size.inline; + self.pending_line.bounds.size.block = Au::max(self.pending_line.bounds.size.block, + fragment.border_box.size.block); + self.new_fragments.push(fragment); + } +} + +/// Represents a list of inline fragments, including element ranges. +#[deriving(Encodable)] +pub struct InlineFragments { + /// The fragments themselves. + pub fragments: Vec<Fragment>, +} + +impl InlineFragments { + /// Creates an empty set of inline fragments. + pub fn new() -> InlineFragments { + InlineFragments { + fragments: vec![], + } + } + + /// Returns the number of inline fragments. + pub fn len(&self) -> uint { + self.fragments.len() + } + + /// Returns true if this list contains no fragments and false if it contains at least one fragment. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Pushes a new inline fragment. + pub fn push(&mut self, fragment: &mut Fragment, style: Arc<ComputedValues>) { + fragment.add_inline_context_style(style); + self.fragments.push(fragment.clone()); + } + + /// Merges another set of inline fragments with this one. + pub fn push_all(&mut self, fragments: InlineFragments) { + self.fragments.push_all_move(fragments.fragments); + } + + /// A convenience function to return the fragment at a given index. + pub fn get<'a>(&'a self, index: uint) -> &'a Fragment { + &self.fragments[index] + } + + /// A convenience function to return a mutable reference to the fragment at a given index. + pub fn get_mut<'a>(&'a mut self, index: uint) -> &'a mut Fragment { + self.fragments.get_mut(index) + } + + /// Strips ignorable whitespace from the start of a list of fragments. + pub fn strip_ignorable_whitespace_from_start(&mut self) { + if self.is_empty() { return }; // Fast path + + // FIXME (rust#16151): This can be reverted back to using skip_while once + // the upstream bug is fixed. + let mut fragments = mem::replace(&mut self.fragments, vec![]).move_iter(); + let mut new_fragments = Vec::new(); + let mut skipping = true; + for fragment in fragments { + if skipping && fragment.is_whitespace_only() { + debug!("stripping ignorable whitespace from start"); + continue + } + + skipping = false; + new_fragments.push(fragment); + } + + self.fragments = new_fragments; + } + + /// Strips ignorable whitespace from the end of a list of fragments. + pub fn strip_ignorable_whitespace_from_end(&mut self) { + if self.is_empty() { + return; + } + + let mut new_fragments = self.fragments.clone(); + while new_fragments.len() > 0 && new_fragments.as_slice().last().get_ref().is_whitespace_only() { + debug!("stripping ignorable whitespace from end"); + drop(new_fragments.pop()); + } + + + self.fragments = new_fragments; + } +} + +/// Flows for inline layout. +#[deriving(Encodable)] +pub struct InlineFlow { + /// Data common to all flows. + pub base: BaseFlow, + + /// A vector of all inline fragments. Several fragments may correspond to one node/element. + pub fragments: InlineFragments, + + /// A vector of ranges into fragments that represents line positions. These ranges are disjoint and + /// are the result of inline layout. This also includes some metadata used for positioning + /// lines. + pub lines: Vec<Line>, + + /// The minimum block-size above the baseline for each line, as specified by the line block-size and + /// font style. + pub minimum_block_size_above_baseline: Au, + + /// The minimum depth below the baseline for each line, as specified by the line block-size and + /// font style. + pub minimum_depth_below_baseline: Au, +} + +impl InlineFlow { + pub fn from_fragments(node: ThreadSafeLayoutNode, fragments: InlineFragments) -> InlineFlow { + InlineFlow { + base: BaseFlow::new(node), + fragments: fragments, + lines: Vec::new(), + minimum_block_size_above_baseline: Au(0), + minimum_depth_below_baseline: Au(0), + } + } + + pub fn build_display_list_inline(&mut self, layout_context: &LayoutContext) { + let size = self.base.position.size.to_physical(self.base.writing_mode); + if !Rect(self.base.abs_position, size).intersects(&layout_context.shared.dirty) { + return + } + + // TODO(#228): Once we form lines and have their cached bounds, we can be smarter and + // not recurse on a line if nothing in it can intersect the dirty region. + debug!("Flow: building display list for {:u} inline fragments", self.fragments.len()); + + for fragment in self.fragments.fragments.mut_iter() { + let rel_offset = fragment.relative_position(&self.base + .absolute_position_info + .relative_containing_block_size); + drop(fragment.build_display_list(&mut self.base.display_list, + layout_context, + self.base.abs_position.add_size( + &rel_offset.to_physical(self.base.writing_mode)), + ContentLevel)); + } + + // TODO(#225): Should `inline-block` elements have flows as children of the inline flow or + // should the flow be nested inside the fragment somehow? + + // For now, don't traverse the subtree rooted here. + } + + /// Returns the distance from the baseline for the logical block-start inline-start corner of this fragment, + /// taking into account the value of the CSS `vertical-align` property. Negative values mean + /// "toward the logical block-start" and positive values mean "toward the logical block-end". + /// + /// The extra boolean is set if and only if `biggest_block-start` and/or `biggest_block-end` were updated. + /// That is, if the box has a `block-start` or `block-end` value, true is returned. + fn distance_from_baseline(fragment: &Fragment, + ascent: Au, + parent_text_block_start: Au, + parent_text_block_end: Au, + block_size_above_baseline: &mut Au, + depth_below_baseline: &mut Au, + largest_block_size_for_top_fragments: &mut Au, + largest_block_size_for_bottom_fragments: &mut Au, + layout_context: &LayoutContext) + -> (Au, bool) { + match fragment.vertical_align() { + vertical_align::baseline => (-ascent, false), + vertical_align::middle => { + // TODO: x-block-size value should be used from font info. + let xblock_size = Au(0); + let fragment_block_size = fragment.content_block_size(layout_context); + let offset_block_start = -(xblock_size + fragment_block_size).scale_by(0.5); + *block_size_above_baseline = offset_block_start.scale_by(-1.0); + *depth_below_baseline = fragment_block_size - *block_size_above_baseline; + (offset_block_start, false) + }, + vertical_align::sub => { + // TODO: The proper position for subscripts should be used. Lower the baseline to + // the proper position for subscripts. + let sub_offset = Au(0); + (sub_offset - ascent, false) + }, + vertical_align::super_ => { + // TODO: The proper position for superscripts should be used. Raise the baseline to + // the proper position for superscripts. + let super_offset = Au(0); + (-super_offset - ascent, false) + }, + vertical_align::text_top => { + let fragment_block_size = *block_size_above_baseline + *depth_below_baseline; + let prev_depth_below_baseline = *depth_below_baseline; + *block_size_above_baseline = parent_text_block_start; + *depth_below_baseline = fragment_block_size - *block_size_above_baseline; + (*depth_below_baseline - prev_depth_below_baseline - ascent, false) + }, + vertical_align::text_bottom => { + let fragment_block_size = *block_size_above_baseline + *depth_below_baseline; + let prev_depth_below_baseline = *depth_below_baseline; + *depth_below_baseline = parent_text_block_end; + *block_size_above_baseline = fragment_block_size - *depth_below_baseline; + (*depth_below_baseline - prev_depth_below_baseline - ascent, false) + }, + vertical_align::top => { + *largest_block_size_for_top_fragments = + Au::max(*largest_block_size_for_top_fragments, + *block_size_above_baseline + *depth_below_baseline); + let offset_top = *block_size_above_baseline - ascent; + (offset_top, true) + }, + vertical_align::bottom => { + *largest_block_size_for_bottom_fragments = + Au::max(*largest_block_size_for_bottom_fragments, + *block_size_above_baseline + *depth_below_baseline); + let offset_bottom = -(*depth_below_baseline + ascent); + (offset_bottom, true) + }, + vertical_align::Length(length) => (-(length + ascent), false), + vertical_align::Percentage(p) => { + let line_height = fragment.calculate_line_height(layout_context); + let percent_offset = line_height.scale_by(p); + (-(percent_offset + ascent), false) + } + } + } + + /// Sets fragment X positions based on alignment for one line. + fn set_horizontal_fragment_positions(fragments: &mut InlineFragments, + line: &Line, + line_align: text_align::T) { + // Figure out how much inline-size we have. + let slack_inline_size = Au::max(Au(0), line.green_zone.inline - line.bounds.size.inline); + + // Set the fragment x positions based on that alignment. + let mut offset_x = line.bounds.start.i; + offset_x = offset_x + match line_align { + // So sorry, but justified text is more complicated than shuffling line + // coordinates. + // + // TODO(burg, issue #213): Implement `text-align: justify`. + text_align::left | text_align::justify => Au(0), + text_align::center => slack_inline_size.scale_by(0.5), + text_align::right => slack_inline_size, + }; + + for i in each_fragment_index(&line.range) { + let fragment = fragments.get_mut(i.to_uint()); + let size = fragment.border_box.size; + fragment.border_box = LogicalRect::new( + fragment.style.writing_mode, offset_x, fragment.border_box.start.b, + size.inline, size.block); + offset_x = offset_x + size.inline; + } + } + + /// Computes the minimum ascent and descent for each line. This is done during flow + /// construction. + /// + /// `style` is the style of the block. + pub fn compute_minimum_ascent_and_descent(&self, + font_context: &mut FontContext, + style: &ComputedValues) -> (Au, Au) { + let font_style = text::computed_style_to_font_style(style); + let font_metrics = text::font_metrics_for_style(font_context, &font_style); + let line_height = text::line_height_from_style(style, &font_metrics); + let inline_metrics = InlineMetrics::from_font_metrics(&font_metrics, line_height); + (inline_metrics.block_size_above_baseline, inline_metrics.depth_below_baseline) + } +} + +impl Flow for InlineFlow { + fn class(&self) -> FlowClass { + InlineFlowClass + } + + fn as_immutable_inline<'a>(&'a self) -> &'a InlineFlow { + self + } + + fn as_inline<'a>(&'a mut self) -> &'a mut InlineFlow { + self + } + + fn bubble_inline_sizes(&mut self, _: &LayoutContext) { + let _scope = layout_debug_scope!("inline::bubble_inline_sizes {:s}", self.base.debug_id()); + + let writing_mode = self.base.writing_mode; + for kid in self.base.child_iter() { + flow::mut_base(kid).floats = Floats::new(writing_mode); + } + + let mut intrinsic_inline_sizes = IntrinsicISizes::new(); + for fragment in self.fragments.fragments.mut_iter() { + debug!("Flow: measuring {}", *fragment); + + let fragment_intrinsic_inline_sizes = + fragment.intrinsic_inline_sizes(); + intrinsic_inline_sizes.minimum_inline_size = geometry::max( + intrinsic_inline_sizes.minimum_inline_size, + fragment_intrinsic_inline_sizes.minimum_inline_size); + intrinsic_inline_sizes.preferred_inline_size = + intrinsic_inline_sizes.preferred_inline_size + + fragment_intrinsic_inline_sizes.preferred_inline_size; + } + + self.base.intrinsic_inline_sizes = intrinsic_inline_sizes; + } + + /// 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. + fn assign_inline_sizes(&mut self, _: &LayoutContext) { + let _scope = layout_debug_scope!("inline::assign_inline_sizes {:s}", self.base.debug_id()); + + // Initialize content fragment inline-sizes if they haven't been initialized already. + // + // TODO: Combine this with `LineBreaker`'s walk in the fragment list, or put this into `Fragment`. + + debug!("InlineFlow::assign_inline_sizes: floats in: {:?}", self.base.floats); + + { + let inline_size = self.base.position.size.inline; + let this = &mut *self; + for fragment in this.fragments.fragments.mut_iter() { + fragment.assign_replaced_inline_size_if_necessary(inline_size); + } + } + + assert!(self.base.children.len() == 0, + "InlineFlow: should not have children flows in the current layout implementation."); + + // There are no child contexts, so stop here. + + // TODO(Issue #225): once there are 'inline-block' elements, this won't be + // true. In that case, set the InlineBlockFragment's inline-size to the + // shrink-to-fit inline-size, perform inline flow, and set the block + // flow context's inline-size as the assigned inline-size of the + // 'inline-block' fragment that created this flow before recursing. + } + + /// Calculate and set the block-size of this flow. See CSS 2.1 § 10.6.1. + fn assign_block_size(&mut self, ctx: &LayoutContext) { + let _scope = layout_debug_scope!("inline::assign_block_size {:s}", self.base.debug_id()); + + // Divide the fragments into lines. + // + // TODO(#226): Get the CSS `line-block-size` property from the containing block's style to + // determine minimum line block-size. + // + // TODO(#226): Get the CSS `line-block-size` property from each non-replaced inline element to + // determine its block-size for computing line block-size. + // + // TODO(pcwalton): Cache the line scanner? + debug!("assign_block_size_inline: floats in: {:?}", self.base.floats); + + // assign block-size for inline fragments + for fragment in self.fragments.fragments.mut_iter() { + fragment.assign_replaced_block_size_if_necessary(); + } + + let scanner_floats = self.base.floats.clone(); + let mut scanner = LineBreaker::new(scanner_floats); + scanner.scan_for_lines(self, ctx); + + // All lines use text alignment of the flow. + let text_align = self.base.flags.text_align(); + + // Now, go through each line and lay out the fragments inside. + let mut line_distance_from_flow_block_start = Au(0); + for line in self.lines.mut_iter() { + // Lay out fragments horizontally. + InlineFlow::set_horizontal_fragment_positions(&mut self.fragments, line, text_align); + + // Set the block-start y position of the current line. + // `line_height_offset` is updated at the end of the previous loop. + line.bounds.start.b = line_distance_from_flow_block_start; + + // Calculate the distance from the baseline to the block-start and block-end of the line. + let mut largest_block_size_above_baseline = self.minimum_block_size_above_baseline; + let mut largest_depth_below_baseline = self.minimum_depth_below_baseline; + + // Calculate the largest block-size among fragments with 'top' and 'bottom' values + // respectively. + let (mut largest_block_size_for_top_fragments, mut largest_block_size_for_bottom_fragments) = + (Au(0), Au(0)); + + for fragment_i in each_fragment_index(&line.range) { + let fragment = self.fragments.fragments.get_mut(fragment_i.to_uint()); + + let InlineMetrics { + block_size_above_baseline: mut block_size_above_baseline, + depth_below_baseline: mut depth_below_baseline, + ascent + } = fragment.inline_metrics(ctx); + + // To calculate text-top and text-bottom value when `vertical-align` is involved, + // we should find the top and bottom of the content area of the parent fragment. + // "Content area" is defined in CSS 2.1 § 10.6.1. + // + // TODO: We should extract em-box info from the font size of the parent and + // calculate the distances from the baseline to the block-start and the block-end of the + // parent's content area. + + // We should calculate the distance from baseline to the top of parent's content + // area. But for now we assume it's the font size. + // + // CSS 2.1 does not state which font to use. Previous versions of the code used + // the parent's font; this code uses the current font. + let parent_text_top = fragment.style().get_font().font_size; + + // We should calculate the distance from baseline to the bottom of the parent's + // content area. But for now we assume it's zero. + let parent_text_bottom = Au(0); + + // Calculate the final block-size above the baseline for this fragment. + // + // The no-update flag decides whether `largest_block-size_for_top_fragments` and + // `largest_block-size_for_bottom_fragments` are to be updated or not. This will be set + // if and only if the fragment has `vertical-align` set to `top` or `bottom`. + let (distance_from_baseline, no_update_flag) = + InlineFlow::distance_from_baseline( + fragment, + ascent, + parent_text_top, + parent_text_bottom, + &mut block_size_above_baseline, + &mut depth_below_baseline, + &mut largest_block_size_for_top_fragments, + &mut largest_block_size_for_bottom_fragments, + ctx); + + // Unless the current fragment has `vertical-align` set to `top` or `bottom`, + // `largest_block-size_above_baseline` and `largest_depth_below_baseline` are updated. + if !no_update_flag { + largest_block_size_above_baseline = Au::max(block_size_above_baseline, + largest_block_size_above_baseline); + largest_depth_below_baseline = Au::max(depth_below_baseline, + largest_depth_below_baseline); + } + + // Temporarily use `fragment.border_box.start.b` to mean "the distance from the + // baseline". We will assign the real value later. + fragment.border_box.start.b = distance_from_baseline + } + + // Calculate the distance from the baseline to the top of the largest fragment with a + // value for `bottom`. Then, if necessary, update `largest_block-size_above_baseline`. + largest_block_size_above_baseline = + Au::max(largest_block_size_above_baseline, + largest_block_size_for_bottom_fragments - largest_depth_below_baseline); + + // Calculate the distance from baseline to the bottom of the largest fragment with a value + // for `top`. Then, if necessary, update `largest_depth_below_baseline`. + largest_depth_below_baseline = + Au::max(largest_depth_below_baseline, + largest_block_size_for_top_fragments - largest_block_size_above_baseline); + + // Now, the distance from the logical block-start of the line to the baseline can be + // computed as `largest_block-size_above_baseline`. + let baseline_distance_from_block_start = largest_block_size_above_baseline; + + // Compute the final positions in the block direction of each fragment. Recall that + // `fragment.border_box.start.b` was set to the distance from the baseline above. + for fragment_i in each_fragment_index(&line.range) { + let fragment = self.fragments.get_mut(fragment_i.to_uint()); + match fragment.vertical_align() { + vertical_align::top => { + fragment.border_box.start.b = fragment.border_box.start.b + + line_distance_from_flow_block_start + } + vertical_align::bottom => { + fragment.border_box.start.b = fragment.border_box.start.b + + line_distance_from_flow_block_start + baseline_distance_from_block_start + + largest_depth_below_baseline + } + _ => { + fragment.border_box.start.b = fragment.border_box.start.b + + line_distance_from_flow_block_start + baseline_distance_from_block_start + } + } + } + + // This is used to set the block-start y position of the next line in the next loop. + line.bounds.size.block = largest_block_size_above_baseline + largest_depth_below_baseline; + line_distance_from_flow_block_start = line_distance_from_flow_block_start + line.bounds.size.block; + } // End of `lines.each` loop. + + self.base.position.size.block = match self.lines.as_slice().last() { + Some(ref last_line) => last_line.bounds.start.b + last_line.bounds.size.block, + None => Au::new(0) + }; + + self.base.floats = scanner.floats(); + self.base.floats.translate(LogicalSize::new( + self.base.writing_mode, Au::new(0), -self.base.position.size.block)); + } +} + +impl fmt::Show for InlineFlow { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + try!(write!(f, "InlineFlow")); + for (i, fragment) in self.fragments.fragments.iter().enumerate() { + if i == 0 { + try!(write!(f, ": {}", fragment)) + } else { + try!(write!(f, ", {}", fragment)) + } + } + Ok(()) + } +} + +#[deriving(Clone)] +pub struct InlineFragmentContext { + pub styles: Vec<Arc<ComputedValues>>, +} + +impl InlineFragmentContext { + pub fn new() -> InlineFragmentContext { + InlineFragmentContext { + styles: vec!() + } + } +} + +/// BSize above the baseline, depth below the baseline, and ascent for a fragment. See CSS 2.1 § +/// 10.8.1. +pub struct InlineMetrics { + pub block_size_above_baseline: Au, + pub depth_below_baseline: Au, + pub ascent: Au, +} + +impl InlineMetrics { + /// Calculates inline metrics from font metrics and line block-size per CSS 2.1 § 10.8.1. + #[inline] + pub fn from_font_metrics(font_metrics: &FontMetrics, line_height: Au) -> InlineMetrics { + let leading = line_height - (font_metrics.ascent + font_metrics.descent); + InlineMetrics { + block_size_above_baseline: font_metrics.ascent + leading.scale_by(0.5), + depth_below_baseline: font_metrics.descent + leading.scale_by(0.5), + ascent: font_metrics.ascent, + } + } +} + diff --git a/components/layout/layout_debug.rs b/components/layout/layout_debug.rs new file mode 100644 index 00000000000..58db599c9e2 --- /dev/null +++ b/components/layout/layout_debug.rs @@ -0,0 +1,126 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Supports writing a trace file created during each layout scope +//! that can be viewed by an external tool to make layout debugging easier. + +#![macro_escape] + +use flow_ref::FlowRef; +use serialize::json; +use std::cell::RefCell; +use std::io::File; +use std::sync::atomics::{AtomicUint, SeqCst, INIT_ATOMIC_UINT}; + +local_data_key!(state_key: RefCell<State>) + +static mut DEBUG_ID_COUNTER: AtomicUint = INIT_ATOMIC_UINT; + +pub struct Scope; + +#[macro_export] +macro_rules! layout_debug_scope( + ($($arg:tt)*) => ( + if cfg!(not(ndebug)) { + layout_debug::Scope::new(format!($($arg)*)) + } else { + layout_debug::Scope + } + ) +) + +#[deriving(Encodable)] +struct ScopeData { + name: String, + pre: String, + post: String, + children: Vec<Box<ScopeData>>, +} + +impl ScopeData { + fn new(name: String, pre: String) -> ScopeData { + ScopeData { + name: name, + pre: pre, + post: String::new(), + children: vec!(), + } + } +} + +struct State { + flow_root: FlowRef, + scope_stack: Vec<Box<ScopeData>>, +} + +/// A layout debugging scope. The entire state of the flow tree +/// will be output at the beginning and end of this scope. +impl Scope { + pub fn new(name: String) -> Scope { + let maybe_refcell = state_key.get(); + match maybe_refcell { + Some(refcell) => { + let mut state = refcell.borrow_mut(); + let flow_trace = json::encode(&state.flow_root.get()); + let data = box ScopeData::new(name, flow_trace); + state.scope_stack.push(data); + } + None => {} + } + Scope + } +} + +#[cfg(not(ndebug))] +impl Drop for Scope { + fn drop(&mut self) { + let maybe_refcell = state_key.get(); + match maybe_refcell { + Some(refcell) => { + let mut state = refcell.borrow_mut(); + let mut current_scope = state.scope_stack.pop().unwrap(); + current_scope.post = json::encode(&state.flow_root.get()); + let previous_scope = state.scope_stack.mut_last().unwrap(); + previous_scope.children.push(current_scope); + } + None => {} + } + } +} + +/// Generate a unique ID. This is used for items such as Fragment +/// which are often reallocated but represent essentially the +/// same data. +pub fn generate_unique_debug_id() -> uint { + unsafe { DEBUG_ID_COUNTER.fetch_add(1, SeqCst) } +} + +/// Begin a layout debug trace. If this has not been called, +/// creating debug scopes has no effect. +pub fn begin_trace(flow_root: FlowRef) { + assert!(state_key.get().is_none()); + + let flow_trace = json::encode(&flow_root.get()); + let state = State { + scope_stack: vec![box ScopeData::new("root".to_string(), flow_trace)], + flow_root: flow_root, + }; + state_key.replace(Some(RefCell::new(state))); +} + +/// End the debug layout trace. This will write the layout +/// trace to disk in the current directory. The output +/// file can then be viewed with an external tool. +pub fn end_trace() { + let task_state_cell = state_key.replace(None).unwrap(); + let mut task_state = task_state_cell.borrow_mut(); + assert!(task_state.scope_stack.len() == 1); + let mut root_scope = task_state.scope_stack.pop().unwrap(); + root_scope.post = json::encode(&task_state.flow_root.get()); + + let result = json::encode(&root_scope); + let path = Path::new("layout_trace.json"); + let mut file = File::create(&path).unwrap(); + file.write_str(result.as_slice()).unwrap(); +} diff --git a/components/layout/layout_task.rs b/components/layout/layout_task.rs new file mode 100644 index 00000000000..c3c463408e6 --- /dev/null +++ b/components/layout/layout_task.rs @@ -0,0 +1,1020 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! The layout task. Performs layout on the DOM, builds display lists and sends them to be +//! rendered. + +use css::matching::{ApplicableDeclarations, MatchMethods}; +use css::node_style::StyledNode; +use construct::{FlowConstructionResult, NoConstructionResult}; +use context::{LayoutContext, SharedLayoutContext}; +use flow::{Flow, ImmutableFlowUtils, MutableFlowUtils, MutableOwnedFlowUtils}; +use flow::{PreorderFlowTraversal, PostorderFlowTraversal}; +use flow; +use flow_ref::FlowRef; +use incremental::RestyleDamage; +use layout_debug; +use parallel::UnsafeFlow; +use parallel; +use util::{LayoutDataAccess, LayoutDataWrapper, OpaqueNodeMethods, ToGfxColor}; +use wrapper::{LayoutNode, TLayoutNode, ThreadSafeLayoutNode}; + +use collections::dlist::DList; +use geom::point::Point2D; +use geom::rect::Rect; +use geom::size::Size2D; +use gfx::display_list::{ClipDisplayItemClass, ContentStackingLevel, DisplayItem}; +use gfx::display_list::{DisplayItemIterator, DisplayList, OpaqueNode}; +use gfx::render_task::{RenderInitMsg, RenderChan, RenderLayer}; +use gfx::{render_task, color}; +use layout_traits; +use layout_traits::{LayoutControlMsg, LayoutTaskFactory}; +use script::dom::bindings::js::JS; +use script::dom::node::{ElementNodeTypeId, LayoutDataRef, Node}; +use script::dom::element::{HTMLBodyElementTypeId, HTMLHtmlElementTypeId}; +use script::layout_interface::{AddStylesheetMsg, ScriptLayoutChan}; +use script::layout_interface::{TrustedNodeAddress, ContentBoxesResponse, ExitNowMsg}; +use script::layout_interface::{ContentBoxResponse, HitTestResponse, MouseOverResponse}; +use script::layout_interface::{ContentChangedDocumentDamage, LayoutChan, Msg, PrepareToExitMsg}; +use script::layout_interface::{GetRPCMsg, LayoutRPC, ReapLayoutDataMsg, Reflow, UntrustedNodeAddress}; +use script::layout_interface::{ReflowForDisplay, ReflowMsg}; +use script_traits::{SendEventMsg, ReflowEvent, ReflowCompleteMsg, OpaqueScriptLayoutChannel, ScriptControlChan}; +use servo_msg::compositor_msg::Scrollable; +use servo_msg::constellation_msg::{ConstellationChan, PipelineId, Failure, FailureMsg}; +use servo_net::image_cache_task::{ImageCacheTask, ImageResponseMsg}; +use gfx::font_cache_task::{FontCacheTask}; +use servo_net::local_image_cache::{ImageResponder, LocalImageCache}; +use servo_util::geometry::Au; +use servo_util::geometry; +use servo_util::logical_geometry::LogicalPoint; +use servo_util::opts::Opts; +use servo_util::smallvec::{SmallVec, SmallVec1}; +use servo_util::time::{TimeProfilerChan, profile}; +use servo_util::time; +use servo_util::task::spawn_named_with_send_on_failure; +use servo_util::workqueue::WorkQueue; +use std::comm::{channel, Sender, Receiver, Select}; +use std::mem; +use std::ptr; +use style::{AuthorOrigin, Stylesheet, Stylist}; +use style::iter_font_face_rules; +use sync::{Arc, Mutex}; +use url::Url; + +/// Mutable data belonging to the LayoutTask. +/// +/// This needs to be protected by a mutex so we can do fast RPCs. +pub struct LayoutTaskData { + /// The local image cache. + pub local_image_cache: Arc<Mutex<LocalImageCache>>, + + /// The size of the viewport. + pub screen_size: Size2D<Au>, + + /// A cached display list. + pub display_list: Option<Arc<DisplayList>>, + + pub stylist: Box<Stylist>, + + /// The workers that we use for parallel operation. + pub parallel_traversal: Option<WorkQueue<*const SharedLayoutContext, UnsafeFlow>>, + + /// The dirty rect. Used during display list construction. + pub dirty: Rect<Au>, +} + +/// Information needed by the layout task. +pub struct LayoutTask { + /// The ID of the pipeline that we belong to. + pub id: PipelineId, + + /// The port on which we receive messages from the script task. + pub port: Receiver<Msg>, + + /// The port on which we receive messages from the constellation + pub pipeline_port: Receiver<LayoutControlMsg>, + + //// The channel to send messages to ourself. + pub chan: LayoutChan, + + /// The channel on which messages can be sent to the constellation. + pub constellation_chan: ConstellationChan, + + /// The channel on which messages can be sent to the script task. + pub script_chan: ScriptControlChan, + + /// The channel on which messages can be sent to the painting task. + pub render_chan: RenderChan, + + /// The channel on which messages can be sent to the time profiler. + pub time_profiler_chan: TimeProfilerChan, + + /// The channel on which messages can be sent to the image cache. + pub image_cache_task: ImageCacheTask, + + /// Public interface to the font cache task. + pub font_cache_task: FontCacheTask, + + /// The command-line options. + pub opts: Opts, + + /// A mutex to allow for fast, read-only RPC of layout's internal data + /// structures, while still letting the LayoutTask modify them. + /// + /// All the other elements of this struct are read-only. + pub rw_data: Arc<Mutex<LayoutTaskData>>, +} + +/// The damage computation traversal. +#[deriving(Clone)] +struct ComputeDamageTraversal; + +impl PostorderFlowTraversal for ComputeDamageTraversal { + #[inline] + fn process(&mut self, flow: &mut Flow) -> bool { + let mut damage = flow::base(flow).restyle_damage; + for child in flow::child_iter(flow) { + damage.insert(flow::base(child).restyle_damage.propagate_up()) + } + flow::mut_base(flow).restyle_damage = damage; + true + } +} + +/// Propagates restyle damage up and down the tree as appropriate. +/// +/// FIXME(pcwalton): Merge this with flow tree building and/or other traversals. +struct PropagateDamageTraversal { + all_style_damage: bool, +} + +impl PreorderFlowTraversal for PropagateDamageTraversal { + #[inline] + fn process(&mut self, flow: &mut Flow) -> bool { + if self.all_style_damage { + flow::mut_base(flow).restyle_damage.insert(RestyleDamage::all()) + } + debug!("restyle damage = {:?}", flow::base(flow).restyle_damage); + + let prop = flow::base(flow).restyle_damage.propagate_down(); + if !prop.is_empty() { + for kid_ctx in flow::child_iter(flow) { + flow::mut_base(kid_ctx).restyle_damage.insert(prop) + } + } + true + } +} + +/// The flow tree verification traversal. This is only on in debug builds. +#[cfg(debug)] +struct FlowTreeVerificationTraversal; + +#[cfg(debug)] +impl PreorderFlowTraversal for FlowTreeVerificationTraversal { + #[inline] + fn process(&mut self, flow: &mut Flow) -> bool { + let base = flow::base(flow); + if !base.flags.is_leaf() && !base.flags.is_nonleaf() { + println("flow tree verification failed: flow wasn't a leaf or a nonleaf!"); + flow.dump(); + fail!("flow tree verification failed") + } + true + } +} + +/// The bubble-inline-sizes traversal, the first part of layout computation. This computes preferred +/// and intrinsic inline-sizes and bubbles them up the tree. +pub struct BubbleISizesTraversal<'a> { + pub layout_context: &'a LayoutContext<'a>, +} + +impl<'a> PostorderFlowTraversal for BubbleISizesTraversal<'a> { + #[inline] + fn process(&mut self, flow: &mut Flow) -> bool { + flow.bubble_inline_sizes(self.layout_context); + true + } + + // FIXME: We can't prune until we start reusing flows + /* + #[inline] + fn should_prune(&mut self, flow: &mut Flow) -> bool { + flow::mut_base(flow).restyle_damage.lacks(BubbleISizes) + } + */ +} + +/// The assign-inline-sizes traversal. In Gecko this corresponds to `Reflow`. +pub struct AssignISizesTraversal<'a> { + pub layout_context: &'a LayoutContext<'a>, +} + +impl<'a> PreorderFlowTraversal for AssignISizesTraversal<'a> { + #[inline] + fn process(&mut self, flow: &mut Flow) -> bool { + flow.assign_inline_sizes(self.layout_context); + true + } +} + +/// The assign-block-sizes-and-store-overflow traversal, the last (and most expensive) part of layout +/// computation. Determines the final block-sizes for all layout objects, computes positions, and +/// computes overflow regions. In Gecko this corresponds to `FinishAndStoreOverflow`. +pub struct AssignBSizesAndStoreOverflowTraversal<'a> { + pub layout_context: &'a LayoutContext<'a>, +} + +impl<'a> PostorderFlowTraversal for AssignBSizesAndStoreOverflowTraversal<'a> { + #[inline] + fn process(&mut self, flow: &mut Flow) -> bool { + flow.assign_block_size(self.layout_context); + // Skip store-overflow for absolutely positioned flows. That will be + // done in a separate traversal. + if !flow.is_store_overflow_delayed() { + flow.store_overflow(self.layout_context); + } + true + } + + #[inline] + fn should_process(&mut self, flow: &mut Flow) -> bool { + !flow::base(flow).flags.impacted_by_floats() + } +} + +/// The display list construction traversal. +pub struct BuildDisplayListTraversal<'a> { + layout_context: &'a LayoutContext<'a>, +} + +impl<'a> BuildDisplayListTraversal<'a> { + #[inline] + fn process(&mut self, flow: &mut Flow) { + flow.compute_absolute_position(); + + for kid in flow::mut_base(flow).child_iter() { + if !kid.is_absolutely_positioned() { + self.process(kid) + } + } + + for absolute_descendant_link in flow::mut_base(flow).abs_descendants.iter() { + self.process(absolute_descendant_link) + } + + flow.build_display_list(self.layout_context) + } +} + +struct LayoutImageResponder { + id: PipelineId, + script_chan: ScriptControlChan, +} + +impl ImageResponder for LayoutImageResponder { + fn respond(&self) -> proc(ImageResponseMsg):Send { + let id = self.id.clone(); + let script_chan = self.script_chan.clone(); + let f: proc(ImageResponseMsg):Send = proc(_) { + let ScriptControlChan(chan) = script_chan; + drop(chan.send_opt(SendEventMsg(id.clone(), ReflowEvent))) + }; + f + } +} + +impl LayoutTaskFactory for LayoutTask { + /// Spawns a new layout task. + fn create(_phantom: Option<&mut LayoutTask>, + id: PipelineId, + chan: OpaqueScriptLayoutChannel, + pipeline_port: Receiver<LayoutControlMsg>, + constellation_chan: ConstellationChan, + failure_msg: Failure, + script_chan: ScriptControlChan, + render_chan: RenderChan, + img_cache_task: ImageCacheTask, + font_cache_task: FontCacheTask, + opts: Opts, + time_profiler_chan: TimeProfilerChan, + shutdown_chan: Sender<()>) { + let ConstellationChan(con_chan) = constellation_chan.clone(); + spawn_named_with_send_on_failure("LayoutTask", proc() { + { // Ensures layout task is destroyed before we send shutdown message + let sender = chan.sender(); + let layout = + LayoutTask::new( + id, + chan.receiver(), + LayoutChan(sender), + pipeline_port, + constellation_chan, + script_chan, + render_chan, + img_cache_task, + font_cache_task, + &opts, + time_profiler_chan); + layout.start(); + } + shutdown_chan.send(()); + }, FailureMsg(failure_msg), con_chan, false); + } +} + +impl LayoutTask { + /// Creates a new `LayoutTask` structure. + fn new(id: PipelineId, + port: Receiver<Msg>, + chan: LayoutChan, + pipeline_port: Receiver<LayoutControlMsg>, + constellation_chan: ConstellationChan, + script_chan: ScriptControlChan, + render_chan: RenderChan, + image_cache_task: ImageCacheTask, + font_cache_task: FontCacheTask, + opts: &Opts, + time_profiler_chan: TimeProfilerChan) + -> LayoutTask { + let local_image_cache = Arc::new(Mutex::new(LocalImageCache::new(image_cache_task.clone()))); + let screen_size = Size2D(Au(0), Au(0)); + let parallel_traversal = if opts.layout_threads != 1 { + Some(WorkQueue::new("LayoutWorker", opts.layout_threads, ptr::null())) + } else { + None + }; + + LayoutTask { + id: id, + port: port, + pipeline_port: pipeline_port, + chan: chan, + constellation_chan: constellation_chan, + script_chan: script_chan, + render_chan: render_chan, + time_profiler_chan: time_profiler_chan, + image_cache_task: image_cache_task.clone(), + font_cache_task: font_cache_task, + opts: opts.clone(), + rw_data: Arc::new(Mutex::new( + LayoutTaskData { + local_image_cache: local_image_cache, + screen_size: screen_size, + display_list: None, + stylist: box Stylist::new(), + parallel_traversal: parallel_traversal, + dirty: Rect::zero(), + })), + } + } + + /// Starts listening on the port. + fn start(self) { + while self.handle_request() { + // Loop indefinitely. + } + } + + // Create a layout context for use in building display lists, hit testing, &c. + fn build_shared_layout_context(&self, rw_data: &LayoutTaskData, reflow_root: &LayoutNode, url: &Url) -> SharedLayoutContext { + SharedLayoutContext { + image_cache: rw_data.local_image_cache.clone(), + screen_size: rw_data.screen_size.clone(), + constellation_chan: self.constellation_chan.clone(), + layout_chan: self.chan.clone(), + font_cache_task: self.font_cache_task.clone(), + stylist: &*rw_data.stylist, + url: (*url).clone(), + reflow_root: OpaqueNodeMethods::from_layout_node(reflow_root), + opts: self.opts.clone(), + dirty: Rect::zero(), + } + } + + /// Receives and dispatches messages from the script and constellation tasks + fn handle_request(&self) -> bool { + enum PortToRead { + Pipeline, + Script, + } + + let port_to_read = { + let sel = Select::new(); + let mut port1 = sel.handle(&self.port); + let mut port2 = sel.handle(&self.pipeline_port); + unsafe { + port1.add(); + port2.add(); + } + let ret = sel.wait(); + if ret == port1.id() { + Script + } else if ret == port2.id() { + Pipeline + } else { + fail!("invalid select result"); + } + }; + + match port_to_read { + Pipeline => match self.pipeline_port.recv() { + layout_traits::ExitNowMsg => self.handle_script_request(ExitNowMsg), + }, + Script => { + let msg = self.port.recv(); + self.handle_script_request(msg) + } + } + } + + /// Receives and dispatches messages from the script task. + fn handle_script_request(&self, request: Msg) -> bool { + match request { + AddStylesheetMsg(sheet) => self.handle_add_stylesheet(sheet), + GetRPCMsg(response_chan) => { + response_chan.send( + box LayoutRPCImpl( + self.rw_data.clone()) as Box<LayoutRPC + Send>); + }, + ReflowMsg(data) => { + profile(time::LayoutPerformCategory, self.time_profiler_chan.clone(), || { + self.handle_reflow(&*data); + }); + }, + ReapLayoutDataMsg(dead_layout_data) => { + unsafe { + LayoutTask::handle_reap_layout_data(dead_layout_data) + } + }, + PrepareToExitMsg(response_chan) => { + debug!("layout: PrepareToExitMsg received"); + self.prepare_to_exit(response_chan); + return false + }, + ExitNowMsg => { + debug!("layout: ExitNowMsg received"); + self.exit_now(); + return false + } + } + + true + } + + /// Enters a quiescent state in which no new messages except for `ReapLayoutDataMsg` will be + /// processed until an `ExitNowMsg` is received. A pong is immediately sent on the given + /// response channel. + fn prepare_to_exit(&self, response_chan: Sender<()>) { + response_chan.send(()); + loop { + match self.port.recv() { + ReapLayoutDataMsg(dead_layout_data) => { + unsafe { + LayoutTask::handle_reap_layout_data(dead_layout_data) + } + } + ExitNowMsg => { + debug!("layout task is exiting..."); + self.exit_now(); + break + } + _ => { + fail!("layout: message that wasn't `ExitNowMsg` received after \ + `PrepareToExitMsg`") + } + } + } + } + + /// Shuts down the layout task now. If there are any DOM nodes left, layout will now (safely) + /// crash. + fn exit_now(&self) { + let (response_chan, response_port) = channel(); + + { + let mut rw_data = self.rw_data.lock(); + match rw_data.deref_mut().parallel_traversal { + None => {} + Some(ref mut traversal) => traversal.shutdown(), + } + } + + self.render_chan.send(render_task::ExitMsg(Some(response_chan))); + response_port.recv() + } + + fn handle_add_stylesheet(&self, sheet: Stylesheet) { + // Find all font-face rules and notify the font cache of them. + // GWTODO: Need to handle unloading web fonts (when we handle unloading stylesheets!) + iter_font_face_rules(&sheet, |family, url| { + self.font_cache_task.add_web_font(family.to_string(), url.clone()); + }); + let mut rw_data = self.rw_data.lock(); + rw_data.stylist.add_stylesheet(sheet, AuthorOrigin); + } + + /// Retrieves the flow tree root from the root node. + fn get_layout_root(&self, node: LayoutNode) -> FlowRef { + let mut layout_data_ref = node.mutate_layout_data(); + let result = match &mut *layout_data_ref { + &Some(ref mut layout_data) => { + mem::replace(&mut layout_data.data.flow_construction_result, NoConstructionResult) + } + &None => fail!("no layout data for root node"), + }; + let mut flow = match result { + FlowConstructionResult(mut flow, abs_descendants) => { + // Note: Assuming that the root has display 'static' (as per + // CSS Section 9.3.1). Otherwise, if it were absolutely + // positioned, it would return a reference to itself in + // `abs_descendants` and would lead to a circular reference. + // Set Root as CB for any remaining absolute descendants. + flow.set_abs_descendants(abs_descendants); + flow + } + _ => fail!("Flow construction didn't result in a flow at the root of the tree!"), + }; + flow.get_mut().mark_as_root(); + flow + } + + /// Performs layout constraint solving. + /// + /// This corresponds to `Reflow()` in Gecko and `layout()` in WebKit/Blink and should be + /// benchmarked against those two. It is marked `#[inline(never)]` to aid profiling. + #[inline(never)] + fn solve_constraints<'a>(&self, + layout_root: &mut Flow, + layout_context: &'a LayoutContext<'a>) { + let _scope = layout_debug_scope!("solve_constraints"); + + if layout_context.shared.opts.bubble_inline_sizes_separately { + let mut traversal = BubbleISizesTraversal { + layout_context: layout_context, + }; + layout_root.traverse_postorder(&mut traversal); + } + + // FIXME(kmc): We want to prune nodes without the Reflow restyle damage + // bit, but FloatContext values can't be reused, so we need to + // recompute them every time. + // NOTE: this currently computes borders, so any pruning should separate that operation + // out. + { + let mut traversal = AssignISizesTraversal { + layout_context: layout_context, + }; + layout_root.traverse_preorder(&mut traversal); + } + + // FIXME(pcwalton): Prune this pass as well. + { + let mut traversal = AssignBSizesAndStoreOverflowTraversal { + layout_context: layout_context, + }; + layout_root.traverse_postorder(&mut traversal); + } + } + + /// Performs layout constraint solving in parallel. + /// + /// This corresponds to `Reflow()` in Gecko and `layout()` in WebKit/Blink and should be + /// benchmarked against those two. It is marked `#[inline(never)]` to aid profiling. + #[inline(never)] + fn solve_constraints_parallel(&self, + rw_data: &mut LayoutTaskData, + layout_root: &mut FlowRef, + shared_layout_context: &SharedLayoutContext) { + if shared_layout_context.opts.bubble_inline_sizes_separately { + let mut traversal = BubbleISizesTraversal { + layout_context: &LayoutContext::new(shared_layout_context), + }; + layout_root.get_mut().traverse_postorder(&mut traversal); + } + + match rw_data.parallel_traversal { + None => fail!("solve_contraints_parallel() called with no parallel traversal ready"), + Some(ref mut traversal) => { + // NOTE: this currently computes borders, so any pruning should separate that + // operation out. + parallel::traverse_flow_tree_preorder(layout_root, + self.time_profiler_chan.clone(), + shared_layout_context, + traversal); + } + } + } + + /// Verifies that every node was either marked as a leaf or as a nonleaf in the flow tree. + /// This is only on in debug builds. + #[inline(never)] + #[cfg(debug)] + fn verify_flow_tree(&self, layout_root: &mut FlowRef) { + let mut traversal = FlowTreeVerificationTraversal; + layout_root.traverse_preorder(&mut traversal); + } + + #[cfg(not(debug))] + fn verify_flow_tree(&self, _: &mut FlowRef) { + } + + /// The high-level routine that performs layout tasks. + fn handle_reflow(&self, data: &Reflow) { + // FIXME: Isolate this transmutation into a "bridge" module. + // FIXME(rust#16366): The following line had to be moved because of a + // rustc bug. It should be in the next unsafe block. + let mut node: JS<Node> = unsafe { JS::from_trusted_node_address(data.document_root) }; + let node: &mut LayoutNode = unsafe { + mem::transmute(&mut node) + }; + + debug!("layout: received layout request for: {:s}", data.url.serialize()); + debug!("layout: damage is {:?}", data.damage); + debug!("layout: parsed Node tree"); + debug!("{:?}", node.dump()); + + let mut rw_data = self.rw_data.lock(); + + { + // Reset the image cache. + let mut local_image_cache = rw_data.local_image_cache.lock(); + local_image_cache.next_round(self.make_on_image_available_cb()); + } + + // true => Do the reflow with full style damage, because content + // changed or the window was resized. + let mut all_style_damage = match data.damage.level { + ContentChangedDocumentDamage => true, + _ => false + }; + + // TODO: Calculate the "actual viewport": + // http://www.w3.org/TR/css-device-adapt/#actual-viewport + let viewport_size = data.window_size.initial_viewport; + + let current_screen_size = Size2D(Au::from_frac32_px(viewport_size.width.get()), + Au::from_frac32_px(viewport_size.height.get())); + if rw_data.screen_size != current_screen_size { + all_style_damage = true + } + rw_data.screen_size = current_screen_size; + + // Create a layout context for use throughout the following passes. + let mut shared_layout_ctx = self.build_shared_layout_context(rw_data.deref(), node, &data.url); + + let mut layout_root = profile(time::LayoutStyleRecalcCategory, + self.time_profiler_chan.clone(), + || { + // Perform CSS selector matching and flow construction. + let rw_data = rw_data.deref_mut(); + match rw_data.parallel_traversal { + None => { + let layout_ctx = LayoutContext::new(&shared_layout_ctx); + let mut applicable_declarations = ApplicableDeclarations::new(); + node.recalc_style_for_subtree(&*rw_data.stylist, + &layout_ctx, + &mut applicable_declarations, + None) + } + Some(ref mut traversal) => { + parallel::recalc_style_for_subtree(node, &mut shared_layout_ctx, traversal) + } + } + + self.get_layout_root((*node).clone()) + }); + + // Verification of the flow tree, which ensures that all nodes were either marked as leaves + // or as non-leaves. This becomes a no-op in release builds. (It is inconsequential to + // memory safety but is a useful debugging tool.) + self.verify_flow_tree(&mut layout_root); + + if self.opts.trace_layout { + layout_debug::begin_trace(layout_root.clone()); + } + + // Propagate damage. + profile(time::LayoutDamagePropagateCategory, self.time_profiler_chan.clone(), || { + layout_root.get_mut().traverse_preorder(&mut PropagateDamageTraversal { + all_style_damage: all_style_damage + }); + layout_root.get_mut().traverse_postorder(&mut ComputeDamageTraversal.clone()); + }); + + // Perform the primary layout passes over the flow tree to compute the locations of all + // the boxes. + profile(time::LayoutMainCategory, self.time_profiler_chan.clone(), || { + let rw_data = rw_data.deref_mut(); + match rw_data.parallel_traversal { + None => { + // Sequential mode. + let layout_ctx = LayoutContext::new(&shared_layout_ctx); + self.solve_constraints(layout_root.get_mut(), &layout_ctx) + } + Some(_) => { + // Parallel mode. + self.solve_constraints_parallel(rw_data, &mut layout_root, &mut shared_layout_ctx) + } + } + }); + + // Build the display list if necessary, and send it to the renderer. + if data.goal == ReflowForDisplay { + let writing_mode = flow::base(layout_root.get()).writing_mode; + profile(time::LayoutDispListBuildCategory, self.time_profiler_chan.clone(), || { + shared_layout_ctx.dirty = flow::base(layout_root.get()).position.to_physical( + writing_mode, rw_data.screen_size); + flow::mut_base(layout_root.get_mut()).abs_position = + LogicalPoint::zero(writing_mode).to_physical(writing_mode, rw_data.screen_size); + + let rw_data = rw_data.deref_mut(); + match rw_data.parallel_traversal { + None => { + let layout_ctx = LayoutContext::new(&shared_layout_ctx); + let mut traversal = BuildDisplayListTraversal { + layout_context: &layout_ctx, + }; + traversal.process(layout_root.get_mut()); + } + Some(ref mut traversal) => { + parallel::build_display_list_for_subtree(&mut layout_root, + self.time_profiler_chan.clone(), + &mut shared_layout_ctx, + traversal); + } + } + + let root_display_list = + mem::replace(&mut flow::mut_base(layout_root.get_mut()).display_list, + DisplayList::new()); + root_display_list.debug(); + let display_list = Arc::new(root_display_list.flatten(ContentStackingLevel)); + + // FIXME(pcwalton): This is really ugly and can't handle overflow: scroll. Refactor + // it with extreme prejudice. + let mut color = color::rgba(1.0, 1.0, 1.0, 1.0); + for child in node.traverse_preorder() { + if child.type_id() == Some(ElementNodeTypeId(HTMLHtmlElementTypeId)) || + child.type_id() == Some(ElementNodeTypeId(HTMLBodyElementTypeId)) { + let element_bg_color = { + let thread_safe_child = ThreadSafeLayoutNode::new(&child); + thread_safe_child.style() + .resolve_color(thread_safe_child.style() + .get_background() + .background_color) + .to_gfx_color() + }; + match element_bg_color { + color::rgba(0., 0., 0., 0.) => {} + _ => { + color = element_bg_color; + break; + } + } + } + } + + let root_size = { + let root_flow = flow::base(layout_root.get()); + root_flow.position.size.to_physical(root_flow.writing_mode) + }; + let root_size = Size2D(root_size.width.to_nearest_px() as uint, + root_size.height.to_nearest_px() as uint); + let render_layer = RenderLayer { + id: layout_root.get().layer_id(0), + display_list: display_list.clone(), + position: Rect(Point2D(0u, 0u), root_size), + background_color: color, + scroll_policy: Scrollable, + }; + + rw_data.display_list = Some(display_list.clone()); + + // TODO(pcwalton): Eventually, when we have incremental reflow, this will have to + // be smarter in order to handle retained layer contents properly from reflow to + // reflow. + let mut layers = SmallVec1::new(); + layers.push(render_layer); + for layer in mem::replace(&mut flow::mut_base(layout_root.get_mut()).layers, + DList::new()).move_iter() { + layers.push(layer) + } + + debug!("Layout done!"); + + self.render_chan.send(RenderInitMsg(layers)); + }); + } + + if self.opts.trace_layout { + layout_debug::end_trace(); + } + + // Tell script that we're done. + // + // FIXME(pcwalton): This should probably be *one* channel, but we can't fix this without + // either select or a filtered recv() that only looks for messages of a given type. + data.script_join_chan.send(()); + let ScriptControlChan(ref chan) = data.script_chan; + chan.send(ReflowCompleteMsg(self.id, data.id)); + } + + + // When images can't be loaded in time to display they trigger + // this callback in some task somewhere. This will send a message + // to the script task, and ultimately cause the image to be + // re-requested. We probably don't need to go all the way back to + // the script task for this. + fn make_on_image_available_cb(&self) -> Box<ImageResponder+Send> { + // This has a crazy signature because the image cache needs to + // make multiple copies of the callback, and the dom event + // channel is not a copyable type, so this is actually a + // little factory to produce callbacks + box LayoutImageResponder { + id: self.id.clone(), + script_chan: self.script_chan.clone(), + } as Box<ImageResponder+Send> + } + + /// Handles a message to destroy layout data. Layout data must be destroyed on *this* task + /// because it contains local managed pointers. + unsafe fn handle_reap_layout_data(layout_data: LayoutDataRef) { + let mut layout_data_ref = layout_data.borrow_mut(); + let _: Option<LayoutDataWrapper> = mem::transmute( + mem::replace(&mut *layout_data_ref, None)); + } +} + +struct LayoutRPCImpl(Arc<Mutex<LayoutTaskData>>); + +impl LayoutRPC for LayoutRPCImpl { + // The neat thing here is that in order to answer the following two queries we only + // need to compare nodes for equality. Thus we can safely work only with `OpaqueNode`. + fn content_box(&self, node: TrustedNodeAddress) -> ContentBoxResponse { + let node: OpaqueNode = OpaqueNodeMethods::from_script_node(node); + fn union_boxes_for_node(accumulator: &mut Option<Rect<Au>>, + mut iter: DisplayItemIterator, + node: OpaqueNode) { + for item in iter { + union_boxes_for_node(accumulator, item.children(), node); + if item.base().node == node { + match *accumulator { + None => *accumulator = Some(item.base().bounds), + Some(ref mut acc) => *acc = acc.union(&item.base().bounds), + } + } + } + } + + let mut rect = None; + { + let &LayoutRPCImpl(ref rw_data) = self; + let rw_data = rw_data.lock(); + match rw_data.display_list { + None => fail!("no display list!"), + Some(ref display_list) => { + union_boxes_for_node(&mut rect, display_list.iter(), node) + } + } + } + ContentBoxResponse(rect.unwrap_or(Rect::zero())) + } + + /// Requests the dimensions of all the content boxes, as in the `getClientRects()` call. + fn content_boxes(&self, node: TrustedNodeAddress) -> ContentBoxesResponse { + let node: OpaqueNode = OpaqueNodeMethods::from_script_node(node); + + fn add_boxes_for_node(accumulator: &mut Vec<Rect<Au>>, + mut iter: DisplayItemIterator, + node: OpaqueNode) { + for item in iter { + add_boxes_for_node(accumulator, item.children(), node); + if item.base().node == node { + accumulator.push(item.base().bounds) + } + } + } + + let mut boxes = vec!(); + { + let &LayoutRPCImpl(ref rw_data) = self; + let rw_data = rw_data.lock(); + match rw_data.display_list { + None => fail!("no display list!"), + Some(ref display_list) => { + add_boxes_for_node(&mut boxes, display_list.iter(), node) + } + } + } + ContentBoxesResponse(boxes) + } + + /// Requests the node containing the point of interest + fn hit_test(&self, _: TrustedNodeAddress, point: Point2D<f32>) -> Result<HitTestResponse, ()> { + fn hit_test<'a,I:Iterator<&'a DisplayItem>>(x: Au, y: Au, mut iterator: I) + -> Option<HitTestResponse> { + for item in iterator { + match *item { + ClipDisplayItemClass(ref cc) => { + if geometry::rect_contains_point(cc.base.bounds, Point2D(x, y)) { + let ret = hit_test(x, y, cc.children.list.iter().rev()); + if !ret.is_none() { + return ret + } + } + continue + } + _ => {} + } + + let bounds = item.bounds(); + + // TODO(tikue): This check should really be performed by a method of + // DisplayItem. + if x < bounds.origin.x + bounds.size.width && + bounds.origin.x <= x && + y < bounds.origin.y + bounds.size.height && + bounds.origin.y <= y { + return Some(HitTestResponse(item.base() + .node + .to_untrusted_node_address())) + } + } + let ret: Option<HitTestResponse> = None; + ret + } + let (x, y) = (Au::from_frac_px(point.x as f64), + Au::from_frac_px(point.y as f64)); + + let resp = { + let &LayoutRPCImpl(ref rw_data) = self; + let rw_data = rw_data.lock(); + match rw_data.display_list { + None => fail!("no display list!"), + Some(ref display_list) => hit_test(x, y, display_list.list.iter().rev()), + } + }; + + if resp.is_some() { + return Ok(resp.unwrap()); + } + Err(()) + } + + fn mouse_over(&self, _: TrustedNodeAddress, point: Point2D<f32>) -> Result<MouseOverResponse, ()> { + fn mouse_over_test<'a, + I:Iterator<&'a DisplayItem>>( + x: Au, + y: Au, + mut iterator: I, + result: &mut Vec<UntrustedNodeAddress>) { + for item in iterator { + match *item { + ClipDisplayItemClass(ref cc) => { + mouse_over_test(x, y, cc.children.list.iter().rev(), result); + } + _ => { + let bounds = item.bounds(); + + // TODO(tikue): This check should really be performed by a method + // of DisplayItem. + if x < bounds.origin.x + bounds.size.width && + bounds.origin.x <= x && + y < bounds.origin.y + bounds.size.height && + bounds.origin.y <= y { + result.push(item.base() + .node + .to_untrusted_node_address()); + } + } + } + } + } + + let mut mouse_over_list: Vec<UntrustedNodeAddress> = vec!(); + let (x, y) = (Au::from_frac_px(point.x as f64), Au::from_frac_px(point.y as f64)); + + { + let &LayoutRPCImpl(ref rw_data) = self; + let rw_data = rw_data.lock(); + match rw_data.display_list { + None => fail!("no display list!"), + Some(ref display_list) => { + mouse_over_test(x, + y, + display_list.list.iter().rev(), + &mut mouse_over_list); + } + }; + } + + if mouse_over_list.is_empty() { + Err(()) + } else { + Ok(MouseOverResponse(mouse_over_list)) + } + } +} diff --git a/components/layout/lib.rs b/components/layout/lib.rs new file mode 100644 index 00000000000..a782f4e1355 --- /dev/null +++ b/components/layout/lib.rs @@ -0,0 +1,68 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#![comment = "The Servo Parallel Browser Project"] +#![license = "MPL"] + +#![feature(globs, macro_rules, phase, thread_local, unsafe_destructor)] + +#[phase(plugin, link)] +extern crate log; + +extern crate debug; + +extern crate geom; +extern crate gfx; +extern crate layout_traits; +extern crate script; +extern crate script_traits; +extern crate serialize; +extern crate style; +#[phase(plugin)] +extern crate servo_macros = "macros"; +extern crate servo_net = "net"; +extern crate servo_msg = "msg"; +#[phase(plugin, link)] +extern crate servo_util = "util"; + +extern crate collections; +extern crate green; +extern crate libc; +extern crate sync; +extern crate url; + +// Listed first because of macro definitions +pub mod layout_debug; + +pub mod block; +pub mod construct; +pub mod context; +pub mod floats; +pub mod flow; +pub mod flow_list; +pub mod flow_ref; +pub mod fragment; +pub mod layout_task; +pub mod inline; +pub mod model; +pub mod parallel; +pub mod table_wrapper; +pub mod table; +pub mod table_caption; +pub mod table_colgroup; +pub mod table_rowgroup; +pub mod table_row; +pub mod table_cell; +pub mod text; +pub mod util; +pub mod incremental; +pub mod wrapper; +pub mod extra; + +pub mod css { + mod node_util; + + pub mod matching; + pub mod node_style; +} diff --git a/components/layout/model.rs b/components/layout/model.rs new file mode 100644 index 00000000000..23647b1b77d --- /dev/null +++ b/components/layout/model.rs @@ -0,0 +1,337 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Borders, padding, and margins. + +#![deny(unsafe_block)] + +use fragment::Fragment; + +use computed = style::computed_values; +use geom::SideOffsets2D; +use style::computed_values::{LPA_Auto, LPA_Length, LPA_Percentage, LP_Length, LP_Percentage}; +use style::ComputedValues; +use servo_util::geometry::Au; +use servo_util::geometry; +use servo_util::logical_geometry::LogicalMargin; +use std::fmt; + +/// A collapsible margin. See CSS 2.1 § 8.3.1. +pub struct AdjoiningMargins { + /// The value of the greatest positive margin. + pub 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. + pub 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 block-start and block-end 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 block-start and block-end 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 doesn’t + /// have any border, padding, or out-of-flow (floating or positioned) content + MarginsCollapseThrough(AdjoiningMargins), +} + +impl CollapsibleMargins { + pub fn new() -> CollapsibleMargins { + NoCollapsibleMargins(Au(0), Au(0)) + } +} + +enum FinalMarginState { + MarginsCollapseThroughFinalMarginState, + BottomMarginCollapsesFinalMarginState, +} + +pub struct MarginCollapseInfo { + pub state: MarginCollapseState, + pub block_start_margin: AdjoiningMargins, + pub margin_in: AdjoiningMargins, +} + +impl MarginCollapseInfo { + /// TODO(#2012, pcwalton): Remove this method once `fragment` is not an `Option`. + pub fn new() -> MarginCollapseInfo { + MarginCollapseInfo { + state: AccumulatingCollapsibleTopMargin, + block_start_margin: AdjoiningMargins::new(), + margin_in: AdjoiningMargins::new(), + } + } + + pub fn initialize_block_start_margin(&mut self, + fragment: &Fragment, + can_collapse_block_start_margin_with_kids: bool) { + if !can_collapse_block_start_margin_with_kids { + self.state = AccumulatingMarginIn + } + + self.block_start_margin = AdjoiningMargins::from_margin(fragment.margin.block_start) + } + + pub fn finish_and_compute_collapsible_margins(mut self, + fragment: &Fragment, + can_collapse_block_end_margin_with_kids: bool) + -> (CollapsibleMargins, Au) { + let state = match self.state { + AccumulatingCollapsibleTopMargin => { + match fragment.style().content_block_size() { + LPA_Auto | LPA_Length(Au(0)) | LPA_Percentage(0.) => { + match fragment.style().min_block_size() { + LP_Length(Au(0)) | LP_Percentage(0.) => { + MarginsCollapseThroughFinalMarginState + }, + _ => { + // If the fragment has non-zero min-block-size, margins may not + // collapse through it. + BottomMarginCollapsesFinalMarginState + } + } + }, + _ => { + // If the fragment has an explicitly specified block-size, margins may not + // collapse through it. + BottomMarginCollapsesFinalMarginState + } + } + } + AccumulatingMarginIn => BottomMarginCollapsesFinalMarginState, + }; + + // Different logic is needed here depending on whether this flow can collapse its block-end + // margin with its children. + let block_end_margin = fragment.margin.block_end; + if !can_collapse_block_end_margin_with_kids { + match state { + MarginsCollapseThroughFinalMarginState => { + let advance = self.block_start_margin.collapse(); + self.margin_in.union(AdjoiningMargins::from_margin(block_end_margin)); + (MarginsCollapse(self.block_start_margin, self.margin_in), advance) + } + BottomMarginCollapsesFinalMarginState => { + let advance = self.margin_in.collapse(); + self.margin_in.union(AdjoiningMargins::from_margin(block_end_margin)); + (MarginsCollapse(self.block_start_margin, self.margin_in), advance) + } + } + } else { + match state { + MarginsCollapseThroughFinalMarginState => { + self.block_start_margin.union(AdjoiningMargins::from_margin(block_end_margin)); + (MarginsCollapseThrough(self.block_start_margin), Au(0)) + } + BottomMarginCollapsesFinalMarginState => { + self.margin_in.union(AdjoiningMargins::from_margin(block_end_margin)); + (MarginsCollapse(self.block_start_margin, self.margin_in), Au(0)) + } + } + } + } + + pub fn current_float_ceiling(&mut self) -> Au { + match self.state { + AccumulatingCollapsibleTopMargin => self.block_start_margin.collapse(), + AccumulatingMarginIn => self.margin_in.collapse(), + } + } + + /// Adds the child's potentially collapsible block-start 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_block_start_margin(&mut self, child_collapsible_margins: &CollapsibleMargins) -> Au { + match (self.state, *child_collapsible_margins) { + (AccumulatingCollapsibleTopMargin, NoCollapsibleMargins(block_start, _)) => { + self.state = AccumulatingMarginIn; + block_start + } + (AccumulatingCollapsibleTopMargin, MarginsCollapse(block_start, _)) => { + self.block_start_margin.union(block_start); + self.state = AccumulatingMarginIn; + Au(0) + } + (AccumulatingMarginIn, NoCollapsibleMargins(block_start, _)) => { + let previous_margin_value = self.margin_in.collapse(); + self.margin_in = AdjoiningMargins::new(); + previous_margin_value + block_start + } + (AccumulatingMarginIn, MarginsCollapse(block_start, _)) => { + self.margin_in.union(block_start); + 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_block-end_margin` below. + Au(0) + } + } + } + + /// Adds the child's potentially collapsible block-end 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_block_end_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 block_start margins anymore!") + } + (AccumulatingCollapsibleTopMargin, MarginsCollapseThrough(margin)) => { + self.block_start_margin.union(margin); + Au(0) + } + (AccumulatingMarginIn, NoCollapsibleMargins(_, block_end)) => { + assert_eq!(self.margin_in.most_positive, Au(0)); + assert_eq!(self.margin_in.most_negative, Au(0)); + block_end + } + (AccumulatingMarginIn, MarginsCollapse(_, block_end)) | + (AccumulatingMarginIn, MarginsCollapseThrough(block_end)) => { + self.margin_in.union(block_end); + Au(0) + } + } + } +} + +pub enum MarginCollapseState { + AccumulatingCollapsibleTopMargin, + AccumulatingMarginIn, +} + +/// Intrinsic inline-sizes, which consist of minimum and preferred. +#[deriving(Encodable)] +pub struct IntrinsicISizes { + /// The *minimum inline-size* of the content. + pub minimum_inline_size: Au, + /// The *preferred inline-size* of the content. + pub preferred_inline_size: Au, + /// The estimated sum of borders, padding, and margins. Some calculations use this information + /// when computing intrinsic inline-sizes. + pub surround_inline_size: Au, +} + +impl fmt::Show for IntrinsicISizes { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "min={}, pref={}, surr={}", self.minimum_inline_size, self.preferred_inline_size, self.surround_inline_size) + } +} + +impl IntrinsicISizes { + pub fn new() -> IntrinsicISizes { + IntrinsicISizes { + minimum_inline_size: Au(0), + preferred_inline_size: Au(0), + surround_inline_size: Au(0), + } + } + + pub fn total_minimum_inline_size(&self) -> Au { + self.minimum_inline_size + self.surround_inline_size + } + + pub fn total_preferred_inline_size(&self) -> Au { + self.preferred_inline_size + self.surround_inline_size + } +} + +/// Useful helper data type when computing values for blocks and positioned elements. +pub enum MaybeAuto { + Auto, + Specified(Au), +} + +impl MaybeAuto { + #[inline] + pub fn from_style(length: computed::LengthOrPercentageOrAuto, containing_length: Au) + -> MaybeAuto { + match length { + computed::LPA_Auto => Auto, + computed::LPA_Percentage(percent) => Specified(containing_length.scale_by(percent)), + computed::LPA_Length(length) => Specified(length) + } + } + + #[inline] + pub fn specified_or_default(&self, default: Au) -> Au { + match *self { + Auto => default, + Specified(value) => value, + } + } + + #[inline] + pub fn specified_or_zero(&self) -> Au { + self.specified_or_default(Au::new(0)) + } +} + +pub fn specified_or_none(length: computed::LengthOrPercentageOrNone, containing_length: Au) -> Option<Au> { + match length { + computed::LPN_None => None, + computed::LPN_Percentage(percent) => Some(containing_length.scale_by(percent)), + computed::LPN_Length(length) => Some(length), + } +} + +pub fn specified(length: computed::LengthOrPercentage, containing_length: Au) -> Au { + match length { + computed::LP_Length(length) => length, + computed::LP_Percentage(p) => containing_length.scale_by(p) + } +} + +#[inline] +pub fn padding_from_style(style: &ComputedValues, containing_block_inline_size: Au) + -> LogicalMargin<Au> { + let padding_style = style.get_padding(); + LogicalMargin::from_physical(style.writing_mode, SideOffsets2D::new( + specified(padding_style.padding_top, containing_block_inline_size), + specified(padding_style.padding_right, containing_block_inline_size), + specified(padding_style.padding_bottom, containing_block_inline_size), + specified(padding_style.padding_left, containing_block_inline_size))) +} + diff --git a/components/layout/parallel.rs b/components/layout/parallel.rs new file mode 100644 index 00000000000..a2786e8ba91 --- /dev/null +++ b/components/layout/parallel.rs @@ -0,0 +1,561 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Implements parallel traversals over the DOM and flow trees. +//! +//! This code is highly unsafe. Keep this file small and easy to audit. + +use css::matching::{ApplicableDeclarations, CannotShare, MatchMethods, StyleWasShared}; +use construct::FlowConstructor; +use context::{LayoutContext, SharedLayoutContext}; +use extra::LayoutAuxMethods; +use flow::{Flow, MutableFlowUtils, PreorderFlowTraversal, PostorderFlowTraversal}; +use flow; +use flow_ref::FlowRef; +use layout_task::{AssignBSizesAndStoreOverflowTraversal, AssignISizesTraversal}; +use layout_task::{BubbleISizesTraversal}; +use util::{LayoutDataAccess, LayoutDataWrapper, OpaqueNodeMethods}; +use wrapper::{layout_node_to_unsafe_layout_node, layout_node_from_unsafe_layout_node, LayoutNode, PostorderNodeMutTraversal}; +use wrapper::{ThreadSafeLayoutNode, UnsafeLayoutNode}; + +use gfx::display_list::OpaqueNode; +use servo_util::time::{TimeProfilerChan, profile}; +use servo_util::time; +use servo_util::workqueue::{WorkQueue, WorkUnit, WorkerProxy}; +use std::mem; +use std::ptr; +use std::sync::atomics::{AtomicInt, Relaxed, SeqCst}; +use style::TNode; + +#[allow(dead_code)] +fn static_assertion(node: UnsafeLayoutNode) { + unsafe { + let _: UnsafeFlow = ::std::intrinsics::transmute(node); + } +} + +/// Vtable + pointer representation of a Flow trait object. +pub type UnsafeFlow = (uint, uint); + +fn null_unsafe_flow() -> UnsafeFlow { + (0, 0) +} + +pub fn owned_flow_to_unsafe_flow(flow: *const FlowRef) -> UnsafeFlow { + unsafe { + mem::transmute_copy(&*flow) + } +} + +pub fn mut_owned_flow_to_unsafe_flow(flow: *mut FlowRef) -> UnsafeFlow { + unsafe { + mem::transmute_copy(&*flow) + } +} + +pub fn borrowed_flow_to_unsafe_flow(flow: &Flow) -> UnsafeFlow { + unsafe { + mem::transmute_copy(&flow) + } +} + +pub fn mut_borrowed_flow_to_unsafe_flow(flow: &mut Flow) -> UnsafeFlow { + unsafe { + mem::transmute_copy(&flow) + } +} + +/// Information that we need stored in each DOM node. +pub struct DomParallelInfo { + /// The number of children that still need work done. + pub children_count: AtomicInt, +} + +impl DomParallelInfo { + pub fn new() -> DomParallelInfo { + DomParallelInfo { + children_count: AtomicInt::new(0), + } + } +} + +/// Information that we need stored in each flow. +pub struct FlowParallelInfo { + /// The number of children that still need work done. + pub children_count: AtomicInt, + /// The number of children and absolute descendants that still need work done. + pub children_and_absolute_descendant_count: AtomicInt, + /// The address of the parent flow. + pub parent: UnsafeFlow, +} + +impl FlowParallelInfo { + pub fn new() -> FlowParallelInfo { + FlowParallelInfo { + children_count: AtomicInt::new(0), + children_and_absolute_descendant_count: AtomicInt::new(0), + parent: null_unsafe_flow(), + } + } +} + +/// A parallel bottom-up flow traversal. +trait ParallelPostorderFlowTraversal : PostorderFlowTraversal { + /// Process current flow and potentially traverse its ancestors. + /// + /// If we are the last child that finished processing, recursively process + /// our parent. Else, stop. + /// Also, stop at the root (obviously :P). + /// + /// Thus, if we start with all the leaves of a tree, we end up traversing + /// the whole tree bottom-up because each parent will be processed exactly + /// once (by the last child that finishes processing). + /// + /// The only communication between siblings is that they both + /// fetch-and-subtract the parent's children count. + fn run_parallel(&mut self, + mut unsafe_flow: UnsafeFlow, + _: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>) { + loop { + unsafe { + // Get a real flow. + let flow: &mut FlowRef = mem::transmute(&unsafe_flow); + + // Perform the appropriate traversal. + if self.should_process(flow.get_mut()) { + self.process(flow.get_mut()); + } + + let base = flow::mut_base(flow.get_mut()); + + // Reset the count of children for the next layout traversal. + base.parallel.children_count.store(base.children.len() as int, Relaxed); + + // Possibly enqueue the parent. + let unsafe_parent = base.parallel.parent; + if unsafe_parent == null_unsafe_flow() { + // We're done! + break + } + + // No, we're not at the root yet. Then are we the last child + // of our parent to finish processing? If so, we can continue + // on with our parent; otherwise, we've gotta wait. + let parent: &mut FlowRef = mem::transmute(&unsafe_parent); + let parent_base = flow::mut_base(parent.get_mut()); + if parent_base.parallel.children_count.fetch_sub(1, SeqCst) == 1 { + // We were the last child of our parent. Reflow our parent. + unsafe_flow = unsafe_parent + } else { + // Stop. + break + } + } + } + } +} + +/// A parallel top-down flow traversal. +trait ParallelPreorderFlowTraversal : PreorderFlowTraversal { + fn run_parallel(&mut self, + unsafe_flow: UnsafeFlow, + proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>); + + #[inline(always)] + fn run_parallel_helper(&mut self, + unsafe_flow: UnsafeFlow, + proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>, + top_down_func: extern "Rust" fn(UnsafeFlow, + &mut WorkerProxy<*const SharedLayoutContext, + UnsafeFlow>), + bottom_up_func: extern "Rust" fn(UnsafeFlow, + &mut WorkerProxy<*const SharedLayoutContext, + UnsafeFlow>)) { + let mut had_children = false; + unsafe { + // Get a real flow. + let flow: &mut FlowRef = mem::transmute(&unsafe_flow); + + // Perform the appropriate traversal. + self.process(flow.get_mut()); + + // Possibly enqueue the children. + for kid in flow::child_iter(flow.get_mut()) { + had_children = true; + proxy.push(WorkUnit { + fun: top_down_func, + data: borrowed_flow_to_unsafe_flow(kid), + }); + } + + } + + // If there were no more children, start assigning block-sizes. + if !had_children { + bottom_up_func(unsafe_flow, proxy) + } + } +} + +impl<'a> ParallelPostorderFlowTraversal for BubbleISizesTraversal<'a> {} + +impl<'a> ParallelPreorderFlowTraversal for AssignISizesTraversal<'a> { + fn run_parallel(&mut self, + unsafe_flow: UnsafeFlow, + proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>) { + self.run_parallel_helper(unsafe_flow, + proxy, + assign_inline_sizes, + assign_block_sizes_and_store_overflow) + } +} + +impl<'a> ParallelPostorderFlowTraversal for AssignBSizesAndStoreOverflowTraversal<'a> {} + +fn recalc_style_for_node(unsafe_layout_node: UnsafeLayoutNode, + proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeLayoutNode>) { + let shared_layout_context = unsafe { &**proxy.user_data() }; + let layout_context = LayoutContext::new(shared_layout_context); + + // Get a real layout node. + let node: LayoutNode = unsafe { + layout_node_from_unsafe_layout_node(&unsafe_layout_node) + }; + + // Initialize layout data. + // + // FIXME(pcwalton): Stop allocating here. Ideally this should just be done by the HTML + // parser. + node.initialize_layout_data(layout_context.shared.layout_chan.clone()); + + // Get the parent node. + let opaque_node: OpaqueNode = OpaqueNodeMethods::from_layout_node(&node); + let parent_opt = if opaque_node == layout_context.shared.reflow_root { + None + } else { + node.parent_node() + }; + + // First, check to see whether we can share a style with someone. + let style_sharing_candidate_cache = layout_context.style_sharing_candidate_cache(); + let sharing_result = unsafe { + node.share_style_if_possible(style_sharing_candidate_cache, + parent_opt.clone()) + }; + + // Otherwise, match and cascade selectors. + match sharing_result { + CannotShare(mut shareable) => { + let mut applicable_declarations = ApplicableDeclarations::new(); + + if node.is_element() { + // Perform the CSS selector matching. + let stylist = unsafe { &*layout_context.shared.stylist }; + node.match_node(stylist, &mut applicable_declarations, &mut shareable); + } + + // Perform the CSS cascade. + unsafe { + node.cascade_node(parent_opt, + &applicable_declarations, + layout_context.applicable_declarations_cache()); + } + + // Add ourselves to the LRU cache. + if shareable { + style_sharing_candidate_cache.insert_if_possible(&node); + } + } + StyleWasShared(index) => style_sharing_candidate_cache.touch(index), + } + + // Prepare for flow construction by counting the node's children and storing that count. + let mut child_count = 0u; + for _ in node.children() { + child_count += 1; + } + if child_count != 0 { + let mut layout_data_ref = node.mutate_layout_data(); + match &mut *layout_data_ref { + &Some(ref mut layout_data) => { + layout_data.data.parallel.children_count.store(child_count as int, Relaxed) + } + &None => fail!("no layout data"), + } + } + + // It's *very* important that this block is in a separate scope to the block above, + // to avoid a data race that can occur (github issue #2308). The block above issues + // a borrow on the node layout data. That borrow must be dropped before the child + // nodes are actually pushed into the work queue. Otherwise, it's possible for a child + // node to get into construct_flows() and move up it's parent hierarchy, which can call + // borrow on the layout data before it is dropped from the block above. + if child_count != 0 { + // Enqueue kids. + for kid in node.children() { + proxy.push(WorkUnit { + fun: recalc_style_for_node, + data: layout_node_to_unsafe_layout_node(&kid), + }); + } + return + } + + // If we got here, we're a leaf. Start construction of flows for this node. + construct_flows(unsafe_layout_node, &layout_context) +} + +fn construct_flows(mut unsafe_layout_node: UnsafeLayoutNode, + layout_context: &LayoutContext) { + loop { + // Get a real layout node. + let node: LayoutNode = unsafe { + layout_node_from_unsafe_layout_node(&unsafe_layout_node) + }; + + // Construct flows for this node. + { + let mut flow_constructor = FlowConstructor::new(layout_context); + flow_constructor.process(&ThreadSafeLayoutNode::new(&node)); + } + + // Reset the count of children for the next traversal. + // + // FIXME(pcwalton): Use children().len() when the implementation of that is efficient. + let mut child_count = 0u; + for _ in node.children() { + child_count += 1 + } + { + let mut layout_data_ref = node.mutate_layout_data(); + match &mut *layout_data_ref { + &Some(ref mut layout_data) => { + layout_data.data.parallel.children_count.store(child_count as int, Relaxed) + } + &None => fail!("no layout data"), + } + } + + // If this is the reflow root, we're done. + let opaque_node: OpaqueNode = OpaqueNodeMethods::from_layout_node(&node); + if layout_context.shared.reflow_root == opaque_node { + break + } + + // Otherwise, enqueue the parent. + match node.parent_node() { + Some(parent) => { + + // No, we're not at the root yet. Then are we the last sibling of our parent? + // If so, we can continue on with our parent; otherwise, we've gotta wait. + unsafe { + match *parent.borrow_layout_data_unchecked() { + Some(ref parent_layout_data) => { + let parent_layout_data: &mut LayoutDataWrapper = mem::transmute(parent_layout_data); + if parent_layout_data.data + .parallel + .children_count + .fetch_sub(1, SeqCst) == 1 { + // We were the last child of our parent. Construct flows for our + // parent. + unsafe_layout_node = layout_node_to_unsafe_layout_node(&parent) + } else { + // Get out of here and find another node to work on. + break + } + } + None => fail!("no layout data for parent?!"), + } + } + } + None => fail!("no parent and weren't at reflow root?!"), + } + } +} + +fn assign_inline_sizes(unsafe_flow: UnsafeFlow, + proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>) { + let shared_layout_context = unsafe { &**proxy.user_data() }; + let layout_context = LayoutContext::new(shared_layout_context); + let mut assign_inline_sizes_traversal = AssignISizesTraversal { + layout_context: &layout_context, + }; + assign_inline_sizes_traversal.run_parallel(unsafe_flow, proxy) +} + +fn assign_block_sizes_and_store_overflow(unsafe_flow: UnsafeFlow, + proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>) { + let shared_layout_context = unsafe { &**proxy.user_data() }; + let layout_context = LayoutContext::new(shared_layout_context); + let mut assign_block_sizes_traversal = AssignBSizesAndStoreOverflowTraversal { + layout_context: &layout_context, + }; + assign_block_sizes_traversal.run_parallel(unsafe_flow, proxy) +} + +fn compute_absolute_position(unsafe_flow: UnsafeFlow, + proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>) { + let mut had_descendants = false; + unsafe { + // Get a real flow. + let flow: &mut FlowRef = mem::transmute(&unsafe_flow); + + // Compute the absolute position for the flow. + flow.get_mut().compute_absolute_position(); + + // Count the number of absolutely-positioned children, so that we can subtract it from + // from `children_and_absolute_descendant_count` to get the number of real children. + let mut absolutely_positioned_child_count = 0u; + for kid in flow::child_iter(flow.get_mut()) { + if kid.is_absolutely_positioned() { + absolutely_positioned_child_count += 1; + } + } + + // Don't enqueue absolutely positioned children. + drop(flow::mut_base(flow.get_mut()).parallel + .children_and_absolute_descendant_count + .fetch_sub(absolutely_positioned_child_count as int, + SeqCst)); + + // Possibly enqueue the children. + for kid in flow::child_iter(flow.get_mut()) { + if !kid.is_absolutely_positioned() { + had_descendants = true; + proxy.push(WorkUnit { + fun: compute_absolute_position, + data: borrowed_flow_to_unsafe_flow(kid), + }); + } + } + + // Possibly enqueue absolute descendants. + for absolute_descendant_link in flow::mut_base(flow.get_mut()).abs_descendants.iter() { + had_descendants = true; + let descendant = absolute_descendant_link; + proxy.push(WorkUnit { + fun: compute_absolute_position, + data: borrowed_flow_to_unsafe_flow(descendant), + }); + } + + // If there were no more descendants, start building the display list. + if !had_descendants { + build_display_list(mut_owned_flow_to_unsafe_flow(flow), + proxy) + } + } +} + +fn build_display_list(mut unsafe_flow: UnsafeFlow, + proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>) { + let shared_layout_context = unsafe { &**proxy.user_data() }; + let layout_context = LayoutContext::new(shared_layout_context); + + loop { + unsafe { + // Get a real flow. + let flow: &mut FlowRef = mem::transmute(&unsafe_flow); + + // Build display lists. + flow.get_mut().build_display_list(&layout_context); + + { + let base = flow::mut_base(flow.get_mut()); + + // Reset the count of children and absolute descendants for the next layout + // traversal. + let children_and_absolute_descendant_count = base.children.len() + + base.abs_descendants.len(); + base.parallel + .children_and_absolute_descendant_count + .store(children_and_absolute_descendant_count as int, Relaxed); + } + + // Possibly enqueue the parent. + let unsafe_parent = if flow.get().is_absolutely_positioned() { + match *flow::mut_base(flow.get_mut()).absolute_cb.get() { + None => fail!("no absolute containing block for absolutely positioned?!"), + Some(ref mut absolute_cb) => { + mut_borrowed_flow_to_unsafe_flow(absolute_cb.get_mut()) + } + } + } else { + flow::mut_base(flow.get_mut()).parallel.parent + }; + if unsafe_parent == null_unsafe_flow() { + // We're done! + break + } + + // No, we're not at the root yet. Then are we the last child + // of our parent to finish processing? If so, we can continue + // on with our parent; otherwise, we've gotta wait. + let parent: &mut FlowRef = mem::transmute(&unsafe_parent); + let parent_base = flow::mut_base(parent.get_mut()); + if parent_base.parallel + .children_and_absolute_descendant_count + .fetch_sub(1, SeqCst) == 1 { + // We were the last child of our parent. Build display lists for our parent. + unsafe_flow = unsafe_parent + } else { + // Stop. + break + } + } + } +} + +pub fn recalc_style_for_subtree(root_node: &LayoutNode, + shared_layout_context: &SharedLayoutContext, + queue: &mut WorkQueue<*const SharedLayoutContext,UnsafeLayoutNode>) { + queue.data = shared_layout_context as *const _; + + // Enqueue the root node. + queue.push(WorkUnit { + fun: recalc_style_for_node, + data: layout_node_to_unsafe_layout_node(root_node), + }); + + queue.run(); + + queue.data = ptr::null() +} + +pub fn traverse_flow_tree_preorder(root: &mut FlowRef, + time_profiler_chan: TimeProfilerChan, + shared_layout_context: &SharedLayoutContext, + queue: &mut WorkQueue<*const SharedLayoutContext,UnsafeFlow>) { + queue.data = shared_layout_context as *const _; + + profile(time::LayoutParallelWarmupCategory, time_profiler_chan, || { + queue.push(WorkUnit { + fun: assign_inline_sizes, + data: mut_owned_flow_to_unsafe_flow(root), + }) + }); + + queue.run(); + + queue.data = ptr::null() +} + +pub fn build_display_list_for_subtree(root: &mut FlowRef, + time_profiler_chan: TimeProfilerChan, + shared_layout_context: &SharedLayoutContext, + queue: &mut WorkQueue<*const SharedLayoutContext,UnsafeFlow>) { + queue.data = shared_layout_context as *const _; + + profile(time::LayoutParallelWarmupCategory, time_profiler_chan, || { + queue.push(WorkUnit { + fun: compute_absolute_position, + data: mut_owned_flow_to_unsafe_flow(root), + }) + }); + + queue.run(); + + queue.data = ptr::null() +} + diff --git a/components/layout/table.rs b/components/layout/table.rs new file mode 100644 index 00000000000..98569d68c95 --- /dev/null +++ b/components/layout/table.rs @@ -0,0 +1,324 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! CSS table formatting contexts. + +#![deny(unsafe_block)] + +use block::{BlockFlow, MarginsMayNotCollapse, ISizeAndMarginsComputer}; +use block::{ISizeConstraintInput, ISizeConstraintSolution}; +use construct::FlowConstructor; +use context::LayoutContext; +use floats::FloatKind; +use flow::{TableFlowClass, FlowClass, Flow, ImmutableFlowUtils}; +use fragment::Fragment; +use table_wrapper::{TableLayout, FixedLayout, AutoLayout}; +use wrapper::ThreadSafeLayoutNode; + +use servo_util::geometry::Au; +use servo_util::geometry; +use std::fmt; +use style::computed_values::table_layout; + +/// A table flow corresponded to the table's internal table fragment under a table wrapper flow. +/// The properties `position`, `float`, and `margin-*` are used on the table wrapper fragment, +/// not table fragment per CSS 2.1 § 10.5. +pub struct TableFlow { + pub block_flow: BlockFlow, + + /// Column inline-sizes + pub col_inline_sizes: Vec<Au>, + + /// Column min inline-sizes. + pub col_min_inline_sizes: Vec<Au>, + + /// Column pref inline-sizes. + pub col_pref_inline_sizes: Vec<Au>, + + /// Table-layout property + pub table_layout: TableLayout, +} + +impl TableFlow { + pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode, + fragment: Fragment) + -> TableFlow { + let mut block_flow = BlockFlow::from_node_and_fragment(node, fragment); + let table_layout = if block_flow.fragment().style().get_table().table_layout == + table_layout::fixed { + FixedLayout + } else { + AutoLayout + }; + TableFlow { + block_flow: block_flow, + col_inline_sizes: vec!(), + col_min_inline_sizes: vec!(), + col_pref_inline_sizes: vec!(), + table_layout: table_layout + } + } + + pub fn from_node(constructor: &mut FlowConstructor, + node: &ThreadSafeLayoutNode) + -> TableFlow { + let mut block_flow = BlockFlow::from_node(constructor, node); + let table_layout = if block_flow.fragment().style().get_table().table_layout == + table_layout::fixed { + FixedLayout + } else { + AutoLayout + }; + TableFlow { + block_flow: block_flow, + col_inline_sizes: vec!(), + col_min_inline_sizes: vec!(), + col_pref_inline_sizes: vec!(), + table_layout: table_layout + } + } + + pub fn float_from_node(constructor: &mut FlowConstructor, + node: &ThreadSafeLayoutNode, + float_kind: FloatKind) + -> TableFlow { + let mut block_flow = BlockFlow::float_from_node(constructor, node, float_kind); + let table_layout = if block_flow.fragment().style().get_table().table_layout == + table_layout::fixed { + FixedLayout + } else { + AutoLayout + }; + TableFlow { + block_flow: block_flow, + col_inline_sizes: vec!(), + col_min_inline_sizes: vec!(), + col_pref_inline_sizes: vec!(), + table_layout: table_layout + } + } + + /// Update the corresponding value of self_inline-sizes if a value of kid_inline-sizes has larger value + /// than one of self_inline-sizes. + pub fn update_col_inline_sizes(self_inline_sizes: &mut Vec<Au>, kid_inline_sizes: &Vec<Au>) -> Au { + let mut sum_inline_sizes = Au(0); + let mut kid_inline_sizes_it = kid_inline_sizes.iter(); + for self_inline_size in self_inline_sizes.mut_iter() { + match kid_inline_sizes_it.next() { + Some(kid_inline_size) => { + if *self_inline_size < *kid_inline_size { + *self_inline_size = *kid_inline_size; + } + }, + None => {} + } + sum_inline_sizes = sum_inline_sizes + *self_inline_size; + } + sum_inline_sizes + } + + /// Assign block-size for table flow. + /// + /// TODO(#2014, pcwalton): This probably doesn't handle margin collapse right. + /// + /// inline(always) because this is only ever called by in-order or non-in-order top-level + /// methods + #[inline(always)] + fn assign_block_size_table_base<'a>(&mut self, layout_context: &'a LayoutContext<'a>) { + self.block_flow.assign_block_size_block_base(layout_context, MarginsMayNotCollapse); + } + + pub fn build_display_list_table(&mut self, layout_context: &LayoutContext) { + debug!("build_display_list_table: same process as block flow"); + self.block_flow.build_display_list_block(layout_context); + } +} + +impl Flow for TableFlow { + fn class(&self) -> FlowClass { + TableFlowClass + } + + fn as_table<'a>(&'a mut self) -> &'a mut TableFlow { + self + } + + fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow { + &mut self.block_flow + } + + fn col_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<Au> { + &mut self.col_inline_sizes + } + + fn col_min_inline_sizes<'a>(&'a self) -> &'a Vec<Au> { + &self.col_min_inline_sizes + } + + fn col_pref_inline_sizes<'a>(&'a self) -> &'a Vec<Au> { + &self.col_pref_inline_sizes + } + + /// The specified column inline-sizes are set from column group and the first row for the fixed + /// table layout calculation. + /// The maximum min/pref inline-sizes of each column are set from the rows for the automatic + /// table layout calculation. + fn bubble_inline_sizes(&mut self, _: &LayoutContext) { + let mut min_inline_size = Au(0); + let mut pref_inline_size = Au(0); + let mut did_first_row = false; + + for kid in self.block_flow.base.child_iter() { + assert!(kid.is_proper_table_child()); + + if kid.is_table_colgroup() { + self.col_inline_sizes.push_all(kid.as_table_colgroup().inline_sizes.as_slice()); + self.col_min_inline_sizes = self.col_inline_sizes.clone(); + self.col_pref_inline_sizes = self.col_inline_sizes.clone(); + } else if kid.is_table_rowgroup() || kid.is_table_row() { + // read column inline-sizes from table-row-group/table-row, and assign + // inline-size=0 for the columns not defined in column-group + // FIXME: need to read inline-sizes from either table-header-group OR + // first table-row + match self.table_layout { + FixedLayout => { + let kid_col_inline_sizes = kid.col_inline_sizes(); + if !did_first_row { + did_first_row = true; + let mut child_inline_sizes = kid_col_inline_sizes.iter(); + for col_inline_size in self.col_inline_sizes.mut_iter() { + match child_inline_sizes.next() { + Some(child_inline_size) => { + if *col_inline_size == Au::new(0) { + *col_inline_size = *child_inline_size; + } + }, + None => break + } + } + } + let num_child_cols = kid_col_inline_sizes.len(); + let num_cols = self.col_inline_sizes.len(); + debug!("table until the previous row has {} column(s) and this row has {} column(s)", + num_cols, num_child_cols); + for i in range(num_cols, num_child_cols) { + self.col_inline_sizes.push((*kid_col_inline_sizes)[i]); + } + }, + AutoLayout => { + min_inline_size = TableFlow::update_col_inline_sizes(&mut self.col_min_inline_sizes, kid.col_min_inline_sizes()); + pref_inline_size = TableFlow::update_col_inline_sizes(&mut self.col_pref_inline_sizes, kid.col_pref_inline_sizes()); + + // update the number of column inline-sizes from table-rows. + let num_cols = self.col_min_inline_sizes.len(); + let num_child_cols = kid.col_min_inline_sizes().len(); + debug!("table until the previous row has {} column(s) and this row has {} column(s)", + num_cols, num_child_cols); + for i in range(num_cols, num_child_cols) { + self.col_inline_sizes.push(Au::new(0)); + let new_kid_min = kid.col_min_inline_sizes()[i]; + self.col_min_inline_sizes.push( new_kid_min ); + let new_kid_pref = kid.col_pref_inline_sizes()[i]; + self.col_pref_inline_sizes.push( new_kid_pref ); + min_inline_size = min_inline_size + new_kid_min; + pref_inline_size = pref_inline_size + new_kid_pref; + } + } + } + } + } + self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size = min_inline_size; + self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size = + geometry::max(min_inline_size, pref_inline_size); + } + + /// 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. + fn assign_inline_sizes(&mut self, ctx: &LayoutContext) { + debug!("assign_inline_sizes({}): assigning inline_size for flow", "table"); + + // The position was set to the containing block by the flow's parent. + let containing_block_inline_size = self.block_flow.base.position.size.inline; + + let mut num_unspecified_inline_sizes = 0; + let mut total_column_inline_size = Au::new(0); + for col_inline_size in self.col_inline_sizes.iter() { + if *col_inline_size == Au::new(0) { + num_unspecified_inline_sizes += 1; + } else { + total_column_inline_size = total_column_inline_size.add(col_inline_size); + } + } + + let inline_size_computer = InternalTable; + inline_size_computer.compute_used_inline_size(&mut self.block_flow, ctx, containing_block_inline_size); + + let inline_start_content_edge = self.block_flow.fragment.border_padding.inline_start; + 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; + + match self.table_layout { + FixedLayout => { + // In fixed table layout, we distribute extra space among the unspecified columns if there are + // any, or among all the columns if all are specified. + if (total_column_inline_size < content_inline_size) && (num_unspecified_inline_sizes == 0) { + let ratio = content_inline_size.to_f64().unwrap() / total_column_inline_size.to_f64().unwrap(); + for col_inline_size in self.col_inline_sizes.mut_iter() { + *col_inline_size = (*col_inline_size).scale_by(ratio); + } + } else if num_unspecified_inline_sizes != 0 { + let extra_column_inline_size = (content_inline_size - total_column_inline_size) / Au::new(num_unspecified_inline_sizes); + for col_inline_size in self.col_inline_sizes.mut_iter() { + if *col_inline_size == Au(0) { + *col_inline_size = extra_column_inline_size; + } + } + } + } + _ => {} + } + + self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge, content_inline_size, Some(self.col_inline_sizes.clone())); + } + + fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) { + debug!("assign_block_size: assigning block_size for table"); + self.assign_block_size_table_base(ctx); + } + + fn compute_absolute_position(&mut self) { + self.block_flow.compute_absolute_position() + } +} + +impl fmt::Show for TableFlow { + /// Outputs a debugging string describing this table flow. + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "TableFlow: {}", self.block_flow) + } +} + +/// Table, TableRowGroup, TableRow, TableCell types. +/// Their inline-sizes are calculated in the same way and do not have margins. +pub struct InternalTable; + +impl ISizeAndMarginsComputer for InternalTable { + /// Compute the used value of inline-size, taking care of min-inline-size and max-inline-size. + /// + /// CSS Section 10.4: Minimum and Maximum inline-sizes + fn compute_used_inline_size(&self, + block: &mut BlockFlow, + ctx: &LayoutContext, + parent_flow_inline_size: Au) { + let input = self.compute_inline_size_constraint_inputs(block, parent_flow_inline_size, ctx); + let solution = self.solve_inline_size_constraints(block, &input); + self.set_inline_size_constraint_solutions(block, solution); + } + + /// Solve the inline-size and margins constraints for this block flow. + fn solve_inline_size_constraints(&self, _: &mut BlockFlow, input: &ISizeConstraintInput) + -> ISizeConstraintSolution { + ISizeConstraintSolution::new(input.available_inline_size, Au::new(0), Au::new(0)) + } +} diff --git a/components/layout/table_caption.rs b/components/layout/table_caption.rs new file mode 100644 index 00000000000..8c1dba3e7ca --- /dev/null +++ b/components/layout/table_caption.rs @@ -0,0 +1,73 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! CSS table formatting contexts. + +#![deny(unsafe_block)] + +use block::BlockFlow; +use construct::FlowConstructor; +use context::LayoutContext; +use flow::{TableCaptionFlowClass, FlowClass, Flow}; +use wrapper::ThreadSafeLayoutNode; + +use std::fmt; + +/// A table formatting context. +pub struct TableCaptionFlow { + pub block_flow: BlockFlow, +} + +impl TableCaptionFlow { + pub fn from_node(constructor: &mut FlowConstructor, + node: &ThreadSafeLayoutNode) + -> TableCaptionFlow { + TableCaptionFlow { + block_flow: BlockFlow::from_node(constructor, node) + } + } + + pub fn build_display_list_table_caption(&mut self, layout_context: &LayoutContext) { + debug!("build_display_list_table_caption: same process as block flow"); + self.block_flow.build_display_list_block(layout_context) + } +} + +impl Flow for TableCaptionFlow { + fn class(&self) -> FlowClass { + TableCaptionFlowClass + } + + fn as_table_caption<'a>(&'a mut self) -> &'a mut TableCaptionFlow { + self + } + + fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow { + &mut self.block_flow + } + + fn bubble_inline_sizes(&mut self, ctx: &LayoutContext) { + self.block_flow.bubble_inline_sizes(ctx); + } + + fn assign_inline_sizes(&mut self, ctx: &LayoutContext) { + debug!("assign_inline_sizes({}): assigning inline_size for flow", "table_caption"); + self.block_flow.assign_inline_sizes(ctx); + } + + fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) { + debug!("assign_block_size: assigning block_size for table_caption"); + self.block_flow.assign_block_size(ctx); + } + + fn compute_absolute_position(&mut self) { + self.block_flow.compute_absolute_position() + } +} + +impl fmt::Show for TableCaptionFlow { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "TableCaptionFlow: {}", self.block_flow) + } +} diff --git a/components/layout/table_cell.rs b/components/layout/table_cell.rs new file mode 100644 index 00000000000..0011cd29fbc --- /dev/null +++ b/components/layout/table_cell.rs @@ -0,0 +1,121 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! CSS table formatting contexts. + +#![deny(unsafe_block)] + +use block::{BlockFlow, MarginsMayNotCollapse, ISizeAndMarginsComputer}; +use context::LayoutContext; +use flow::{TableCellFlowClass, FlowClass, Flow}; +use fragment::Fragment; +use model::{MaybeAuto}; +use table::InternalTable; +use wrapper::ThreadSafeLayoutNode; + +use servo_util::geometry::Au; +use std::fmt; + +/// A table formatting context. +pub struct TableCellFlow { + /// Data common to all flows. + pub block_flow: BlockFlow, +} + +impl TableCellFlow { + pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode, fragment: Fragment) -> TableCellFlow { + TableCellFlow { + block_flow: BlockFlow::from_node_and_fragment(node, fragment) + } + } + + pub fn fragment<'a>(&'a mut self) -> &'a Fragment { + &self.block_flow.fragment + } + + pub fn mut_fragment<'a>(&'a mut self) -> &'a mut Fragment { + &mut self.block_flow.fragment + } + + /// Assign block-size for table-cell flow. + /// + /// TODO(#2015, pcwalton): This doesn't handle floats right. + /// + /// inline(always) because this is only ever called by in-order or non-in-order top-level + /// 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(layout_context, MarginsMayNotCollapse) + } + + pub fn build_display_list_table_cell(&mut self, layout_context: &LayoutContext) { + debug!("build_display_list_table: same process as block flow"); + self.block_flow.build_display_list_block(layout_context) + } +} + +impl Flow for TableCellFlow { + fn class(&self) -> FlowClass { + TableCellFlowClass + } + + fn as_table_cell<'a>(&'a mut self) -> &'a mut TableCellFlow { + self + } + + fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow { + &mut self.block_flow + } + + /// Minimum/preferred inline-sizes set by this function are used in automatic table layout calculation. + fn bubble_inline_sizes(&mut self, ctx: &LayoutContext) { + self.block_flow.bubble_inline_sizes(ctx); + let specified_inline_size = MaybeAuto::from_style(self.block_flow.fragment.style().content_inline_size(), + Au::new(0)).specified_or_zero(); + if self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size < specified_inline_size { + self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size = specified_inline_size; + } + if self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size < + self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size { + self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size = + self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size; + } + } + + /// 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 table row. + fn assign_inline_sizes(&mut self, ctx: &LayoutContext) { + debug!("assign_inline_sizes({}): assigning inline_size for flow", "table_cell"); + + // The position was set to the column inline-size by the parent flow, table row flow. + let containing_block_inline_size = self.block_flow.base.position.size.inline; + + let inline_size_computer = InternalTable; + inline_size_computer.compute_used_inline_size(&mut self.block_flow, ctx, containing_block_inline_size); + + let inline_start_content_edge = self.block_flow.fragment.border_box.start.i + + self.block_flow.fragment.border_padding.inline_start; + 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; + + self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge, + content_inline_size, + None); + } + + fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) { + debug!("assign_block_size: assigning block_size for table_cell"); + self.assign_block_size_table_cell_base(ctx); + } + + fn compute_absolute_position(&mut self) { + self.block_flow.compute_absolute_position() + } +} + +impl fmt::Show for TableCellFlow { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "TableCellFlow: {}", self.block_flow) + } +} diff --git a/components/layout/table_colgroup.rs b/components/layout/table_colgroup.rs new file mode 100644 index 00000000000..270f55970b2 --- /dev/null +++ b/components/layout/table_colgroup.rs @@ -0,0 +1,88 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! CSS table formatting contexts. + +#![deny(unsafe_block)] + +use context::LayoutContext; +use flow::{BaseFlow, TableColGroupFlowClass, FlowClass, Flow}; +use fragment::{Fragment, TableColumnFragment}; +use model::{MaybeAuto}; +use wrapper::ThreadSafeLayoutNode; + +use servo_util::geometry::Au; +use std::fmt; + +/// A table formatting context. +pub struct TableColGroupFlow { + /// Data common to all flows. + pub base: BaseFlow, + + /// The associated fragment. + pub fragment: Option<Fragment>, + + /// The table column fragments + pub cols: Vec<Fragment>, + + /// The specified inline-sizes of table columns + pub inline_sizes: Vec<Au>, +} + +impl TableColGroupFlow { + pub fn from_node_and_fragments(node: &ThreadSafeLayoutNode, + fragment: Fragment, + fragments: Vec<Fragment>) -> TableColGroupFlow { + TableColGroupFlow { + base: BaseFlow::new((*node).clone()), + fragment: Some(fragment), + cols: fragments, + inline_sizes: vec!(), + } + } +} + +impl Flow for TableColGroupFlow { + fn class(&self) -> FlowClass { + TableColGroupFlowClass + } + + fn as_table_colgroup<'a>(&'a mut self) -> &'a mut TableColGroupFlow { + self + } + + fn bubble_inline_sizes(&mut self, _: &LayoutContext) { + for fragment in self.cols.iter() { + // get the specified value from inline-size property + let inline_size = MaybeAuto::from_style(fragment.style().content_inline_size(), + Au::new(0)).specified_or_zero(); + + let span: int = match fragment.specific { + TableColumnFragment(col_fragment) => col_fragment.span.unwrap_or(1), + _ => fail!("Other fragment come out in TableColGroupFlow. {:?}", fragment.specific) + }; + for _ in range(0, span) { + self.inline_sizes.push(inline_size); + } + } + } + + /// Table column inline-sizes are assigned in table flow and propagated to table row or rowgroup flow. + /// Therefore, table colgroup flow does not need to assign its inline-size. + fn assign_inline_sizes(&mut self, _ctx: &LayoutContext) { + } + + /// Table column do not have block-size. + fn assign_block_size(&mut self, _ctx: &LayoutContext) { + } +} + +impl fmt::Show for TableColGroupFlow { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.fragment { + Some(ref rb) => write!(f, "TableColGroupFlow: {}", rb), + None => write!(f, "TableColGroupFlow"), + } + } +} diff --git a/components/layout/table_row.rs b/components/layout/table_row.rs new file mode 100644 index 00000000000..101f00eb5cc --- /dev/null +++ b/components/layout/table_row.rs @@ -0,0 +1,225 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! CSS table formatting contexts. + +#![deny(unsafe_block)] + +use block::BlockFlow; +use block::ISizeAndMarginsComputer; +use construct::FlowConstructor; +use context::LayoutContext; +use flow::{TableRowFlowClass, FlowClass, Flow, ImmutableFlowUtils}; +use flow; +use fragment::Fragment; +use table::InternalTable; +use model::{MaybeAuto, Specified, Auto}; +use wrapper::ThreadSafeLayoutNode; + +use servo_util::geometry::Au; +use servo_util::geometry; +use std::fmt; + +/// A table formatting context. +pub struct TableRowFlow { + pub block_flow: BlockFlow, + + /// Column inline-sizes. + pub col_inline_sizes: Vec<Au>, + + /// Column min inline-sizes. + pub col_min_inline_sizes: Vec<Au>, + + /// Column pref inline-sizes. + pub col_pref_inline_sizes: Vec<Au>, +} + +impl TableRowFlow { + pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode, + fragment: Fragment) + -> TableRowFlow { + TableRowFlow { + block_flow: BlockFlow::from_node_and_fragment(node, fragment), + col_inline_sizes: vec!(), + col_min_inline_sizes: vec!(), + col_pref_inline_sizes: vec!(), + } + } + + pub fn from_node(constructor: &mut FlowConstructor, + node: &ThreadSafeLayoutNode) + -> TableRowFlow { + TableRowFlow { + block_flow: BlockFlow::from_node(constructor, node), + col_inline_sizes: vec!(), + col_min_inline_sizes: vec!(), + col_pref_inline_sizes: vec!(), + } + } + + pub fn fragment<'a>(&'a mut self) -> &'a Fragment { + &self.block_flow.fragment + } + + fn initialize_offsets(&mut self) -> (Au, Au, Au) { + // TODO: If border-collapse: collapse, block-start_offset, block-end_offset, and inline-start_offset + // should be updated. Currently, they are set as Au(0). + (Au(0), Au(0), Au(0)) + } + + /// Assign block-size for table-row flow. + /// + /// TODO(pcwalton): This doesn't handle floats and positioned elements right. + /// + /// inline(always) because this is only ever called by in-order or non-in-order top-level + /// methods + #[inline(always)] + fn assign_block_size_table_row_base<'a>(&mut self, layout_context: &'a LayoutContext<'a>) { + let (block_start_offset, _, _) = self.initialize_offsets(); + + let /* mut */ cur_y = block_start_offset; + + // Per CSS 2.1 § 17.5.3, find max_y = max( computed `block-size`, minimum block-size of all cells ) + let mut max_y = Au::new(0); + for kid in self.block_flow.base.child_iter() { + kid.assign_block_size_for_inorder_child_if_necessary(layout_context); + + { + let child_fragment = kid.as_table_cell().fragment(); + // TODO: Percentage block-size + let child_specified_block_size = MaybeAuto::from_style(child_fragment.style().content_block_size(), + Au::new(0)).specified_or_zero(); + max_y = + geometry::max(max_y, + child_specified_block_size + child_fragment.border_padding.block_start_end()); + } + let child_node = flow::mut_base(kid); + child_node.position.start.b = cur_y; + max_y = geometry::max(max_y, child_node.position.size.block); + } + + let mut block_size = max_y; + // TODO: Percentage block-size + block_size = match MaybeAuto::from_style(self.block_flow.fragment.style().content_block_size(), Au(0)) { + Auto => block_size, + Specified(value) => geometry::max(value, block_size) + }; + // cur_y = cur_y + block-size; + + // Assign the block-size of own fragment + // + // FIXME(pcwalton): Take `cur_y` into account. + let mut position = self.block_flow.fragment.border_box; + position.size.block = block_size; + self.block_flow.fragment.border_box = position; + self.block_flow.base.position.size.block = block_size; + + // Assign the block-size of kid fragments, which is the same value as own block-size. + for kid in self.block_flow.base.child_iter() { + { + let kid_fragment = kid.as_table_cell().mut_fragment(); + let mut position = kid_fragment.border_box; + position.size.block = block_size; + kid_fragment.border_box = position; + } + let child_node = flow::mut_base(kid); + child_node.position.size.block = block_size; + } + } + + pub fn build_display_list_table_row(&mut self, layout_context: &LayoutContext) { + debug!("build_display_list_table_row: same process as block flow"); + self.block_flow.build_display_list_block(layout_context) + } +} + +impl Flow for TableRowFlow { + fn class(&self) -> FlowClass { + TableRowFlowClass + } + + fn as_table_row<'a>(&'a mut self) -> &'a mut TableRowFlow { + self + } + + fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow { + &mut self.block_flow + } + + fn col_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<Au> { + &mut self.col_inline_sizes + } + + fn col_min_inline_sizes<'a>(&'a self) -> &'a Vec<Au> { + &self.col_min_inline_sizes + } + + fn col_pref_inline_sizes<'a>(&'a self) -> &'a Vec<Au> { + &self.col_pref_inline_sizes + } + + /// Recursively (bottom-up) determines the context's preferred and minimum inline-sizes. When called + /// on this context, all child contexts have had their min/pref inline-sizes set. This function must + /// decide min/pref inline-sizes based on child context inline-sizes and dimensions of any fragments it is + /// responsible for flowing. + /// Min/pref inline-sizes set by this function are used in automatic table layout calculation. + /// The specified column inline-sizes of children cells are used in fixed table layout calculation. + fn bubble_inline_sizes(&mut self, _: &LayoutContext) { + let mut min_inline_size = Au(0); + let mut pref_inline_size = Au(0); + /* find the specified inline_sizes from child table-cell contexts */ + for kid in self.block_flow.base.child_iter() { + assert!(kid.is_table_cell()); + + // collect the specified column inline-sizes of cells. These are used in fixed table layout calculation. + { + let child_fragment = kid.as_table_cell().fragment(); + let child_specified_inline_size = MaybeAuto::from_style(child_fragment.style().content_inline_size(), + Au::new(0)).specified_or_zero(); + self.col_inline_sizes.push(child_specified_inline_size); + } + + // collect min_inline-size & pref_inline-size of children cells for automatic table layout calculation. + let child_base = flow::mut_base(kid); + self.col_min_inline_sizes.push(child_base.intrinsic_inline_sizes.minimum_inline_size); + self.col_pref_inline_sizes.push(child_base.intrinsic_inline_sizes.preferred_inline_size); + min_inline_size = min_inline_size + child_base.intrinsic_inline_sizes.minimum_inline_size; + pref_inline_size = pref_inline_size + child_base.intrinsic_inline_sizes.preferred_inline_size; + } + self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size = min_inline_size; + self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size = geometry::max(min_inline_size, + pref_inline_size); + } + + /// 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. + fn assign_inline_sizes(&mut self, ctx: &LayoutContext) { + debug!("assign_inline_sizes({}): assigning inline_size for flow", "table_row"); + + // The position was set to the containing block by the flow's parent. + let containing_block_inline_size = self.block_flow.base.position.size.inline; + // FIXME: In case of border-collapse: collapse, inline-start_content_edge should be border-inline-start + let inline_start_content_edge = Au::new(0); + + let inline_size_computer = InternalTable; + inline_size_computer.compute_used_inline_size(&mut self.block_flow, ctx, containing_block_inline_size); + + self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge, Au(0), Some(self.col_inline_sizes.clone())); + } + + fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) { + debug!("assign_block_size: assigning block_size for table_row"); + self.assign_block_size_table_row_base(ctx); + } + + fn compute_absolute_position(&mut self) { + self.block_flow.compute_absolute_position() + } +} + +impl fmt::Show for TableRowFlow { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "TableRowFlow: {}", self.block_flow.fragment) + } +} diff --git a/components/layout/table_rowgroup.rs b/components/layout/table_rowgroup.rs new file mode 100644 index 00000000000..48f9d376af3 --- /dev/null +++ b/components/layout/table_rowgroup.rs @@ -0,0 +1,208 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! CSS table formatting contexts. + +#![deny(unsafe_block)] + +use block::BlockFlow; +use block::ISizeAndMarginsComputer; +use construct::FlowConstructor; +use context::LayoutContext; +use flow::{TableRowGroupFlowClass, FlowClass, Flow, ImmutableFlowUtils}; +use flow; +use fragment::Fragment; +use table::{InternalTable, TableFlow}; +use wrapper::ThreadSafeLayoutNode; + +use servo_util::geometry::Au; +use servo_util::geometry; +use std::fmt; + +/// A table formatting context. +pub struct TableRowGroupFlow { + pub block_flow: BlockFlow, + + /// Column inline-sizes + pub col_inline_sizes: Vec<Au>, + + /// Column min inline-sizes. + pub col_min_inline_sizes: Vec<Au>, + + /// Column pref inline-sizes. + pub col_pref_inline_sizes: Vec<Au>, +} + +impl TableRowGroupFlow { + pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode, + fragment: Fragment) + -> TableRowGroupFlow { + TableRowGroupFlow { + block_flow: BlockFlow::from_node_and_fragment(node, fragment), + col_inline_sizes: vec!(), + col_min_inline_sizes: vec!(), + col_pref_inline_sizes: vec!(), + } + } + + pub fn from_node(constructor: &mut FlowConstructor, + node: &ThreadSafeLayoutNode) + -> TableRowGroupFlow { + TableRowGroupFlow { + block_flow: BlockFlow::from_node(constructor, node), + col_inline_sizes: vec!(), + col_min_inline_sizes: vec!(), + col_pref_inline_sizes: vec!(), + } + } + + pub fn fragment<'a>(&'a mut self) -> &'a Fragment { + &self.block_flow.fragment + } + + fn initialize_offsets(&mut self) -> (Au, Au, Au) { + // TODO: If border-collapse: collapse, block-start_offset, block-end_offset, and inline-start_offset + // should be updated. Currently, they are set as Au(0). + (Au(0), Au(0), Au(0)) + } + + /// Assign block-size for table-rowgroup flow. + /// + /// FIXME(pcwalton): This doesn't handle floats right. + /// + /// inline(always) because this is only ever called by in-order or non-in-order top-level + /// methods + #[inline(always)] + fn assign_block_size_table_rowgroup_base<'a>(&mut self, layout_context: &'a LayoutContext<'a>) { + let (block_start_offset, _, _) = self.initialize_offsets(); + + let mut cur_y = block_start_offset; + + for kid in self.block_flow.base.child_iter() { + kid.assign_block_size_for_inorder_child_if_necessary(layout_context); + + let child_node = flow::mut_base(kid); + child_node.position.start.b = cur_y; + cur_y = cur_y + child_node.position.size.block; + } + + let block_size = cur_y - block_start_offset; + + let mut position = self.block_flow.fragment.border_box; + position.size.block = block_size; + self.block_flow.fragment.border_box = position; + self.block_flow.base.position.size.block = block_size; + } + + pub fn build_display_list_table_rowgroup(&mut self, layout_context: &LayoutContext) { + debug!("build_display_list_table_rowgroup: same process as block flow"); + self.block_flow.build_display_list_block(layout_context) + } +} + +impl Flow for TableRowGroupFlow { + fn class(&self) -> FlowClass { + TableRowGroupFlowClass + } + + fn as_table_rowgroup<'a>(&'a mut self) -> &'a mut TableRowGroupFlow { + self + } + + fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow { + &mut self.block_flow + } + + fn col_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<Au> { + &mut self.col_inline_sizes + } + + fn col_min_inline_sizes<'a>(&'a self) -> &'a Vec<Au> { + &self.col_min_inline_sizes + } + + fn col_pref_inline_sizes<'a>(&'a self) -> &'a Vec<Au> { + &self.col_pref_inline_sizes + } + + /// Recursively (bottom-up) determines the context's preferred and minimum inline-sizes. When called + /// on this context, all child contexts have had their min/pref inline-sizes set. This function must + /// decide min/pref inline-sizes based on child context inline-sizes and dimensions of any fragments it is + /// responsible for flowing. + /// Min/pref inline-sizes set by this function are used in automatic table layout calculation. + /// Also, this function finds the specified column inline-sizes from the first row. + /// Those are used in fixed table layout calculation + fn bubble_inline_sizes(&mut self, _: &LayoutContext) { + let mut min_inline_size = Au(0); + let mut pref_inline_size = Au(0); + + for kid in self.block_flow.base.child_iter() { + assert!(kid.is_table_row()); + + // calculate min_inline-size & pref_inline-size for automatic table layout calculation + // 'self.col_min_inline-sizes' collects the maximum value of cells' min-inline-sizes for each column. + // 'self.col_pref_inline-sizes' collects the maximum value of cells' pref-inline-sizes for each column. + if self.col_inline_sizes.is_empty() { // First Row + assert!(self.col_min_inline_sizes.is_empty() && self.col_pref_inline_sizes.is_empty()); + // 'self.col_inline-sizes' collects the specified column inline-sizes from the first table-row for fixed table layout calculation. + self.col_inline_sizes = kid.col_inline_sizes().clone(); + self.col_min_inline_sizes = kid.col_min_inline_sizes().clone(); + self.col_pref_inline_sizes = kid.col_pref_inline_sizes().clone(); + } else { + min_inline_size = TableFlow::update_col_inline_sizes(&mut self.col_min_inline_sizes, kid.col_min_inline_sizes()); + pref_inline_size = TableFlow::update_col_inline_sizes(&mut self.col_pref_inline_sizes, kid.col_pref_inline_sizes()); + + // update the number of column inline-sizes from table-rows. + let num_cols = self.col_inline_sizes.len(); + let num_child_cols = kid.col_min_inline_sizes().len(); + for i in range(num_cols, num_child_cols) { + self.col_inline_sizes.push(Au::new(0)); + let new_kid_min = kid.col_min_inline_sizes()[i]; + self.col_min_inline_sizes.push(kid.col_min_inline_sizes()[i]); + let new_kid_pref = kid.col_pref_inline_sizes()[i]; + self.col_pref_inline_sizes.push(kid.col_pref_inline_sizes()[i]); + min_inline_size = min_inline_size + new_kid_min; + pref_inline_size = pref_inline_size + new_kid_pref; + } + } + } + + self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size = min_inline_size; + self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size = geometry::max(min_inline_size, + pref_inline_size); + } + + /// 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. + fn assign_inline_sizes(&mut self, ctx: &LayoutContext) { + debug!("assign_inline_sizes({}): assigning inline_size for flow", "table_rowgroup"); + + // The position was set to the containing block by the flow's parent. + let containing_block_inline_size = self.block_flow.base.position.size.inline; + // FIXME: In case of border-collapse: collapse, inline-start_content_edge should be + // the border width on the inline-start side. + let inline_start_content_edge = Au::new(0); + let content_inline_size = containing_block_inline_size; + + let inline_size_computer = InternalTable; + inline_size_computer.compute_used_inline_size(&mut self.block_flow, ctx, containing_block_inline_size); + + self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge, content_inline_size, Some(self.col_inline_sizes.clone())); + } + + fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) { + debug!("assign_block_size: assigning block_size for table_rowgroup"); + self.assign_block_size_table_rowgroup_base(ctx); + } + + fn compute_absolute_position(&mut self) { + self.block_flow.compute_absolute_position() + } +} + +impl fmt::Show for TableRowGroupFlow { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "TableRowGroupFlow: {}", self.block_flow.fragment) + } +} diff --git a/components/layout/table_wrapper.rs b/components/layout/table_wrapper.rs new file mode 100644 index 00000000000..2084ef52bdc --- /dev/null +++ b/components/layout/table_wrapper.rs @@ -0,0 +1,325 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! CSS table formatting contexts. + +#![deny(unsafe_block)] + +use block::{BlockFlow, MarginsMayNotCollapse, ISizeAndMarginsComputer}; +use block::{ISizeConstraintInput, ISizeConstraintSolution}; +use construct::FlowConstructor; +use context::LayoutContext; +use floats::FloatKind; +use flow::{TableWrapperFlowClass, FlowClass, Flow, ImmutableFlowUtils}; +use fragment::Fragment; +use model::{Specified, Auto, specified}; +use wrapper::ThreadSafeLayoutNode; + +use servo_util::geometry::Au; +use servo_util::geometry; +use std::fmt; +use style::computed_values::table_layout; + +pub enum TableLayout { + FixedLayout, + AutoLayout +} + +/// A table wrapper flow based on a block formatting context. +pub struct TableWrapperFlow { + pub block_flow: BlockFlow, + + /// Column inline-sizes + pub col_inline_sizes: Vec<Au>, + + /// Table-layout property + pub table_layout: TableLayout, +} + +impl TableWrapperFlow { + pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode, + fragment: Fragment) + -> TableWrapperFlow { + let mut block_flow = BlockFlow::from_node_and_fragment(node, fragment); + let table_layout = if block_flow.fragment().style().get_table().table_layout == + table_layout::fixed { + FixedLayout + } else { + AutoLayout + }; + TableWrapperFlow { + block_flow: block_flow, + col_inline_sizes: vec!(), + table_layout: table_layout + } + } + + pub fn from_node(constructor: &mut FlowConstructor, + node: &ThreadSafeLayoutNode) + -> TableWrapperFlow { + let mut block_flow = BlockFlow::from_node(constructor, node); + let table_layout = if block_flow.fragment().style().get_table().table_layout == + table_layout::fixed { + FixedLayout + } else { + AutoLayout + }; + TableWrapperFlow { + block_flow: block_flow, + col_inline_sizes: vec!(), + table_layout: table_layout + } + } + + pub fn float_from_node(constructor: &mut FlowConstructor, + node: &ThreadSafeLayoutNode, + float_kind: FloatKind) + -> TableWrapperFlow { + let mut block_flow = BlockFlow::float_from_node(constructor, node, float_kind); + let table_layout = if block_flow.fragment().style().get_table().table_layout == + table_layout::fixed { + FixedLayout + } else { + AutoLayout + }; + TableWrapperFlow { + block_flow: block_flow, + col_inline_sizes: vec!(), + table_layout: table_layout + } + } + + pub fn is_float(&self) -> bool { + self.block_flow.float.is_some() + } + + /// Assign block-size for table-wrapper flow. + /// `Assign block-size` of table-wrapper flow follows a similar process to that of block flow. + /// + /// inline(always) because this is only ever called by in-order or non-in-order top-level + /// methods + #[inline(always)] + fn assign_block_size_table_wrapper_base<'a>(&mut self, layout_context: &'a LayoutContext<'a>) { + self.block_flow.assign_block_size_block_base(layout_context, MarginsMayNotCollapse); + } + + pub fn build_display_list_table_wrapper(&mut self, layout_context: &LayoutContext) { + debug!("build_display_list_table_wrapper: same process as block flow"); + self.block_flow.build_display_list_block(layout_context); + } +} + +impl Flow for TableWrapperFlow { + fn class(&self) -> FlowClass { + TableWrapperFlowClass + } + + fn as_table_wrapper<'a>(&'a mut self) -> &'a mut TableWrapperFlow { + self + } + + fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow { + &mut self.block_flow + } + + /* Recursively (bottom-up) determine the context's preferred and + minimum inline_sizes. When called on this context, all child contexts + have had their min/pref inline_sizes set. This function must decide + min/pref inline_sizes based on child context inline_sizes and dimensions of + any fragments it is responsible for flowing. */ + + fn bubble_inline_sizes(&mut self, ctx: &LayoutContext) { + // get column inline-sizes info from table flow + for kid in self.block_flow.base.child_iter() { + assert!(kid.is_table_caption() || kid.is_table()); + + if kid.is_table() { + self.col_inline_sizes.push_all(kid.as_table().col_inline_sizes.as_slice()); + } + } + + self.block_flow.bubble_inline_sizes(ctx); + } + + /// 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, ctx: &LayoutContext) { + debug!("assign_inline_sizes({}): assigning inline_size for flow", + if self.is_float() { + "floated table_wrapper" + } else { + "table_wrapper" + }); + + // The position was set to the containing block by the flow's parent. + let containing_block_inline_size = self.block_flow.base.position.size.inline; + + let inline_size_computer = TableWrapper; + inline_size_computer.compute_used_inline_size_table_wrapper(self, ctx, containing_block_inline_size); + + let inline_start_content_edge = self.block_flow.fragment.border_box.start.i; + let content_inline_size = self.block_flow.fragment.border_box.size.inline; + + match self.table_layout { + FixedLayout | _ if self.is_float() => + self.block_flow.base.position.size.inline = content_inline_size, + _ => {} + } + + // In case of fixed layout, column inline-sizes are calculated in table flow. + let assigned_col_inline_sizes = match self.table_layout { + FixedLayout => None, + AutoLayout => Some(self.col_inline_sizes.clone()) + }; + self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge, content_inline_size, assigned_col_inline_sizes); + } + + fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) { + if self.is_float() { + debug!("assign_block_size_float: assigning block_size for floated table_wrapper"); + self.block_flow.assign_block_size_float(ctx); + } else { + debug!("assign_block_size: assigning block_size for table_wrapper"); + self.assign_block_size_table_wrapper_base(ctx); + } + } + + fn compute_absolute_position(&mut self) { + self.block_flow.compute_absolute_position() + } +} + +impl fmt::Show for TableWrapperFlow { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.is_float() { + write!(f, "TableWrapperFlow(Float): {}", self.block_flow.fragment) + } else { + write!(f, "TableWrapperFlow: {}", self.block_flow.fragment) + } + } +} + +struct TableWrapper; + +impl TableWrapper { + fn compute_used_inline_size_table_wrapper(&self, + table_wrapper: &mut TableWrapperFlow, + ctx: &LayoutContext, + parent_flow_inline_size: Au) { + let input = self.compute_inline_size_constraint_inputs_table_wrapper(table_wrapper, + parent_flow_inline_size, + ctx); + + let solution = self.solve_inline_size_constraints(&mut table_wrapper.block_flow, &input); + + self.set_inline_size_constraint_solutions(&mut table_wrapper.block_flow, solution); + self.set_flow_x_coord_if_necessary(&mut table_wrapper.block_flow, solution); + } + + fn compute_inline_size_constraint_inputs_table_wrapper(&self, + table_wrapper: &mut TableWrapperFlow, + parent_flow_inline_size: Au, + ctx: &LayoutContext) + -> ISizeConstraintInput { + let mut input = self.compute_inline_size_constraint_inputs(&mut table_wrapper.block_flow, + parent_flow_inline_size, + ctx); + let computed_inline_size = match table_wrapper.table_layout { + FixedLayout => { + let fixed_cells_inline_size = table_wrapper.col_inline_sizes.iter().fold(Au(0), + |sum, inline_size| sum.add(inline_size)); + + let mut computed_inline_size = input.computed_inline_size.specified_or_zero(); + let style = table_wrapper.block_flow.fragment.style(); + + // Get inline-start and inline-end paddings, borders for table. + // We get these values from the fragment's style since table_wrapper doesn't have it's own border or padding. + // input.available_inline-size is same as containing_block_inline-size in table_wrapper. + let padding = style.logical_padding(); + let border = style.logical_border_width(); + let padding_and_borders = + specified(padding.inline_start, input.available_inline_size) + + specified(padding.inline_end, input.available_inline_size) + + border.inline_start + + border.inline_end; + // Compare border-edge inline-sizes. Because fixed_cells_inline-size indicates content-inline-size, + // padding and border values are added to fixed_cells_inline-size. + computed_inline_size = geometry::max( + fixed_cells_inline_size + padding_and_borders, computed_inline_size); + computed_inline_size + }, + AutoLayout => { + // Automatic table layout is calculated according to CSS 2.1 § 17.5.2.2. + let mut cap_min = Au(0); + let mut cols_min = Au(0); + let mut cols_max = Au(0); + let mut col_min_inline_sizes = &vec!(); + let mut col_pref_inline_sizes = &vec!(); + for kid in table_wrapper.block_flow.base.child_iter() { + if kid.is_table_caption() { + cap_min = kid.as_block().base.intrinsic_inline_sizes.minimum_inline_size; + } else { + assert!(kid.is_table()); + cols_min = kid.as_block().base.intrinsic_inline_sizes.minimum_inline_size; + cols_max = kid.as_block().base.intrinsic_inline_sizes.preferred_inline_size; + col_min_inline_sizes = kid.col_min_inline_sizes(); + col_pref_inline_sizes = kid.col_pref_inline_sizes(); + } + } + // 'extra_inline-size': difference between the calculated table inline-size and minimum inline-size + // required by all columns. It will be distributed over the columns. + let (inline_size, extra_inline_size) = match input.computed_inline_size { + Auto => { + if input.available_inline_size > geometry::max(cols_max, cap_min) { + if cols_max > cap_min { + table_wrapper.col_inline_sizes = col_pref_inline_sizes.clone(); + (cols_max, Au(0)) + } else { + (cap_min, cap_min - cols_min) + } + } else { + let max = if cols_min >= input.available_inline_size && cols_min >= cap_min { + table_wrapper.col_inline_sizes = col_min_inline_sizes.clone(); + cols_min + } else { + geometry::max(input.available_inline_size, cap_min) + }; + (max, max - cols_min) + } + }, + Specified(inline_size) => { + let max = if cols_min >= inline_size && cols_min >= cap_min { + table_wrapper.col_inline_sizes = col_min_inline_sizes.clone(); + cols_min + } else { + geometry::max(inline_size, cap_min) + }; + (max, max - cols_min) + } + }; + // The extra inline-size is distributed over the columns + if extra_inline_size > Au(0) { + let cell_len = table_wrapper.col_inline_sizes.len() as f64; + table_wrapper.col_inline_sizes = col_min_inline_sizes.iter().map(|inline_size| { + inline_size + extra_inline_size.scale_by(1.0 / cell_len) + }).collect(); + } + inline_size + } + }; + input.computed_inline_size = Specified(computed_inline_size); + input + } +} + +impl ISizeAndMarginsComputer for TableWrapper { + /// Solve the inline-size and margins constraints for this block flow. + fn solve_inline_size_constraints(&self, block: &mut BlockFlow, input: &ISizeConstraintInput) + -> ISizeConstraintSolution { + self.solve_block_inline_size_constraints(block, input) + } +} diff --git a/components/layout/text.rs b/components/layout/text.rs new file mode 100644 index 00000000000..e90272e218a --- /dev/null +++ b/components/layout/text.rs @@ -0,0 +1,327 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! Text layout. + +#![deny(unsafe_block)] + +use flow::Flow; +use fragment::{Fragment, ScannedTextFragment, ScannedTextFragmentInfo, UnscannedTextFragment}; + +use gfx::font::{FontMetrics, FontStyle, RunMetrics}; +use gfx::font_context::FontContext; +use gfx::text::glyph::CharIndex; +use gfx::text::text_run::TextRun; +use gfx::text::util::{CompressWhitespaceNewline, transform_text, CompressNone}; +use servo_util::geometry::Au; +use servo_util::logical_geometry::{LogicalSize, WritingMode}; +use servo_util::range::Range; +use style::ComputedValues; +use style::computed_values::{font_family, line_height, text_orientation, white_space}; +use sync::Arc; + +struct NewLinePositions { + new_line_pos: Vec<CharIndex>, +} + +// A helper function. +fn can_coalesce_text_nodes(fragments: &[Fragment], left_i: uint, right_i: uint) -> bool { + assert!(left_i != right_i); + fragments[left_i].can_merge_with_fragment(&fragments[right_i]) +} + +/// A stack-allocated object for scanning an inline flow into `TextRun`-containing `TextFragment`s. +pub struct TextRunScanner { + pub clump: Range<CharIndex>, +} + +impl TextRunScanner { + pub fn new() -> TextRunScanner { + TextRunScanner { + clump: Range::empty(), + } + } + + pub fn scan_for_runs(&mut self, font_context: &mut FontContext, flow: &mut Flow) { + { + let inline = flow.as_immutable_inline(); + debug!("TextRunScanner: scanning {:u} fragments for text runs...", inline.fragments.len()); + } + + let fragments = &mut flow.as_inline().fragments; + + let mut last_whitespace = true; + let mut new_fragments = Vec::new(); + for fragment_i in range(0, fragments.fragments.len()) { + debug!("TextRunScanner: considering fragment: {:u}", fragment_i); + if fragment_i > 0 && !can_coalesce_text_nodes(fragments.fragments.as_slice(), fragment_i - 1, fragment_i) { + last_whitespace = self.flush_clump_to_list(font_context, + fragments.fragments.as_slice(), + &mut new_fragments, + last_whitespace); + } + + self.clump.extend_by(CharIndex(1)); + } + + // Handle remaining clumps. + if self.clump.length() > CharIndex(0) { + drop(self.flush_clump_to_list(font_context, + fragments.fragments.as_slice(), + &mut new_fragments, + last_whitespace)) + } + + debug!("TextRunScanner: swapping out fragments."); + + fragments.fragments = new_fragments; + } + + /// A "clump" is a range of inline flow leaves that can be merged together into a single + /// fragment. Adjacent text with the same style can be merged, and nothing else can. + /// + /// The flow keeps track of the fragments contained by all non-leaf DOM nodes. This is necessary + /// for correct painting order. Since we compress several leaf fragments here, the mapping must + /// be adjusted. + /// + /// FIXME(#2267, pcwalton): Stop cloning fragments. Instead we will need to replace each + /// `in_fragment` with some smaller stub. + fn flush_clump_to_list(&mut self, + font_context: &mut FontContext, + in_fragments: &[Fragment], + out_fragments: &mut Vec<Fragment>, + last_whitespace: bool) + -> bool { + assert!(self.clump.length() > CharIndex(0)); + + debug!("TextRunScanner: flushing fragments in range={}", self.clump); + let is_singleton = self.clump.length() == CharIndex(1); + + let is_text_clump = match in_fragments[self.clump.begin().to_uint()].specific { + UnscannedTextFragment(_) => true, + _ => false, + }; + + let mut new_whitespace = last_whitespace; + match (is_singleton, is_text_clump) { + (false, false) => { + fail!("WAT: can't coalesce non-text nodes in flush_clump_to_list()!") + } + (true, false) => { + // FIXME(pcwalton): Stop cloning fragments, as above. + debug!("TextRunScanner: pushing single non-text fragment in range: {}", self.clump); + let new_fragment = in_fragments[self.clump.begin().to_uint()].clone(); + out_fragments.push(new_fragment) + }, + (true, true) => { + let old_fragment = &in_fragments[self.clump.begin().to_uint()]; + let text = match old_fragment.specific { + UnscannedTextFragment(ref text_fragment_info) => &text_fragment_info.text, + _ => fail!("Expected an unscanned text fragment!"), + }; + + let font_style = old_fragment.font_style(); + + let compression = match old_fragment.white_space() { + white_space::normal => CompressWhitespaceNewline, + white_space::pre => CompressNone, + }; + + let mut new_line_pos = vec![]; + + let (transformed_text, whitespace) = transform_text(text.as_slice(), + compression, + last_whitespace, + &mut new_line_pos); + + new_whitespace = whitespace; + + if transformed_text.len() > 0 { + // TODO(#177): Text run creation must account for the renderability of text by + // font group fonts. This is probably achieved by creating the font group above + // and then letting `FontGroup` decide which `Font` to stick into the text run. + let fontgroup = font_context.get_layout_font_group_for_style(&font_style); + let run = box fontgroup.create_textrun( + transformed_text.clone()); + + debug!("TextRunScanner: pushing single text fragment in range: {} ({})", + self.clump, + *text); + let range = Range::new(CharIndex(0), run.char_len()); + let new_metrics = run.metrics_for_range(&range); + let bounding_box_size = bounding_box_for_run_metrics( + &new_metrics, old_fragment.style.writing_mode); + let new_text_fragment_info = ScannedTextFragmentInfo::new(Arc::new(run), range); + let mut new_fragment = old_fragment.transform( + bounding_box_size, ScannedTextFragment(new_text_fragment_info)); + new_fragment.new_line_pos = new_line_pos; + out_fragments.push(new_fragment) + } + }, + (false, true) => { + // TODO(#177): Text run creation must account for the renderability of text by + // font group fonts. This is probably achieved by creating the font group above + // and then letting `FontGroup` decide which `Font` to stick into the text run. + let in_fragment = &in_fragments[self.clump.begin().to_uint()]; + let font_style = in_fragment.font_style(); + let fontgroup = font_context.get_layout_font_group_for_style(&font_style); + + let compression = match in_fragment.white_space() { + white_space::normal => CompressWhitespaceNewline, + white_space::pre => CompressNone, + }; + + let mut new_line_positions: Vec<NewLinePositions> = vec![]; + + // First, transform/compress text of all the nodes. + let mut last_whitespace_in_clump = new_whitespace; + let transformed_strs: Vec<String> = Vec::from_fn(self.clump.length().to_uint(), |i| { + let idx = CharIndex(i as int) + self.clump.begin(); + let in_fragment = match in_fragments[idx.to_uint()].specific { + UnscannedTextFragment(ref text_fragment_info) => &text_fragment_info.text, + _ => fail!("Expected an unscanned text fragment!"), + }; + + let mut new_line_pos = vec![]; + + let (new_str, new_whitespace) = transform_text(in_fragment.as_slice(), + compression, + last_whitespace_in_clump, + &mut new_line_pos); + new_line_positions.push(NewLinePositions { new_line_pos: new_line_pos }); + + last_whitespace_in_clump = new_whitespace; + new_str + }); + new_whitespace = last_whitespace_in_clump; + + // Next, concatenate all of the transformed strings together, saving the new + // character indices. + let mut run_str = String::new(); + let mut new_ranges: Vec<Range<CharIndex>> = vec![]; + let mut char_total = CharIndex(0); + for i in range(0, transformed_strs.len() as int) { + let added_chars = CharIndex(transformed_strs[i as uint].as_slice().char_len() as int); + new_ranges.push(Range::new(char_total, added_chars)); + run_str.push_str(transformed_strs[i as uint].as_slice()); + char_total = char_total + added_chars; + } + + // Now create the run. + // TextRuns contain a cycle which is usually resolved by the teardown + // sequence. If no clump takes ownership, however, it will leak. + let clump = self.clump; + let run = if clump.length() != CharIndex(0) && run_str.len() > 0 { + Some(Arc::new(box TextRun::new( + &mut *fontgroup.fonts[0].borrow_mut(), + run_str.to_string()))) + } else { + None + }; + + // Make new fragments with the run and adjusted text indices. + debug!("TextRunScanner: pushing fragment(s) in range: {}", self.clump); + for i in clump.each_index() { + let logical_offset = i - self.clump.begin(); + let range = new_ranges[logical_offset.to_uint()]; + if range.length() == CharIndex(0) { + debug!("Elided an `UnscannedTextFragment` because it was zero-length after \ + compression; {}", in_fragments[i.to_uint()]); + continue + } + + let new_text_fragment_info = ScannedTextFragmentInfo::new(run.get_ref().clone(), range); + let old_fragment = &in_fragments[i.to_uint()]; + let new_metrics = new_text_fragment_info.run.metrics_for_range(&range); + let bounding_box_size = bounding_box_for_run_metrics( + &new_metrics, old_fragment.style.writing_mode); + let mut new_fragment = old_fragment.transform( + bounding_box_size, ScannedTextFragment(new_text_fragment_info)); + new_fragment.new_line_pos = new_line_positions[logical_offset.to_uint()].new_line_pos.clone(); + out_fragments.push(new_fragment) + } + } + } // End of match. + + let end = self.clump.end(); // FIXME: borrow checker workaround + self.clump.reset(end, CharIndex(0)); + + new_whitespace + } // End of `flush_clump_to_list`. +} + + +#[inline] +fn bounding_box_for_run_metrics(metrics: &RunMetrics, writing_mode: WritingMode) + -> LogicalSize<Au> { + + // This does nothing, but it will fail to build + // when more values are added to the `text-orientation` CSS property. + // This will be a reminder to update the code below. + let dummy: Option<text_orientation::T> = None; + match dummy { + Some(text_orientation::sideways_right) | + Some(text_orientation::sideways_left) | + Some(text_orientation::sideways) | + None => {} + } + + // In vertical sideways or horizontal upgright text, + // the "width" of text metrics is always inline + // This will need to be updated when other text orientations are supported. + LogicalSize::new( + writing_mode, + metrics.bounding_box.size.width, + metrics.bounding_box.size.height) + +} + +/// Returns the metrics of the font represented by the given `FontStyle`, respectively. +/// +/// `#[inline]` because often the caller only needs a few fields from the font metrics. +#[inline] +pub fn font_metrics_for_style(font_context: &mut FontContext, font_style: &FontStyle) + -> FontMetrics { + let fontgroup = font_context.get_layout_font_group_for_style(font_style); + fontgroup.fonts[0].borrow().metrics.clone() +} + +/// Converts a computed style to a font style used for rendering. +/// +/// FIXME(pcwalton): This should not be necessary; just make the font part of the style sharable +/// with the display list somehow. (Perhaps we should use an ARC.) +pub fn computed_style_to_font_style(style: &ComputedValues) -> FontStyle { + debug!("(font style) start"); + + // FIXME: Too much allocation here. + let mut font_families = style.get_font().font_family.iter().map(|family| { + match *family { + font_family::FamilyName(ref name) => (*name).clone(), + } + }); + debug!("(font style) font families: `{:?}`", font_families); + + let font_size = style.get_font().font_size.to_f64().unwrap() / 60.0; + debug!("(font style) font size: `{:f}px`", font_size); + + FontStyle { + pt_size: font_size, + weight: style.get_font().font_weight, + style: style.get_font().font_style, + families: font_families.collect(), + } +} + +/// Returns the line block-size needed by the given computed style and font size. +pub fn line_height_from_style(style: &ComputedValues, metrics: &FontMetrics) -> Au { + let font_size = style.get_font().font_size; + let from_inline = match style.get_inheritedbox().line_height { + line_height::Normal => metrics.line_gap, + line_height::Number(l) => font_size.scale_by(l), + line_height::Length(l) => l + }; + let minimum = style.get_inheritedbox()._servo_minimum_line_height; + Au::max(from_inline, minimum) +} + diff --git a/components/layout/util.rs b/components/layout/util.rs new file mode 100644 index 00000000000..a0e466e8875 --- /dev/null +++ b/components/layout/util.rs @@ -0,0 +1,164 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use construct::{ConstructionResult, NoConstructionResult}; +use incremental::RestyleDamage; +use parallel::DomParallelInfo; +use wrapper::{LayoutNode, TLayoutNode, ThreadSafeLayoutNode}; + +use gfx::display_list::OpaqueNode; +use gfx; +use libc::uintptr_t; +use script::dom::bindings::js::JS; +use script::dom::bindings::utils::Reflectable; +use script::dom::node::{Node, SharedLayoutData}; +use script::layout_interface::{LayoutChan, UntrustedNodeAddress, TrustedNodeAddress}; +use std::mem; +use std::cell::{Ref, RefMut}; +use style::ComputedValues; +use style; +use sync::Arc; + +/// Data that layout associates with a node. +pub struct PrivateLayoutData { + /// The results of CSS styling for this node's `before` pseudo-element, if any. + pub before_style: Option<Arc<ComputedValues>>, + + /// The results of CSS styling for this node's `after` pseudo-element, if any. + pub after_style: Option<Arc<ComputedValues>>, + + /// Description of how to account for recent style changes. + pub restyle_damage: Option<RestyleDamage>, + + /// The current results of flow construction for this node. This is either a flow or a + /// `ConstructionItem`. See comments in `construct.rs` for more details. + pub flow_construction_result: ConstructionResult, + + pub before_flow_construction_result: ConstructionResult, + + pub after_flow_construction_result: ConstructionResult, + + /// Information needed during parallel traversals. + pub parallel: DomParallelInfo, +} + +impl PrivateLayoutData { + /// Creates new layout data. + pub fn new() -> PrivateLayoutData { + PrivateLayoutData { + before_style: None, + after_style: None, + restyle_damage: None, + flow_construction_result: NoConstructionResult, + before_flow_construction_result: NoConstructionResult, + after_flow_construction_result: NoConstructionResult, + parallel: DomParallelInfo::new(), + } + } +} + +pub struct LayoutDataWrapper { + pub chan: Option<LayoutChan>, + pub shared_data: SharedLayoutData, + pub data: Box<PrivateLayoutData>, +} + +/// A trait that allows access to the layout data of a DOM node. +pub trait LayoutDataAccess { + /// Borrows the layout data without checks. + unsafe fn borrow_layout_data_unchecked(&self) -> *const Option<LayoutDataWrapper>; + /// Borrows the layout data immutably. Fails on a conflicting borrow. + fn borrow_layout_data<'a>(&'a self) -> Ref<'a,Option<LayoutDataWrapper>>; + /// Borrows the layout data mutably. Fails on a conflicting borrow. + fn mutate_layout_data<'a>(&'a self) -> RefMut<'a,Option<LayoutDataWrapper>>; +} + +impl<'ln> LayoutDataAccess for LayoutNode<'ln> { + #[inline(always)] + unsafe fn borrow_layout_data_unchecked(&self) -> *const Option<LayoutDataWrapper> { + mem::transmute(self.get().layout_data.borrow_unchecked()) + } + + #[inline(always)] + fn borrow_layout_data<'a>(&'a self) -> Ref<'a,Option<LayoutDataWrapper>> { + unsafe { + mem::transmute(self.get().layout_data.borrow()) + } + } + + #[inline(always)] + fn mutate_layout_data<'a>(&'a self) -> RefMut<'a,Option<LayoutDataWrapper>> { + unsafe { + mem::transmute(self.get().layout_data.borrow_mut()) + } + } +} + +pub trait OpaqueNodeMethods { + /// Converts a DOM node (layout view) to an `OpaqueNode`. + fn from_layout_node(node: &LayoutNode) -> Self; + + /// Converts a thread-safe DOM node (layout view) to an `OpaqueNode`. + fn from_thread_safe_layout_node(node: &ThreadSafeLayoutNode) -> Self; + + /// Converts a DOM node (script view) to an `OpaqueNode`. + fn from_script_node(node: TrustedNodeAddress) -> Self; + + /// Converts a DOM node to an `OpaqueNode'. + fn from_jsmanaged(node: &JS<Node>) -> Self; + + /// Converts this node to an `UntrustedNodeAddress`. An `UntrustedNodeAddress` is just the type + /// of node that script expects to receive in a hit test. + fn to_untrusted_node_address(&self) -> UntrustedNodeAddress; +} + +impl OpaqueNodeMethods for OpaqueNode { + fn from_layout_node(node: &LayoutNode) -> OpaqueNode { + unsafe { + OpaqueNodeMethods::from_jsmanaged(node.get_jsmanaged()) + } + } + + fn from_thread_safe_layout_node(node: &ThreadSafeLayoutNode) -> OpaqueNode { + unsafe { + let abstract_node = node.get_jsmanaged(); + let ptr: uintptr_t = abstract_node.reflector().get_jsobject() as uint; + OpaqueNode(ptr) + } + } + + fn from_script_node(node: TrustedNodeAddress) -> OpaqueNode { + unsafe { + OpaqueNodeMethods::from_jsmanaged(&JS::from_trusted_node_address(node)) + } + } + + fn from_jsmanaged(node: &JS<Node>) -> OpaqueNode { + unsafe { + let ptr: uintptr_t = mem::transmute(node.reflector().get_jsobject()); + OpaqueNode(ptr) + } + } + + fn to_untrusted_node_address(&self) -> UntrustedNodeAddress { + unsafe { + let OpaqueNode(addr) = *self; + let addr: UntrustedNodeAddress = mem::transmute(addr); + addr + } + } +} + +/// Allows a CSS color to be converted into a graphics color. +pub trait ToGfxColor { + /// Converts a CSS color to a graphics color. + fn to_gfx_color(&self) -> gfx::color::Color; +} + +impl ToGfxColor for style::computed_values::RGBA { + fn to_gfx_color(&self) -> gfx::color::Color { + gfx::color::rgba(self.red, self.green, self.blue, self.alpha) + } +} + diff --git a/components/layout/wrapper.rs b/components/layout/wrapper.rs new file mode 100644 index 00000000000..d052c263655 --- /dev/null +++ b/components/layout/wrapper.rs @@ -0,0 +1,783 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! A safe wrapper for DOM nodes that prevents layout from mutating the DOM, from letting DOM nodes +//! escape, and from generally doing anything that it isn't supposed to. This is accomplished via +//! a simple whitelist of allowed operations, along with some lifetime magic to prevent nodes from +//! escaping. +//! +//! As a security wrapper is only as good as its whitelist, be careful when adding operations to +//! this list. The cardinal rules are: +//! +//! 1. Layout is not allowed to mutate the DOM. +//! +//! 2. Layout is not allowed to see anything with `JS` in the name, because it could hang +//! onto these objects and cause use-after-free. +//! +//! When implementing wrapper functions, be careful that you do not touch the borrow flags, or you +//! will race and cause spurious task failure. (Note that I do not believe these races are +//! exploitable, but they'll result in brokenness nonetheless.) +//! +//! Rules of the road for this file: +//! +//! * In general, you must not use the `Cast` functions; use explicit checks and `transmute_copy` +//! instead. +//! +//! * You must also not use `.get()`; instead, use `.unsafe_get()`. +//! +//! * Do not call any methods on DOM nodes without checking to see whether they use borrow flags. +//! +//! o Instead of `get_attr()`, use `.get_attr_val_for_layout()`. +//! +//! o Instead of `html_element_in_html_document()`, use +//! `html_element_in_html_document_for_layout()`. + +use css::node_style::StyledNode; +use util::LayoutDataWrapper; + +use script::dom::bindings::codegen::InheritTypes::{HTMLIFrameElementDerived}; +use script::dom::bindings::codegen::InheritTypes::{HTMLImageElementDerived, TextDerived}; +use script::dom::bindings::js::JS; +use script::dom::element::{Element, HTMLAreaElementTypeId, HTMLAnchorElementTypeId}; +use script::dom::element::{HTMLLinkElementTypeId, LayoutElementHelpers, RawLayoutElementHelpers}; +use script::dom::htmliframeelement::HTMLIFrameElement; +use script::dom::htmlimageelement::{HTMLImageElement, LayoutHTMLImageElementHelpers}; +use script::dom::node::{DocumentNodeTypeId, ElementNodeTypeId, Node, NodeTypeId}; +use script::dom::node::{LayoutNodeHelpers, RawLayoutNodeHelpers, TextNodeTypeId}; +use script::dom::text::Text; +use servo_msg::constellation_msg::{PipelineId, SubpageId}; +use servo_util::atom::Atom; +use servo_util::namespace::Namespace; +use servo_util::namespace; +use servo_util::str::is_whitespace; +use std::cell::{RefCell, Ref, RefMut}; +use std::kinds::marker::ContravariantLifetime; +use std::mem; +use style::computed_values::{content, display, white_space}; +use style::{AnyNamespace, AttrSelector, PropertyDeclarationBlock, SpecificNamespace, TElement}; +use style::{TNode}; +use url::Url; + +/// Allows some convenience methods on generic layout nodes. +pub trait TLayoutNode { + /// Creates a new layout node with the same lifetime as this layout node. + unsafe fn new_with_this_lifetime(&self, node: &JS<Node>) -> Self; + + /// Returns the type ID of this node. Fails if this node is borrowed mutably. Returns `None` + /// if this is a pseudo-element; otherwise, returns `Some`. + fn type_id(&self) -> Option<NodeTypeId>; + + /// Returns the interior of this node as a `JS`. This is highly unsafe for layout to + /// call and as such is marked `unsafe`. + unsafe fn get_jsmanaged<'a>(&'a self) -> &'a JS<Node>; + + /// Returns the interior of this node as a `Node`. This is highly unsafe for layout to call + /// and as such is marked `unsafe`. + unsafe fn get<'a>(&'a self) -> &'a Node { + &*self.get_jsmanaged().unsafe_get() + } + + fn node_is_element(&self) -> bool { + match self.type_id() { + Some(ElementNodeTypeId(..)) => true, + _ => false + } + } + + fn node_is_document(&self) -> bool { + match self.type_id() { + Some(DocumentNodeTypeId(..)) => true, + _ => false + } + } + + /// If this is an image element, returns its URL. If this is not an image element, fails. + /// + /// FIXME(pcwalton): Don't copy URLs. + fn image_url(&self) -> Option<Url> { + unsafe { + if !self.get().is_htmlimageelement() { + fail!("not an image!") + } + let image_element: JS<HTMLImageElement> = self.get_jsmanaged().transmute_copy(); + image_element.image().as_ref().map(|url| (*url).clone()) + } + } + + /// If this node is an iframe element, returns its pipeline and subpage IDs. If this node is + /// not an iframe element, fails. + fn iframe_pipeline_and_subpage_ids(&self) -> (PipelineId, SubpageId) { + unsafe { + if !self.get().is_htmliframeelement() { + fail!("not an iframe element!") + } + let iframe_element: JS<HTMLIFrameElement> = self.get_jsmanaged().transmute_copy(); + let size = (*iframe_element.unsafe_get()).size.deref().get().unwrap(); + (size.pipeline_id, size.subpage_id) + } + } + + /// If this is a text node, copies out the text. If this is not a text node, fails. + /// + /// FIXME(pcwalton): Don't copy text. Atomically reference count instead. + fn text(&self) -> String; + + /// Returns the first child of this node. + fn first_child(&self) -> Option<Self>; + + /// Dumps this node tree, for debugging. + fn dump(&self) { + // TODO(pcwalton): Reimplement this in a way that's safe for layout to call. + } +} + +/// A wrapper so that layout can access only the methods that it should have access to. Layout must +/// only ever see these and must never see instances of `JS`. +pub struct LayoutNode<'a> { + /// The wrapped node. + node: JS<Node>, + + /// Being chained to a ContravariantLifetime prevents `LayoutNode`s from escaping. + pub chain: ContravariantLifetime<'a>, +} + +impl<'ln> Clone for LayoutNode<'ln> { + fn clone(&self) -> LayoutNode<'ln> { + LayoutNode { + node: self.node.clone(), + chain: self.chain, + } + } +} + +impl<'a> PartialEq for LayoutNode<'a> { + #[inline] + fn eq(&self, other: &LayoutNode) -> bool { + self.node == other.node + } +} + + +impl<'ln> TLayoutNode for LayoutNode<'ln> { + unsafe fn new_with_this_lifetime(&self, node: &JS<Node>) -> LayoutNode<'ln> { + LayoutNode { + node: node.transmute_copy(), + chain: self.chain, + } + } + + fn type_id(&self) -> Option<NodeTypeId> { + unsafe { + Some(self.node.type_id_for_layout()) + } + } + + unsafe fn get_jsmanaged<'a>(&'a self) -> &'a JS<Node> { + &self.node + } + + fn first_child(&self) -> Option<LayoutNode<'ln>> { + unsafe { + self.get_jsmanaged().first_child_ref().map(|node| self.new_with_this_lifetime(&node)) + } + } + + fn text(&self) -> String { + unsafe { + if !self.get().is_text() { + fail!("not text!") + } + let text: JS<Text> = self.get_jsmanaged().transmute_copy(); + (*text.unsafe_get()).characterdata.data.deref().borrow().clone() + } + } +} + +impl<'ln> LayoutNode<'ln> { + /// Creates a new layout node, scoped to the given closure. + pub unsafe fn with_layout_node<R>(node: JS<Node>, f: <'a> |LayoutNode<'a>| -> R) -> R { + f(LayoutNode { + node: node, + chain: ContravariantLifetime, + }) + } + + /// Iterates over this node and all its descendants, in preorder. + /// + /// FIXME(pcwalton): Terribly inefficient. We should use parallelism. + pub fn traverse_preorder(&self) -> LayoutTreeIterator<'ln> { + let mut nodes = vec!(); + gather_layout_nodes(self, &mut nodes, false); + LayoutTreeIterator::new(nodes) + } + + /// Returns an iterator over this node's children. + pub fn children(&self) -> LayoutNodeChildrenIterator<'ln> { + LayoutNodeChildrenIterator { + current_node: self.first_child(), + } + } + + pub unsafe fn get_jsmanaged<'a>(&'a self) -> &'a JS<Node> { + &self.node + } +} + +impl<'ln> TNode<LayoutElement<'ln>> for LayoutNode<'ln> { + fn parent_node(&self) -> Option<LayoutNode<'ln>> { + unsafe { + self.node.parent_node_ref().map(|node| self.new_with_this_lifetime(&node)) + } + } + + fn prev_sibling(&self) -> Option<LayoutNode<'ln>> { + unsafe { + self.node.prev_sibling_ref().map(|node| self.new_with_this_lifetime(&node)) + } + } + + fn next_sibling(&self) -> Option<LayoutNode<'ln>> { + unsafe { + self.node.next_sibling_ref().map(|node| self.new_with_this_lifetime(&node)) + } + } + + /// If this is an element, accesses the element data. Fails if this is not an element node. + #[inline] + fn as_element(&self) -> LayoutElement<'ln> { + unsafe { + assert!(self.node.is_element_for_layout()); + let elem: JS<Element> = self.node.transmute_copy(); + let element = &*elem.unsafe_get(); + LayoutElement { + element: mem::transmute(element), + } + } + } + + fn is_element(&self) -> bool { + self.node_is_element() + } + + fn is_document(&self) -> bool { + self.node_is_document() + } + + fn match_attr(&self, attr: &AttrSelector, test: |&str| -> bool) -> bool { + assert!(self.is_element()) + let name = if self.is_html_element_in_html_document() { + attr.lower_name.as_slice() + } else { + attr.name.as_slice() + }; + match attr.namespace { + SpecificNamespace(ref ns) => { + let element = self.as_element(); + element.get_attr(ns, name) + .map_or(false, |attr| test(attr)) + }, + // FIXME: https://github.com/mozilla/servo/issues/1558 + AnyNamespace => false, + } + } + + fn is_html_element_in_html_document(&self) -> bool { + unsafe { + self.is_element() && { + let element: JS<Element> = self.node.transmute_copy(); + element.html_element_in_html_document_for_layout() + } + } + } +} + +pub struct LayoutNodeChildrenIterator<'a> { + current_node: Option<LayoutNode<'a>>, +} + +impl<'a> Iterator<LayoutNode<'a>> for LayoutNodeChildrenIterator<'a> { + fn next(&mut self) -> Option<LayoutNode<'a>> { + let node = self.current_node.clone(); + self.current_node = node.clone().and_then(|node| { + node.next_sibling() + }); + node + } +} + +// FIXME: Do this without precomputing a vector of refs. +// Easy for preorder; harder for postorder. +// +// FIXME(pcwalton): Parallelism! Eventually this should just be nuked. +pub struct LayoutTreeIterator<'a> { + nodes: Vec<LayoutNode<'a>>, + index: uint, +} + +impl<'a> LayoutTreeIterator<'a> { + fn new(nodes: Vec<LayoutNode<'a>>) -> LayoutTreeIterator<'a> { + LayoutTreeIterator { + nodes: nodes, + index: 0, + } + } +} + +impl<'a> Iterator<LayoutNode<'a>> for LayoutTreeIterator<'a> { + fn next(&mut self) -> Option<LayoutNode<'a>> { + if self.index >= self.nodes.len() { + None + } else { + let v = self.nodes[self.index].clone(); + self.index += 1; + Some(v) + } + } +} + +/// FIXME(pcwalton): This is super inefficient. +fn gather_layout_nodes<'a>(cur: &LayoutNode<'a>, refs: &mut Vec<LayoutNode<'a>>, postorder: bool) { + if !postorder { + refs.push(cur.clone()); + } + for kid in cur.children() { + gather_layout_nodes(&kid, refs, postorder) + } + if postorder { + refs.push(cur.clone()); + } +} + +/// A wrapper around elements that ensures layout can only ever access safe properties. +pub struct LayoutElement<'le> { + element: &'le Element, +} + +impl<'le> LayoutElement<'le> { + pub fn style_attribute(&self) -> &'le Option<PropertyDeclarationBlock> { + let style: &Option<PropertyDeclarationBlock> = unsafe { + let style: &RefCell<Option<PropertyDeclarationBlock>> = self.element.style_attribute.deref(); + // cast to the direct reference to T placed on the head of RefCell<T> + mem::transmute(style) + }; + style + } +} + +impl<'le> TElement for LayoutElement<'le> { + #[inline] + fn get_local_name<'a>(&'a self) -> &'a Atom { + &self.element.local_name + } + + #[inline] + fn get_namespace<'a>(&'a self) -> &'a Namespace { + &self.element.namespace + } + + #[inline] + fn get_attr(&self, namespace: &Namespace, name: &str) -> Option<&'static str> { + unsafe { self.element.get_attr_val_for_layout(namespace, name) } + } + + fn get_link(&self) -> Option<&'static str> { + // FIXME: This is HTML only. + match self.element.node.type_id_for_layout() { + // http://www.whatwg.org/specs/web-apps/current-work/multipage/selectors.html# + // selector-link + ElementNodeTypeId(HTMLAnchorElementTypeId) | + ElementNodeTypeId(HTMLAreaElementTypeId) | + ElementNodeTypeId(HTMLLinkElementTypeId) => { + unsafe { self.element.get_attr_val_for_layout(&namespace::Null, "href") } + } + _ => None, + } + } + + fn get_hover_state(&self) -> bool { + unsafe { + self.element.node.get_hover_state_for_layout() + } + } + + #[inline] + fn get_id(&self) -> Option<Atom> { + unsafe { self.element.get_attr_atom_for_layout(&namespace::Null, "id") } + } + + fn get_disabled_state(&self) -> bool { + unsafe { + self.element.node.get_disabled_state_for_layout() + } + } + + fn get_enabled_state(&self) -> bool { + unsafe { + self.element.node.get_enabled_state_for_layout() + } + } +} + +fn get_content(content_list: &content::T) -> String { + match *content_list { + content::Content(ref value) => { + let iter = &mut value.clone().move_iter().peekable(); + match iter.next() { + Some(content::StringContent(content)) => content, + _ => "".to_string(), + } + } + _ => "".to_string(), + } +} + +#[deriving(PartialEq, Clone)] +pub enum PseudoElementType { + Normal, + Before, + After, + BeforeBlock, + AfterBlock, +} + +/// A thread-safe version of `LayoutNode`, used during flow construction. This type of layout +/// node does not allow any parents or siblings of nodes to be accessed, to avoid races. +#[deriving(Clone)] +pub struct ThreadSafeLayoutNode<'ln> { + /// The wrapped node. + node: LayoutNode<'ln>, + + pseudo: PseudoElementType, +} + +impl<'ln> TLayoutNode for ThreadSafeLayoutNode<'ln> { + /// Creates a new layout node with the same lifetime as this layout node. + unsafe fn new_with_this_lifetime(&self, node: &JS<Node>) -> ThreadSafeLayoutNode<'ln> { + ThreadSafeLayoutNode { + node: LayoutNode { + node: node.transmute_copy(), + chain: self.node.chain, + }, + pseudo: Normal, + } + } + + /// Returns `None` if this is a pseudo-element. + fn type_id(&self) -> Option<NodeTypeId> { + if self.pseudo == Before || self.pseudo == After { + return None + } + + self.node.type_id() + } + + unsafe fn get_jsmanaged<'a>(&'a self) -> &'a JS<Node> { + self.node.get_jsmanaged() + } + + unsafe fn get<'a>(&'a self) -> &'a Node { // this change. + mem::transmute::<*mut Node,&'a Node>(self.get_jsmanaged().unsafe_get()) + } + + fn first_child(&self) -> Option<ThreadSafeLayoutNode<'ln>> { + if self.pseudo == Before || self.pseudo == After { + return None + } + + if self.has_before_pseudo() { + if self.is_block(Before) && self.pseudo == Normal { + let pseudo_before_node = self.with_pseudo(BeforeBlock); + return Some(pseudo_before_node) + } else if self.pseudo == Normal || self.pseudo == BeforeBlock { + let pseudo_before_node = self.with_pseudo(Before); + return Some(pseudo_before_node) + } + } + + unsafe { + self.get_jsmanaged().first_child_ref().map(|node| self.new_with_this_lifetime(&node)) + } + } + + fn text(&self) -> String { + if self.pseudo == Before || self.pseudo == After { + let layout_data_ref = self.borrow_layout_data(); + let node_layout_data_wrapper = layout_data_ref.get_ref(); + + if self.pseudo == Before { + let before_style = node_layout_data_wrapper.data.before_style.get_ref(); + return get_content(&before_style.get_box().content) + } else { + let after_style = node_layout_data_wrapper.data.after_style.get_ref(); + return get_content(&after_style.get_box().content) + } + } + + unsafe { + if !self.get().is_text() { + fail!("not text!") + } + let text: JS<Text> = self.get_jsmanaged().transmute_copy(); + (*text.unsafe_get()).characterdata.data.deref().borrow().clone() + } + } +} + + +impl<'ln> ThreadSafeLayoutNode<'ln> { + /// Creates a new `ThreadSafeLayoutNode` from the given `LayoutNode`. + pub fn new<'a>(node: &LayoutNode<'a>) -> ThreadSafeLayoutNode<'a> { + ThreadSafeLayoutNode { + node: node.clone(), + pseudo: Normal, + } + } + + /// Creates a new `ThreadSafeLayoutNode` for the same `LayoutNode` + /// with a different pseudo-element type. + fn with_pseudo(&self, pseudo: PseudoElementType) -> ThreadSafeLayoutNode<'ln> { + ThreadSafeLayoutNode { + node: self.node.clone(), + pseudo: pseudo, + } + } + + /// Returns the next sibling of this node. Unsafe and private because this can lead to races. + unsafe fn next_sibling(&self) -> Option<ThreadSafeLayoutNode<'ln>> { + if self.pseudo == Before || self.pseudo == BeforeBlock { + return self.get_jsmanaged().first_child_ref().map(|node| self.new_with_this_lifetime(&node)) + } + + self.get_jsmanaged().next_sibling_ref().map(|node| self.new_with_this_lifetime(&node)) + } + + /// Returns an iterator over this node's children. + pub fn children(&self) -> ThreadSafeLayoutNodeChildrenIterator<'ln> { + ThreadSafeLayoutNodeChildrenIterator { + current_node: self.first_child(), + parent_node: Some(self.clone()), + } + } + + /// If this is an element, accesses the element data. Fails if this is not an element node. + #[inline] + pub fn as_element(&self) -> ThreadSafeLayoutElement { + unsafe { + assert!(self.get_jsmanaged().is_element_for_layout()); + let elem: JS<Element> = self.get_jsmanaged().transmute_copy(); + let element = elem.unsafe_get(); + // FIXME(pcwalton): Workaround until Rust gets multiple lifetime parameters on + // implementations. + ThreadSafeLayoutElement { + element: &mut *element, + } + } + } + + pub fn get_pseudo_element_type(&self) -> PseudoElementType { + self.pseudo + } + + pub fn is_block(&self, kind: PseudoElementType) -> bool { + let mut layout_data_ref = self.mutate_layout_data(); + let node_layout_data_wrapper = layout_data_ref.get_mut_ref(); + + let display = match kind { + Before | BeforeBlock => { + let before_style = node_layout_data_wrapper.data.before_style.get_ref(); + before_style.get_box().display + } + After | AfterBlock => { + let after_style = node_layout_data_wrapper.data.after_style.get_ref(); + after_style.get_box().display + } + Normal => { + let after_style = node_layout_data_wrapper.shared_data.style.get_ref(); + after_style.get_box().display + } + }; + + display == display::block + } + + pub fn has_before_pseudo(&self) -> bool { + let layout_data_wrapper = self.borrow_layout_data(); + let layout_data_wrapper_ref = layout_data_wrapper.get_ref(); + layout_data_wrapper_ref.data.before_style.is_some() + } + + pub fn has_after_pseudo(&self) -> bool { + let layout_data_wrapper = self.borrow_layout_data(); + let layout_data_wrapper_ref = layout_data_wrapper.get_ref(); + layout_data_wrapper_ref.data.after_style.is_some() + } + + /// Borrows the layout data immutably. Fails on a conflicting borrow. + #[inline(always)] + pub fn borrow_layout_data<'a>(&'a self) -> Ref<'a,Option<LayoutDataWrapper>> { + unsafe { + mem::transmute(self.get().layout_data.borrow()) + } + } + + /// Borrows the layout data mutably. Fails on a conflicting borrow. + #[inline(always)] + pub fn mutate_layout_data<'a>(&'a self) -> RefMut<'a,Option<LayoutDataWrapper>> { + unsafe { + mem::transmute(self.get().layout_data.borrow_mut()) + } + } + + /// Traverses the tree in postorder. + /// + /// TODO(pcwalton): Offer a parallel version with a compatible API. + pub fn traverse_postorder_mut<T:PostorderNodeMutTraversal>(&mut self, traversal: &mut T) + -> bool { + if traversal.should_prune(self) { + return true + } + + let mut opt_kid = self.first_child(); + loop { + match opt_kid { + None => break, + Some(mut kid) => { + if !kid.traverse_postorder_mut(traversal) { + return false + } + unsafe { + opt_kid = kid.next_sibling() + } + } + } + } + + traversal.process(self) + } + + pub fn is_ignorable_whitespace(&self) -> bool { + match self.type_id() { + Some(TextNodeTypeId) => { + unsafe { + let text: JS<Text> = self.get_jsmanaged().transmute_copy(); + if !is_whitespace((*text.unsafe_get()).characterdata.data.deref().borrow().as_slice()) { + return false + } + + // NB: See the rules for `white-space` here: + // + // http://www.w3.org/TR/CSS21/text.html#propdef-white-space + // + // If you implement other values for this property, you will almost certainly + // want to update this check. + match self.style().get_inheritedtext().white_space { + white_space::normal => true, + _ => false, + } + } + } + _ => false + } + } +} + +pub struct ThreadSafeLayoutNodeChildrenIterator<'a> { + current_node: Option<ThreadSafeLayoutNode<'a>>, + parent_node: Option<ThreadSafeLayoutNode<'a>>, +} + +impl<'a> Iterator<ThreadSafeLayoutNode<'a>> for ThreadSafeLayoutNodeChildrenIterator<'a> { + fn next(&mut self) -> Option<ThreadSafeLayoutNode<'a>> { + let node = self.current_node.clone(); + + match node { + Some(ref node) => { + if node.pseudo == After || node.pseudo == AfterBlock { + return None + } + + match self.parent_node { + Some(ref parent_node) => { + if parent_node.pseudo == Normal { + self.current_node = self.current_node.clone().and_then(|node| { + unsafe { + node.next_sibling() + } + }); + } else { + self.current_node = None; + } + } + None => {} + } + } + None => { + match self.parent_node { + Some(ref parent_node) => { + if parent_node.has_after_pseudo() { + let pseudo_after_node = if parent_node.is_block(After) && parent_node.pseudo == Normal { + let pseudo_after_node = parent_node.with_pseudo(AfterBlock); + Some(pseudo_after_node) + } else if parent_node.pseudo == Normal || parent_node.pseudo == AfterBlock { + let pseudo_after_node = parent_node.with_pseudo(After); + Some(pseudo_after_node) + } else { + None + }; + self.current_node = pseudo_after_node; + return self.current_node.clone() + } + } + None => {} + } + } + } + + node + } +} + +/// A wrapper around elements that ensures layout can only ever access safe properties and cannot +/// race on elements. +pub struct ThreadSafeLayoutElement<'le> { + element: &'le Element, +} + +impl<'le> ThreadSafeLayoutElement<'le> { + #[inline] + pub fn get_attr(&self, namespace: &Namespace, name: &str) -> Option<&'static str> { + unsafe { self.element.get_attr_val_for_layout(namespace, name) } + } +} + +/// A bottom-up, parallelizable traversal. +pub trait PostorderNodeMutTraversal { + /// The operation to perform. Return true to continue or false to stop. + fn process<'a>(&'a mut self, node: &ThreadSafeLayoutNode<'a>) -> bool; + + /// Returns true if this node should be pruned. If this returns true, we skip the operation + /// entirely and do not process any descendant nodes. This is called *before* child nodes are + /// visited. The default implementation never prunes any nodes. + fn should_prune<'a>(&'a self, _node: &ThreadSafeLayoutNode<'a>) -> bool { + false + } +} + +/// Opaque type stored in type-unsafe work queues for parallel layout. +/// Must be transmutable to and from LayoutNode/ThreadSafeLayoutNode. +pub type UnsafeLayoutNode = (uint, uint); + +pub fn layout_node_to_unsafe_layout_node(node: &LayoutNode) -> UnsafeLayoutNode { + unsafe { + let ptr: uint = mem::transmute_copy(node); + (ptr, 0) + } +} + +// FIXME(#3044): This should be updated to use a real lifetime instead of +// faking one. +pub unsafe fn layout_node_from_unsafe_layout_node(node: &UnsafeLayoutNode) -> LayoutNode<'static> { + let (node, _) = *node; + mem::transmute(node) +} |