diff options
Diffstat (limited to 'src/components/layout')
29 files changed, 0 insertions, 13877 deletions
diff --git a/src/components/layout/block.rs b/src/components/layout/block.rs deleted file mode 100644 index b84e0da50f7..00000000000 --- a/src/components/layout/block.rs +++ /dev/null @@ -1,2428 +0,0 @@ -/* 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/src/components/layout/construct.rs b/src/components/layout/construct.rs deleted file mode 100644 index 0f832bacfb8..00000000000 --- a/src/components/layout/construct.rs +++ /dev/null @@ -1,1049 +0,0 @@ -/* 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/src/components/layout/context.rs b/src/components/layout/context.rs deleted file mode 100644 index 936314dcb63..00000000000 --- a/src/components/layout/context.rs +++ /dev/null @@ -1,123 +0,0 @@ -/* 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/src/components/layout/css/matching.rs b/src/components/layout/css/matching.rs deleted file mode 100644 index c1d766c32ca..00000000000 --- a/src/components/layout/css/matching.rs +++ /dev/null @@ -1,558 +0,0 @@ -/* 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/src/components/layout/css/node_style.rs b/src/components/layout/css/node_style.rs deleted file mode 100644 index e201b0050aa..00000000000 --- a/src/components/layout/css/node_style.rs +++ /dev/null @@ -1,30 +0,0 @@ -/* 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/src/components/layout/css/node_util.rs b/src/components/layout/css/node_util.rs deleted file mode 100644 index 150995428ea..00000000000 --- a/src/components/layout/css/node_util.rs +++ /dev/null @@ -1,90 +0,0 @@ -/* 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/src/components/layout/extra.rs b/src/components/layout/extra.rs deleted file mode 100644 index 7b731185272..00000000000 --- a/src/components/layout/extra.rs +++ /dev/null @@ -1,44 +0,0 @@ -/* 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/src/components/layout/floats.rs b/src/components/layout/floats.rs deleted file mode 100644 index 94017e6b3f7..00000000000 --- a/src/components/layout/floats.rs +++ /dev/null @@ -1,439 +0,0 @@ -/* 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/src/components/layout/flow.rs b/src/components/layout/flow.rs deleted file mode 100644 index 2759ebbe74b..00000000000 --- a/src/components/layout/flow.rs +++ /dev/null @@ -1,1138 +0,0 @@ -/* 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/src/components/layout/flow_list.rs b/src/components/layout/flow_list.rs deleted file mode 100644 index 4277326a624..00000000000 --- a/src/components/layout/flow_list.rs +++ /dev/null @@ -1,296 +0,0 @@ -/* 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/src/components/layout/flow_ref.rs b/src/components/layout/flow_ref.rs deleted file mode 100644 index d90d9ac4cc0..00000000000 --- a/src/components/layout/flow_ref.rs +++ /dev/null @@ -1,84 +0,0 @@ -/* 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/src/components/layout/fragment.rs b/src/components/layout/fragment.rs deleted file mode 100644 index 191283603b9..00000000000 --- a/src/components/layout/fragment.rs +++ /dev/null @@ -1,1597 +0,0 @@ -/* 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/src/components/layout/incremental.rs b/src/components/layout/incremental.rs deleted file mode 100644 index d04c068b6aa..00000000000 --- a/src/components/layout/incremental.rs +++ /dev/null @@ -1,78 +0,0 @@ -/* 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/src/components/layout/inline.rs b/src/components/layout/inline.rs deleted file mode 100644 index 6c22b25746e..00000000000 --- a/src/components/layout/inline.rs +++ /dev/null @@ -1,1170 +0,0 @@ -/* 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/src/components/layout/layout.rs b/src/components/layout/layout.rs deleted file mode 100644 index a9ee00319f7..00000000000 --- a/src/components/layout/layout.rs +++ /dev/null @@ -1,71 +0,0 @@ -/* 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/. */ - -#![crate_name = "layout"] -#![crate_type = "rlib"] - -#![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/src/components/layout/layout_debug.rs b/src/components/layout/layout_debug.rs deleted file mode 100644 index 58db599c9e2..00000000000 --- a/src/components/layout/layout_debug.rs +++ /dev/null @@ -1,126 +0,0 @@ -/* 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/src/components/layout/layout_task.rs b/src/components/layout/layout_task.rs deleted file mode 100644 index c3c463408e6..00000000000 --- a/src/components/layout/layout_task.rs +++ /dev/null @@ -1,1020 +0,0 @@ -/* 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/src/components/layout/model.rs b/src/components/layout/model.rs deleted file mode 100644 index 23647b1b77d..00000000000 --- a/src/components/layout/model.rs +++ /dev/null @@ -1,337 +0,0 @@ -/* 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/src/components/layout/parallel.rs b/src/components/layout/parallel.rs deleted file mode 100644 index a2786e8ba91..00000000000 --- a/src/components/layout/parallel.rs +++ /dev/null @@ -1,561 +0,0 @@ -/* 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/src/components/layout/table.rs b/src/components/layout/table.rs deleted file mode 100644 index 98569d68c95..00000000000 --- a/src/components/layout/table.rs +++ /dev/null @@ -1,324 +0,0 @@ -/* 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/src/components/layout/table_caption.rs b/src/components/layout/table_caption.rs deleted file mode 100644 index 8c1dba3e7ca..00000000000 --- a/src/components/layout/table_caption.rs +++ /dev/null @@ -1,73 +0,0 @@ -/* 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/src/components/layout/table_cell.rs b/src/components/layout/table_cell.rs deleted file mode 100644 index 0011cd29fbc..00000000000 --- a/src/components/layout/table_cell.rs +++ /dev/null @@ -1,121 +0,0 @@ -/* 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/src/components/layout/table_colgroup.rs b/src/components/layout/table_colgroup.rs deleted file mode 100644 index 270f55970b2..00000000000 --- a/src/components/layout/table_colgroup.rs +++ /dev/null @@ -1,88 +0,0 @@ -/* 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/src/components/layout/table_row.rs b/src/components/layout/table_row.rs deleted file mode 100644 index 101f00eb5cc..00000000000 --- a/src/components/layout/table_row.rs +++ /dev/null @@ -1,225 +0,0 @@ -/* 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/src/components/layout/table_rowgroup.rs b/src/components/layout/table_rowgroup.rs deleted file mode 100644 index 48f9d376af3..00000000000 --- a/src/components/layout/table_rowgroup.rs +++ /dev/null @@ -1,208 +0,0 @@ -/* 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/src/components/layout/table_wrapper.rs b/src/components/layout/table_wrapper.rs deleted file mode 100644 index 2084ef52bdc..00000000000 --- a/src/components/layout/table_wrapper.rs +++ /dev/null @@ -1,325 +0,0 @@ -/* 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/src/components/layout/text.rs b/src/components/layout/text.rs deleted file mode 100644 index e90272e218a..00000000000 --- a/src/components/layout/text.rs +++ /dev/null @@ -1,327 +0,0 @@ -/* 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/src/components/layout/util.rs b/src/components/layout/util.rs deleted file mode 100644 index a0e466e8875..00000000000 --- a/src/components/layout/util.rs +++ /dev/null @@ -1,164 +0,0 @@ -/* 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/src/components/layout/wrapper.rs b/src/components/layout/wrapper.rs deleted file mode 100644 index d052c263655..00000000000 --- a/src/components/layout/wrapper.rs +++ /dev/null @@ -1,783 +0,0 @@ -/* 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) -} |