aboutsummaryrefslogtreecommitdiffstats
path: root/components/layout
diff options
context:
space:
mode:
Diffstat (limited to 'components/layout')
-rw-r--r--components/layout/Cargo.toml38
-rw-r--r--components/layout/block.rs2428
-rw-r--r--components/layout/construct.rs1049
-rw-r--r--components/layout/context.rs123
-rw-r--r--components/layout/css/matching.rs558
-rw-r--r--components/layout/css/node_style.rs30
-rw-r--r--components/layout/css/node_util.rs90
-rw-r--r--components/layout/extra.rs44
-rw-r--r--components/layout/floats.rs439
-rw-r--r--components/layout/flow.rs1138
-rw-r--r--components/layout/flow_list.rs296
-rw-r--r--components/layout/flow_ref.rs84
-rw-r--r--components/layout/fragment.rs1597
-rw-r--r--components/layout/incremental.rs78
-rw-r--r--components/layout/inline.rs1170
-rw-r--r--components/layout/layout_debug.rs126
-rw-r--r--components/layout/layout_task.rs1020
-rw-r--r--components/layout/lib.rs68
-rw-r--r--components/layout/model.rs337
-rw-r--r--components/layout/parallel.rs561
-rw-r--r--components/layout/table.rs324
-rw-r--r--components/layout/table_caption.rs73
-rw-r--r--components/layout/table_cell.rs121
-rw-r--r--components/layout/table_colgroup.rs88
-rw-r--r--components/layout/table_row.rs225
-rw-r--r--components/layout/table_rowgroup.rs208
-rw-r--r--components/layout/table_wrapper.rs325
-rw-r--r--components/layout/text.rs327
-rw-r--r--components/layout/util.rs164
-rw-r--r--components/layout/wrapper.rs783
30 files changed, 13912 insertions, 0 deletions
diff --git a/components/layout/Cargo.toml b/components/layout/Cargo.toml
new file mode 100644
index 00000000000..9d8e8abbdaa
--- /dev/null
+++ b/components/layout/Cargo.toml
@@ -0,0 +1,38 @@
+[package]
+name = "layout"
+version = "0.0.1"
+authors = ["The Servo Project Developers"]
+
+[lib]
+name = "layout"
+path = "lib.rs"
+
+[dependencies.gfx]
+path = "../gfx"
+
+[dependencies.script]
+path = "../script"
+
+[dependencies.layout_traits]
+path = "../layout_traits"
+
+[dependencies.script_traits]
+path = "../script_traits"
+
+[dependencies.style]
+path = "../style"
+
+[dependencies.macros]
+path = "../macros"
+
+[dependencies.net]
+path = "../net"
+
+[dependencies.util]
+path = "../util"
+
+[dependencies.geom]
+git = "https://github.com/servo/rust-geom"
+
+[dependencies.url]
+git = "https://github.com/servo/rust-url"
diff --git a/components/layout/block.rs b/components/layout/block.rs
new file mode 100644
index 00000000000..b84e0da50f7
--- /dev/null
+++ b/components/layout/block.rs
@@ -0,0 +1,2428 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! CSS block formatting contexts.
+//!
+//! Terminology Note:
+//! As per the CSS Spec, the term 'absolute positioning' here refers to
+//! elements with position = 'absolute' or 'fixed'.
+//! The term 'positioned element' refers to elements with position =
+//! 'relative', 'absolute', or 'fixed'.
+//!
+//! CB: Containing Block of the current flow.
+
+#![deny(unsafe_block)]
+
+use construct::FlowConstructor;
+use context::LayoutContext;
+use floats::{ClearBoth, ClearLeft, ClearRight, FloatKind, Floats, PlacementInfo};
+use flow::{BaseFlow, BlockFlowClass, FlowClass, Flow, ImmutableFlowUtils};
+use flow::{MutableFlowUtils, PreorderFlowTraversal, PostorderFlowTraversal, mut_base};
+use flow;
+use fragment::{Fragment, ImageFragment, ScannedTextFragment};
+use layout_debug;
+use model::{Auto, IntrinsicISizes, MarginCollapseInfo, MarginsCollapse};
+use model::{MarginsCollapseThrough, MaybeAuto, NoCollapsibleMargins, Specified, specified};
+use model::{specified_or_none};
+use wrapper::ThreadSafeLayoutNode;
+use style::ComputedValues;
+use style::computed_values::{clear, position};
+
+use collections::dlist::DList;
+use geom::{Size2D, Point2D, Rect};
+use gfx::color;
+use gfx::display_list::{BackgroundAndBorderLevel, BlockLevel, ContentStackingLevel, DisplayList};
+use gfx::display_list::{FloatStackingLevel, PositionedDescendantStackingLevel};
+use gfx::display_list::{RootOfStackingContextLevel};
+use gfx::render_task::RenderLayer;
+use servo_msg::compositor_msg::{FixedPosition, LayerId, Scrollable};
+use servo_util::geometry::Au;
+use servo_util::geometry;
+use servo_util::logical_geometry::WritingMode;
+use servo_util::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize};
+use std::fmt;
+use std::mem;
+use style::computed_values::{LPA_Auto, LPA_Length, LPA_Percentage, LPN_Length, LPN_None};
+use style::computed_values::{LPN_Percentage, LP_Length, LP_Percentage};
+use style::computed_values::{display, float, overflow};
+use sync::Arc;
+
+/// Information specific to floated blocks.
+#[deriving(Encodable)]
+pub struct FloatedBlockInfo {
+ pub containing_inline_size: Au,
+
+ /// Offset relative to where the parent tried to position this flow
+ pub rel_pos: LogicalPoint<Au>,
+
+ /// Index into the fragment list for inline floats
+ pub index: Option<uint>,
+
+ /// Left or right?
+ pub float_kind: FloatKind,
+}
+
+impl FloatedBlockInfo {
+ pub fn new(float_kind: FloatKind, writing_mode: WritingMode) -> FloatedBlockInfo {
+ FloatedBlockInfo {
+ containing_inline_size: Au(0),
+ rel_pos: LogicalPoint::new(writing_mode, Au(0), Au(0)),
+ index: None,
+ float_kind: float_kind,
+ }
+ }
+}
+
+/// The solutions for the block-size-and-margins constraint equation.
+struct BSizeConstraintSolution {
+ block_start: Au,
+ _block_end: Au,
+ block_size: Au,
+ margin_block_start: Au,
+ margin_block_end: Au
+}
+
+impl BSizeConstraintSolution {
+ fn new(block_start: Au, block_end: Au, block_size: Au, margin_block_start: Au, margin_block_end: Au)
+ -> BSizeConstraintSolution {
+ BSizeConstraintSolution {
+ block_start: block_start,
+ _block_end: block_end,
+ block_size: block_size,
+ margin_block_start: margin_block_start,
+ margin_block_end: margin_block_end,
+ }
+ }
+
+ /// Solve the vertical constraint equation for absolute non-replaced elements.
+ ///
+ /// CSS Section 10.6.4
+ /// Constraint equation:
+ /// block-start + block-end + block-size + margin-block-start + margin-block-end
+ /// = absolute containing block block-size - (vertical padding and border)
+ /// [aka available_block-size]
+ ///
+ /// Return the solution for the equation.
+ fn solve_vertical_constraints_abs_nonreplaced(block_size: MaybeAuto,
+ block_start_margin: MaybeAuto,
+ block_end_margin: MaybeAuto,
+ block_start: MaybeAuto,
+ block_end: MaybeAuto,
+ content_block_size: Au,
+ available_block_size: Au,
+ static_b_offset: Au)
+ -> BSizeConstraintSolution {
+ // Distance from the block-start edge of the Absolute Containing Block to the
+ // block-start margin edge of a hypothetical box that would have been the
+ // first box of the element.
+ let static_position_block_start = static_b_offset;
+
+ let (block_start, block_end, block_size, margin_block_start, margin_block_end) = match (block_start, block_end, block_size) {
+ (Auto, Auto, Auto) => {
+ let margin_block_start = block_start_margin.specified_or_zero();
+ let margin_block_end = block_end_margin.specified_or_zero();
+ let block_start = static_position_block_start;
+ // Now it is the same situation as block-start Specified and block-end
+ // and block-size Auto.
+
+ let block_size = content_block_size;
+ let sum = block_start + block_size + margin_block_start + margin_block_end;
+ (block_start, available_block_size - sum, block_size, margin_block_start, margin_block_end)
+ }
+ (Specified(block_start), Specified(block_end), Specified(block_size)) => {
+ match (block_start_margin, block_end_margin) {
+ (Auto, Auto) => {
+ let total_margin_val = available_block_size - block_start - block_end - block_size;
+ (block_start, block_end, block_size,
+ total_margin_val.scale_by(0.5),
+ total_margin_val.scale_by(0.5))
+ }
+ (Specified(margin_block_start), Auto) => {
+ let sum = block_start + block_end + block_size + margin_block_start;
+ (block_start, block_end, block_size, margin_block_start, available_block_size - sum)
+ }
+ (Auto, Specified(margin_block_end)) => {
+ let sum = block_start + block_end + block_size + margin_block_end;
+ (block_start, block_end, block_size, available_block_size - sum, margin_block_end)
+ }
+ (Specified(margin_block_start), Specified(margin_block_end)) => {
+ // Values are over-constrained. Ignore value for 'block-end'.
+ let sum = block_start + block_size + margin_block_start + margin_block_end;
+ (block_start, available_block_size - sum, block_size, margin_block_start, margin_block_end)
+ }
+ }
+ }
+
+ // For the rest of the cases, auto values for margin are set to 0
+
+ // If only one is Auto, solve for it
+ (Auto, Specified(block_end), Specified(block_size)) => {
+ let margin_block_start = block_start_margin.specified_or_zero();
+ let margin_block_end = block_end_margin.specified_or_zero();
+ let sum = block_end + block_size + margin_block_start + margin_block_end;
+ (available_block_size - sum, block_end, block_size, margin_block_start, margin_block_end)
+ }
+ (Specified(block_start), Auto, Specified(block_size)) => {
+ let margin_block_start = block_start_margin.specified_or_zero();
+ let margin_block_end = block_end_margin.specified_or_zero();
+ let sum = block_start + block_size + margin_block_start + margin_block_end;
+ (block_start, available_block_size - sum, block_size, margin_block_start, margin_block_end)
+ }
+ (Specified(block_start), Specified(block_end), Auto) => {
+ let margin_block_start = block_start_margin.specified_or_zero();
+ let margin_block_end = block_end_margin.specified_or_zero();
+ let sum = block_start + block_end + margin_block_start + margin_block_end;
+ (block_start, block_end, available_block_size - sum, margin_block_start, margin_block_end)
+ }
+
+ // If block-size is auto, then block-size is content block-size. Solve for the
+ // non-auto value.
+ (Specified(block_start), Auto, Auto) => {
+ let margin_block_start = block_start_margin.specified_or_zero();
+ let margin_block_end = block_end_margin.specified_or_zero();
+ let block_size = content_block_size;
+ let sum = block_start + block_size + margin_block_start + margin_block_end;
+ (block_start, available_block_size - sum, block_size, margin_block_start, margin_block_end)
+ }
+ (Auto, Specified(block_end), Auto) => {
+ let margin_block_start = block_start_margin.specified_or_zero();
+ let margin_block_end = block_end_margin.specified_or_zero();
+ let block_size = content_block_size;
+ let sum = block_end + block_size + margin_block_start + margin_block_end;
+ (available_block_size - sum, block_end, block_size, margin_block_start, margin_block_end)
+ }
+
+ (Auto, Auto, Specified(block_size)) => {
+ let margin_block_start = block_start_margin.specified_or_zero();
+ let margin_block_end = block_end_margin.specified_or_zero();
+ let block_start = static_position_block_start;
+ let sum = block_start + block_size + margin_block_start + margin_block_end;
+ (block_start, available_block_size - sum, block_size, margin_block_start, margin_block_end)
+ }
+ };
+ BSizeConstraintSolution::new(block_start, block_end, block_size, margin_block_start, margin_block_end)
+ }
+
+ /// Solve the vertical constraint equation for absolute replaced elements.
+ ///
+ /// Assumption: The used value for block-size has already been calculated.
+ ///
+ /// CSS Section 10.6.5
+ /// Constraint equation:
+ /// block-start + block-end + block-size + margin-block-start + margin-block-end
+ /// = absolute containing block block-size - (vertical padding and border)
+ /// [aka available_block-size]
+ ///
+ /// Return the solution for the equation.
+ fn solve_vertical_constraints_abs_replaced(block_size: Au,
+ block_start_margin: MaybeAuto,
+ block_end_margin: MaybeAuto,
+ block_start: MaybeAuto,
+ block_end: MaybeAuto,
+ _: Au,
+ available_block_size: Au,
+ static_b_offset: Au)
+ -> BSizeConstraintSolution {
+ // Distance from the block-start edge of the Absolute Containing Block to the
+ // block-start margin edge of a hypothetical box that would have been the
+ // first box of the element.
+ let static_position_block_start = static_b_offset;
+
+ let (block_start, block_end, block_size, margin_block_start, margin_block_end) = match (block_start, block_end) {
+ (Auto, Auto) => {
+ let margin_block_start = block_start_margin.specified_or_zero();
+ let margin_block_end = block_end_margin.specified_or_zero();
+ let block_start = static_position_block_start;
+ let sum = block_start + block_size + margin_block_start + margin_block_end;
+ (block_start, available_block_size - sum, block_size, margin_block_start, margin_block_end)
+ }
+ (Specified(block_start), Specified(block_end)) => {
+ match (block_start_margin, block_end_margin) {
+ (Auto, Auto) => {
+ let total_margin_val = available_block_size - block_start - block_end - block_size;
+ (block_start, block_end, block_size,
+ total_margin_val.scale_by(0.5),
+ total_margin_val.scale_by(0.5))
+ }
+ (Specified(margin_block_start), Auto) => {
+ let sum = block_start + block_end + block_size + margin_block_start;
+ (block_start, block_end, block_size, margin_block_start, available_block_size - sum)
+ }
+ (Auto, Specified(margin_block_end)) => {
+ let sum = block_start + block_end + block_size + margin_block_end;
+ (block_start, block_end, block_size, available_block_size - sum, margin_block_end)
+ }
+ (Specified(margin_block_start), Specified(margin_block_end)) => {
+ // Values are over-constrained. Ignore value for 'block-end'.
+ let sum = block_start + block_size + margin_block_start + margin_block_end;
+ (block_start, available_block_size - sum, block_size, margin_block_start, margin_block_end)
+ }
+ }
+ }
+
+ // If only one is Auto, solve for it
+ (Auto, Specified(block_end)) => {
+ let margin_block_start = block_start_margin.specified_or_zero();
+ let margin_block_end = block_end_margin.specified_or_zero();
+ let sum = block_end + block_size + margin_block_start + margin_block_end;
+ (available_block_size - sum, block_end, block_size, margin_block_start, margin_block_end)
+ }
+ (Specified(block_start), Auto) => {
+ let margin_block_start = block_start_margin.specified_or_zero();
+ let margin_block_end = block_end_margin.specified_or_zero();
+ let sum = block_start + block_size + margin_block_start + margin_block_end;
+ (block_start, available_block_size - sum, block_size, margin_block_start, margin_block_end)
+ }
+ };
+ BSizeConstraintSolution::new(block_start, block_end, block_size, margin_block_start, margin_block_end)
+ }
+}
+
+/// Performs block-size calculations potentially multiple times, taking
+/// (assuming an horizontal writing mode) `height`, `min-height`, and `max-height`
+/// into account. After each call to `next()`, the caller must call `.try()` with the
+/// current calculated value of `height`.
+///
+/// See CSS 2.1 § 10.7.
+struct CandidateBSizeIterator {
+ block_size: MaybeAuto,
+ max_block_size: Option<Au>,
+ min_block_size: Au,
+ candidate_value: Au,
+ status: CandidateBSizeIteratorStatus,
+}
+
+impl CandidateBSizeIterator {
+ /// Creates a new candidate block-size iterator. `block_container_block-size` is `None` if the block-size
+ /// of the block container has not been determined yet. It will always be `Some` in the case of
+ /// absolutely-positioned containing blocks.
+ pub fn new(style: &ComputedValues, block_container_block_size: Option<Au>)
+ -> CandidateBSizeIterator {
+ // Per CSS 2.1 § 10.7, (assuming an horizontal writing mode,)
+ // percentages in `min-height` and `max-height` refer to the height of
+ // the containing block.
+ // If that is not determined yet by the time we need to resolve
+ // `min-height` and `max-height`, percentage values are ignored.
+
+ let block_size = match (style.content_block_size(), block_container_block_size) {
+ (LPA_Percentage(percent), Some(block_container_block_size)) => {
+ Specified(block_container_block_size.scale_by(percent))
+ }
+ (LPA_Percentage(_), None) | (LPA_Auto, _) => Auto,
+ (LPA_Length(length), _) => Specified(length),
+ };
+ let max_block_size = match (style.max_block_size(), block_container_block_size) {
+ (LPN_Percentage(percent), Some(block_container_block_size)) => {
+ Some(block_container_block_size.scale_by(percent))
+ }
+ (LPN_Percentage(_), None) | (LPN_None, _) => None,
+ (LPN_Length(length), _) => Some(length),
+ };
+ let min_block_size = match (style.min_block_size(), block_container_block_size) {
+ (LP_Percentage(percent), Some(block_container_block_size)) => {
+ block_container_block_size.scale_by(percent)
+ }
+ (LP_Percentage(_), None) => Au(0),
+ (LP_Length(length), _) => length,
+ };
+
+ CandidateBSizeIterator {
+ block_size: block_size,
+ max_block_size: max_block_size,
+ min_block_size: min_block_size,
+ candidate_value: Au(0),
+ status: InitialCandidateBSizeStatus,
+ }
+ }
+}
+
+impl Iterator<MaybeAuto> for CandidateBSizeIterator {
+ fn next(&mut self) -> Option<MaybeAuto> {
+ self.status = match self.status {
+ InitialCandidateBSizeStatus => TryingBSizeCandidateBSizeStatus,
+ TryingBSizeCandidateBSizeStatus => {
+ match self.max_block_size {
+ Some(max_block_size) if self.candidate_value > max_block_size => {
+ TryingMaxCandidateBSizeStatus
+ }
+ _ if self.candidate_value < self.min_block_size => TryingMinCandidateBSizeStatus,
+ _ => FoundCandidateBSizeStatus,
+ }
+ }
+ TryingMaxCandidateBSizeStatus => {
+ if self.candidate_value < self.min_block_size {
+ TryingMinCandidateBSizeStatus
+ } else {
+ FoundCandidateBSizeStatus
+ }
+ }
+ TryingMinCandidateBSizeStatus | FoundCandidateBSizeStatus => {
+ FoundCandidateBSizeStatus
+ }
+ };
+
+ match self.status {
+ TryingBSizeCandidateBSizeStatus => Some(self.block_size),
+ TryingMaxCandidateBSizeStatus => {
+ Some(Specified(self.max_block_size.unwrap()))
+ }
+ TryingMinCandidateBSizeStatus => {
+ Some(Specified(self.min_block_size))
+ }
+ FoundCandidateBSizeStatus => None,
+ InitialCandidateBSizeStatus => fail!(),
+ }
+ }
+}
+
+enum CandidateBSizeIteratorStatus {
+ InitialCandidateBSizeStatus,
+ TryingBSizeCandidateBSizeStatus,
+ TryingMaxCandidateBSizeStatus,
+ TryingMinCandidateBSizeStatus,
+ FoundCandidateBSizeStatus,
+}
+
+// A helper function used in block-size calculation.
+fn translate_including_floats(cur_b: &mut Au, delta: Au, floats: &mut Floats) {
+ *cur_b = *cur_b + delta;
+ let writing_mode = floats.writing_mode;
+ floats.translate(LogicalSize::new(writing_mode, Au(0), -delta));
+}
+
+/// The real assign-block-sizes traversal for flows with position 'absolute'.
+///
+/// This is a traversal of an Absolute Flow tree.
+/// - Relatively positioned flows and the Root flow start new Absolute flow trees.
+/// - The kids of a flow in this tree will be the flows for which it is the
+/// absolute Containing Block.
+/// - Thus, leaf nodes and inner non-root nodes are all Absolute Flows.
+///
+/// A Flow tree can have several Absolute Flow trees (depending on the number
+/// of relatively positioned flows it has).
+///
+/// Note that flows with position 'fixed' just form a flat list as they all
+/// have the Root flow as their CB.
+struct AbsoluteAssignBSizesTraversal<'a>(&'a LayoutContext<'a>);
+
+impl<'a> PreorderFlowTraversal for AbsoluteAssignBSizesTraversal<'a> {
+ #[inline]
+ fn process(&mut self, flow: &mut Flow) -> bool {
+ let block_flow = flow.as_block();
+
+ // The root of the absolute flow tree is definitely not absolutely
+ // positioned. Nothing to process here.
+ if block_flow.is_root_of_absolute_flow_tree() {
+ return true;
+ }
+
+
+ let AbsoluteAssignBSizesTraversal(ref ctx) = *self;
+ block_flow.calculate_abs_block_size_and_margins(*ctx);
+ true
+ }
+}
+
+/// The store-overflow traversal particular to absolute flows.
+///
+/// Propagate overflow up the Absolute flow tree and update overflow up to and
+/// not including the root of the Absolute flow tree.
+/// After that, it is up to the normal store-overflow traversal to propagate
+/// it further up.
+struct AbsoluteStoreOverflowTraversal<'a>{
+ layout_context: &'a LayoutContext<'a>,
+}
+
+impl<'a> PostorderFlowTraversal for AbsoluteStoreOverflowTraversal<'a> {
+ #[inline]
+ fn process(&mut self, flow: &mut Flow) -> bool {
+ // This will be taken care of by the normal store-overflow traversal.
+ if flow.is_root_of_absolute_flow_tree() {
+ return true;
+ }
+
+ flow.store_overflow(self.layout_context);
+ true
+ }
+}
+
+enum BlockType {
+ BlockReplacedType,
+ BlockNonReplacedType,
+ AbsoluteReplacedType,
+ AbsoluteNonReplacedType,
+ FloatReplacedType,
+ FloatNonReplacedType,
+}
+
+#[deriving(Clone, PartialEq)]
+pub enum MarginsMayCollapseFlag {
+ MarginsMayCollapse,
+ MarginsMayNotCollapse,
+}
+
+#[deriving(PartialEq)]
+enum FormattingContextType {
+ NonformattingContext,
+ BlockFormattingContext,
+ OtherFormattingContext,
+}
+
+// Propagates the `layers_needed_for_descendants` flag appropriately from a child. This is called
+// as part of block-size assignment.
+//
+// If any fixed descendants of kids are present, this kid needs a layer.
+//
+// FIXME(#2006, pcwalton): This is too layer-happy. Like WebKit, we shouldn't do this unless
+// the positioned descendants are actually on top of the fixed kids.
+//
+// TODO(#1244, #2007, pcwalton): Do this for CSS transforms and opacity too, at least if they're
+// animating.
+fn propagate_layer_flag_from_child(layers_needed_for_descendants: &mut bool, kid: &mut Flow) {
+ if kid.is_absolute_containing_block() {
+ let kid_base = flow::mut_base(kid);
+ if kid_base.flags.needs_layer() {
+ *layers_needed_for_descendants = true
+ }
+ } else {
+ let kid_base = flow::mut_base(kid);
+ if kid_base.flags.layers_needed_for_descendants() {
+ *layers_needed_for_descendants = true
+ }
+ }
+}
+
+// A block formatting context.
+#[deriving(Encodable)]
+pub struct BlockFlow {
+ /// Data common to all flows.
+ pub base: BaseFlow,
+
+ /// The associated fragment.
+ pub fragment: Fragment,
+
+ /// TODO: is_root should be a bit field to conserve memory.
+ /// Whether this block flow is the root flow.
+ pub is_root: bool,
+
+ /// Static y offset of an absolute flow from its CB.
+ pub static_b_offset: Au,
+
+ /// The inline-size of the last float prior to this block. This is used to speculatively lay out
+ /// block formatting contexts.
+ previous_float_inline_size: Option<Au>,
+
+ /// Additional floating flow members.
+ pub float: Option<Box<FloatedBlockInfo>>
+}
+
+impl BlockFlow {
+ pub fn from_node(constructor: &mut FlowConstructor, node: &ThreadSafeLayoutNode) -> BlockFlow {
+ BlockFlow {
+ base: BaseFlow::new((*node).clone()),
+ fragment: Fragment::new(constructor, node),
+ is_root: false,
+ static_b_offset: Au::new(0),
+ previous_float_inline_size: None,
+ float: None
+ }
+ }
+
+ pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode, fragment: Fragment) -> BlockFlow {
+ BlockFlow {
+ base: BaseFlow::new((*node).clone()),
+ fragment: fragment,
+ is_root: false,
+ static_b_offset: Au::new(0),
+ previous_float_inline_size: None,
+ float: None
+ }
+ }
+
+ pub fn float_from_node(constructor: &mut FlowConstructor,
+ node: &ThreadSafeLayoutNode,
+ float_kind: FloatKind)
+ -> BlockFlow {
+ let base = BaseFlow::new((*node).clone());
+ BlockFlow {
+ fragment: Fragment::new(constructor, node),
+ is_root: false,
+ static_b_offset: Au::new(0),
+ previous_float_inline_size: None,
+ float: Some(box FloatedBlockInfo::new(float_kind, base.writing_mode)),
+ base: base,
+ }
+ }
+
+ /// Return the type of this block.
+ ///
+ /// This determines the algorithm used to calculate inline-size, block-size, and the
+ /// relevant margins for this Block.
+ fn block_type(&self) -> BlockType {
+ if self.is_absolutely_positioned() {
+ if self.is_replaced_content() {
+ AbsoluteReplacedType
+ } else {
+ AbsoluteNonReplacedType
+ }
+ } else if self.is_float() {
+ if self.is_replaced_content() {
+ FloatReplacedType
+ } else {
+ FloatNonReplacedType
+ }
+ } else {
+ if self.is_replaced_content() {
+ BlockReplacedType
+ } else {
+ BlockNonReplacedType
+ }
+ }
+ }
+
+ /// Compute the used value of inline-size for this Block.
+ fn compute_used_inline_size(&mut self, ctx: &LayoutContext, containing_block_inline_size: Au) {
+ let block_type = self.block_type();
+ match block_type {
+ AbsoluteReplacedType => {
+ let inline_size_computer = AbsoluteReplaced;
+ inline_size_computer.compute_used_inline_size(self, ctx, containing_block_inline_size);
+ }
+ AbsoluteNonReplacedType => {
+ let inline_size_computer = AbsoluteNonReplaced;
+ inline_size_computer.compute_used_inline_size(self, ctx, containing_block_inline_size);
+ }
+ FloatReplacedType => {
+ let inline_size_computer = FloatReplaced;
+ inline_size_computer.compute_used_inline_size(self, ctx, containing_block_inline_size);
+ }
+ FloatNonReplacedType => {
+ let inline_size_computer = FloatNonReplaced;
+ inline_size_computer.compute_used_inline_size(self, ctx, containing_block_inline_size);
+ }
+ BlockReplacedType => {
+ let inline_size_computer = BlockReplaced;
+ inline_size_computer.compute_used_inline_size(self, ctx, containing_block_inline_size);
+ }
+ BlockNonReplacedType => {
+ let inline_size_computer = BlockNonReplaced;
+ inline_size_computer.compute_used_inline_size(self, ctx, containing_block_inline_size);
+ }
+ }
+ }
+
+ /// Return this flow's fragment.
+ pub fn fragment<'a>(&'a mut self) -> &'a mut Fragment {
+ &mut self.fragment
+ }
+
+ /// Return the static x offset from the appropriate Containing Block for this flow.
+ pub fn static_i_offset(&self) -> Au {
+ if self.is_fixed() {
+ self.base.fixed_static_i_offset
+ } else {
+ self.base.absolute_static_i_offset
+ }
+ }
+
+ /// Return the size of the Containing Block for this flow.
+ ///
+ /// Right now, this only gets the Containing Block size for absolutely
+ /// positioned elements.
+ /// Note: Assume this is called in a top-down traversal, so it is ok to
+ /// reference the CB.
+ #[inline]
+ pub fn containing_block_size(&mut self, viewport_size: Size2D<Au>) -> LogicalSize<Au> {
+ assert!(self.is_absolutely_positioned());
+ if self.is_fixed() {
+ // Initial containing block is the CB for the root
+ LogicalSize::from_physical(self.base.writing_mode, viewport_size)
+ } else {
+ self.base.absolute_cb.generated_containing_block_rect().size
+ }
+ }
+
+ /// Traverse the Absolute flow tree in preorder.
+ ///
+ /// Traverse all your direct absolute descendants, who will then traverse
+ /// their direct absolute descendants.
+ /// Also, set the static y offsets for each descendant (using the value
+ /// which was bubbled up during normal assign-block-size).
+ ///
+ /// Return true if the traversal is to continue or false to stop.
+ fn traverse_preorder_absolute_flows<T:PreorderFlowTraversal>(&mut self,
+ traversal: &mut T)
+ -> bool {
+ let flow = self as &mut Flow;
+ if traversal.should_prune(flow) {
+ return true
+ }
+
+ if !traversal.process(flow) {
+ return false
+ }
+
+ let cb_block_start_edge_offset = flow.generated_containing_block_rect().start.b;
+ let mut descendant_offset_iter = mut_base(flow).abs_descendants.iter_with_offset();
+ // Pass in the respective static y offset for each descendant.
+ for (ref mut descendant_link, ref y_offset) in descendant_offset_iter {
+ let block = descendant_link.as_block();
+ // The stored y_offset is wrt to the flow box.
+ // Translate it to the CB (which is the padding box).
+ block.static_b_offset = **y_offset - cb_block_start_edge_offset;
+ if !block.traverse_preorder_absolute_flows(traversal) {
+ return false
+ }
+ }
+
+ true
+ }
+
+ /// Traverse the Absolute flow tree in postorder.
+ ///
+ /// Return true if the traversal is to continue or false to stop.
+ fn traverse_postorder_absolute_flows<T:PostorderFlowTraversal>(&mut self,
+ traversal: &mut T)
+ -> bool {
+ let flow = self as &mut Flow;
+ if traversal.should_prune(flow) {
+ return true
+ }
+
+ for descendant_link in mut_base(flow).abs_descendants.iter() {
+ let block = descendant_link.as_block();
+ if !block.traverse_postorder_absolute_flows(traversal) {
+ return false
+ }
+ }
+
+ traversal.process(flow)
+ }
+
+ /// Return true if this has a replaced fragment.
+ ///
+ /// The only two types of replaced fragments currently are text fragments
+ /// and image fragments.
+ fn is_replaced_content(&self) -> bool {
+ match self.fragment.specific {
+ ScannedTextFragment(_) | ImageFragment(_) => true,
+ _ => false,
+ }
+ }
+
+ /// Return shrink-to-fit inline-size.
+ ///
+ /// This is where we use the preferred inline-sizes and minimum inline-sizes
+ /// calculated in the bubble-inline-sizes traversal.
+ fn get_shrink_to_fit_inline_size(&self, available_inline_size: Au) -> Au {
+ geometry::min(self.base.intrinsic_inline_sizes.preferred_inline_size,
+ geometry::max(self.base.intrinsic_inline_sizes.minimum_inline_size, available_inline_size))
+ }
+
+ /// Collect and update static y-offsets bubbled up by kids.
+ ///
+ /// This would essentially give us offsets of all absolutely positioned
+ /// direct descendants and all fixed descendants, in tree order.
+ ///
+ /// Assume that this is called in a bottom-up traversal (specifically, the
+ /// assign-block-size traversal). So, kids have their flow origin already set.
+ /// In the case of absolute flow kids, they have their hypothetical box
+ /// position already set.
+ fn collect_static_b_offsets_from_kids(&mut self) {
+ let mut abs_descendant_y_offsets = Vec::new();
+ for kid in self.base.child_iter() {
+ let mut gives_abs_offsets = true;
+ if kid.is_block_like() {
+ let kid_block = kid.as_block();
+ if kid_block.is_fixed() || kid_block.is_absolutely_positioned() {
+ // It won't contribute any offsets for descendants because it
+ // would be the CB for them.
+ gives_abs_offsets = false;
+ // Give the offset for the current absolute flow alone.
+ abs_descendant_y_offsets.push(kid_block.get_hypothetical_block_start_edge());
+ } else if kid_block.is_positioned() {
+ // It won't contribute any offsets because it would be the CB
+ // for the descendants.
+ gives_abs_offsets = false;
+ }
+ }
+
+ if gives_abs_offsets {
+ let kid_base = flow::mut_base(kid);
+ // Avoid copying the offset vector.
+ let offsets = mem::replace(&mut kid_base.abs_descendants.static_b_offsets, Vec::new());
+ // Consume all the static y-offsets bubbled up by kid.
+ for y_offset in offsets.move_iter() {
+ // The offsets are wrt the kid flow box. Translate them to current flow.
+ abs_descendant_y_offsets.push(y_offset + kid_base.position.start.b);
+ }
+ }
+ }
+ self.base.abs_descendants.static_b_offsets = abs_descendant_y_offsets;
+ }
+
+ /// If this is the root flow, shifts all kids down and adjusts our size to account for
+ /// root flow margins, which should never be collapsed according to CSS § 8.3.1.
+ ///
+ /// TODO(#2017, pcwalton): This is somewhat inefficient (traverses kids twice); can we do
+ /// better?
+ fn adjust_fragments_for_collapsed_margins_if_root(&mut self) {
+ if !self.is_root() {
+ return
+ }
+
+ let (block_start_margin_value, block_end_margin_value) = match self.base.collapsible_margins {
+ MarginsCollapseThrough(_) => fail!("Margins unexpectedly collapsed through root flow."),
+ MarginsCollapse(block_start_margin, block_end_margin) => {
+ (block_start_margin.collapse(), block_end_margin.collapse())
+ }
+ NoCollapsibleMargins(block_start, block_end) => (block_start, block_end),
+ };
+
+ // Shift all kids down (or up, if margins are negative) if necessary.
+ if block_start_margin_value != Au(0) {
+ for kid in self.base.child_iter() {
+ let kid_base = flow::mut_base(kid);
+ kid_base.position.start.b = kid_base.position.start.b + block_start_margin_value
+ }
+ }
+
+ self.base.position.size.block = self.base.position.size.block + block_start_margin_value +
+ block_end_margin_value;
+ self.fragment.border_box.size.block = self.fragment.border_box.size.block + block_start_margin_value +
+ block_end_margin_value;
+ }
+
+ /// Assign block-size for current flow.
+ ///
+ /// * Collapse margins for flow's children and set in-flow child flows' y-coordinates now that
+ /// we know their block-sizes.
+ /// * Calculate and set the block-size of the current flow.
+ /// * Calculate block-size, vertical margins, and y-coordinate for the flow's box. Ideally, this
+ /// should be calculated using CSS § 10.6.7.
+ ///
+ /// For absolute flows, we store the calculated content block-size for the flow. We defer the
+ /// calculation of the other values until a later traversal.
+ ///
+ /// `inline(always)` because this is only ever called by in-order or non-in-order top-level
+ /// methods
+ #[inline(always)]
+ pub fn assign_block_size_block_base<'a>(&mut self,
+ layout_context: &'a LayoutContext<'a>,
+ margins_may_collapse: MarginsMayCollapseFlag) {
+ let _scope = layout_debug_scope!("assign_block_size_block_base {:s}", self.base.debug_id());
+
+ // Our current border-box position.
+ let mut cur_b = Au(0);
+
+ // Absolute positioning establishes a block formatting context. Don't propagate floats
+ // in or out. (But do propagate them between kids.)
+ if self.is_absolutely_positioned() {
+ self.base.floats = Floats::new(self.fragment.style.writing_mode);
+ }
+ if margins_may_collapse != MarginsMayCollapse {
+ self.base.floats = Floats::new(self.fragment.style.writing_mode);
+ }
+
+ let mut margin_collapse_info = MarginCollapseInfo::new();
+ self.base.floats.translate(LogicalSize::new(
+ self.fragment.style.writing_mode, -self.fragment.inline_start_offset(), Au(0)));
+
+ // The sum of our block-start border and block-start padding.
+ let block_start_offset = self.fragment.border_padding.block_start;
+ translate_including_floats(&mut cur_b, block_start_offset, &mut self.base.floats);
+
+ let can_collapse_block_start_margin_with_kids =
+ margins_may_collapse == MarginsMayCollapse &&
+ !self.is_absolutely_positioned() &&
+ self.fragment.border_padding.block_start == Au(0);
+ margin_collapse_info.initialize_block_start_margin(&self.fragment,
+ can_collapse_block_start_margin_with_kids);
+
+ // At this point, `cur_b` is at the content edge of our box. Now iterate over children.
+ let mut floats = self.base.floats.clone();
+ let mut layers_needed_for_descendants = false;
+ for kid in self.base.child_iter() {
+ if kid.is_absolutely_positioned() {
+ // Assume that the *hypothetical box* for an absolute flow starts immediately after
+ // the block-end border edge of the previous flow.
+ flow::mut_base(kid).position.start.b = cur_b;
+ kid.assign_block_size_for_inorder_child_if_necessary(layout_context);
+ propagate_layer_flag_from_child(&mut layers_needed_for_descendants, kid);
+
+ // Skip the collapsing and float processing for absolute flow kids and continue
+ // with the next flow.
+ continue
+ }
+
+ // Assign block-size now for the child if it was impacted by floats and we couldn't before.
+ flow::mut_base(kid).floats = floats.clone();
+ if kid.is_float() {
+ // FIXME(pcwalton): Using `position.start.b` to mean the float ceiling is a
+ // bit of a hack.
+ flow::mut_base(kid).position.start.b =
+ margin_collapse_info.current_float_ceiling();
+ propagate_layer_flag_from_child(&mut layers_needed_for_descendants, kid);
+
+ let kid_was_impacted_by_floats =
+ kid.assign_block_size_for_inorder_child_if_necessary(layout_context);
+ assert!(kid_was_impacted_by_floats); // As it was a float itself...
+
+ let kid_base = flow::mut_base(kid);
+ kid_base.position.start.b = cur_b;
+ floats = kid_base.floats.clone();
+ continue
+ }
+
+
+ // If we have clearance, assume there are no floats in.
+ //
+ // FIXME(#2008, pcwalton): This could be wrong if we have `clear: left` or `clear:
+ // right` and there are still floats to impact, of course. But this gets complicated
+ // with margin collapse. Possibly the right thing to do is to lay out the block again
+ // in this rare case. (Note that WebKit can lay blocks out twice; this may be related,
+ // although I haven't looked into it closely.)
+ if kid.float_clearance() != clear::none {
+ flow::mut_base(kid).floats = Floats::new(self.fragment.style.writing_mode)
+ }
+
+ // Lay the child out if this was an in-order traversal.
+ let kid_was_impacted_by_floats =
+ kid.assign_block_size_for_inorder_child_if_necessary(layout_context);
+
+ // Mark flows for layerization if necessary to handle painting order correctly.
+ propagate_layer_flag_from_child(&mut layers_needed_for_descendants, kid);
+
+ // Handle any (possibly collapsed) top margin.
+ let delta = margin_collapse_info.advance_block_start_margin(
+ &flow::base(kid).collapsible_margins);
+ translate_including_floats(&mut cur_b, delta, &mut floats);
+
+ // Clear past the floats that came in, if necessary.
+ let clearance = match kid.float_clearance() {
+ clear::none => Au(0),
+ clear::left => floats.clearance(ClearLeft),
+ clear::right => floats.clearance(ClearRight),
+ clear::both => floats.clearance(ClearBoth),
+ };
+ cur_b = cur_b + clearance;
+
+ // At this point, `cur_b` is at the border edge of the child.
+ flow::mut_base(kid).position.start.b = cur_b;
+
+ // Now pull out the child's outgoing floats. We didn't do this immediately after the
+ // `assign_block-size_for_inorder_child_if_necessary` call because clearance on a block
+ // operates on the floats that come *in*, not the floats that go *out*.
+ if kid_was_impacted_by_floats {
+ floats = flow::mut_base(kid).floats.clone()
+ }
+
+ // Move past the child's border box. Do not use the `translate_including_floats`
+ // function here because the child has already translated floats past its border box.
+ let kid_base = flow::mut_base(kid);
+ cur_b = cur_b + kid_base.position.size.block;
+
+ // Handle any (possibly collapsed) block-end margin.
+ let delta = margin_collapse_info.advance_block_end_margin(&kid_base.collapsible_margins);
+ translate_including_floats(&mut cur_b, delta, &mut floats);
+ }
+
+ // Mark ourselves for layerization if that will be necessary to paint in the proper order
+ // (CSS 2.1, Appendix E).
+ self.base.flags.set_layers_needed_for_descendants(layers_needed_for_descendants);
+
+ // Collect various offsets needed by absolutely positioned descendants.
+ self.collect_static_b_offsets_from_kids();
+
+ // Add in our block-end margin and compute our collapsible margins.
+ let can_collapse_block_end_margin_with_kids =
+ margins_may_collapse == MarginsMayCollapse &&
+ !self.is_absolutely_positioned() &&
+ self.fragment.border_padding.block_end == Au(0);
+ let (collapsible_margins, delta) =
+ margin_collapse_info.finish_and_compute_collapsible_margins(
+ &self.fragment,
+ can_collapse_block_end_margin_with_kids);
+ self.base.collapsible_margins = collapsible_margins;
+ translate_including_floats(&mut cur_b, delta, &mut floats);
+
+ // FIXME(#2003, pcwalton): The max is taken here so that you can scroll the page, but this
+ // is not correct behavior according to CSS 2.1 § 10.5. Instead I think we should treat the
+ // root element as having `overflow: scroll` and use the layers-based scrolling
+ // infrastructure to make it scrollable.
+ let mut block_size = cur_b - block_start_offset;
+ if self.is_root() {
+ let screen_size = LogicalSize::from_physical(
+ self.fragment.style.writing_mode, layout_context.shared.screen_size);
+ block_size = Au::max(screen_size.block, block_size)
+ }
+
+ if self.is_absolutely_positioned() {
+ // The content block-size includes all the floats per CSS 2.1 § 10.6.7. The easiest way to
+ // handle this is to just treat this as clearance.
+ block_size = block_size + floats.clearance(ClearBoth);
+
+ // Fixed position layers get layers.
+ if self.is_fixed() {
+ self.base.flags.set_needs_layer(true)
+ }
+
+ // Store the content block-size for use in calculating the absolute flow's dimensions
+ // later.
+ self.fragment.border_box.size.block = block_size;
+ return
+ }
+
+ let mut candidate_block_size_iterator = CandidateBSizeIterator::new(self.fragment.style(),
+ None);
+ for candidate_block_size in candidate_block_size_iterator {
+ candidate_block_size_iterator.candidate_value = match candidate_block_size {
+ Auto => block_size,
+ Specified(value) => value
+ }
+ }
+
+ // Adjust `cur_b` as necessary to account for the explicitly-specified block-size.
+ block_size = candidate_block_size_iterator.candidate_value;
+ let delta = block_size - (cur_b - block_start_offset);
+ translate_including_floats(&mut cur_b, delta, &mut floats);
+
+ // Compute content block-size and noncontent block-size.
+ let block_end_offset = self.fragment.border_padding.block_end;
+ translate_including_floats(&mut cur_b, block_end_offset, &mut floats);
+
+ // Now that `cur_b` is at the block-end of the border box, compute the final border box
+ // position.
+ self.fragment.border_box.size.block = cur_b;
+ self.fragment.border_box.start.b = Au(0);
+ self.base.position.size.block = cur_b;
+
+ self.base.floats = floats.clone();
+ self.adjust_fragments_for_collapsed_margins_if_root();
+
+ if self.is_root_of_absolute_flow_tree() {
+ // Assign block-sizes for all flows in this Absolute flow tree.
+ // This is preorder because the block-size of an absolute flow may depend on
+ // the block-size of its CB, which may also be an absolute flow.
+ self.traverse_preorder_absolute_flows(&mut AbsoluteAssignBSizesTraversal(
+ layout_context));
+ // Store overflow for all absolute descendants.
+ self.traverse_postorder_absolute_flows(&mut AbsoluteStoreOverflowTraversal {
+ layout_context: layout_context,
+ });
+ }
+ }
+
+ /// Add placement information about current float flow for use by the parent.
+ ///
+ /// Also, use information given by parent about other floats to find out our relative position.
+ ///
+ /// This does not give any information about any float descendants because they do not affect
+ /// elements outside of the subtree rooted at this float.
+ ///
+ /// This function is called on a kid flow by a parent. Therefore, `assign_block-size_float` was
+ /// already called on this kid flow by the traversal function. So, the values used are
+ /// well-defined.
+ pub fn place_float(&mut self) {
+ let block_size = self.fragment.border_box.size.block;
+ let clearance = match self.fragment.clear() {
+ None => Au(0),
+ Some(clear) => self.base.floats.clearance(clear),
+ };
+
+ let margin_block_size = self.fragment.margin.block_start_end();
+ let info = PlacementInfo {
+ size: LogicalSize::new(
+ self.fragment.style.writing_mode,
+ self.base.position.size.inline + self.fragment.margin.inline_start_end() +
+ self.fragment.border_padding.inline_start_end(),
+ block_size + margin_block_size),
+ ceiling: clearance + self.base.position.start.b,
+ max_inline_size: self.float.get_ref().containing_inline_size,
+ kind: self.float.get_ref().float_kind,
+ };
+
+ // Place the float and return the `Floats` back to the parent flow.
+ // After, grab the position and use that to set our position.
+ self.base.floats.add_float(&info);
+
+ self.float.get_mut_ref().rel_pos = self.base.floats.last_float_pos().unwrap();
+ }
+
+ /// Assign block-size for current flow.
+ ///
+ /// + Set in-flow child flows' y-coordinates now that we know their
+ /// block-sizes. This _doesn't_ do any margin collapsing for its children.
+ /// + Calculate block-size and y-coordinate for the flow's box. Ideally, this
+ /// should be calculated using CSS Section 10.6.7
+ ///
+ /// It does not calculate the block-size of the flow itself.
+ pub fn assign_block_size_float<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
+ let _scope = layout_debug_scope!("assign_block_size_float {:s}", self.base.debug_id());
+
+ let mut floats = Floats::new(self.fragment.style.writing_mode);
+ for kid in self.base.child_iter() {
+ flow::mut_base(kid).floats = floats.clone();
+ kid.assign_block_size_for_inorder_child_if_necessary(ctx);
+ floats = flow::mut_base(kid).floats.clone();
+ }
+
+ let block_start_offset = self.fragment.margin.block_start + self.fragment.border_padding.block_start;
+ let mut cur_b = block_start_offset;
+
+ // cur_b is now at the block-start content edge
+
+ for kid in self.base.child_iter() {
+ let child_base = flow::mut_base(kid);
+ child_base.position.start.b = cur_b;
+ // cur_b is now at the block-end margin edge of kid
+ cur_b = cur_b + child_base.position.size.block;
+ }
+
+ // Intrinsic height should include floating descendants with a margin
+ // below the element's bottom edge (see CSS Section 10.6.7).
+ let content_block_size = geometry::max(
+ cur_b - block_start_offset,
+ floats.clearance(ClearBoth));
+
+ // Floats establish a block formatting context, so we discard the output floats here.
+ drop(floats);
+
+ // The associated fragment has the border box of this flow.
+ self.fragment.border_box.start.b = self.fragment.margin.block_start;
+
+ // Calculate content block-size, taking `min-block-size` and `max-block-size` into account.
+ let mut candidate_block_size_iterator = CandidateBSizeIterator::new(self.fragment.style(), None);
+ for candidate_block_size in candidate_block_size_iterator {
+ candidate_block_size_iterator.candidate_value = match candidate_block_size {
+ Auto => content_block_size,
+ Specified(value) => value,
+ }
+ }
+
+ let content_block_size = candidate_block_size_iterator.candidate_value;
+ let noncontent_block_size = self.fragment.border_padding.block_start_end();
+ debug!("assign_block_size_float -- block_size: {}", content_block_size + noncontent_block_size);
+ self.fragment.border_box.size.block = content_block_size + noncontent_block_size;
+ }
+
+ fn build_display_list_block_common(&mut self,
+ layout_context: &LayoutContext,
+ offset: LogicalPoint<Au>,
+ background_border_level: BackgroundAndBorderLevel) {
+ let rel_offset =
+ self.fragment.relative_position(&self.base
+ .absolute_position_info
+ .relative_containing_block_size);
+
+ // FIXME(#2795): Get the real container size
+ let container_size = Size2D::zero();
+
+ // Add the box that starts the block context.
+ let mut display_list = DisplayList::new();
+ let mut accumulator = self.fragment.build_display_list(
+ &mut display_list,
+ layout_context,
+ self.base.abs_position + (offset + rel_offset).to_physical(
+ self.base.writing_mode, container_size),
+ background_border_level);
+
+ let mut child_layers = DList::new();
+ for kid in self.base.child_iter() {
+ if kid.is_absolutely_positioned() {
+ // All absolute flows will be handled by their containing block.
+ continue
+ }
+
+ accumulator.push_child(&mut display_list, kid);
+ child_layers.append(mem::replace(&mut flow::mut_base(kid).layers, DList::new()))
+ }
+
+ // Process absolute descendant links.
+ for abs_descendant_link in self.base.abs_descendants.iter() {
+ // TODO(pradeep): Send in our absolute position directly.
+ accumulator.push_child(&mut display_list, abs_descendant_link);
+ child_layers.append(mem::replace(&mut flow::mut_base(abs_descendant_link).layers,
+ DList::new()));
+ }
+
+ accumulator.finish(&mut *self, display_list);
+ self.base.layers = child_layers
+ }
+
+ /// Add display items for current block.
+ ///
+ /// Set the absolute position for children after doing any offsetting for
+ /// position: relative.
+ pub fn build_display_list_block(&mut self, layout_context: &LayoutContext) {
+ if self.is_float() {
+ // TODO(#2009, pcwalton): This is a pseudo-stacking context. We need to merge `z-index:
+ // auto` kids into the parent stacking context, when that is supported.
+ self.build_display_list_float(layout_context)
+ } else if self.is_absolutely_positioned() {
+ self.build_display_list_abs(layout_context)
+ } else {
+ let writing_mode = self.base.writing_mode;
+ self.build_display_list_block_common(
+ layout_context, LogicalPoint::zero(writing_mode), BlockLevel)
+ }
+ }
+
+ pub fn build_display_list_float(&mut self, layout_context: &LayoutContext) {
+ let float_offset = self.float.get_ref().rel_pos;
+ self.build_display_list_block_common(layout_context,
+ float_offset,
+ RootOfStackingContextLevel);
+ self.base.display_list = mem::replace(&mut self.base.display_list,
+ DisplayList::new()).flatten(FloatStackingLevel)
+ }
+
+ /// Calculate and set the block-size, offsets, etc. for absolutely positioned flow.
+ ///
+ /// The layout for its in-flow children has been done during normal layout.
+ /// This is just the calculation of:
+ /// + block-size for the flow
+ /// + y-coordinate of the flow wrt its Containing Block.
+ /// + block-size, vertical margins, and y-coordinate for the flow's box.
+ fn calculate_abs_block_size_and_margins(&mut self, ctx: &LayoutContext) {
+ let containing_block_block_size = self.containing_block_size(ctx.shared.screen_size).block;
+ let static_b_offset = self.static_b_offset;
+
+ // This is the stored content block-size value from assign-block-size
+ let content_block_size = self.fragment.content_box().size.block;
+
+ let mut solution = None;
+ {
+ // Non-auto margin-block-start and margin-block-end values have already been
+ // calculated during assign-inline-size.
+ let margin = self.fragment.style().logical_margin();
+ let margin_block_start = match margin.block_start {
+ LPA_Auto => Auto,
+ _ => Specified(self.fragment.margin.block_start)
+ };
+ let margin_block_end = match margin.block_end {
+ LPA_Auto => Auto,
+ _ => Specified(self.fragment.margin.block_end)
+ };
+
+ let block_start;
+ let block_end;
+ {
+ let position = self.fragment.style().logical_position();
+ block_start = MaybeAuto::from_style(position.block_start, containing_block_block_size);
+ block_end = MaybeAuto::from_style(position.block_end, containing_block_block_size);
+ }
+
+ let available_block_size = containing_block_block_size - self.fragment.border_padding.block_start_end();
+ if self.is_replaced_content() {
+ // Calculate used value of block-size just like we do for inline replaced elements.
+ // TODO: Pass in the containing block block-size when Fragment's
+ // assign-block-size can handle it correctly.
+ self.fragment.assign_replaced_block_size_if_necessary();
+ // TODO: Right now, this content block-size value includes the
+ // margin because of erroneous block-size calculation in fragment.
+ // Check this when that has been fixed.
+ let block_size_used_val = self.fragment.border_box.size.block;
+ solution = Some(BSizeConstraintSolution::solve_vertical_constraints_abs_replaced(
+ block_size_used_val,
+ margin_block_start,
+ margin_block_end,
+ block_start,
+ block_end,
+ content_block_size,
+ available_block_size,
+ static_b_offset));
+ } else {
+ let style = self.fragment.style();
+ let mut candidate_block_size_iterator =
+ CandidateBSizeIterator::new(style, Some(containing_block_block_size));
+
+ for block_size_used_val in candidate_block_size_iterator {
+ solution =
+ Some(BSizeConstraintSolution::solve_vertical_constraints_abs_nonreplaced(
+ block_size_used_val,
+ margin_block_start,
+ margin_block_end,
+ block_start,
+ block_end,
+ content_block_size,
+ available_block_size,
+ static_b_offset));
+
+ candidate_block_size_iterator.candidate_value = solution.unwrap().block_size
+ }
+ }
+ }
+
+ let solution = solution.unwrap();
+ self.fragment.margin.block_start = solution.margin_block_start;
+ self.fragment.margin.block_end = solution.margin_block_end;
+ self.fragment.border_box.start.b = Au(0);
+ self.fragment.border_box.size.block = solution.block_size + self.fragment.border_padding.block_start_end();
+
+ self.base.position.start.b = solution.block_start + self.fragment.margin.block_start;
+ self.base.position.size.block = solution.block_size + self.fragment.border_padding.block_start_end();
+ }
+
+ /// Add display items for Absolutely Positioned flow.
+ fn build_display_list_abs(&mut self, layout_context: &LayoutContext) {
+ let writing_mode = self.base.writing_mode;
+ self.build_display_list_block_common(layout_context,
+ LogicalPoint::zero(writing_mode),
+ RootOfStackingContextLevel);
+
+ if !self.base.absolute_position_info.layers_needed_for_positioned_flows &&
+ !self.base.flags.needs_layer() {
+ // We didn't need a layer.
+ //
+ // TODO(#781, pcwalton): `z-index`.
+ self.base.display_list =
+ mem::replace(&mut self.base.display_list,
+ DisplayList::new()).flatten(PositionedDescendantStackingLevel(0));
+ return
+ }
+
+ // If we got here, then we need a new layer.
+ let layer_rect = self.base.position.union(&self.base.overflow);
+ let size = Size2D(layer_rect.size.inline.to_nearest_px() as uint,
+ layer_rect.size.block.to_nearest_px() as uint);
+ let origin = Point2D(layer_rect.start.i.to_nearest_px() as uint,
+ layer_rect.start.b.to_nearest_px() as uint);
+ let scroll_policy = if self.is_fixed() {
+ FixedPosition
+ } else {
+ Scrollable
+ };
+ let display_list = mem::replace(&mut self.base.display_list, DisplayList::new());
+ let new_layer = RenderLayer {
+ id: self.layer_id(0),
+ display_list: Arc::new(display_list.flatten(ContentStackingLevel)),
+ position: Rect(origin, size),
+ background_color: color::rgba(1.0, 1.0, 1.0, 0.0),
+ scroll_policy: scroll_policy,
+ };
+ self.base.layers.push(new_layer)
+ }
+
+ /// Return the block-start outer edge of the hypothetical box for an absolute flow.
+ ///
+ /// This is wrt its parent flow box.
+ ///
+ /// During normal layout assign-block-size, the absolute flow's position is
+ /// roughly set to its static position (the position it would have had in
+ /// the normal flow).
+ fn get_hypothetical_block_start_edge(&self) -> Au {
+ self.base.position.start.b
+ }
+
+ /// Assigns the computed inline-start content edge and inline-size to all the children of this block flow.
+ /// Also computes whether each child will be impacted by floats.
+ ///
+ /// `#[inline(always)]` because this is called only from block or table inline-size assignment and
+ /// the code for block layout is significantly simpler.
+ #[inline(always)]
+ pub fn propagate_assigned_inline_size_to_children(&mut self,
+ inline_start_content_edge: Au,
+ content_inline_size: Au,
+ opt_col_inline_sizes: Option<Vec<Au>>) {
+ // Keep track of whether floats could impact each child.
+ let mut inline_start_floats_impact_child = self.base.flags.impacted_by_left_floats();
+ let mut inline_end_floats_impact_child = self.base.flags.impacted_by_right_floats();
+
+ let absolute_static_i_offset = if self.is_positioned() {
+ // This flow is the containing block. The static X offset will be the inline-start padding
+ // edge.
+ self.fragment.border_padding.inline_start
+ - self.fragment.style().logical_border_width().inline_start
+ } else {
+ // For kids, the inline-start margin edge will be at our inline-start content edge. The current static
+ // offset is at our inline-start margin edge. So move in to the inline-start content edge.
+ self.base.absolute_static_i_offset + inline_start_content_edge
+ };
+
+ let fixed_static_i_offset = self.base.fixed_static_i_offset + inline_start_content_edge;
+ let flags = self.base.flags.clone();
+
+ // This value is used only for table cells.
+ let mut inline_start_margin_edge = inline_start_content_edge;
+
+ // The inline-size of the last float, if there was one. This is used for estimating the inline-sizes of
+ // block formatting contexts. (We estimate that the inline-size of any block formatting context
+ // that we see will be based on the inline-size of the containing block as well as the last float
+ // seen before it.)
+ let mut last_float_inline_size = None;
+
+ for (i, kid) in self.base.child_iter().enumerate() {
+ if kid.is_block_flow() {
+ let kid_block = kid.as_block();
+ kid_block.base.absolute_static_i_offset = absolute_static_i_offset;
+ kid_block.base.fixed_static_i_offset = fixed_static_i_offset;
+
+ if kid_block.is_float() {
+ last_float_inline_size = Some(kid_block.base.intrinsic_inline_sizes.preferred_inline_size)
+ } else {
+ kid_block.previous_float_inline_size = last_float_inline_size
+ }
+ }
+
+ // The inline-start margin edge of the child flow is at our inline-start content edge, and its inline-size
+ // is our content inline-size.
+ flow::mut_base(kid).position.start.i = inline_start_content_edge;
+ flow::mut_base(kid).position.size.inline = content_inline_size;
+
+ // Determine float impaction.
+ match kid.float_clearance() {
+ clear::none => {}
+ clear::left => inline_start_floats_impact_child = false,
+ clear::right => inline_end_floats_impact_child = false,
+ clear::both => {
+ inline_start_floats_impact_child = false;
+ inline_end_floats_impact_child = false;
+ }
+ }
+ {
+ let kid_base = flow::mut_base(kid);
+ inline_start_floats_impact_child = inline_start_floats_impact_child ||
+ kid_base.flags.has_left_floated_descendants();
+ inline_end_floats_impact_child = inline_end_floats_impact_child ||
+ kid_base.flags.has_right_floated_descendants();
+ kid_base.flags.set_impacted_by_left_floats(inline_start_floats_impact_child);
+ kid_base.flags.set_impacted_by_right_floats(inline_end_floats_impact_child);
+ }
+
+ // Handle tables.
+ match opt_col_inline_sizes {
+ Some(ref col_inline_sizes) => {
+ propagate_column_inline_sizes_to_child(kid,
+ i,
+ content_inline_size,
+ col_inline_sizes.as_slice(),
+ &mut inline_start_margin_edge)
+ }
+ None => {}
+ }
+
+ // Per CSS 2.1 § 16.3.1, text alignment propagates to all children in flow.
+ //
+ // TODO(#2018, pcwalton): Do this in the cascade instead.
+ flow::mut_base(kid).flags.propagate_text_alignment_from_parent(flags.clone())
+ }
+ }
+
+ /// Determines the type of formatting context this is. See the definition of
+ /// `FormattingContextType`.
+ fn formatting_context_type(&self) -> FormattingContextType {
+ let style = self.fragment.style();
+ if style.get_box().float != float::none {
+ return OtherFormattingContext
+ }
+ match style.get_box().display {
+ display::table_cell | display::table_caption | display::inline_block => {
+ OtherFormattingContext
+ }
+ _ if style.get_box().position == position::static_ &&
+ style.get_box().overflow != overflow::visible => {
+ BlockFormattingContext
+ }
+ _ => NonformattingContext,
+ }
+ }
+}
+
+impl Flow for BlockFlow {
+ fn class(&self) -> FlowClass {
+ BlockFlowClass
+ }
+
+ fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow {
+ self
+ }
+
+ fn as_immutable_block<'a>(&'a self) -> &'a BlockFlow {
+ self
+ }
+
+ /// Returns the direction that this flow clears floats in, if any.
+ fn float_clearance(&self) -> clear::T {
+ self.fragment.style().get_box().clear
+ }
+
+ /// Pass 1 of reflow: computes minimum and preferred inline-sizes.
+ ///
+ /// Recursively (bottom-up) determine the flow's minimum and preferred inline-sizes. When called on
+ /// this flow, all child flows have had their minimum and preferred inline-sizes set. This function
+ /// must decide minimum/preferred inline-sizes based on its children's inline-sizes and the dimensions of
+ /// any fragments it is responsible for flowing.
+ ///
+ /// TODO(pcwalton): Inline blocks.
+ fn bubble_inline_sizes(&mut self, _: &LayoutContext) {
+ let _scope = layout_debug_scope!("bubble_inline_sizes {:s}", self.base.debug_id());
+
+ let mut flags = self.base.flags;
+ flags.set_has_left_floated_descendants(false);
+ flags.set_has_right_floated_descendants(false);
+
+ // If this block has a fixed width, just use that for the minimum
+ // and preferred width, rather than bubbling up children inline
+ // width.
+ let fixed_width = match self.fragment.style().get_box().width {
+ LPA_Length(_) => true,
+ _ => false,
+ };
+
+ // Find the maximum inline-size from children.
+ let mut intrinsic_inline_sizes = IntrinsicISizes::new();
+ for child_ctx in self.base.child_iter() {
+ assert!(child_ctx.is_block_flow() ||
+ child_ctx.is_inline_flow() ||
+ child_ctx.is_table_kind());
+
+ let child_base = flow::mut_base(child_ctx);
+
+ if !fixed_width {
+ intrinsic_inline_sizes.minimum_inline_size =
+ geometry::max(intrinsic_inline_sizes.minimum_inline_size,
+ child_base.intrinsic_inline_sizes.total_minimum_inline_size());
+ intrinsic_inline_sizes.preferred_inline_size =
+ geometry::max(intrinsic_inline_sizes.preferred_inline_size,
+ child_base.intrinsic_inline_sizes.total_preferred_inline_size());
+ }
+
+ flags.union_floated_descendants_flags(child_base.flags);
+ }
+
+ let fragment_intrinsic_inline_sizes = self.fragment.intrinsic_inline_sizes();
+ intrinsic_inline_sizes.minimum_inline_size = geometry::max(intrinsic_inline_sizes.minimum_inline_size,
+ fragment_intrinsic_inline_sizes.minimum_inline_size);
+ intrinsic_inline_sizes.preferred_inline_size = geometry::max(intrinsic_inline_sizes.preferred_inline_size,
+ fragment_intrinsic_inline_sizes.preferred_inline_size);
+ intrinsic_inline_sizes.surround_inline_size = fragment_intrinsic_inline_sizes.surround_inline_size;
+ self.base.intrinsic_inline_sizes = intrinsic_inline_sizes;
+
+ match self.fragment.style().get_box().float {
+ float::none => {}
+ float::left => flags.set_has_left_floated_descendants(true),
+ float::right => flags.set_has_right_floated_descendants(true),
+ }
+ self.base.flags = flags
+ }
+
+ /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. When
+ /// called on this context, the context has had its inline-size set by the parent context.
+ ///
+ /// Dual fragments consume some inline-size first, and the remainder is assigned to all child (block)
+ /// contexts.
+ fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
+ let _scope = layout_debug_scope!("block::assign_inline_sizes {:s}", self.base.debug_id());
+
+ debug!("assign_inline_sizes({}): assigning inline_size for flow",
+ if self.is_float() {
+ "float"
+ } else {
+ "block"
+ });
+
+ if self.is_root() {
+ debug!("Setting root position");
+ self.base.position.start = LogicalPoint::zero(self.base.writing_mode);
+ self.base.position.size.inline = LogicalSize::from_physical(
+ self.base.writing_mode, layout_context.shared.screen_size).inline;
+ self.base.floats = Floats::new(self.base.writing_mode);
+
+ // The root element is never impacted by floats.
+ self.base.flags.set_impacted_by_left_floats(false);
+ self.base.flags.set_impacted_by_right_floats(false);
+ }
+
+ // Our inline-size was set to the inline-size of the containing block by the flow's parent. Now compute
+ // the real value.
+ let containing_block_inline_size = self.base.position.size.inline;
+ self.compute_used_inline_size(layout_context, containing_block_inline_size);
+ if self.is_float() {
+ self.float.get_mut_ref().containing_inline_size = containing_block_inline_size;
+ }
+
+ // Formatting contexts are never impacted by floats.
+ match self.formatting_context_type() {
+ NonformattingContext => {}
+ BlockFormattingContext => {
+ self.base.flags.set_impacted_by_left_floats(false);
+ self.base.flags.set_impacted_by_right_floats(false);
+
+ // We can't actually compute the inline-size of this block now, because floats might
+ // affect it. Speculate that its inline-size is equal to the inline-size computed above minus
+ // the inline-size of the previous float.
+ match self.previous_float_inline_size {
+ None => {}
+ Some(previous_float_inline_size) => {
+ self.fragment.border_box.size.inline =
+ self.fragment.border_box.size.inline - previous_float_inline_size
+ }
+ }
+ }
+ OtherFormattingContext => {
+ self.base.flags.set_impacted_by_left_floats(false);
+ self.base.flags.set_impacted_by_right_floats(false);
+ }
+ }
+
+ // Move in from the inline-start border edge
+ let inline_start_content_edge = self.fragment.border_box.start.i + self.fragment.border_padding.inline_start;
+ let padding_and_borders = self.fragment.border_padding.inline_start_end();
+ let content_inline_size = self.fragment.border_box.size.inline - padding_and_borders;
+
+ if self.is_float() {
+ self.base.position.size.inline = content_inline_size;
+ }
+
+ self.propagate_assigned_inline_size_to_children(inline_start_content_edge, content_inline_size, None);
+ }
+
+ /// Assigns block-sizes in-order; or, if this is a float, places the float. The default
+ /// implementation simply assigns block-sizes if this flow is impacted by floats. Returns true if
+ /// this child was impacted by floats or false otherwise.
+ ///
+ /// This is called on child flows by the parent. Hence, we can assume that `assign_block-size` has
+ /// already been called on the child (because of the bottom-up traversal).
+ fn assign_block_size_for_inorder_child_if_necessary<'a>(&mut self, layout_context: &'a LayoutContext<'a>)
+ -> bool {
+ if self.is_float() {
+ self.place_float();
+ return true
+ }
+
+ let impacted = self.base.flags.impacted_by_floats();
+ if impacted {
+ self.assign_block_size(layout_context);
+ }
+ impacted
+ }
+
+ fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
+
+ if self.is_replaced_content() {
+ // Assign block-size for fragment if it is an image fragment.
+ self.fragment.assign_replaced_block_size_if_necessary();
+ } else if self.is_float() {
+ debug!("assign_block_size_float: assigning block_size for float");
+ self.assign_block_size_float(ctx);
+ } else if self.is_root() {
+ // Root element margins should never be collapsed according to CSS § 8.3.1.
+ debug!("assign_block_size: assigning block_size for root flow");
+ self.assign_block_size_block_base(ctx, MarginsMayNotCollapse);
+ } else {
+ debug!("assign_block_size: assigning block_size for block");
+ self.assign_block_size_block_base(ctx, MarginsMayCollapse);
+ }
+ }
+
+ fn compute_absolute_position(&mut self) {
+ // FIXME(#2795): Get the real container size
+ let container_size = Size2D::zero();
+
+ if self.is_absolutely_positioned() {
+ let position_start = self.base.position.start.to_physical(
+ self.base.writing_mode, container_size);
+ self.base
+ .absolute_position_info
+ .absolute_containing_block_position = if self.is_fixed() {
+ // The viewport is initially at (0, 0).
+ position_start
+ } else {
+ // Absolute position of the containing block + position of absolute flow w/r/t the
+ // containing block.
+ self.base.absolute_position_info.absolute_containing_block_position
+ + position_start
+ };
+
+ // Set the absolute position, which will be passed down later as part
+ // of containing block details for absolute descendants.
+ self.base.abs_position =
+ self.base.absolute_position_info.absolute_containing_block_position;
+ }
+
+ // For relatively-positioned descendants, the containing block formed by a block is just
+ // the content box. The containing block for absolutely-positioned descendants, on the
+ // other hand, is only established if we are positioned.
+ let relative_offset =
+ self.fragment.relative_position(&self.base
+ .absolute_position_info
+ .relative_containing_block_size);
+ if self.is_positioned() {
+ self.base.absolute_position_info.absolute_containing_block_position =
+ self.base.abs_position
+ + (self.generated_containing_block_rect().start
+ + relative_offset).to_physical(self.base.writing_mode, container_size)
+ }
+
+ let float_offset = if self.is_float() {
+ self.float.get_ref().rel_pos
+ } else {
+ LogicalPoint::zero(self.base.writing_mode)
+ };
+
+ // Compute absolute position info for children.
+ let mut absolute_position_info = self.base.absolute_position_info;
+ absolute_position_info.relative_containing_block_size = self.fragment.content_box().size;
+ absolute_position_info.layers_needed_for_positioned_flows =
+ self.base.flags.layers_needed_for_descendants();
+
+ // Process children.
+ let this_position = self.base.abs_position;
+ let writing_mode = self.base.writing_mode;
+ for kid in self.base.child_iter() {
+ if !kid.is_absolutely_positioned() {
+ let kid_base = flow::mut_base(kid);
+ kid_base.abs_position = this_position + (
+ kid_base.position.start
+ .add_point(&float_offset)
+ + relative_offset).to_physical(writing_mode, container_size);
+ kid_base.absolute_position_info = absolute_position_info
+ }
+ }
+
+ // Process absolute descendant links.
+ for absolute_descendant in self.base.abs_descendants.iter() {
+ flow::mut_base(absolute_descendant).absolute_position_info = absolute_position_info
+ }
+ }
+
+ fn mark_as_root(&mut self) {
+ self.is_root = true
+ }
+
+ /// Return true if store overflow is delayed for this flow.
+ ///
+ /// Currently happens only for absolutely positioned flows.
+ fn is_store_overflow_delayed(&mut self) -> bool {
+ self.is_absolutely_positioned()
+ }
+
+ fn is_root(&self) -> bool {
+ self.is_root
+ }
+
+ fn is_float(&self) -> bool {
+ self.float.is_some()
+ }
+
+ /// The 'position' property of this flow.
+ fn positioning(&self) -> position::T {
+ self.fragment.style.get_box().position
+ }
+
+ /// Return true if this is the root of an Absolute flow tree.
+ ///
+ /// It has to be either relatively positioned or the Root flow.
+ fn is_root_of_absolute_flow_tree(&self) -> bool {
+ self.is_relatively_positioned() || self.is_root()
+ }
+
+ /// Return the dimensions of the containing block generated by this flow for absolutely-
+ /// positioned descendants. For block flows, this is the padding box.
+ fn generated_containing_block_rect(&self) -> LogicalRect<Au> {
+ self.fragment.border_box - self.fragment.style().logical_border_width()
+ }
+
+ fn layer_id(&self, fragment_index: uint) -> LayerId {
+ // FIXME(#2010, pcwalton): This is a hack and is totally bogus in the presence of pseudo-
+ // elements. But until we have incremental reflow we can't do better--we recreate the flow
+ // for every DOM node so otherwise we nuke layers on every reflow.
+ LayerId(self.fragment.node.id(), fragment_index)
+ }
+
+ fn is_absolute_containing_block(&self) -> bool {
+ self.is_positioned()
+ }
+}
+
+impl fmt::Show for BlockFlow {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ if self.is_float() {
+ write!(f, "FloatFlow: {}", self.fragment)
+ } else if self.is_root() {
+ write!(f, "RootFlow: {}", self.fragment)
+ } else {
+ write!(f, "BlockFlow: {}", self.fragment)
+ }
+ }
+}
+
+/// The inputs for the inline-sizes-and-margins constraint equation.
+pub struct ISizeConstraintInput {
+ pub computed_inline_size: MaybeAuto,
+ pub inline_start_margin: MaybeAuto,
+ pub inline_end_margin: MaybeAuto,
+ pub inline_start: MaybeAuto,
+ pub inline_end: MaybeAuto,
+ pub available_inline_size: Au,
+ pub static_i_offset: Au,
+}
+
+impl ISizeConstraintInput {
+ pub fn new(computed_inline_size: MaybeAuto,
+ inline_start_margin: MaybeAuto,
+ inline_end_margin: MaybeAuto,
+ inline_start: MaybeAuto,
+ inline_end: MaybeAuto,
+ available_inline_size: Au,
+ static_i_offset: Au)
+ -> ISizeConstraintInput {
+ ISizeConstraintInput {
+ computed_inline_size: computed_inline_size,
+ inline_start_margin: inline_start_margin,
+ inline_end_margin: inline_end_margin,
+ inline_start: inline_start,
+ inline_end: inline_end,
+ available_inline_size: available_inline_size,
+ static_i_offset: static_i_offset,
+ }
+ }
+}
+
+/// The solutions for the inline-size-and-margins constraint equation.
+pub struct ISizeConstraintSolution {
+ pub inline_start: Au,
+ pub inline_end: Au,
+ pub inline_size: Au,
+ pub margin_inline_start: Au,
+ pub margin_inline_end: Au
+}
+
+impl ISizeConstraintSolution {
+ pub fn new(inline_size: Au, margin_inline_start: Au, margin_inline_end: Au) -> ISizeConstraintSolution {
+ ISizeConstraintSolution {
+ inline_start: Au(0),
+ inline_end: Au(0),
+ inline_size: inline_size,
+ margin_inline_start: margin_inline_start,
+ margin_inline_end: margin_inline_end,
+ }
+ }
+
+ fn for_absolute_flow(inline_start: Au,
+ inline_end: Au,
+ inline_size: Au,
+ margin_inline_start: Au,
+ margin_inline_end: Au)
+ -> ISizeConstraintSolution {
+ ISizeConstraintSolution {
+ inline_start: inline_start,
+ inline_end: inline_end,
+ inline_size: inline_size,
+ margin_inline_start: margin_inline_start,
+ margin_inline_end: margin_inline_end,
+ }
+ }
+}
+
+// Trait to encapsulate the ISize and Margin calculation.
+//
+// CSS Section 10.3
+pub trait ISizeAndMarginsComputer {
+ /// Compute the inputs for the ISize constraint equation.
+ ///
+ /// This is called only once to compute the initial inputs. For
+ /// calculation involving min-inline-size and max-inline-size, we don't need to
+ /// recompute these.
+ fn compute_inline_size_constraint_inputs(&self,
+ block: &mut BlockFlow,
+ parent_flow_inline_size: Au,
+ ctx: &LayoutContext)
+ -> ISizeConstraintInput {
+ let containing_block_inline_size = self.containing_block_inline_size(block, parent_flow_inline_size, ctx);
+ let computed_inline_size = self.initial_computed_inline_size(block, parent_flow_inline_size, ctx);
+
+ block.fragment.compute_border_padding_margins(containing_block_inline_size);
+
+ let style = block.fragment.style();
+
+ // The text alignment of a block flow is the text alignment of its box's style.
+ block.base.flags.set_text_align(style.get_inheritedtext().text_align);
+
+ let margin = style.logical_margin();
+ let position = style.logical_position();
+
+ let available_inline_size = containing_block_inline_size - block.fragment.border_padding.inline_start_end();
+ return ISizeConstraintInput::new(
+ computed_inline_size,
+ MaybeAuto::from_style(margin.inline_start, containing_block_inline_size),
+ MaybeAuto::from_style(margin.inline_end, containing_block_inline_size),
+ MaybeAuto::from_style(position.inline_start, containing_block_inline_size),
+ MaybeAuto::from_style(position.inline_end, containing_block_inline_size),
+ available_inline_size,
+ block.static_i_offset());
+ }
+
+ /// Set the used values for inline-size and margins got from the relevant constraint equation.
+ ///
+ /// This is called only once.
+ ///
+ /// Set:
+ /// + used values for content inline-size, inline-start margin, and inline-end margin for this flow's box.
+ /// + x-coordinate of this flow's box.
+ /// + x-coordinate of the flow wrt its Containing Block (if this is an absolute flow).
+ fn set_inline_size_constraint_solutions(&self,
+ block: &mut BlockFlow,
+ solution: ISizeConstraintSolution) {
+ let inline_size;
+ {
+ let fragment = block.fragment();
+ fragment.margin.inline_start = solution.margin_inline_start;
+ fragment.margin.inline_end = solution.margin_inline_end;
+
+ // The associated fragment has the border box of this flow.
+ // Left border edge.
+ fragment.border_box.start.i = fragment.margin.inline_start;
+ // Border box inline-size.
+ inline_size = solution.inline_size + fragment.border_padding.inline_start_end();
+ fragment.border_box.size.inline = inline_size;
+ }
+
+ // We also resize the block itself, to ensure that overflow is not calculated
+ // as the inline-size of our parent. We might be smaller and we might be larger if we
+ // overflow.
+ let flow = flow::mut_base(block);
+ flow.position.size.inline = inline_size;
+ }
+
+ /// Set the x coordinate of the given flow if it is absolutely positioned.
+ fn set_flow_x_coord_if_necessary(&self, _: &mut BlockFlow, _: ISizeConstraintSolution) {}
+
+ /// Solve the inline-size and margins constraints for this block flow.
+ fn solve_inline_size_constraints(&self,
+ block: &mut BlockFlow,
+ input: &ISizeConstraintInput)
+ -> ISizeConstraintSolution;
+
+ fn initial_computed_inline_size(&self,
+ block: &mut BlockFlow,
+ parent_flow_inline_size: Au,
+ ctx: &LayoutContext)
+ -> MaybeAuto {
+ MaybeAuto::from_style(block.fragment().style().content_inline_size(),
+ self.containing_block_inline_size(block, parent_flow_inline_size, ctx))
+ }
+
+ fn containing_block_inline_size(&self,
+ _: &mut BlockFlow,
+ parent_flow_inline_size: Au,
+ _: &LayoutContext)
+ -> Au {
+ parent_flow_inline_size
+ }
+
+ /// Compute the used value of inline-size, taking care of min-inline-size and max-inline-size.
+ ///
+ /// CSS Section 10.4: Minimum and Maximum inline-sizes
+ fn compute_used_inline_size(&self,
+ block: &mut BlockFlow,
+ ctx: &LayoutContext,
+ parent_flow_inline_size: Au) {
+ let mut input = self.compute_inline_size_constraint_inputs(block, parent_flow_inline_size, ctx);
+
+ let containing_block_inline_size = self.containing_block_inline_size(block, parent_flow_inline_size, ctx);
+
+ let mut solution = self.solve_inline_size_constraints(block, &input);
+
+ // If the tentative used inline-size is greater than 'max-inline-size', inline-size should be recalculated,
+ // but this time using the computed value of 'max-inline-size' as the computed value for 'inline-size'.
+ match specified_or_none(block.fragment().style().max_inline_size(), containing_block_inline_size) {
+ Some(max_inline_size) if max_inline_size < solution.inline_size => {
+ input.computed_inline_size = Specified(max_inline_size);
+ solution = self.solve_inline_size_constraints(block, &input);
+ }
+ _ => {}
+ }
+
+ // If the resulting inline-size is smaller than 'min-inline-size', inline-size should be recalculated,
+ // but this time using the value of 'min-inline-size' as the computed value for 'inline-size'.
+ let computed_min_inline_size = specified(block.fragment().style().min_inline_size(),
+ containing_block_inline_size);
+ if computed_min_inline_size > solution.inline_size {
+ input.computed_inline_size = Specified(computed_min_inline_size);
+ solution = self.solve_inline_size_constraints(block, &input);
+ }
+
+ self.set_inline_size_constraint_solutions(block, solution);
+ self.set_flow_x_coord_if_necessary(block, solution);
+ }
+
+ /// Computes inline-start and inline-end margins and inline-size.
+ ///
+ /// This is used by both replaced and non-replaced Blocks.
+ ///
+ /// CSS 2.1 Section 10.3.3.
+ /// Constraint Equation: margin-inline-start + margin-inline-end + inline-size = available_inline-size
+ /// where available_inline-size = CB inline-size - (horizontal border + padding)
+ fn solve_block_inline_size_constraints(&self,
+ _: &mut BlockFlow,
+ input: &ISizeConstraintInput)
+ -> ISizeConstraintSolution {
+ let (computed_inline_size, inline_start_margin, inline_end_margin, available_inline_size) = (input.computed_inline_size,
+ input.inline_start_margin,
+ input.inline_end_margin,
+ input.available_inline_size);
+
+ // If inline-size is not 'auto', and inline-size + margins > available_inline-size, all
+ // 'auto' margins are treated as 0.
+ let (inline_start_margin, inline_end_margin) = match computed_inline_size {
+ Auto => (inline_start_margin, inline_end_margin),
+ Specified(inline_size) => {
+ let inline_start = inline_start_margin.specified_or_zero();
+ let inline_end = inline_end_margin.specified_or_zero();
+
+ if (inline_start + inline_end + inline_size) > available_inline_size {
+ (Specified(inline_start), Specified(inline_end))
+ } else {
+ (inline_start_margin, inline_end_margin)
+ }
+ }
+ };
+
+ // Invariant: inline-start_margin + inline-size + inline-end_margin == available_inline-size
+ let (inline_start_margin, inline_size, inline_end_margin) = match (inline_start_margin, computed_inline_size, inline_end_margin) {
+ // If all have a computed value other than 'auto', the system is
+ // over-constrained so we discard the end margin.
+ (Specified(margin_start), Specified(inline_size), Specified(_margin_end)) =>
+ (margin_start, inline_size, available_inline_size - (margin_start + inline_size)),
+
+ // If exactly one value is 'auto', solve for it
+ (Auto, Specified(inline_size), Specified(margin_end)) =>
+ (available_inline_size - (inline_size + margin_end), inline_size, margin_end),
+ (Specified(margin_start), Auto, Specified(margin_end)) =>
+ (margin_start, available_inline_size - (margin_start + margin_end), margin_end),
+ (Specified(margin_start), Specified(inline_size), Auto) =>
+ (margin_start, inline_size, available_inline_size - (margin_start + inline_size)),
+
+ // If inline-size is set to 'auto', any other 'auto' value becomes '0',
+ // and inline-size is solved for
+ (Auto, Auto, Specified(margin_end)) =>
+ (Au::new(0), available_inline_size - margin_end, margin_end),
+ (Specified(margin_start), Auto, Auto) =>
+ (margin_start, available_inline_size - margin_start, Au::new(0)),
+ (Auto, Auto, Auto) =>
+ (Au::new(0), available_inline_size, Au::new(0)),
+
+ // If inline-start and inline-end margins are auto, they become equal
+ (Auto, Specified(inline_size), Auto) => {
+ let margin = (available_inline_size - inline_size).scale_by(0.5);
+ (margin, inline_size, margin)
+ }
+ };
+ ISizeConstraintSolution::new(inline_size, inline_start_margin, inline_end_margin)
+ }
+}
+
+/// The different types of Blocks.
+///
+/// They mainly differ in the way inline-size and block-sizes and margins are calculated
+/// for them.
+struct AbsoluteNonReplaced;
+struct AbsoluteReplaced;
+struct BlockNonReplaced;
+struct BlockReplaced;
+struct FloatNonReplaced;
+struct FloatReplaced;
+
+impl ISizeAndMarginsComputer for AbsoluteNonReplaced {
+ /// Solve the horizontal constraint equation for absolute non-replaced elements.
+ ///
+ /// CSS Section 10.3.7
+ /// Constraint equation:
+ /// inline-start + inline-end + inline-size + margin-inline-start + margin-inline-end
+ /// = absolute containing block inline-size - (horizontal padding and border)
+ /// [aka available_inline-size]
+ ///
+ /// Return the solution for the equation.
+ fn solve_inline_size_constraints(&self,
+ block: &mut BlockFlow,
+ input: &ISizeConstraintInput)
+ -> ISizeConstraintSolution {
+ let &ISizeConstraintInput {
+ computed_inline_size,
+ inline_start_margin,
+ inline_end_margin,
+ inline_start,
+ inline_end,
+ available_inline_size,
+ static_i_offset,
+ ..
+ } = input;
+
+ // TODO: Check for direction of parent flow (NOT Containing Block)
+ // when right-to-left is implemented.
+ // Assume direction is 'ltr' for now
+
+ // Distance from the inline-start edge of the Absolute Containing Block to the
+ // inline-start margin edge of a hypothetical box that would have been the
+ // first box of the element.
+ let static_position_inline_start = static_i_offset;
+
+ let (inline_start, inline_end, inline_size, margin_inline_start, margin_inline_end) = match (inline_start, inline_end, computed_inline_size) {
+ (Auto, Auto, Auto) => {
+ let margin_start = inline_start_margin.specified_or_zero();
+ let margin_end = inline_end_margin.specified_or_zero();
+ let inline_start = static_position_inline_start;
+ // Now it is the same situation as inline-start Specified and inline-end
+ // and inline-size Auto.
+
+ // Set inline-end to zero to calculate inline-size
+ let inline_size = block.get_shrink_to_fit_inline_size(
+ available_inline_size - (inline_start + margin_start + margin_end));
+ let sum = inline_start + inline_size + margin_start + margin_end;
+ (inline_start, available_inline_size - sum, inline_size, margin_start, margin_end)
+ }
+ (Specified(inline_start), Specified(inline_end), Specified(inline_size)) => {
+ match (inline_start_margin, inline_end_margin) {
+ (Auto, Auto) => {
+ let total_margin_val = available_inline_size - inline_start - inline_end - inline_size;
+ if total_margin_val < Au(0) {
+ // margin-inline-start becomes 0 because direction is 'ltr'.
+ // TODO: Handle 'rtl' when it is implemented.
+ (inline_start, inline_end, inline_size, Au(0), total_margin_val)
+ } else {
+ // Equal margins
+ (inline_start, inline_end, inline_size,
+ total_margin_val.scale_by(0.5),
+ total_margin_val.scale_by(0.5))
+ }
+ }
+ (Specified(margin_start), Auto) => {
+ let sum = inline_start + inline_end + inline_size + margin_start;
+ (inline_start, inline_end, inline_size, margin_start, available_inline_size - sum)
+ }
+ (Auto, Specified(margin_end)) => {
+ let sum = inline_start + inline_end + inline_size + margin_end;
+ (inline_start, inline_end, inline_size, available_inline_size - sum, margin_end)
+ }
+ (Specified(margin_start), Specified(margin_end)) => {
+ // Values are over-constrained.
+ // Ignore value for 'inline-end' cos direction is 'ltr'.
+ // TODO: Handle 'rtl' when it is implemented.
+ let sum = inline_start + inline_size + margin_start + margin_end;
+ (inline_start, available_inline_size - sum, inline_size, margin_start, margin_end)
+ }
+ }
+ }
+ // For the rest of the cases, auto values for margin are set to 0
+
+ // If only one is Auto, solve for it
+ (Auto, Specified(inline_end), Specified(inline_size)) => {
+ let margin_start = inline_start_margin.specified_or_zero();
+ let margin_end = inline_end_margin.specified_or_zero();
+ let sum = inline_end + inline_size + margin_start + margin_end;
+ (available_inline_size - sum, inline_end, inline_size, margin_start, margin_end)
+ }
+ (Specified(inline_start), Auto, Specified(inline_size)) => {
+ let margin_start = inline_start_margin.specified_or_zero();
+ let margin_end = inline_end_margin.specified_or_zero();
+ let sum = inline_start + inline_size + margin_start + margin_end;
+ (inline_start, available_inline_size - sum, inline_size, margin_start, margin_end)
+ }
+ (Specified(inline_start), Specified(inline_end), Auto) => {
+ let margin_start = inline_start_margin.specified_or_zero();
+ let margin_end = inline_end_margin.specified_or_zero();
+ let sum = inline_start + inline_end + margin_start + margin_end;
+ (inline_start, inline_end, available_inline_size - sum, margin_start, margin_end)
+ }
+
+ // If inline-size is auto, then inline-size is shrink-to-fit. Solve for the
+ // non-auto value.
+ (Specified(inline_start), Auto, Auto) => {
+ let margin_start = inline_start_margin.specified_or_zero();
+ let margin_end = inline_end_margin.specified_or_zero();
+ // Set inline-end to zero to calculate inline-size
+ let inline_size = block.get_shrink_to_fit_inline_size(
+ available_inline_size - (inline_start + margin_start + margin_end));
+ let sum = inline_start + inline_size + margin_start + margin_end;
+ (inline_start, available_inline_size - sum, inline_size, margin_start, margin_end)
+ }
+ (Auto, Specified(inline_end), Auto) => {
+ let margin_start = inline_start_margin.specified_or_zero();
+ let margin_end = inline_end_margin.specified_or_zero();
+ // Set inline-start to zero to calculate inline-size
+ let inline_size = block.get_shrink_to_fit_inline_size(
+ available_inline_size - (inline_end + margin_start + margin_end));
+ let sum = inline_end + inline_size + margin_start + margin_end;
+ (available_inline_size - sum, inline_end, inline_size, margin_start, margin_end)
+ }
+
+ (Auto, Auto, Specified(inline_size)) => {
+ let margin_start = inline_start_margin.specified_or_zero();
+ let margin_end = inline_end_margin.specified_or_zero();
+ // Setting 'inline-start' to static position because direction is 'ltr'.
+ // TODO: Handle 'rtl' when it is implemented.
+ let inline_start = static_position_inline_start;
+ let sum = inline_start + inline_size + margin_start + margin_end;
+ (inline_start, available_inline_size - sum, inline_size, margin_start, margin_end)
+ }
+ };
+ ISizeConstraintSolution::for_absolute_flow(inline_start, inline_end, inline_size, margin_inline_start, margin_inline_end)
+ }
+
+ fn containing_block_inline_size(&self, block: &mut BlockFlow, _: Au, ctx: &LayoutContext) -> Au {
+ block.containing_block_size(ctx.shared.screen_size).inline
+ }
+
+ fn set_flow_x_coord_if_necessary(&self,
+ block: &mut BlockFlow,
+ solution: ISizeConstraintSolution) {
+ // Set the x-coordinate of the absolute flow wrt to its containing block.
+ block.base.position.start.i = solution.inline_start;
+ }
+}
+
+impl ISizeAndMarginsComputer for AbsoluteReplaced {
+ /// Solve the horizontal constraint equation for absolute replaced elements.
+ ///
+ /// `static_i_offset`: total offset of current flow's hypothetical
+ /// position (static position) from its actual Containing Block.
+ ///
+ /// CSS Section 10.3.8
+ /// Constraint equation:
+ /// inline-start + inline-end + inline-size + margin-inline-start + margin-inline-end
+ /// = absolute containing block inline-size - (horizontal padding and border)
+ /// [aka available_inline-size]
+ ///
+ /// Return the solution for the equation.
+ fn solve_inline_size_constraints(&self, _: &mut BlockFlow, input: &ISizeConstraintInput)
+ -> ISizeConstraintSolution {
+ let &ISizeConstraintInput {
+ computed_inline_size,
+ inline_start_margin,
+ inline_end_margin,
+ inline_start,
+ inline_end,
+ available_inline_size,
+ static_i_offset,
+ ..
+ } = input;
+ // TODO: Check for direction of static-position Containing Block (aka
+ // parent flow, _not_ the actual Containing Block) when right-to-left
+ // is implemented
+ // Assume direction is 'ltr' for now
+ // TODO: Handle all the cases for 'rtl' direction.
+
+ let inline_size = match computed_inline_size {
+ Specified(w) => w,
+ _ => fail!("{} {}",
+ "The used value for inline_size for absolute replaced flow",
+ "should have already been calculated by now.")
+ };
+
+ // Distance from the inline-start edge of the Absolute Containing Block to the
+ // inline-start margin edge of a hypothetical box that would have been the
+ // first box of the element.
+ let static_position_inline_start = static_i_offset;
+
+ let (inline_start, inline_end, inline_size, margin_inline_start, margin_inline_end) = match (inline_start, inline_end) {
+ (Auto, Auto) => {
+ let inline_start = static_position_inline_start;
+ let margin_start = inline_start_margin.specified_or_zero();
+ let margin_end = inline_end_margin.specified_or_zero();
+ let sum = inline_start + inline_size + margin_start + margin_end;
+ (inline_start, available_inline_size - sum, inline_size, margin_start, margin_end)
+ }
+ // If only one is Auto, solve for it
+ (Auto, Specified(inline_end)) => {
+ let margin_start = inline_start_margin.specified_or_zero();
+ let margin_end = inline_end_margin.specified_or_zero();
+ let sum = inline_end + inline_size + margin_start + margin_end;
+ (available_inline_size - sum, inline_end, inline_size, margin_start, margin_end)
+ }
+ (Specified(inline_start), Auto) => {
+ let margin_start = inline_start_margin.specified_or_zero();
+ let margin_end = inline_end_margin.specified_or_zero();
+ let sum = inline_start + inline_size + margin_start + margin_end;
+ (inline_start, available_inline_size - sum, inline_size, margin_start, margin_end)
+ }
+ (Specified(inline_start), Specified(inline_end)) => {
+ match (inline_start_margin, inline_end_margin) {
+ (Auto, Auto) => {
+ let total_margin_val = available_inline_size - inline_start - inline_end - inline_size;
+ if total_margin_val < Au(0) {
+ // margin-inline-start becomes 0 because direction is 'ltr'.
+ (inline_start, inline_end, inline_size, Au(0), total_margin_val)
+ } else {
+ // Equal margins
+ (inline_start, inline_end, inline_size,
+ total_margin_val.scale_by(0.5),
+ total_margin_val.scale_by(0.5))
+ }
+ }
+ (Specified(margin_start), Auto) => {
+ let sum = inline_start + inline_end + inline_size + margin_start;
+ (inline_start, inline_end, inline_size, margin_start, available_inline_size - sum)
+ }
+ (Auto, Specified(margin_end)) => {
+ let sum = inline_start + inline_end + inline_size + margin_end;
+ (inline_start, inline_end, inline_size, available_inline_size - sum, margin_end)
+ }
+ (Specified(margin_start), Specified(margin_end)) => {
+ // Values are over-constrained.
+ // Ignore value for 'inline-end' cos direction is 'ltr'.
+ let sum = inline_start + inline_size + margin_start + margin_end;
+ (inline_start, available_inline_size - sum, inline_size, margin_start, margin_end)
+ }
+ }
+ }
+ };
+ ISizeConstraintSolution::for_absolute_flow(inline_start, inline_end, inline_size, margin_inline_start, margin_inline_end)
+ }
+
+ /// Calculate used value of inline-size just like we do for inline replaced elements.
+ fn initial_computed_inline_size(&self,
+ block: &mut BlockFlow,
+ _: Au,
+ ctx: &LayoutContext)
+ -> MaybeAuto {
+ let containing_block_inline_size = block.containing_block_size(ctx.shared.screen_size).inline;
+ let fragment = block.fragment();
+ fragment.assign_replaced_inline_size_if_necessary(containing_block_inline_size);
+ // For replaced absolute flow, the rest of the constraint solving will
+ // take inline-size to be specified as the value computed here.
+ Specified(fragment.content_inline_size())
+ }
+
+ fn containing_block_inline_size(&self, block: &mut BlockFlow, _: Au, ctx: &LayoutContext) -> Au {
+ block.containing_block_size(ctx.shared.screen_size).inline
+ }
+
+ fn set_flow_x_coord_if_necessary(&self, block: &mut BlockFlow, solution: ISizeConstraintSolution) {
+ // Set the x-coordinate of the absolute flow wrt to its containing block.
+ block.base.position.start.i = solution.inline_start;
+ }
+}
+
+impl ISizeAndMarginsComputer for BlockNonReplaced {
+ /// Compute inline-start and inline-end margins and inline-size.
+ fn solve_inline_size_constraints(&self,
+ block: &mut BlockFlow,
+ input: &ISizeConstraintInput)
+ -> ISizeConstraintSolution {
+ self.solve_block_inline_size_constraints(block, input)
+ }
+}
+
+impl ISizeAndMarginsComputer for BlockReplaced {
+ /// Compute inline-start and inline-end margins and inline-size.
+ ///
+ /// ISize has already been calculated. We now calculate the margins just
+ /// like for non-replaced blocks.
+ fn solve_inline_size_constraints(&self,
+ block: &mut BlockFlow,
+ input: &ISizeConstraintInput)
+ -> ISizeConstraintSolution {
+ match input.computed_inline_size {
+ Specified(_) => {},
+ Auto => fail!("BlockReplaced: inline_size should have been computed by now")
+ };
+ self.solve_block_inline_size_constraints(block, input)
+ }
+
+ /// Calculate used value of inline-size just like we do for inline replaced elements.
+ fn initial_computed_inline_size(&self,
+ block: &mut BlockFlow,
+ parent_flow_inline_size: Au,
+ _: &LayoutContext)
+ -> MaybeAuto {
+ let fragment = block.fragment();
+ fragment.assign_replaced_inline_size_if_necessary(parent_flow_inline_size);
+ // For replaced block flow, the rest of the constraint solving will
+ // take inline-size to be specified as the value computed here.
+ Specified(fragment.content_inline_size())
+ }
+
+}
+
+impl ISizeAndMarginsComputer for FloatNonReplaced {
+ /// CSS Section 10.3.5
+ ///
+ /// If inline-size is computed as 'auto', the used value is the 'shrink-to-fit' inline-size.
+ fn solve_inline_size_constraints(&self,
+ block: &mut BlockFlow,
+ input: &ISizeConstraintInput)
+ -> ISizeConstraintSolution {
+ let (computed_inline_size, inline_start_margin, inline_end_margin, available_inline_size) = (input.computed_inline_size,
+ input.inline_start_margin,
+ input.inline_end_margin,
+ input.available_inline_size);
+ let margin_inline_start = inline_start_margin.specified_or_zero();
+ let margin_inline_end = inline_end_margin.specified_or_zero();
+ let available_inline_size_float = available_inline_size - margin_inline_start - margin_inline_end;
+ let shrink_to_fit = block.get_shrink_to_fit_inline_size(available_inline_size_float);
+ let inline_size = computed_inline_size.specified_or_default(shrink_to_fit);
+ debug!("assign_inline_sizes_float -- inline_size: {}", inline_size);
+ ISizeConstraintSolution::new(inline_size, margin_inline_start, margin_inline_end)
+ }
+}
+
+impl ISizeAndMarginsComputer for FloatReplaced {
+ /// CSS Section 10.3.5
+ ///
+ /// If inline-size is computed as 'auto', the used value is the 'shrink-to-fit' inline-size.
+ fn solve_inline_size_constraints(&self, _: &mut BlockFlow, input: &ISizeConstraintInput)
+ -> ISizeConstraintSolution {
+ let (computed_inline_size, inline_start_margin, inline_end_margin) = (input.computed_inline_size,
+ input.inline_start_margin,
+ input.inline_end_margin);
+ let margin_inline_start = inline_start_margin.specified_or_zero();
+ let margin_inline_end = inline_end_margin.specified_or_zero();
+ let inline_size = match computed_inline_size {
+ Specified(w) => w,
+ Auto => fail!("FloatReplaced: inline_size should have been computed by now")
+ };
+ debug!("assign_inline_sizes_float -- inline_size: {}", inline_size);
+ ISizeConstraintSolution::new(inline_size, margin_inline_start, margin_inline_end)
+ }
+
+ /// Calculate used value of inline-size just like we do for inline replaced elements.
+ fn initial_computed_inline_size(&self,
+ block: &mut BlockFlow,
+ parent_flow_inline_size: Au,
+ _: &LayoutContext)
+ -> MaybeAuto {
+ let fragment = block.fragment();
+ fragment.assign_replaced_inline_size_if_necessary(parent_flow_inline_size);
+ // For replaced block flow, the rest of the constraint solving will
+ // take inline-size to be specified as the value computed here.
+ Specified(fragment.content_inline_size())
+ }
+}
+
+fn propagate_column_inline_sizes_to_child(kid: &mut Flow,
+ child_index: uint,
+ content_inline_size: Au,
+ column_inline_sizes: &[Au],
+ inline_start_margin_edge: &mut Au) {
+ // If kid is table_rowgroup or table_row, the column inline-sizes info should be copied from its
+ // parent.
+ //
+ // FIXME(pcwalton): This seems inefficient. Reference count it instead?
+ let inline_size = if kid.is_table() || kid.is_table_rowgroup() || kid.is_table_row() {
+ *kid.col_inline_sizes() = column_inline_sizes.iter().map(|&x| x).collect();
+
+ // ISize of kid flow is our content inline-size.
+ content_inline_size
+ } else if kid.is_table_cell() {
+ // If kid is table_cell, the x offset and inline-size for each cell should be
+ // calculated from parent's column inline-sizes info.
+ *inline_start_margin_edge = if child_index == 0 {
+ Au(0)
+ } else {
+ *inline_start_margin_edge + column_inline_sizes[child_index - 1]
+ };
+
+ column_inline_sizes[child_index]
+ } else {
+ // ISize of kid flow is our content inline-size.
+ content_inline_size
+ };
+
+ let kid_base = flow::mut_base(kid);
+ kid_base.position.start.i = *inline_start_margin_edge;
+ kid_base.position.size.inline = inline_size;
+}
+
diff --git a/components/layout/construct.rs b/components/layout/construct.rs
new file mode 100644
index 00000000000..0f832bacfb8
--- /dev/null
+++ b/components/layout/construct.rs
@@ -0,0 +1,1049 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Creates flows and fragments from a DOM tree via a bottom-up, incremental traversal of the DOM.
+//!
+//! Each step of the traversal considers the node and existing flow, if there is one. If a node is
+//! not dirty and an existing flow exists, then the traversal reuses that flow. Otherwise, it
+//! proceeds to construct either a flow or a `ConstructionItem`. A construction item is a piece of
+//! intermediate data that goes with a DOM node and hasn't found its "home" yet-maybe it's a box,
+//! maybe it's an absolute or fixed position thing that hasn't found its containing block yet.
+//! Construction items bubble up the tree from children to parents until they find their homes.
+//!
+//! TODO(pcwalton): There is no incremental reflow yet. This scheme requires that nodes either have
+//! weak references to flows or that there be some mechanism to efficiently (O(1) time) "blow
+//! apart" a flow tree and have the flows migrate "home" to their respective DOM nodes while we
+//! perform flow tree construction. The precise mechanism for this will take some experimentation
+//! to get right.
+
+#![deny(unsafe_block)]
+
+use css::node_style::StyledNode;
+use block::BlockFlow;
+use context::LayoutContext;
+use floats::FloatKind;
+use flow::{Flow, ImmutableFlowUtils, MutableOwnedFlowUtils};
+use flow::{Descendants, AbsDescendants};
+use flow;
+use flow_ref::FlowRef;
+use fragment::{Fragment, GenericFragment, IframeFragment, IframeFragmentInfo};
+use fragment::{ImageFragment, ImageFragmentInfo, SpecificFragmentInfo, TableFragment};
+use fragment::{TableCellFragment, TableColumnFragment, TableColumnFragmentInfo};
+use fragment::{TableRowFragment, TableWrapperFragment, UnscannedTextFragment};
+use fragment::{UnscannedTextFragmentInfo};
+use inline::{InlineFragments, InlineFlow};
+use parallel;
+use table_wrapper::TableWrapperFlow;
+use table::TableFlow;
+use table_caption::TableCaptionFlow;
+use table_colgroup::TableColGroupFlow;
+use table_rowgroup::TableRowGroupFlow;
+use table_row::TableRowFlow;
+use table_cell::TableCellFlow;
+use text::TextRunScanner;
+use util::{LayoutDataAccess, OpaqueNodeMethods};
+use wrapper::{PostorderNodeMutTraversal, TLayoutNode, ThreadSafeLayoutNode};
+use wrapper::{Before, BeforeBlock, After, AfterBlock, Normal};
+
+use gfx::display_list::OpaqueNode;
+use script::dom::element::{HTMLIFrameElementTypeId, HTMLImageElementTypeId};
+use script::dom::element::{HTMLObjectElementTypeId};
+use script::dom::element::{HTMLTableColElementTypeId, HTMLTableDataCellElementTypeId};
+use script::dom::element::{HTMLTableElementTypeId, HTMLTableHeaderCellElementTypeId};
+use script::dom::element::{HTMLTableRowElementTypeId, HTMLTableSectionElementTypeId};
+use script::dom::node::{CommentNodeTypeId, DoctypeNodeTypeId, DocumentFragmentNodeTypeId};
+use script::dom::node::{DocumentNodeTypeId, ElementNodeTypeId, ProcessingInstructionNodeTypeId};
+use script::dom::node::{TextNodeTypeId};
+use script::dom::htmlobjectelement::is_image_data;
+use servo_util::namespace;
+use std::mem;
+use std::sync::atomics::Relaxed;
+use style::ComputedValues;
+use style::computed_values::{display, position, float};
+use sync::Arc;
+use url::Url;
+
+/// The results of flow construction for a DOM node.
+pub enum ConstructionResult {
+ /// This node contributes nothing at all (`display: none`). Alternately, this is what newly
+ /// created nodes have their `ConstructionResult` set to.
+ NoConstructionResult,
+
+ /// This node contributed a flow at the proper position in the tree.
+ /// Nothing more needs to be done for this node. It has bubbled up fixed
+ /// and absolute descendant flows that have a CB above it.
+ FlowConstructionResult(FlowRef, AbsDescendants),
+
+ /// This node contributed some object or objects that will be needed to construct a proper flow
+ /// later up the tree, but these objects have not yet found their home.
+ ConstructionItemConstructionResult(ConstructionItem),
+}
+
+/// Represents the output of flow construction for a DOM node that has not yet resulted in a
+/// complete flow. Construction items bubble up the tree until they find a `Flow` to be
+/// attached to.
+pub enum ConstructionItem {
+ /// Inline fragments and associated {ib} splits that have not yet found flows.
+ InlineFragmentsConstructionItem(InlineFragmentsConstructionResult),
+ /// Potentially ignorable whitespace.
+ WhitespaceConstructionItem(OpaqueNode, Arc<ComputedValues>),
+ /// TableColumn Fragment
+ TableColumnFragmentConstructionItem(Fragment),
+}
+
+/// Represents inline fragments and {ib} splits that are bubbling up from an inline.
+pub struct InlineFragmentsConstructionResult {
+ /// Any {ib} splits that we're bubbling up.
+ pub splits: Vec<InlineBlockSplit>,
+
+ /// Any fragments that succeed the {ib} splits.
+ pub fragments: InlineFragments,
+
+ /// Any absolute descendants that we're bubbling up.
+ pub abs_descendants: AbsDescendants,
+}
+
+/// Represents an {ib} split that has not yet found the containing block that it belongs to. This
+/// is somewhat tricky. An example may be helpful. For this DOM fragment:
+///
+/// <span>
+/// A
+/// <div>B</div>
+/// C
+/// </span>
+///
+/// The resulting `ConstructionItem` for the outer `span` will be:
+///
+/// InlineFragmentsConstructionItem(Some(~[
+/// InlineBlockSplit {
+/// predecessor_fragments: ~[
+/// A
+/// ],
+/// block: ~BlockFlow {
+/// B
+/// },
+/// }),~[
+/// C
+/// ])
+pub struct InlineBlockSplit {
+ /// The inline fragments that precede the flow.
+ pub predecessors: InlineFragments,
+
+ /// The flow that caused this {ib} split.
+ pub flow: FlowRef,
+}
+
+/// Holds inline fragments that we're gathering for children of an inline node.
+struct InlineFragmentsAccumulator {
+ /// The list of fragments.
+ fragments: InlineFragments,
+
+ /// Whether we've created a range to enclose all the fragments. This will be Some() if the outer node
+ /// is an inline and None otherwise.
+ enclosing_style: Option<Arc<ComputedValues>>,
+}
+
+impl InlineFragmentsAccumulator {
+ fn new() -> InlineFragmentsAccumulator {
+ InlineFragmentsAccumulator {
+ fragments: InlineFragments::new(),
+ enclosing_style: None,
+ }
+ }
+
+ fn from_inline_node(node: &ThreadSafeLayoutNode) -> InlineFragmentsAccumulator {
+ let fragments = InlineFragments::new();
+ InlineFragmentsAccumulator {
+ fragments: fragments,
+ enclosing_style: Some(node.style().clone()),
+ }
+ }
+
+ fn finish(self) -> InlineFragments {
+ let InlineFragmentsAccumulator {
+ fragments: mut fragments,
+ enclosing_style
+ } = self;
+
+ match enclosing_style {
+ Some(enclosing_style) => {
+ for frag in fragments.fragments.mut_iter() {
+ frag.add_inline_context_style(enclosing_style.clone());
+ }
+ }
+ None => {}
+ }
+ fragments
+ }
+}
+
+enum WhitespaceStrippingMode {
+ NoWhitespaceStripping,
+ StripWhitespaceFromStart,
+ StripWhitespaceFromEnd,
+}
+
+/// An object that knows how to create flows.
+pub struct FlowConstructor<'a, 'b> {
+ /// The layout context.
+ pub layout_context: &'b LayoutContext<'b>,
+}
+
+impl<'a, 'b> FlowConstructor<'a, 'b> {
+ /// Creates a new flow constructor.
+ pub fn new<'b>(layout_context: &'b LayoutContext)
+ -> FlowConstructor<'a, 'b> {
+ FlowConstructor {
+ layout_context: layout_context,
+ }
+ }
+
+ /// Builds the `ImageFragmentInfo` for the given image. This is out of line to guide inlining.
+ fn build_fragment_info_for_image(&mut self, node: &ThreadSafeLayoutNode, url: Option<Url>)
+ -> SpecificFragmentInfo {
+ match url {
+ None => GenericFragment,
+ Some(url) => {
+ // FIXME(pcwalton): The fact that image fragments store the cache within them makes
+ // little sense to me.
+ ImageFragment(ImageFragmentInfo::new(node, url, self.layout_context.shared.image_cache.clone()))
+ }
+ }
+ }
+
+ /// Builds specific `Fragment` info for the given node.
+ pub fn build_specific_fragment_info_for_node(&mut self, node: &ThreadSafeLayoutNode)
+ -> SpecificFragmentInfo {
+ match node.type_id() {
+ Some(ElementNodeTypeId(HTMLImageElementTypeId)) => {
+ self.build_fragment_info_for_image(node, node.image_url())
+ }
+ Some(ElementNodeTypeId(HTMLIFrameElementTypeId)) => {
+ IframeFragment(IframeFragmentInfo::new(node))
+ }
+ Some(ElementNodeTypeId(HTMLObjectElementTypeId)) => {
+ let data = node.get_object_data();
+ self.build_fragment_info_for_image(node, data)
+ }
+ Some(ElementNodeTypeId(HTMLTableElementTypeId)) => TableWrapperFragment,
+ Some(ElementNodeTypeId(HTMLTableColElementTypeId)) => {
+ TableColumnFragment(TableColumnFragmentInfo::new(node))
+ }
+ Some(ElementNodeTypeId(HTMLTableDataCellElementTypeId)) |
+ Some(ElementNodeTypeId(HTMLTableHeaderCellElementTypeId)) => TableCellFragment,
+ Some(ElementNodeTypeId(HTMLTableRowElementTypeId)) |
+ Some(ElementNodeTypeId(HTMLTableSectionElementTypeId)) => TableRowFragment,
+ None | Some(TextNodeTypeId) => UnscannedTextFragment(UnscannedTextFragmentInfo::new(node)),
+ _ => GenericFragment,
+ }
+ }
+
+ /// Creates an inline flow from a set of inline fragments, then adds it as a child of the given flow
+ /// or pushes it onto the given flow list.
+ ///
+ /// `#[inline(always)]` because this is performance critical and LLVM will not inline it
+ /// otherwise.
+ #[inline(always)]
+ fn flush_inline_fragments_to_flow_or_list(&mut self,
+ fragment_accumulator: InlineFragmentsAccumulator,
+ flow: &mut FlowRef,
+ flow_list: &mut Vec<FlowRef>,
+ whitespace_stripping: WhitespaceStrippingMode,
+ node: &ThreadSafeLayoutNode) {
+ let mut fragments = fragment_accumulator.finish();
+ if fragments.is_empty() { return };
+
+ match whitespace_stripping {
+ NoWhitespaceStripping => {}
+ StripWhitespaceFromStart => {
+ fragments.strip_ignorable_whitespace_from_start();
+ if fragments.is_empty() { return };
+ }
+ StripWhitespaceFromEnd => {
+ fragments.strip_ignorable_whitespace_from_end();
+ if fragments.is_empty() { return };
+ }
+ }
+
+ let mut inline_flow = box InlineFlow::from_fragments((*node).clone(), fragments);
+ let (ascent, descent) = inline_flow.compute_minimum_ascent_and_descent(self.layout_context.font_context(), &**node.style());
+ inline_flow.minimum_block_size_above_baseline = ascent;
+ inline_flow.minimum_depth_below_baseline = descent;
+ let mut inline_flow = inline_flow as Box<Flow>;
+ TextRunScanner::new().scan_for_runs(self.layout_context.font_context(), inline_flow);
+ let mut inline_flow = FlowRef::new(inline_flow);
+ inline_flow.finish(self.layout_context);
+
+ if flow.get().need_anonymous_flow(inline_flow.get()) {
+ flow_list.push(inline_flow)
+ } else {
+ flow.add_new_child(inline_flow)
+ }
+ }
+
+ fn build_block_flow_using_children_construction_result(&mut self,
+ flow: &mut FlowRef,
+ consecutive_siblings: &mut Vec<FlowRef>,
+ node: &ThreadSafeLayoutNode,
+ kid: ThreadSafeLayoutNode,
+ inline_fragment_accumulator:
+ &mut InlineFragmentsAccumulator,
+ abs_descendants: &mut Descendants,
+ first_fragment: &mut bool) {
+ match kid.swap_out_construction_result() {
+ NoConstructionResult => {}
+ FlowConstructionResult(kid_flow, kid_abs_descendants) => {
+ // If kid_flow is TableCaptionFlow, kid_flow should be added under
+ // TableWrapperFlow.
+ if flow.get().is_table() && kid_flow.get().is_table_caption() {
+ kid.set_flow_construction_result(FlowConstructionResult(
+ kid_flow,
+ Descendants::new()))
+ } else if flow.get().need_anonymous_flow(kid_flow.get()) {
+ consecutive_siblings.push(kid_flow)
+ } else {
+ // Flush any inline fragments that we were gathering up. This allows us to handle
+ // {ib} splits.
+ debug!("flushing {} inline box(es) to flow A",
+ inline_fragment_accumulator.fragments.len());
+ self.flush_inline_fragments_to_flow_or_list(
+ mem::replace(inline_fragment_accumulator, InlineFragmentsAccumulator::new()),
+ flow,
+ consecutive_siblings,
+ StripWhitespaceFromStart,
+ node);
+ if !consecutive_siblings.is_empty() {
+ let consecutive_siblings = mem::replace(consecutive_siblings, vec!());
+ self.generate_anonymous_missing_child(consecutive_siblings,
+ flow,
+ node);
+ }
+ flow.add_new_child(kid_flow);
+ }
+ abs_descendants.push_descendants(kid_abs_descendants);
+ }
+ ConstructionItemConstructionResult(InlineFragmentsConstructionItem(
+ InlineFragmentsConstructionResult {
+ splits: splits,
+ fragments: successor_fragments,
+ abs_descendants: kid_abs_descendants,
+ })) => {
+ // Add any {ib} splits.
+ for split in splits.move_iter() {
+ // Pull apart the {ib} split object and push its predecessor fragments
+ // onto the list.
+ let InlineBlockSplit {
+ predecessors: predecessors,
+ flow: kid_flow
+ } = split;
+ inline_fragment_accumulator.fragments.push_all(predecessors);
+
+ // If this is the first fragment in flow, then strip ignorable
+ // whitespace per CSS 2.1 § 9.2.1.1.
+ let whitespace_stripping = if *first_fragment {
+ *first_fragment = false;
+ StripWhitespaceFromStart
+ } else {
+ NoWhitespaceStripping
+ };
+
+ // Flush any inline fragments that we were gathering up.
+ debug!("flushing {} inline box(es) to flow A",
+ inline_fragment_accumulator.fragments.len());
+ self.flush_inline_fragments_to_flow_or_list(
+ mem::replace(inline_fragment_accumulator,
+ InlineFragmentsAccumulator::new()),
+ flow,
+ consecutive_siblings,
+ whitespace_stripping,
+ node);
+
+ // Push the flow generated by the {ib} split onto our list of
+ // flows.
+ if flow.get().need_anonymous_flow(kid_flow.get()) {
+ consecutive_siblings.push(kid_flow)
+ } else {
+ flow.add_new_child(kid_flow)
+ }
+ }
+
+ // Add the fragments to the list we're maintaining.
+ inline_fragment_accumulator.fragments.push_all(successor_fragments);
+ abs_descendants.push_descendants(kid_abs_descendants);
+ }
+ ConstructionItemConstructionResult(WhitespaceConstructionItem(whitespace_node, whitespace_style)) => {
+ // Add whitespace results. They will be stripped out later on when
+ // between block elements, and retained when between inline elements.
+ let fragment_info = UnscannedTextFragment(UnscannedTextFragmentInfo::from_text(" ".to_string()));
+ let mut fragment = Fragment::from_opaque_node_and_style(whitespace_node,
+ whitespace_style.clone(),
+ fragment_info);
+ inline_fragment_accumulator.fragments.push(&mut fragment, whitespace_style);
+ }
+ ConstructionItemConstructionResult(TableColumnFragmentConstructionItem(_)) => {
+ // TODO: Implement anonymous table objects for missing parents
+ // CSS 2.1 § 17.2.1, step 3-2
+ }
+ }
+ }
+
+ /// Build block flow for current node using information from children nodes.
+ ///
+ /// Consume results from children and combine them, handling {ib} splits.
+ /// Block flows and inline flows thus created will become the children of
+ /// this block flow.
+ /// Also, deal with the absolute and fixed descendants bubbled up by
+ /// children nodes.
+ fn build_flow_using_children(&mut self, mut flow: FlowRef, node: &ThreadSafeLayoutNode)
+ -> ConstructionResult {
+ // Gather up fragments for the inline flows we might need to create.
+ let mut inline_fragment_accumulator = InlineFragmentsAccumulator::new();
+ let mut consecutive_siblings = vec!();
+ let mut first_fragment = true;
+
+ // List of absolute descendants, in tree order.
+ let mut abs_descendants = Descendants::new();
+ for kid in node.children() {
+ if kid.get_pseudo_element_type() != Normal {
+ self.process(&kid);
+ }
+
+ self.build_block_flow_using_children_construction_result(&mut flow,
+ &mut consecutive_siblings,
+ node,
+ kid,
+ &mut inline_fragment_accumulator,
+ &mut abs_descendants,
+ &mut first_fragment);
+ }
+
+ // Perform a final flush of any inline fragments that we were gathering up to handle {ib}
+ // splits, after stripping ignorable whitespace.
+ self.flush_inline_fragments_to_flow_or_list(inline_fragment_accumulator,
+ &mut flow,
+ &mut consecutive_siblings,
+ StripWhitespaceFromEnd,
+ node);
+ if !consecutive_siblings.is_empty() {
+ self.generate_anonymous_missing_child(consecutive_siblings, &mut flow, node);
+ }
+
+ // The flow is done.
+ flow.finish(self.layout_context);
+ let is_positioned = flow.get_mut().as_block().is_positioned();
+ let is_fixed_positioned = flow.get_mut().as_block().is_fixed();
+ let is_absolutely_positioned = flow.get_mut().as_block().is_absolutely_positioned();
+ if is_positioned {
+ // This is the CB for all the absolute descendants.
+ flow.set_abs_descendants(abs_descendants);
+
+ abs_descendants = Descendants::new();
+
+ if is_fixed_positioned || is_absolutely_positioned {
+ // This is now the only absolute flow in the subtree which hasn't yet
+ // reached its CB.
+ abs_descendants.push(flow.clone());
+ }
+ }
+ FlowConstructionResult(flow, abs_descendants)
+ }
+
+ /// Builds a flow for a node with `display: block`. This yields a `BlockFlow` with possibly
+ /// other `BlockFlow`s or `InlineFlow`s underneath it, depending on whether {ib} splits needed
+ /// to happen.
+ fn build_flow_for_block(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult {
+ let flow = box BlockFlow::from_node(self, node) as Box<Flow>;
+ self.build_flow_using_children(FlowRef::new(flow), node)
+ }
+
+ /// Builds the flow for a node with `float: {left|right}`. This yields a float `BlockFlow` with
+ /// a `BlockFlow` underneath it.
+ fn build_flow_for_floated_block(&mut self, node: &ThreadSafeLayoutNode, float_kind: FloatKind)
+ -> ConstructionResult {
+ let flow = box BlockFlow::float_from_node(self, node, float_kind) as Box<Flow>;
+ self.build_flow_using_children(FlowRef::new(flow), node)
+ }
+
+ /// Concatenates the fragments of kids, adding in our own borders/padding/margins if necessary.
+ /// Returns the `InlineFragmentsConstructionResult`, if any. There will be no
+ /// `InlineFragmentsConstructionResult` if this node consisted entirely of ignorable whitespace.
+ fn build_fragments_for_nonreplaced_inline_content(&mut self, node: &ThreadSafeLayoutNode)
+ -> ConstructionResult {
+ let mut opt_inline_block_splits: Vec<InlineBlockSplit> = Vec::new();
+ let mut fragment_accumulator = InlineFragmentsAccumulator::from_inline_node(node);
+ let mut abs_descendants = Descendants::new();
+
+ // Concatenate all the fragments of our kids, creating {ib} splits as necessary.
+ for kid in node.children() {
+ if kid.get_pseudo_element_type() != Normal {
+ self.process(&kid);
+ }
+ match kid.swap_out_construction_result() {
+ NoConstructionResult => {}
+ FlowConstructionResult(flow, kid_abs_descendants) => {
+ // {ib} split. Flush the accumulator to our new split and make a new
+ // accumulator to hold any subsequent fragments we come across.
+ let split = InlineBlockSplit {
+ predecessors:
+ mem::replace(&mut fragment_accumulator,
+ InlineFragmentsAccumulator::from_inline_node(node)).finish(),
+ flow: flow,
+ };
+ opt_inline_block_splits.push(split);
+ abs_descendants.push_descendants(kid_abs_descendants);
+ }
+ ConstructionItemConstructionResult(InlineFragmentsConstructionItem(
+ InlineFragmentsConstructionResult {
+ splits: splits,
+ fragments: successors,
+ abs_descendants: kid_abs_descendants,
+ })) => {
+
+ // Bubble up {ib} splits.
+ for split in splits.move_iter() {
+ let InlineBlockSplit {
+ predecessors: predecessors,
+ flow: kid_flow
+ } = split;
+ fragment_accumulator.fragments.push_all(predecessors);
+
+ let split = InlineBlockSplit {
+ predecessors:
+ mem::replace(&mut fragment_accumulator,
+ InlineFragmentsAccumulator::from_inline_node(node))
+ .finish(),
+ flow: kid_flow,
+ };
+ opt_inline_block_splits.push(split)
+ }
+
+ // Push residual fragments.
+ fragment_accumulator.fragments.push_all(successors);
+ abs_descendants.push_descendants(kid_abs_descendants);
+ }
+ ConstructionItemConstructionResult(WhitespaceConstructionItem(whitespace_node,
+ whitespace_style))
+ => {
+ // Instantiate the whitespace fragment.
+ let fragment_info = UnscannedTextFragment(UnscannedTextFragmentInfo::from_text(" ".to_string()));
+ let mut fragment = Fragment::from_opaque_node_and_style(whitespace_node,
+ whitespace_style.clone(),
+ fragment_info);
+ fragment_accumulator.fragments.push(&mut fragment, whitespace_style)
+ }
+ ConstructionItemConstructionResult(TableColumnFragmentConstructionItem(_)) => {
+ // TODO: Implement anonymous table objects for missing parents
+ // CSS 2.1 § 17.2.1, step 3-2
+ }
+ }
+ }
+
+ // Finally, make a new construction result.
+ if opt_inline_block_splits.len() > 0 || fragment_accumulator.fragments.len() > 0
+ || abs_descendants.len() > 0 {
+ let construction_item = InlineFragmentsConstructionItem(InlineFragmentsConstructionResult {
+ splits: opt_inline_block_splits,
+ fragments: fragment_accumulator.finish(),
+ abs_descendants: abs_descendants,
+ });
+ ConstructionItemConstructionResult(construction_item)
+ } else {
+ NoConstructionResult
+ }
+ }
+
+ /// Creates an `InlineFragmentsConstructionResult` for replaced content. Replaced content doesn't
+ /// render its children, so this just nukes a child's fragments and creates a `Fragment`.
+ fn build_fragments_for_replaced_inline_content(&mut self, node: &ThreadSafeLayoutNode)
+ -> ConstructionResult {
+ for kid in node.children() {
+ kid.set_flow_construction_result(NoConstructionResult)
+ }
+
+ // If this node is ignorable whitespace, bail out now.
+ //
+ // FIXME(#2001, pcwalton): Don't do this if there's padding or borders.
+ if node.is_ignorable_whitespace() {
+ let opaque_node = OpaqueNodeMethods::from_thread_safe_layout_node(node);
+ return ConstructionItemConstructionResult(WhitespaceConstructionItem(
+ opaque_node,
+ node.style().clone()))
+ }
+
+ let mut fragments = InlineFragments::new();
+ fragments.push(&mut Fragment::new(self, node), node.style().clone());
+
+ let construction_item = InlineFragmentsConstructionItem(InlineFragmentsConstructionResult {
+ splits: Vec::new(),
+ fragments: fragments,
+ abs_descendants: Descendants::new(),
+ });
+ ConstructionItemConstructionResult(construction_item)
+ }
+
+ /// Builds one or more fragments for a node with `display: inline`. This yields an
+ /// `InlineFragmentsConstructionResult`.
+ fn build_fragments_for_inline(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult {
+ // Is this node replaced content?
+ if !node.is_replaced_content() {
+ // Go to a path that concatenates our kids' fragments.
+ self.build_fragments_for_nonreplaced_inline_content(node)
+ } else {
+ // Otherwise, just nuke our kids' fragments, create our fragment if any, and be done
+ // with it.
+ self.build_fragments_for_replaced_inline_content(node)
+ }
+ }
+
+ /// TableCaptionFlow is populated underneath TableWrapperFlow
+ fn place_table_caption_under_table_wrapper(&mut self,
+ table_wrapper_flow: &mut FlowRef,
+ node: &ThreadSafeLayoutNode) {
+ for kid in node.children() {
+ match kid.swap_out_construction_result() {
+ NoConstructionResult | ConstructionItemConstructionResult(_) => {}
+ FlowConstructionResult(kid_flow, _) => {
+ // Only kid flows with table-caption are matched here.
+ assert!(kid_flow.get().is_table_caption());
+ table_wrapper_flow.add_new_child(kid_flow);
+ }
+ }
+ }
+ }
+
+ /// Generates an anonymous table flow according to CSS 2.1 § 17.2.1, step 2.
+ /// If necessary, generate recursively another anonymous table flow.
+ fn generate_anonymous_missing_child(&mut self,
+ child_flows: Vec<FlowRef>,
+ flow: &mut FlowRef,
+ node: &ThreadSafeLayoutNode) {
+ let mut anonymous_flow = flow.get().generate_missing_child_flow(node);
+ let mut consecutive_siblings = vec!();
+ for kid_flow in child_flows.move_iter() {
+ if anonymous_flow.get().need_anonymous_flow(kid_flow.get()) {
+ consecutive_siblings.push(kid_flow);
+ continue;
+ }
+ if !consecutive_siblings.is_empty() {
+ self.generate_anonymous_missing_child(consecutive_siblings,
+ &mut anonymous_flow,
+ node);
+ consecutive_siblings = vec!();
+ }
+ anonymous_flow.add_new_child(kid_flow);
+ }
+ if !consecutive_siblings.is_empty() {
+ self.generate_anonymous_missing_child(consecutive_siblings, &mut anonymous_flow, node);
+ }
+ // The flow is done.
+ anonymous_flow.finish(self.layout_context);
+ flow.add_new_child(anonymous_flow);
+ }
+
+ /// Builds a flow for a node with `display: table`. This yields a `TableWrapperFlow` with possibly
+ /// other `TableCaptionFlow`s or `TableFlow`s underneath it.
+ fn build_flow_for_table_wrapper(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult {
+ let fragment = Fragment::new_from_specific_info(node, TableWrapperFragment);
+ let wrapper_flow = box TableWrapperFlow::from_node_and_fragment(node, fragment);
+ let mut wrapper_flow = FlowRef::new(wrapper_flow as Box<Flow>);
+
+ let table_fragment = Fragment::new_from_specific_info(node, TableFragment);
+ let table_flow = box TableFlow::from_node_and_fragment(node, table_fragment);
+ let table_flow = FlowRef::new(table_flow as Box<Flow>);
+
+ // We first populate the TableFlow with other flows than TableCaptionFlow.
+ // We then populate the TableWrapperFlow with TableCaptionFlow, and attach
+ // the TableFlow to the TableWrapperFlow
+ let construction_result = self.build_flow_using_children(table_flow, node);
+ self.place_table_caption_under_table_wrapper(&mut wrapper_flow, node);
+
+ let mut abs_descendants = Descendants::new();
+ let mut fixed_descendants = Descendants::new();
+
+ // NOTE: The order of captions and table are not the same order as in the DOM tree.
+ // All caption blocks are placed before the table flow
+ match construction_result {
+ FlowConstructionResult(table_flow, table_abs_descendants) => {
+ wrapper_flow.add_new_child(table_flow);
+ abs_descendants.push_descendants(table_abs_descendants);
+ }
+ _ => {}
+ }
+
+ // The flow is done.
+ wrapper_flow.finish(self.layout_context);
+ let is_positioned = wrapper_flow.get_mut().as_block().is_positioned();
+ let is_fixed_positioned = wrapper_flow.get_mut().as_block().is_fixed();
+ let is_absolutely_positioned = wrapper_flow.get_mut()
+ .as_block()
+ .is_absolutely_positioned();
+ if is_positioned {
+ // This is the CB for all the absolute descendants.
+ wrapper_flow.set_abs_descendants(abs_descendants);
+
+ abs_descendants = Descendants::new();
+
+ if is_fixed_positioned {
+ // Send itself along with the other fixed descendants.
+ fixed_descendants.push(wrapper_flow.clone());
+ } else if is_absolutely_positioned {
+ // This is now the only absolute flow in the subtree which hasn't yet
+ // reached its CB.
+ abs_descendants.push(wrapper_flow.clone());
+ }
+ }
+ FlowConstructionResult(wrapper_flow, abs_descendants)
+ }
+
+ /// Builds a flow for a node with `display: table-caption`. This yields a `TableCaptionFlow`
+ /// with possibly other `BlockFlow`s or `InlineFlow`s underneath it.
+ fn build_flow_for_table_caption(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult {
+ let flow = box TableCaptionFlow::from_node(self, node) as Box<Flow>;
+ self.build_flow_using_children(FlowRef::new(flow), node)
+ }
+
+ /// Builds a flow for a node with `display: table-row-group`. This yields a `TableRowGroupFlow`
+ /// with possibly other `TableRowFlow`s underneath it.
+ fn build_flow_for_table_rowgroup(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult {
+ let fragment = Fragment::new_from_specific_info(node, TableRowFragment);
+ let flow = box TableRowGroupFlow::from_node_and_fragment(node, fragment);
+ let flow = flow as Box<Flow>;
+ self.build_flow_using_children(FlowRef::new(flow), node)
+ }
+
+ /// Builds a flow for a node with `display: table-row`. This yields a `TableRowFlow` with
+ /// possibly other `TableCellFlow`s underneath it.
+ fn build_flow_for_table_row(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult {
+ let fragment = Fragment::new_from_specific_info(node, TableRowFragment);
+ let flow = box TableRowFlow::from_node_and_fragment(node, fragment) as Box<Flow>;
+ self.build_flow_using_children(FlowRef::new(flow), node)
+ }
+
+ /// Builds a flow for a node with `display: table-cell`. This yields a `TableCellFlow` with
+ /// possibly other `BlockFlow`s or `InlineFlow`s underneath it.
+ fn build_flow_for_table_cell(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult {
+ let fragment = Fragment::new_from_specific_info(node, TableCellFragment);
+ let flow = box TableCellFlow::from_node_and_fragment(node, fragment) as Box<Flow>;
+ self.build_flow_using_children(FlowRef::new(flow), node)
+ }
+
+ /// Creates a fragment for a node with `display: table-column`.
+ fn build_fragments_for_table_column(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult {
+ // CSS 2.1 § 17.2.1. Treat all child fragments of a `table-column` as `display: none`.
+ for kid in node.children() {
+ kid.set_flow_construction_result(NoConstructionResult)
+ }
+
+ let specific = TableColumnFragment(TableColumnFragmentInfo::new(node));
+ let construction_item = TableColumnFragmentConstructionItem(
+ Fragment::new_from_specific_info(node, specific)
+ );
+ ConstructionItemConstructionResult(construction_item)
+ }
+
+ /// Builds a flow for a node with `display: table-column-group`.
+ /// This yields a `TableColGroupFlow`.
+ fn build_flow_for_table_colgroup(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult {
+ let fragment = Fragment::new_from_specific_info(node,
+ TableColumnFragment(TableColumnFragmentInfo::new(node)));
+ let mut col_fragments = vec!();
+ for kid in node.children() {
+ // CSS 2.1 § 17.2.1. Treat all non-column child fragments of `table-column-group`
+ // as `display: none`.
+ match kid.swap_out_construction_result() {
+ ConstructionItemConstructionResult(TableColumnFragmentConstructionItem(fragment)) => {
+ col_fragments.push(fragment);
+ }
+ _ => {}
+ }
+ }
+ if col_fragments.is_empty() {
+ debug!("add TableColumnFragment for empty colgroup");
+ let specific = TableColumnFragment(TableColumnFragmentInfo::new(node));
+ col_fragments.push(Fragment::new_from_specific_info(node, specific));
+ }
+ let flow = box TableColGroupFlow::from_node_and_fragments(node, fragment, col_fragments);
+ let mut flow = FlowRef::new(flow as Box<Flow>);
+ flow.finish(self.layout_context);
+
+ FlowConstructionResult(flow, Descendants::new())
+ }
+}
+
+impl<'a, 'b> PostorderNodeMutTraversal for FlowConstructor<'a, 'b> {
+ // Construct Flow based on 'display', 'position', and 'float' values.
+ //
+ // CSS 2.1 Section 9.7
+ //
+ // TODO: This should actually consult the table in that section to get the
+ // final computed value for 'display'.
+ //
+ // `#[inline(always)]` because this is always called from the traversal function and for some
+ // reason LLVM's inlining heuristics go awry here.
+ #[inline(always)]
+ fn process(&mut self, node: &ThreadSafeLayoutNode) -> bool {
+ // Get the `display` property for this node, and determine whether this node is floated.
+ let (display, float, positioning) = match node.type_id() {
+ None => {
+ // Pseudo-element.
+ let style = node.style();
+ (display::inline, style.get_box().float, style.get_box().position)
+ }
+ Some(ElementNodeTypeId(_)) => {
+ let style = node.style();
+ (style.get_box().display, style.get_box().float, style.get_box().position)
+ }
+ Some(TextNodeTypeId) => (display::inline, float::none, position::static_),
+ Some(CommentNodeTypeId) |
+ Some(DoctypeNodeTypeId) |
+ Some(DocumentFragmentNodeTypeId) |
+ Some(DocumentNodeTypeId) |
+ Some(ProcessingInstructionNodeTypeId) => {
+ (display::none, float::none, position::static_)
+ }
+ };
+
+ debug!("building flow for node: {:?} {:?}", display, float);
+
+ // Switch on display and floatedness.
+ match (display, float, positioning) {
+ // `display: none` contributes no flow construction result. Nuke the flow construction
+ // results of children.
+ (display::none, _, _) => {
+ for child in node.children() {
+ drop(child.swap_out_construction_result())
+ }
+ }
+
+ // Table items contribute table flow construction results.
+ (display::table, _, _) => {
+ let construction_result = self.build_flow_for_table_wrapper(node);
+ node.set_flow_construction_result(construction_result)
+ }
+
+ // Absolutely positioned elements will have computed value of
+ // `float` as 'none' and `display` as per the table.
+ // Only match here for block items. If an item is absolutely
+ // positioned, but inline we shouldn't try to construct a block
+ // flow here - instead, let it match the inline case
+ // below.
+ (display::block, _, position::absolute) | (_, _, position::fixed) => {
+ node.set_flow_construction_result(self.build_flow_for_block(node))
+ }
+
+ // Inline items contribute inline fragment construction results.
+ (display::inline, float::none, _) => {
+ let construction_result = self.build_fragments_for_inline(node);
+ node.set_flow_construction_result(construction_result)
+ }
+
+ // Table items contribute table flow construction results.
+ (display::table_caption, _, _) => {
+ let construction_result = self.build_flow_for_table_caption(node);
+ node.set_flow_construction_result(construction_result)
+ }
+
+ // Table items contribute table flow construction results.
+ (display::table_column_group, _, _) => {
+ let construction_result = self.build_flow_for_table_colgroup(node);
+ node.set_flow_construction_result(construction_result)
+ }
+
+ // Table items contribute table flow construction results.
+ (display::table_column, _, _) => {
+ let construction_result = self.build_fragments_for_table_column(node);
+ node.set_flow_construction_result(construction_result)
+ }
+
+ // Table items contribute table flow construction results.
+ (display::table_row_group, _, _) | (display::table_header_group, _, _) |
+ (display::table_footer_group, _, _) => {
+ let construction_result = self.build_flow_for_table_rowgroup(node);
+ node.set_flow_construction_result(construction_result)
+ }
+
+ // Table items contribute table flow construction results.
+ (display::table_row, _, _) => {
+ let construction_result = self.build_flow_for_table_row(node);
+ node.set_flow_construction_result(construction_result)
+ }
+
+ // Table items contribute table flow construction results.
+ (display::table_cell, _, _) => {
+ let construction_result = self.build_flow_for_table_cell(node);
+ node.set_flow_construction_result(construction_result)
+ }
+
+ // Block flows that are not floated contribute block flow construction results.
+ //
+ // TODO(pcwalton): Make this only trigger for blocks and handle the other `display`
+ // properties separately.
+
+ (_, float::none, _) => {
+ node.set_flow_construction_result(self.build_flow_for_block(node))
+ }
+
+ // Floated flows contribute float flow construction results.
+ (_, float_value, _) => {
+ let float_kind = FloatKind::from_property(float_value);
+ node.set_flow_construction_result(
+ self.build_flow_for_floated_block(node, float_kind))
+ }
+ }
+
+ true
+ }
+}
+
+/// A utility trait with some useful methods for node queries.
+trait NodeUtils {
+ /// Returns true if this node doesn't render its kids and false otherwise.
+ fn is_replaced_content(&self) -> bool;
+
+ /// Sets the construction result of a flow.
+ fn set_flow_construction_result(&self, result: ConstructionResult);
+
+ /// Replaces the flow construction result in a node with `NoConstructionResult` and returns the
+ /// old value.
+ fn swap_out_construction_result(&self) -> ConstructionResult;
+}
+
+impl<'ln> NodeUtils for ThreadSafeLayoutNode<'ln> {
+ fn is_replaced_content(&self) -> bool {
+ match self.type_id() {
+ Some(TextNodeTypeId) |
+ Some(ProcessingInstructionNodeTypeId) |
+ Some(CommentNodeTypeId) |
+ Some(DoctypeNodeTypeId) |
+ Some(DocumentFragmentNodeTypeId) |
+ Some(DocumentNodeTypeId) |
+ None |
+ Some(ElementNodeTypeId(HTMLImageElementTypeId)) => true,
+ Some(ElementNodeTypeId(HTMLObjectElementTypeId)) => self.has_object_data(),
+ Some(ElementNodeTypeId(_)) => false,
+ }
+ }
+
+ #[inline(always)]
+ fn set_flow_construction_result(&self, result: ConstructionResult) {
+ let mut layout_data_ref = self.mutate_layout_data();
+ match &mut *layout_data_ref {
+ &Some(ref mut layout_data) =>{
+ match self.get_pseudo_element_type() {
+ Before | BeforeBlock => {
+ layout_data.data.before_flow_construction_result = result
+ },
+ After | AfterBlock => {
+ layout_data.data.after_flow_construction_result = result
+ },
+ Normal => layout_data.data.flow_construction_result = result,
+ }
+ },
+ &None => fail!("no layout data"),
+ }
+ }
+
+ #[inline(always)]
+ fn swap_out_construction_result(&self) -> ConstructionResult {
+ let mut layout_data_ref = self.mutate_layout_data();
+ match &mut *layout_data_ref {
+ &Some(ref mut layout_data) => {
+ match self.get_pseudo_element_type() {
+ Before | BeforeBlock => {
+ mem::replace(&mut layout_data.data.before_flow_construction_result,
+ NoConstructionResult)
+ }
+ After | AfterBlock => {
+ mem::replace(&mut layout_data.data.after_flow_construction_result,
+ NoConstructionResult)
+ }
+ Normal => {
+ mem::replace(&mut layout_data.data.flow_construction_result,
+ NoConstructionResult)
+ }
+ }
+ }
+ &None => fail!("no layout data"),
+ }
+ }
+}
+
+/// Methods for interacting with HTMLObjectElement nodes
+trait ObjectElement {
+ /// Returns None if this node is not matching attributes.
+ fn get_type_and_data(&self) -> (Option<&'static str>, Option<&'static str>);
+
+ /// Returns true if this node has object data that is correct uri.
+ fn has_object_data(&self) -> bool;
+
+ /// Returns the "data" attribute value parsed as a URL
+ fn get_object_data(&self) -> Option<Url>;
+}
+
+impl<'ln> ObjectElement for ThreadSafeLayoutNode<'ln> {
+ fn get_type_and_data(&self) -> (Option<&'static str>, Option<&'static str>) {
+ let elem = self.as_element();
+ (elem.get_attr(&namespace::Null, "type"), elem.get_attr(&namespace::Null, "data"))
+ }
+
+ fn has_object_data(&self) -> bool {
+ match self.get_type_and_data() {
+ (None, Some(uri)) => is_image_data(uri),
+ _ => false
+ }
+ }
+
+ fn get_object_data(&self) -> Option<Url> {
+ match self.get_type_and_data() {
+ (None, Some(uri)) if is_image_data(uri) => Url::parse(uri).ok(),
+ _ => None
+ }
+ }
+}
+
+pub trait FlowConstructionUtils {
+ /// Adds a new flow as a child of this flow. Removes the flow from the given leaf set if
+ /// it's present.
+ fn add_new_child(&mut self, new_child: FlowRef);
+
+ /// Finishes a flow. Once a flow is finished, no more child flows or boxes may be added to it.
+ /// This will normally run the bubble-inline-sizes (minimum and preferred -- i.e. intrinsic -- inline-size)
+ /// calculation, unless the global `bubble_inline-sizes_separately` flag is on.
+ ///
+ /// All flows must be finished at some point, or they will not have their intrinsic inline-sizes
+ /// properly computed. (This is not, however, a memory safety problem.)
+ fn finish(&mut self, context: &LayoutContext);
+}
+
+impl FlowConstructionUtils for FlowRef {
+ /// Adds a new flow as a child of this flow. Fails if this flow is marked as a leaf.
+ ///
+ /// This must not be public because only the layout constructor can do this.
+ fn add_new_child(&mut self, mut new_child: FlowRef) {
+ {
+ let kid_base = flow::mut_base(new_child.get_mut());
+ kid_base.parallel.parent = parallel::mut_owned_flow_to_unsafe_flow(self);
+ }
+
+ let base = flow::mut_base(self.get_mut());
+ base.children.push_back(new_child);
+ let _ = base.parallel.children_count.fetch_add(1, Relaxed);
+ let _ = base.parallel.children_and_absolute_descendant_count.fetch_add(1, Relaxed);
+ }
+
+ /// Finishes a flow. Once a flow is finished, no more child flows or fragments may be added to
+ /// it. This will normally run the bubble-inline-sizes (minimum and preferred -- i.e. intrinsic --
+ /// inline-size) calculation, unless the global `bubble_inline-sizes_separately` flag is on.
+ ///
+ /// All flows must be finished at some point, or they will not have their intrinsic inline-sizes
+ /// properly computed. (This is not, however, a memory safety problem.)
+ ///
+ /// This must not be public because only the layout constructor can do this.
+ fn finish(&mut self, context: &LayoutContext) {
+ if !context.shared.opts.bubble_inline_sizes_separately {
+ self.get_mut().bubble_inline_sizes(context)
+ }
+ }
+}
+
diff --git a/components/layout/context.rs b/components/layout/context.rs
new file mode 100644
index 00000000000..936314dcb63
--- /dev/null
+++ b/components/layout/context.rs
@@ -0,0 +1,123 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Data needed by the layout task.
+
+use css::matching::{ApplicableDeclarationsCache, StyleSharingCandidateCache};
+
+use geom::{Rect, Size2D};
+use gfx::display_list::OpaqueNode;
+use gfx::font_context::FontContext;
+use gfx::font_cache_task::FontCacheTask;
+use script::layout_interface::LayoutChan;
+use servo_msg::constellation_msg::ConstellationChan;
+use servo_net::local_image_cache::LocalImageCache;
+use servo_util::geometry::Au;
+use servo_util::opts::Opts;
+use sync::{Arc, Mutex};
+use std::mem;
+use style::Stylist;
+use url::Url;
+
+struct LocalLayoutContext {
+ font_context: FontContext,
+ applicable_declarations_cache: ApplicableDeclarationsCache,
+ style_sharing_candidate_cache: StyleSharingCandidateCache,
+}
+
+local_data_key!(local_context_key: *mut LocalLayoutContext)
+
+fn create_or_get_local_context(shared_layout_context: &SharedLayoutContext) -> *mut LocalLayoutContext {
+ let maybe_context = local_context_key.get();
+
+ let context = match maybe_context {
+ None => {
+ let context = box LocalLayoutContext {
+ font_context: FontContext::new(shared_layout_context.font_cache_task.clone()),
+ applicable_declarations_cache: ApplicableDeclarationsCache::new(),
+ style_sharing_candidate_cache: StyleSharingCandidateCache::new(),
+ };
+ local_context_key.replace(Some(unsafe { mem::transmute(context) }));
+ local_context_key.get().unwrap()
+ },
+ Some(context) => context
+ };
+
+ *context
+}
+
+pub struct SharedLayoutContext {
+ /// The local image cache.
+ pub image_cache: Arc<Mutex<LocalImageCache>>,
+
+ /// The current screen size.
+ pub screen_size: Size2D<Au>,
+
+ /// A channel up to the constellation.
+ pub constellation_chan: ConstellationChan,
+
+ /// A channel up to the layout task.
+ pub layout_chan: LayoutChan,
+
+ /// Interface to the font cache task.
+ pub font_cache_task: FontCacheTask,
+
+ /// The CSS selector stylist.
+ ///
+ /// FIXME(#2604): Make this no longer an unsafe pointer once we have fast `RWArc`s.
+ pub stylist: *const Stylist,
+
+ /// The root node at which we're starting the layout.
+ pub reflow_root: OpaqueNode,
+
+ /// The URL.
+ pub url: Url,
+
+ /// The command line options.
+ pub opts: Opts,
+
+ /// The dirty rectangle, used during display list building.
+ pub dirty: Rect<Au>,
+}
+
+pub struct LayoutContext<'a> {
+ pub shared: &'a SharedLayoutContext,
+ cached_local_layout_context: *mut LocalLayoutContext,
+}
+
+impl<'a> LayoutContext<'a> {
+ pub fn new(shared_layout_context: &'a SharedLayoutContext) -> LayoutContext<'a> {
+
+ let local_context = create_or_get_local_context(shared_layout_context);
+
+ LayoutContext {
+ shared: shared_layout_context,
+ cached_local_layout_context: local_context,
+ }
+ }
+
+ #[inline(always)]
+ pub fn font_context<'a>(&'a self) -> &'a mut FontContext {
+ unsafe {
+ let cached_context = &*self.cached_local_layout_context;
+ mem::transmute(&cached_context.font_context)
+ }
+ }
+
+ #[inline(always)]
+ pub fn applicable_declarations_cache<'a>(&'a self) -> &'a mut ApplicableDeclarationsCache {
+ unsafe {
+ let cached_context = &*self.cached_local_layout_context;
+ mem::transmute(&cached_context.applicable_declarations_cache)
+ }
+ }
+
+ #[inline(always)]
+ pub fn style_sharing_candidate_cache<'a>(&'a self) -> &'a mut StyleSharingCandidateCache {
+ unsafe {
+ let cached_context = &*self.cached_local_layout_context;
+ mem::transmute(&cached_context.style_sharing_candidate_cache)
+ }
+ }
+}
diff --git a/components/layout/css/matching.rs b/components/layout/css/matching.rs
new file mode 100644
index 00000000000..c1d766c32ca
--- /dev/null
+++ b/components/layout/css/matching.rs
@@ -0,0 +1,558 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// High-level interface to CSS selector matching.
+
+use css::node_style::StyledNode;
+use construct::FlowConstructor;
+use context::LayoutContext;
+use extra::LayoutAuxMethods;
+use util::{LayoutDataAccess, LayoutDataWrapper};
+use wrapper::{LayoutElement, LayoutNode, PostorderNodeMutTraversal, ThreadSafeLayoutNode};
+
+use servo_util::atom::Atom;
+use servo_util::cache::{Cache, LRUCache, SimpleHashCache};
+use servo_util::namespace::Null;
+use servo_util::smallvec::{SmallVec, SmallVec16};
+use servo_util::str::DOMString;
+use std::mem;
+use std::hash::{Hash, sip};
+use std::slice::Items;
+use style::{After, Before, ComputedValues, DeclarationBlock, Stylist, TElement, TNode, cascade};
+use sync::Arc;
+
+pub struct ApplicableDeclarations {
+ pub normal: SmallVec16<DeclarationBlock>,
+ pub before: Vec<DeclarationBlock>,
+ pub after: Vec<DeclarationBlock>,
+
+ /// Whether the `normal` declarations are shareable with other nodes.
+ pub normal_shareable: bool,
+}
+
+impl ApplicableDeclarations {
+ pub fn new() -> ApplicableDeclarations {
+ ApplicableDeclarations {
+ normal: SmallVec16::new(),
+ before: Vec::new(),
+ after: Vec::new(),
+ normal_shareable: false,
+ }
+ }
+
+ pub fn clear(&mut self) {
+ self.normal = SmallVec16::new();
+ self.before = Vec::new();
+ self.after = Vec::new();
+ self.normal_shareable = false;
+ }
+}
+
+#[deriving(Clone)]
+pub struct ApplicableDeclarationsCacheEntry {
+ pub declarations: Vec<DeclarationBlock>,
+}
+
+impl ApplicableDeclarationsCacheEntry {
+ fn new(slice: &[DeclarationBlock]) -> ApplicableDeclarationsCacheEntry {
+ let mut entry_declarations = Vec::new();
+ for declarations in slice.iter() {
+ entry_declarations.push(declarations.clone());
+ }
+ ApplicableDeclarationsCacheEntry {
+ declarations: entry_declarations,
+ }
+ }
+}
+
+impl PartialEq for ApplicableDeclarationsCacheEntry {
+ fn eq(&self, other: &ApplicableDeclarationsCacheEntry) -> bool {
+ let this_as_query = ApplicableDeclarationsCacheQuery::new(self.declarations.as_slice());
+ this_as_query.equiv(other)
+ }
+}
+
+impl Hash for ApplicableDeclarationsCacheEntry {
+ fn hash(&self, state: &mut sip::SipState) {
+ let tmp = ApplicableDeclarationsCacheQuery::new(self.declarations.as_slice());
+ tmp.hash(state);
+ }
+}
+
+struct ApplicableDeclarationsCacheQuery<'a> {
+ declarations: &'a [DeclarationBlock],
+}
+
+impl<'a> ApplicableDeclarationsCacheQuery<'a> {
+ fn new(declarations: &'a [DeclarationBlock]) -> ApplicableDeclarationsCacheQuery<'a> {
+ ApplicableDeclarationsCacheQuery {
+ declarations: declarations,
+ }
+ }
+}
+
+// Workaround for lack of `ptr_eq` on Arcs...
+#[inline]
+fn arc_ptr_eq<T>(a: &Arc<T>, b: &Arc<T>) -> bool {
+ unsafe {
+ let a: uint = mem::transmute_copy(a);
+ let b: uint = mem::transmute_copy(b);
+ a == b
+ }
+}
+
+impl<'a> Equiv<ApplicableDeclarationsCacheEntry> for ApplicableDeclarationsCacheQuery<'a> {
+ fn equiv(&self, other: &ApplicableDeclarationsCacheEntry) -> bool {
+ if self.declarations.len() != other.declarations.len() {
+ return false
+ }
+ for (this, other) in self.declarations.iter().zip(other.declarations.iter()) {
+ if !arc_ptr_eq(&this.declarations, &other.declarations) {
+ return false
+ }
+ }
+ return true
+ }
+}
+
+
+impl<'a> Hash for ApplicableDeclarationsCacheQuery<'a> {
+ fn hash(&self, state: &mut sip::SipState) {
+ for declaration in self.declarations.iter() {
+ let ptr: uint = unsafe {
+ mem::transmute_copy(declaration)
+ };
+ ptr.hash(state);
+ }
+ }
+}
+
+static APPLICABLE_DECLARATIONS_CACHE_SIZE: uint = 32;
+
+pub struct ApplicableDeclarationsCache {
+ cache: SimpleHashCache<ApplicableDeclarationsCacheEntry,Arc<ComputedValues>>,
+}
+
+impl ApplicableDeclarationsCache {
+ pub fn new() -> ApplicableDeclarationsCache {
+ ApplicableDeclarationsCache {
+ cache: SimpleHashCache::new(APPLICABLE_DECLARATIONS_CACHE_SIZE),
+ }
+ }
+
+ fn find(&self, declarations: &[DeclarationBlock]) -> Option<Arc<ComputedValues>> {
+ match self.cache.find_equiv(&ApplicableDeclarationsCacheQuery::new(declarations)) {
+ None => None,
+ Some(ref values) => Some((*values).clone()),
+ }
+ }
+
+ fn insert(&mut self, declarations: &[DeclarationBlock], style: Arc<ComputedValues>) {
+ self.cache.insert(ApplicableDeclarationsCacheEntry::new(declarations), style)
+ }
+}
+
+/// An LRU cache of the last few nodes seen, so that we can aggressively try to reuse their styles.
+pub struct StyleSharingCandidateCache {
+ cache: LRUCache<StyleSharingCandidate,()>,
+}
+
+#[deriving(Clone)]
+pub struct StyleSharingCandidate {
+ pub style: Arc<ComputedValues>,
+ pub parent_style: Arc<ComputedValues>,
+ pub local_name: Atom,
+ pub class: Option<DOMString>,
+}
+
+impl PartialEq for StyleSharingCandidate {
+ fn eq(&self, other: &StyleSharingCandidate) -> bool {
+ arc_ptr_eq(&self.style, &other.style) &&
+ arc_ptr_eq(&self.parent_style, &other.parent_style) &&
+ self.local_name == other.local_name &&
+ self.class == other.class
+ }
+}
+
+impl StyleSharingCandidate {
+ /// Attempts to create a style sharing candidate from this node. Returns
+ /// the style sharing candidate or `None` if this node is ineligible for
+ /// style sharing.
+ fn new(node: &LayoutNode) -> Option<StyleSharingCandidate> {
+ let parent_node = match node.parent_node() {
+ None => return None,
+ Some(parent_node) => parent_node,
+ };
+ if !parent_node.is_element() {
+ return None
+ }
+
+ let style = unsafe {
+ match *node.borrow_layout_data_unchecked() {
+ None => return None,
+ Some(ref layout_data_ref) => {
+ match layout_data_ref.shared_data.style {
+ None => return None,
+ Some(ref data) => (*data).clone(),
+ }
+ }
+ }
+ };
+ let parent_style = unsafe {
+ match *parent_node.borrow_layout_data_unchecked() {
+ None => return None,
+ Some(ref parent_layout_data_ref) => {
+ match parent_layout_data_ref.shared_data.style {
+ None => return None,
+ Some(ref data) => (*data).clone(),
+ }
+ }
+ }
+ };
+
+ let mut style = Some(style);
+ let mut parent_style = Some(parent_style);
+ let element = node.as_element();
+ if element.style_attribute().is_some() {
+ return None
+ }
+
+ Some(StyleSharingCandidate {
+ style: style.take_unwrap(),
+ parent_style: parent_style.take_unwrap(),
+ local_name: element.get_local_name().clone(),
+ class: element.get_attr(&Null, "class")
+ .map(|string| string.to_string()),
+ })
+ }
+
+ fn can_share_style_with(&self, element: &LayoutElement) -> bool {
+ if *element.get_local_name() != self.local_name {
+ return false
+ }
+ match (&self.class, element.get_attr(&Null, "class")) {
+ (&None, Some(_)) | (&Some(_), None) => return false,
+ (&Some(ref this_class), Some(element_class)) if element_class != this_class.as_slice() => {
+ return false
+ }
+ (&Some(_), Some(_)) | (&None, None) => {}
+ }
+ true
+ }
+}
+
+static STYLE_SHARING_CANDIDATE_CACHE_SIZE: uint = 40;
+
+impl StyleSharingCandidateCache {
+ pub fn new() -> StyleSharingCandidateCache {
+ StyleSharingCandidateCache {
+ cache: LRUCache::new(STYLE_SHARING_CANDIDATE_CACHE_SIZE),
+ }
+ }
+
+ pub fn iter<'a>(&'a self) -> Items<'a,(StyleSharingCandidate,())> {
+ self.cache.iter()
+ }
+
+ pub fn insert_if_possible(&mut self, node: &LayoutNode) {
+ match StyleSharingCandidate::new(node) {
+ None => {}
+ Some(candidate) => self.cache.insert(candidate, ())
+ }
+ }
+
+ pub fn touch(&mut self, index: uint) {
+ self.cache.touch(index)
+ }
+}
+
+/// The results of attempting to share a style.
+pub enum StyleSharingResult<'ln> {
+ /// We didn't find anybody to share the style with. The boolean indicates whether the style
+ /// is shareable at all.
+ CannotShare(bool),
+ /// The node's style can be shared. The integer specifies the index in the LRU cache that was
+ /// hit.
+ StyleWasShared(uint),
+}
+
+pub trait MatchMethods {
+ /// Performs aux initialization, selector matching, cascading, and flow construction
+ /// sequentially.
+ fn recalc_style_for_subtree(&self,
+ stylist: &Stylist,
+ layout_context: &LayoutContext,
+ applicable_declarations: &mut ApplicableDeclarations,
+ parent: Option<LayoutNode>);
+
+ fn match_node(&self,
+ stylist: &Stylist,
+ applicable_declarations: &mut ApplicableDeclarations,
+ shareable: &mut bool);
+
+ /// Attempts to share a style with another node. This method is unsafe because it depends on
+ /// the `style_sharing_candidate_cache` having only live nodes in it, and we have no way to
+ /// guarantee that at the type system level yet.
+ unsafe fn share_style_if_possible(&self,
+ style_sharing_candidate_cache:
+ &mut StyleSharingCandidateCache,
+ parent: Option<LayoutNode>)
+ -> StyleSharingResult;
+
+ unsafe fn cascade_node(&self,
+ parent: Option<LayoutNode>,
+ applicable_declarations: &ApplicableDeclarations,
+ applicable_declarations_cache: &mut ApplicableDeclarationsCache);
+}
+
+trait PrivateMatchMethods {
+ fn cascade_node_pseudo_element(&self,
+ parent_style: Option<&Arc<ComputedValues>>,
+ applicable_declarations: &[DeclarationBlock],
+ style: &mut Option<Arc<ComputedValues>>,
+ applicable_declarations_cache: &mut
+ ApplicableDeclarationsCache,
+ shareable: bool);
+
+ fn share_style_with_candidate_if_possible(&self,
+ parent_node: Option<LayoutNode>,
+ candidate: &StyleSharingCandidate)
+ -> Option<Arc<ComputedValues>>;
+}
+
+impl<'ln> PrivateMatchMethods for LayoutNode<'ln> {
+ fn cascade_node_pseudo_element(&self,
+ parent_style: Option<&Arc<ComputedValues>>,
+ applicable_declarations: &[DeclarationBlock],
+ style: &mut Option<Arc<ComputedValues>>,
+ applicable_declarations_cache: &mut
+ ApplicableDeclarationsCache,
+ shareable: bool) {
+ let this_style;
+ let cacheable;
+ match parent_style {
+ Some(ref parent_style) => {
+ let cache_entry = applicable_declarations_cache.find(applicable_declarations);
+ let cached_computed_values = match cache_entry {
+ None => None,
+ Some(ref style) => Some(&**style),
+ };
+ let (the_style, is_cacheable) = cascade(applicable_declarations,
+ shareable,
+ Some(&***parent_style),
+ cached_computed_values);
+ cacheable = is_cacheable;
+ this_style = Arc::new(the_style);
+ }
+ None => {
+ let (the_style, is_cacheable) = cascade(applicable_declarations,
+ shareable,
+ None,
+ None);
+ cacheable = is_cacheable;
+ this_style = Arc::new(the_style);
+ }
+ };
+
+ // Cache the resolved style if it was cacheable.
+ if cacheable {
+ applicable_declarations_cache.insert(applicable_declarations, this_style.clone());
+ }
+
+ *style = Some(this_style);
+ }
+
+
+ fn share_style_with_candidate_if_possible(&self,
+ parent_node: Option<LayoutNode>,
+ candidate: &StyleSharingCandidate)
+ -> Option<Arc<ComputedValues>> {
+ assert!(self.is_element());
+
+ let parent_node = match parent_node {
+ Some(ref parent_node) if parent_node.is_element() => parent_node,
+ Some(_) | None => return None,
+ };
+
+ let parent_layout_data: &Option<LayoutDataWrapper> = unsafe {
+ mem::transmute(parent_node.borrow_layout_data_unchecked())
+ };
+ match parent_layout_data {
+ &Some(ref parent_layout_data_ref) => {
+ // Check parent style.
+ let parent_style = parent_layout_data_ref.shared_data.style.as_ref().unwrap();
+ if !arc_ptr_eq(parent_style, &candidate.parent_style) {
+ return None
+ }
+
+ // Check tag names, classes, etc.
+ if !candidate.can_share_style_with(&self.as_element()) {
+ return None
+ }
+
+ return Some(candidate.style.clone())
+ }
+ _ => {}
+ }
+
+ None
+ }
+}
+
+impl<'ln> MatchMethods for LayoutNode<'ln> {
+ fn match_node(&self,
+ stylist: &Stylist,
+ applicable_declarations: &mut ApplicableDeclarations,
+ shareable: &mut bool) {
+ let style_attribute = self.as_element().style_attribute().as_ref();
+
+ applicable_declarations.normal_shareable =
+ stylist.push_applicable_declarations(self,
+ style_attribute,
+ None,
+ &mut applicable_declarations.normal);
+ stylist.push_applicable_declarations(self,
+ None,
+ Some(Before),
+ &mut applicable_declarations.before);
+ stylist.push_applicable_declarations(self,
+ None,
+ Some(After),
+ &mut applicable_declarations.after);
+
+ *shareable = applicable_declarations.normal_shareable
+ }
+
+ unsafe fn share_style_if_possible(&self,
+ style_sharing_candidate_cache:
+ &mut StyleSharingCandidateCache,
+ parent: Option<LayoutNode>)
+ -> StyleSharingResult {
+ if !self.is_element() {
+ return CannotShare(false)
+ }
+ let ok = {
+ let element = self.as_element();
+ element.style_attribute().is_none() && element.get_attr(&Null, "id").is_none()
+ };
+ if !ok {
+ return CannotShare(false)
+ }
+
+ for (i, &(ref candidate, ())) in style_sharing_candidate_cache.iter().enumerate() {
+ match self.share_style_with_candidate_if_possible(parent.clone(), candidate) {
+ Some(shared_style) => {
+ // Yay, cache hit. Share the style.
+ let mut layout_data_ref = self.mutate_layout_data();
+ layout_data_ref.get_mut_ref().shared_data.style = Some(shared_style);
+ return StyleWasShared(i)
+ }
+ None => {}
+ }
+ }
+
+ CannotShare(true)
+ }
+
+ fn recalc_style_for_subtree(&self,
+ stylist: &Stylist,
+ layout_context: &LayoutContext,
+ applicable_declarations: &mut ApplicableDeclarations,
+ parent: Option<LayoutNode>) {
+ self.initialize_layout_data(layout_context.shared.layout_chan.clone());
+
+ // First, check to see whether we can share a style with someone.
+ let sharing_result = unsafe {
+ self.share_style_if_possible(layout_context.style_sharing_candidate_cache(), parent.clone())
+ };
+
+ // Otherwise, match and cascade selectors.
+ match sharing_result {
+ CannotShare(mut shareable) => {
+ if self.is_element() {
+ self.match_node(stylist, applicable_declarations, &mut shareable)
+ }
+
+ unsafe {
+ self.cascade_node(parent,
+ applicable_declarations,
+ layout_context.applicable_declarations_cache())
+ }
+
+ applicable_declarations.clear();
+
+ // Add ourselves to the LRU cache.
+ if shareable {
+ layout_context.style_sharing_candidate_cache().insert_if_possible(self)
+ }
+ }
+ StyleWasShared(index) => layout_context.style_sharing_candidate_cache().touch(index),
+ }
+
+ for kid in self.children() {
+ kid.recalc_style_for_subtree(stylist,
+ layout_context,
+ applicable_declarations,
+ Some(self.clone()))
+ }
+
+ // Construct flows.
+ let layout_node = ThreadSafeLayoutNode::new(self);
+ let mut flow_constructor = FlowConstructor::new(layout_context);
+ flow_constructor.process(&layout_node);
+ }
+
+ unsafe fn cascade_node(&self,
+ parent: Option<LayoutNode>,
+ applicable_declarations: &ApplicableDeclarations,
+ applicable_declarations_cache: &mut ApplicableDeclarationsCache) {
+ // Get our parent's style. This must be unsafe so that we don't touch the parent's
+ // borrow flags.
+ //
+ // FIXME(pcwalton): Isolate this unsafety into the `wrapper` module to allow
+ // enforced safe, race-free access to the parent style.
+ let parent_style = match parent {
+ None => None,
+ Some(parent_node) => {
+ let parent_layout_data = parent_node.borrow_layout_data_unchecked();
+ match *parent_layout_data {
+ None => fail!("no parent data?!"),
+ Some(ref parent_layout_data) => {
+ match parent_layout_data.shared_data.style {
+ None => fail!("parent hasn't been styled yet?!"),
+ Some(ref style) => Some(style),
+ }
+ }
+ }
+ }
+ };
+
+ let mut layout_data_ref = self.mutate_layout_data();
+ match &mut *layout_data_ref {
+ &None => fail!("no layout data"),
+ &Some(ref mut layout_data) => {
+ self.cascade_node_pseudo_element(parent_style,
+ applicable_declarations.normal.as_slice(),
+ &mut layout_data.shared_data.style,
+ applicable_declarations_cache,
+ applicable_declarations.normal_shareable);
+ if applicable_declarations.before.len() > 0 {
+ self.cascade_node_pseudo_element(Some(layout_data.shared_data.style.get_ref()),
+ applicable_declarations.before.as_slice(),
+ &mut layout_data.data.before_style,
+ applicable_declarations_cache,
+ false);
+ }
+ if applicable_declarations.after.len() > 0 {
+ self.cascade_node_pseudo_element(Some(layout_data.shared_data.style.get_ref()),
+ applicable_declarations.after.as_slice(),
+ &mut layout_data.data.after_style,
+ applicable_declarations_cache,
+ false);
+ }
+ }
+ }
+ }
+}
+
diff --git a/components/layout/css/node_style.rs b/components/layout/css/node_style.rs
new file mode 100644
index 00000000000..e201b0050aa
--- /dev/null
+++ b/components/layout/css/node_style.rs
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Style retrieval from DOM elements.
+
+use css::node_util::NodeUtil;
+use incremental::RestyleDamage;
+use wrapper::ThreadSafeLayoutNode;
+
+use style::ComputedValues;
+use sync::Arc;
+
+/// Node mixin providing `style` method that returns a `NodeStyle`
+pub trait StyledNode {
+ fn style<'a>(&'a self) -> &'a Arc<ComputedValues>;
+ fn restyle_damage(&self) -> RestyleDamage;
+}
+
+impl<'ln> StyledNode for ThreadSafeLayoutNode<'ln> {
+ #[inline]
+ fn style<'a>(&'a self) -> &'a Arc<ComputedValues> {
+ self.get_css_select_results()
+ }
+
+ fn restyle_damage(&self) -> RestyleDamage {
+ self.get_restyle_damage()
+ }
+}
+
diff --git a/components/layout/css/node_util.rs b/components/layout/css/node_util.rs
new file mode 100644
index 00000000000..150995428ea
--- /dev/null
+++ b/components/layout/css/node_util.rs
@@ -0,0 +1,90 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use incremental::RestyleDamage;
+use util::LayoutDataAccess;
+use wrapper::{TLayoutNode, ThreadSafeLayoutNode};
+use wrapper::{After, AfterBlock, Before, BeforeBlock, Normal};
+use std::mem;
+use style::ComputedValues;
+use sync::Arc;
+
+pub trait NodeUtil {
+ fn get_css_select_results<'a>(&'a self) -> &'a Arc<ComputedValues>;
+ fn have_css_select_results(&self) -> bool;
+
+ fn get_restyle_damage(&self) -> RestyleDamage;
+ fn set_restyle_damage(&self, damage: RestyleDamage);
+}
+
+impl<'ln> NodeUtil for ThreadSafeLayoutNode<'ln> {
+ /// Returns the style results for the given node. If CSS selector
+ /// matching has not yet been performed, fails.
+ #[inline]
+ fn get_css_select_results<'a>(&'a self) -> &'a Arc<ComputedValues> {
+ unsafe {
+ let layout_data_ref = self.borrow_layout_data();
+ match self.get_pseudo_element_type() {
+ Before | BeforeBlock => {
+ mem::transmute(layout_data_ref.as_ref()
+ .unwrap()
+ .data
+ .before_style
+ .as_ref()
+ .unwrap())
+ }
+ After | AfterBlock => {
+ mem::transmute(layout_data_ref.as_ref()
+ .unwrap()
+ .data
+ .after_style
+ .as_ref()
+ .unwrap())
+ }
+ Normal => {
+ mem::transmute(layout_data_ref.as_ref()
+ .unwrap()
+ .shared_data
+ .style
+ .as_ref()
+ .unwrap())
+ }
+ }
+ }
+ }
+
+ /// Does this node have a computed style yet?
+ fn have_css_select_results(&self) -> bool {
+ let layout_data_ref = self.borrow_layout_data();
+ layout_data_ref.get_ref().shared_data.style.is_some()
+ }
+
+ /// Get the description of how to account for recent style changes.
+ /// This is a simple bitfield and fine to copy by value.
+ fn get_restyle_damage(&self) -> RestyleDamage {
+ // For DOM elements, if we haven't computed damage yet, assume the worst.
+ // Other nodes don't have styles.
+ let default = if self.node_is_element() {
+ RestyleDamage::all()
+ } else {
+ RestyleDamage::empty()
+ };
+
+ let layout_data_ref = self.borrow_layout_data();
+ layout_data_ref
+ .get_ref()
+ .data
+ .restyle_damage
+ .unwrap_or(default)
+ }
+
+ /// Set the restyle damage field.
+ fn set_restyle_damage(&self, damage: RestyleDamage) {
+ let mut layout_data_ref = self.mutate_layout_data();
+ match &mut *layout_data_ref {
+ &Some(ref mut layout_data) => layout_data.data.restyle_damage = Some(damage),
+ _ => fail!("no layout data for this node"),
+ }
+ }
+}
diff --git a/components/layout/extra.rs b/components/layout/extra.rs
new file mode 100644
index 00000000000..7b731185272
--- /dev/null
+++ b/components/layout/extra.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Code for managing the layout data in the DOM.
+
+use util::{PrivateLayoutData, LayoutDataAccess, LayoutDataWrapper};
+use wrapper::LayoutNode;
+use script::dom::node::SharedLayoutData;
+use script::layout_interface::LayoutChan;
+
+/// Functionality useful for querying the layout-specific data on DOM nodes.
+pub trait LayoutAuxMethods {
+ fn initialize_layout_data(&self, chan: LayoutChan);
+ fn initialize_style_for_subtree(&self, chan: LayoutChan);
+}
+
+impl<'ln> LayoutAuxMethods for LayoutNode<'ln> {
+ /// Resets layout data and styles for the node.
+ ///
+ /// FIXME(pcwalton): Do this as part of fragment building instead of in a traversal.
+ fn initialize_layout_data(&self, chan: LayoutChan) {
+ let mut layout_data_ref = self.mutate_layout_data();
+ match *layout_data_ref {
+ None => {
+ *layout_data_ref = Some(LayoutDataWrapper {
+ chan: Some(chan),
+ shared_data: SharedLayoutData { style: None },
+ data: box PrivateLayoutData::new(),
+ });
+ }
+ Some(_) => {}
+ }
+ }
+
+ /// Resets layout data and styles for a Node tree.
+ ///
+ /// FIXME(pcwalton): Do this as part of fragment building instead of in a traversal.
+ fn initialize_style_for_subtree(&self, chan: LayoutChan) {
+ for n in self.traverse_preorder() {
+ n.initialize_layout_data(chan.clone());
+ }
+ }
+}
diff --git a/components/layout/floats.rs b/components/layout/floats.rs
new file mode 100644
index 00000000000..94017e6b3f7
--- /dev/null
+++ b/components/layout/floats.rs
@@ -0,0 +1,439 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use servo_util::geometry::{Au, max, min};
+use servo_util::logical_geometry::WritingMode;
+use servo_util::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize};
+use std::i32;
+use std::fmt;
+use style::computed_values::float;
+use sync::Arc;
+
+/// The kind of float: left or right.
+#[deriving(Clone, Encodable)]
+pub enum FloatKind {
+ FloatLeft,
+ FloatRight
+}
+
+impl FloatKind {
+ pub fn from_property(property: float::T) -> FloatKind {
+ match property {
+ float::none => fail!("can't create a float type from an unfloated property"),
+ float::left => FloatLeft,
+ float::right => FloatRight,
+ }
+ }
+}
+
+/// The kind of clearance: left, right, or both.
+pub enum ClearType {
+ ClearLeft,
+ ClearRight,
+ ClearBoth,
+}
+
+/// Information about a single float.
+#[deriving(Clone)]
+struct Float {
+ /// The boundaries of this float.
+ bounds: LogicalRect<Au>,
+ /// The kind of float: left or right.
+ kind: FloatKind,
+}
+
+impl fmt::Show for Float {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "bounds={} kind={:?}", self.bounds, self.kind)
+ }
+}
+
+/// Information about the floats next to a flow.
+///
+/// FIXME(pcwalton): When we have fast `MutexArc`s, try removing `#[deriving(Clone)]` and wrap in a
+/// mutex.
+#[deriving(Clone)]
+struct FloatList {
+ /// Information about each of the floats here.
+ floats: Vec<Float>,
+ /// Cached copy of the maximum block-start offset of the float.
+ max_block_start: Au,
+}
+
+impl FloatList {
+ fn new() -> FloatList {
+ FloatList {
+ floats: vec!(),
+ max_block_start: Au(0),
+ }
+ }
+}
+
+impl fmt::Show for FloatList {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "max_block_start={} floats={:?}", self.max_block_start, self.floats)
+ }
+}
+
+/// Wraps a `FloatList` to avoid allocation in the common case of no floats.
+///
+/// FIXME(pcwalton): When we have fast `MutexArc`s, try removing `CowArc` and use a mutex instead.
+#[deriving(Clone)]
+struct FloatListRef {
+ list: Option<Arc<FloatList>>,
+}
+
+impl FloatListRef {
+ fn new() -> FloatListRef {
+ FloatListRef {
+ list: None,
+ }
+ }
+
+ /// Returns true if the list is allocated and false otherwise. If false, there are guaranteed
+ /// not to be any floats.
+ fn is_present(&self) -> bool {
+ self.list.is_some()
+ }
+
+ #[inline]
+ fn get<'a>(&'a self) -> Option<&'a FloatList> {
+ match self.list {
+ None => None,
+ Some(ref list) => Some(&**list),
+ }
+ }
+
+ #[allow(experimental)]
+ #[inline]
+ fn get_mut<'a>(&'a mut self) -> &'a mut FloatList {
+ if self.list.is_none() {
+ self.list = Some(Arc::new(FloatList::new()))
+ }
+ self.list.as_mut().unwrap().make_unique()
+ }
+}
+
+/// All the information necessary to place a float.
+pub struct PlacementInfo {
+ /// The dimensions of the float.
+ pub size: LogicalSize<Au>,
+ /// The minimum block-start of the float, as determined by earlier elements.
+ pub ceiling: Au,
+ /// The maximum inline-end position of the float, generally determined by the containing block.
+ pub max_inline_size: Au,
+ /// The kind of float.
+ pub kind: FloatKind
+}
+
+impl fmt::Show for PlacementInfo {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "size={} ceiling={} max_inline_size={} kind={:?}", self.size, self.ceiling, self.max_inline_size, self.kind)
+ }
+}
+
+fn range_intersect(block_start_1: Au, block_end_1: Au, block_start_2: Au, block_end_2: Au) -> (Au, Au) {
+ (max(block_start_1, block_start_2), min(block_end_1, block_end_2))
+}
+
+/// Encapsulates information about floats. This is optimized to avoid allocation if there are
+/// no floats, and to avoid copying when translating the list of floats downward.
+#[deriving(Clone)]
+pub struct Floats {
+ /// The list of floats.
+ list: FloatListRef,
+ /// The offset of the flow relative to the first float.
+ offset: LogicalSize<Au>,
+ pub writing_mode: WritingMode,
+}
+
+impl fmt::Show for Floats {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self.list.get() {
+ None => {
+ write!(f, "[empty]")
+ }
+ Some(list) => {
+ write!(f, "offset={} floats={}", self.offset, list)
+ }
+ }
+ }
+}
+
+impl Floats {
+ /// Creates a new `Floats` object.
+ pub fn new(writing_mode: WritingMode) -> Floats {
+ Floats {
+ list: FloatListRef::new(),
+ offset: LogicalSize::zero(writing_mode),
+ writing_mode: writing_mode,
+ }
+ }
+
+ /// Adjusts the recorded offset of the flow relative to the first float.
+ pub fn translate(&mut self, delta: LogicalSize<Au>) {
+ self.offset = self.offset + delta
+ }
+
+ /// Returns the position of the last float in flow coordinates.
+ pub fn last_float_pos(&self) -> Option<LogicalPoint<Au>> {
+ match self.list.get() {
+ None => None,
+ Some(list) => {
+ match list.floats.last() {
+ None => None,
+ Some(float) => Some(float.bounds.start + self.offset),
+ }
+ }
+ }
+ }
+
+ /// Returns a rectangle that encloses the region from block-start to block-start + block-size, with inline-size small
+ /// enough that it doesn't collide with any floats. max_x is the x-coordinate beyond which
+ /// floats have no effect. (Generally this is the containing block inline-size.)
+ pub fn available_rect(&self, block_start: Au, block_size: Au, max_x: Au) -> Option<LogicalRect<Au>> {
+ let list = match self.list.get() {
+ None => return None,
+ Some(list) => list,
+ };
+
+ let block_start = block_start - self.offset.block;
+
+ debug!("available_rect: trying to find space at {}", block_start);
+
+ // Relevant dimensions for the inline-end-most inline-start float
+ let mut max_inline_start = Au(0) - self.offset.inline;
+ let mut l_block_start = None;
+ let mut l_block_end = None;
+ // Relevant dimensions for the inline-start-most inline-end float
+ let mut min_inline_end = max_x - self.offset.inline;
+ let mut r_block_start = None;
+ let mut r_block_end = None;
+
+ // Find the float collisions for the given vertical range.
+ for float in list.floats.iter() {
+ debug!("available_rect: Checking for collision against float");
+ let float_pos = float.bounds.start;
+ let float_size = float.bounds.size;
+
+ debug!("float_pos: {}, float_size: {}", float_pos, float_size);
+ match float.kind {
+ FloatLeft if float_pos.i + float_size.inline > max_inline_start &&
+ float_pos.b + float_size.block > block_start && float_pos.b < block_start + block_size => {
+ max_inline_start = float_pos.i + float_size.inline;
+
+ l_block_start = Some(float_pos.b);
+ l_block_end = Some(float_pos.b + float_size.block);
+
+ debug!("available_rect: collision with inline_start float: new max_inline_start is {}",
+ max_inline_start);
+ }
+ FloatRight if float_pos.i < min_inline_end &&
+ float_pos.b + float_size.block > block_start && float_pos.b < block_start + block_size => {
+ min_inline_end = float_pos.i;
+
+ r_block_start = Some(float_pos.b);
+ r_block_end = Some(float_pos.b + float_size.block);
+ debug!("available_rect: collision with inline_end float: new min_inline_end is {}",
+ min_inline_end);
+ }
+ FloatLeft | FloatRight => {}
+ }
+ }
+
+ // Extend the vertical range of the rectangle to the closest floats.
+ // If there are floats on both sides, take the intersection of the
+ // two areas. Also make sure we never return a block-start smaller than the
+ // given upper bound.
+ let (block_start, block_end) = match (r_block_start, r_block_end, l_block_start, l_block_end) {
+ (Some(r_block_start), Some(r_block_end), Some(l_block_start), Some(l_block_end)) =>
+ range_intersect(max(block_start, r_block_start), r_block_end, max(block_start, l_block_start), l_block_end),
+
+ (None, None, Some(l_block_start), Some(l_block_end)) => (max(block_start, l_block_start), l_block_end),
+ (Some(r_block_start), Some(r_block_end), None, None) => (max(block_start, r_block_start), r_block_end),
+ (None, None, None, None) => return None,
+ _ => fail!("Reached unreachable state when computing float area")
+ };
+
+ // FIXME(eatkinson): This assertion is too strong and fails in some cases. It is OK to
+ // return negative inline-sizes since we check against that inline-end away, but we should still
+ // undersrtand why they occur and add a stronger assertion here.
+ // assert!(max_inline-start < min_inline-end);
+
+ assert!(block_start <= block_end, "Float position error");
+
+ Some(LogicalRect::new(
+ self.writing_mode, max_inline_start + self.offset.inline, block_start + self.offset.block,
+ min_inline_end - max_inline_start, block_end - block_start
+ ))
+ }
+
+ /// Adds a new float to the list.
+ pub fn add_float(&mut self, info: &PlacementInfo) {
+ let new_info;
+ {
+ let list = self.list.get_mut();
+ new_info = PlacementInfo {
+ size: info.size,
+ ceiling: max(info.ceiling, list.max_block_start + self.offset.block),
+ max_inline_size: info.max_inline_size,
+ kind: info.kind
+ }
+ }
+
+ debug!("add_float: added float with info {:?}", new_info);
+
+ let new_float = Float {
+ bounds: LogicalRect::from_point_size(
+ self.writing_mode,
+ self.place_between_floats(&new_info).start - self.offset,
+ info.size,
+ ),
+ kind: info.kind
+ };
+
+ let list = self.list.get_mut();
+ list.floats.push(new_float);
+ list.max_block_start = max(list.max_block_start, new_float.bounds.start.b);
+ }
+
+ /// Given the block-start 3 sides of the rectangle, finds the largest block-size that will result in the
+ /// rectangle not colliding with any floats. Returns None if that block-size is infinite.
+ fn max_block_size_for_bounds(&self, inline_start: Au, block_start: Au, inline_size: Au) -> Option<Au> {
+ let list = match self.list.get() {
+ None => return None,
+ Some(list) => list,
+ };
+
+ let block_start = block_start - self.offset.block;
+ let inline_start = inline_start - self.offset.inline;
+ let mut max_block_size = None;
+
+ for float in list.floats.iter() {
+ if float.bounds.start.b + float.bounds.size.block > block_start &&
+ float.bounds.start.i + float.bounds.size.inline > inline_start &&
+ float.bounds.start.i < inline_start + inline_size {
+ let new_y = float.bounds.start.b;
+ max_block_size = Some(min(max_block_size.unwrap_or(new_y), new_y));
+ }
+ }
+
+ max_block_size.map(|h| h + self.offset.block)
+ }
+
+ /// Given placement information, finds the closest place a fragment can be positioned without
+ /// colliding with any floats.
+ pub fn place_between_floats(&self, info: &PlacementInfo) -> LogicalRect<Au> {
+ debug!("place_between_floats: Placing object with {}", info.size);
+
+ // If no floats, use this fast path.
+ if !self.list.is_present() {
+ match info.kind {
+ FloatLeft => {
+ return LogicalRect::new(
+ self.writing_mode,
+ Au(0),
+ info.ceiling,
+ info.max_inline_size,
+ Au(i32::MAX))
+ }
+ FloatRight => {
+ return LogicalRect::new(
+ self.writing_mode,
+ info.max_inline_size - info.size.inline,
+ info.ceiling,
+ info.max_inline_size,
+ Au(i32::MAX))
+ }
+ }
+ }
+
+ // Can't go any higher than previous floats or previous elements in the document.
+ let mut float_b = info.ceiling;
+ loop {
+ let maybe_location = self.available_rect(float_b, info.size.block, info.max_inline_size);
+ debug!("place_float: Got available rect: {:?} for y-pos: {}", maybe_location, float_b);
+ match maybe_location {
+ // If there are no floats blocking us, return the current location
+ // TODO(eatkinson): integrate with overflow
+ None => {
+ return match info.kind {
+ FloatLeft => {
+ LogicalRect::new(
+ self.writing_mode,
+ Au(0),
+ float_b,
+ info.max_inline_size,
+ Au(i32::MAX))
+ }
+ FloatRight => {
+ LogicalRect::new(
+ self.writing_mode,
+ info.max_inline_size - info.size.inline,
+ float_b,
+ info.max_inline_size,
+ Au(i32::MAX))
+ }
+ }
+ }
+ Some(rect) => {
+ assert!(rect.start.b + rect.size.block != float_b,
+ "Non-terminating float placement");
+
+ // Place here if there is enough room
+ if rect.size.inline >= info.size.inline {
+ let block_size = self.max_block_size_for_bounds(rect.start.i,
+ rect.start.b,
+ rect.size.inline);
+ let block_size = block_size.unwrap_or(Au(i32::MAX));
+ return match info.kind {
+ FloatLeft => {
+ LogicalRect::new(
+ self.writing_mode,
+ rect.start.i,
+ float_b,
+ rect.size.inline,
+ block_size)
+ }
+ FloatRight => {
+ LogicalRect::new(
+ self.writing_mode,
+ rect.start.i + rect.size.inline - info.size.inline,
+ float_b,
+ rect.size.inline,
+ block_size)
+ }
+ }
+ }
+
+ // Try to place at the next-lowest location.
+ // Need to be careful of fencepost errors.
+ float_b = rect.start.b + rect.size.block;
+ }
+ }
+ }
+ }
+
+ pub fn clearance(&self, clear: ClearType) -> Au {
+ let list = match self.list.get() {
+ None => return Au(0),
+ Some(list) => list,
+ };
+
+ let mut clearance = Au(0);
+ for float in list.floats.iter() {
+ match (clear, float.kind) {
+ (ClearLeft, FloatLeft) |
+ (ClearRight, FloatRight) |
+ (ClearBoth, _) => {
+ let b = self.offset.block + float.bounds.start.b + float.bounds.size.block;
+ clearance = max(clearance, b);
+ }
+ _ => {}
+ }
+ }
+ clearance
+ }
+}
+
diff --git a/components/layout/flow.rs b/components/layout/flow.rs
new file mode 100644
index 00000000000..2759ebbe74b
--- /dev/null
+++ b/components/layout/flow.rs
@@ -0,0 +1,1138 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Servo's experimental layout system builds a tree of `Flow` and `Fragment` objects and solves
+//! layout constraints to obtain positions and display attributes of tree nodes. Positions are
+//! computed in several tree traversals driven by the fundamental data dependencies required by
+/// inline and block layout.
+///
+/// Flows are interior nodes in the layout tree and correspond closely to *flow contexts* in the
+/// CSS specification. Flows are responsible for positioning their child flow contexts and fragments.
+/// Flows have purpose-specific fields, such as auxiliary line structs, out-of-flow child
+/// lists, and so on.
+///
+/// Currently, the important types of flows are:
+///
+/// * `BlockFlow`: A flow that establishes a block context. It has several child flows, each of
+/// which are positioned according to block formatting context rules (CSS block boxes). Block
+/// flows also contain a single box to represent their rendered borders, padding, etc.
+/// The BlockFlow at the root of the tree has special behavior: it stretches to the boundaries of
+/// the viewport.
+///
+/// * `InlineFlow`: A flow that establishes an inline context. It has a flat list of child
+/// fragments/flows that are subject to inline layout and line breaking and structs to represent
+/// line breaks and mapping to CSS boxes, for the purpose of handling `getClientRects()` and
+/// similar methods.
+
+use css::node_style::StyledNode;
+use block::BlockFlow;
+use context::LayoutContext;
+use floats::Floats;
+use flow_list::{FlowList, Link, FlowListIterator, MutFlowListIterator};
+use flow_ref::FlowRef;
+use fragment::{Fragment, TableRowFragment, TableCellFragment};
+use incremental::RestyleDamage;
+use inline::InlineFlow;
+use model::{CollapsibleMargins, IntrinsicISizes, MarginCollapseInfo};
+use parallel::FlowParallelInfo;
+use table_wrapper::TableWrapperFlow;
+use table::TableFlow;
+use table_colgroup::TableColGroupFlow;
+use table_rowgroup::TableRowGroupFlow;
+use table_row::TableRowFlow;
+use table_caption::TableCaptionFlow;
+use table_cell::TableCellFlow;
+use wrapper::ThreadSafeLayoutNode;
+
+use collections::dlist::DList;
+use geom::Point2D;
+use gfx::display_list::DisplayList;
+use gfx::render_task::RenderLayer;
+use serialize::{Encoder, Encodable};
+use servo_msg::compositor_msg::LayerId;
+use servo_util::geometry::Au;
+use servo_util::logical_geometry::WritingMode;
+use servo_util::logical_geometry::{LogicalRect, LogicalSize};
+use std::mem;
+use std::num::Zero;
+use std::fmt;
+use std::iter::Zip;
+use std::raw;
+use std::sync::atomics::{AtomicUint, Relaxed, SeqCst};
+use std::slice::MutItems;
+use style::computed_values::{clear, position, text_align};
+
+/// Virtual methods that make up a float context.
+///
+/// Note that virtual methods have a cost; we should not overuse them in Servo. Consider adding
+/// methods to `ImmutableFlowUtils` or `MutableFlowUtils` before adding more methods here.
+pub trait Flow: fmt::Show + ToString + Share {
+ // RTTI
+ //
+ // TODO(pcwalton): Use Rust's RTTI, once that works.
+
+ /// Returns the class of flow that this is.
+ fn class(&self) -> FlowClass;
+
+ /// If this is a block flow, returns the underlying object, borrowed immutably. Fails
+ /// otherwise.
+ fn as_immutable_block<'a>(&'a self) -> &'a BlockFlow {
+ fail!("called as_immutable_block() on a non-block flow")
+ }
+
+ /// If this is a block flow, returns the underlying object. Fails otherwise.
+ fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow {
+ debug!("called as_block() on a flow of type {}", self.class());
+ fail!("called as_block() on a non-block flow")
+ }
+
+ /// If this is an inline flow, returns the underlying object, borrowed immutably. Fails
+ /// otherwise.
+ fn as_immutable_inline<'a>(&'a self) -> &'a InlineFlow {
+ fail!("called as_immutable_inline() on a non-inline flow")
+ }
+
+ /// If this is an inline flow, returns the underlying object. Fails otherwise.
+ fn as_inline<'a>(&'a mut self) -> &'a mut InlineFlow {
+ fail!("called as_inline() on a non-inline flow")
+ }
+
+ /// If this is a table wrapper flow, returns the underlying object. Fails otherwise.
+ fn as_table_wrapper<'a>(&'a mut self) -> &'a mut TableWrapperFlow {
+ fail!("called as_table_wrapper() on a non-tablewrapper flow")
+ }
+
+ /// If this is a table flow, returns the underlying object. Fails otherwise.
+ fn as_table<'a>(&'a mut self) -> &'a mut TableFlow {
+ fail!("called as_table() on a non-table flow")
+ }
+
+ /// If this is a table colgroup flow, returns the underlying object. Fails otherwise.
+ fn as_table_colgroup<'a>(&'a mut self) -> &'a mut TableColGroupFlow {
+ fail!("called as_table_colgroup() on a non-tablecolgroup flow")
+ }
+
+ /// If this is a table rowgroup flow, returns the underlying object. Fails otherwise.
+ fn as_table_rowgroup<'a>(&'a mut self) -> &'a mut TableRowGroupFlow {
+ fail!("called as_table_rowgroup() on a non-tablerowgroup flow")
+ }
+
+ /// If this is a table row flow, returns the underlying object. Fails otherwise.
+ fn as_table_row<'a>(&'a mut self) -> &'a mut TableRowFlow {
+ fail!("called as_table_row() on a non-tablerow flow")
+ }
+
+ /// If this is a table cell flow, returns the underlying object. Fails otherwise.
+ fn as_table_caption<'a>(&'a mut self) -> &'a mut TableCaptionFlow {
+ fail!("called as_table_caption() on a non-tablecaption flow")
+ }
+
+ /// If this is a table cell flow, returns the underlying object. Fails otherwise.
+ fn as_table_cell<'a>(&'a mut self) -> &'a mut TableCellFlow {
+ fail!("called as_table_cell() on a non-tablecell flow")
+ }
+
+ /// If this is a table row or table rowgroup or table flow, returns column inline-sizes.
+ /// Fails otherwise.
+ fn col_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<Au> {
+ fail!("called col_inline_sizes() on an other flow than table-row/table-rowgroup/table")
+ }
+
+ /// If this is a table row flow or table rowgroup flow or table flow, returns column min inline-sizes.
+ /// Fails otherwise.
+ fn col_min_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
+ fail!("called col_min_inline_sizes() on an other flow than table-row/table-rowgroup/table")
+ }
+
+ /// If this is a table row flow or table rowgroup flow or table flow, returns column min inline-sizes.
+ /// Fails otherwise.
+ fn col_pref_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
+ fail!("called col_pref_inline_sizes() on an other flow than table-row/table-rowgroup/table")
+ }
+
+ // Main methods
+
+ /// Pass 1 of reflow: computes minimum and preferred inline-sizes.
+ ///
+ /// Recursively (bottom-up) determine the flow's minimum and preferred inline-sizes. When called on
+ /// this flow, all child flows have had their minimum and preferred inline-sizes set. This function
+ /// must decide minimum/preferred inline-sizes based on its children's inline-sizes and the dimensions of
+ /// any boxes it is responsible for flowing.
+ fn bubble_inline_sizes(&mut self, _ctx: &LayoutContext) {
+ fail!("bubble_inline_sizes not yet implemented")
+ }
+
+ /// Pass 2 of reflow: computes inline-size.
+ fn assign_inline_sizes(&mut self, _ctx: &LayoutContext) {
+ fail!("assign_inline_sizes not yet implemented")
+ }
+
+ /// Pass 3a of reflow: computes block-size.
+ fn assign_block_size<'a>(&mut self, _ctx: &'a LayoutContext<'a>) {
+ fail!("assign_block_size not yet implemented")
+ }
+
+ /// Assigns block-sizes in-order; or, if this is a float, places the float. The default
+ /// implementation simply assigns block-sizes if this flow is impacted by floats. Returns true if
+ /// this child was impacted by floats or false otherwise.
+ fn assign_block_size_for_inorder_child_if_necessary<'a>(&mut self, layout_context: &'a LayoutContext<'a>)
+ -> bool {
+ let impacted = base(&*self).flags.impacted_by_floats();
+ if impacted {
+ self.assign_block_size(layout_context);
+ }
+ impacted
+ }
+
+ /// Phase 4 of reflow: computes absolute positions.
+ fn compute_absolute_position(&mut self) {
+ // The default implementation is a no-op.
+ }
+
+ /// Returns the direction that this flow clears floats in, if any.
+ fn float_clearance(&self) -> clear::T {
+ clear::none
+ }
+
+ /// Returns true if this float is a block formatting context and false otherwise. The default
+ /// implementation returns false.
+ fn is_block_formatting_context(&self, _only_impactable_by_floats: bool) -> bool {
+ false
+ }
+
+ fn compute_collapsible_block_start_margin(&mut self,
+ _layout_context: &mut LayoutContext,
+ _margin_collapse_info: &mut MarginCollapseInfo) {
+ // The default implementation is a no-op.
+ }
+
+ /// Marks this flow as the root flow. The default implementation is a no-op.
+ fn mark_as_root(&mut self) {}
+
+ // Note that the following functions are mostly called using static method
+ // dispatch, so it's ok to have them in this trait. Plus, they have
+ // different behaviour for different types of Flow, so they can't go into
+ // the Immutable / Mutable Flow Utils traits without additional casts.
+
+ /// Return true if store overflow is delayed for this flow.
+ ///
+ /// Currently happens only for absolutely positioned flows.
+ fn is_store_overflow_delayed(&mut self) -> bool {
+ false
+ }
+
+ fn is_root(&self) -> bool {
+ false
+ }
+
+ fn is_float(&self) -> bool {
+ false
+ }
+
+ /// The 'position' property of this flow.
+ fn positioning(&self) -> position::T {
+ position::static_
+ }
+
+ /// Return true if this flow has position 'fixed'.
+ fn is_fixed(&self) -> bool {
+ self.positioning() == position::fixed
+ }
+
+ fn is_positioned(&self) -> bool {
+ self.is_relatively_positioned() || self.is_absolutely_positioned()
+ }
+
+ fn is_relatively_positioned(&self) -> bool {
+ self.positioning() == position::relative
+ }
+
+ fn is_absolutely_positioned(&self) -> bool {
+ self.positioning() == position::absolute || self.is_fixed()
+ }
+
+ /// Return true if this is the root of an Absolute flow tree.
+ fn is_root_of_absolute_flow_tree(&self) -> bool {
+ false
+ }
+
+ /// Returns true if this is an absolute containing block.
+ fn is_absolute_containing_block(&self) -> bool {
+ false
+ }
+
+ /// Return the dimensions of the containing block generated by this flow for absolutely-
+ /// positioned descendants. For block flows, this is the padding box.
+ fn generated_containing_block_rect(&self) -> LogicalRect<Au> {
+ fail!("generated_containing_block_position not yet implemented for this flow")
+ }
+
+ /// Returns a layer ID for the given fragment.
+ fn layer_id(&self, fragment_id: uint) -> LayerId {
+ unsafe {
+ let pointer: uint = mem::transmute(self);
+ LayerId(pointer, fragment_id)
+ }
+ }
+}
+
+impl<'a, E, S: Encoder<E>> Encodable<S, E> for &'a Flow {
+ fn encode(&self, e: &mut S) -> Result<(), E> {
+ e.emit_struct("flow", 0, |e| {
+ try!(e.emit_struct_field("class", 0, |e| self.class().encode(e)))
+ e.emit_struct_field("data", 1, |e| {
+ match self.class() {
+ BlockFlowClass => self.as_immutable_block().encode(e),
+ InlineFlowClass => self.as_immutable_inline().encode(e),
+ _ => { Ok(()) } // TODO: Support tables
+ }
+ })
+ })
+ }
+}
+
+// Base access
+
+#[inline(always)]
+pub fn base<'a>(this: &'a Flow) -> &'a BaseFlow {
+ unsafe {
+ let obj = mem::transmute::<&'a Flow, raw::TraitObject>(this);
+ mem::transmute::<*mut (), &'a BaseFlow>(obj.data)
+ }
+}
+
+/// Iterates over the children of this immutable flow.
+pub fn imm_child_iter<'a>(flow: &'a Flow) -> FlowListIterator<'a> {
+ base(flow).children.iter()
+}
+
+#[inline(always)]
+pub fn mut_base<'a>(this: &'a mut Flow) -> &'a mut BaseFlow {
+ unsafe {
+ let obj = mem::transmute::<&'a mut Flow, raw::TraitObject>(this);
+ mem::transmute::<*mut (), &'a mut BaseFlow>(obj.data)
+ }
+}
+
+/// Iterates over the children of this flow.
+pub fn child_iter<'a>(flow: &'a mut Flow) -> MutFlowListIterator<'a> {
+ mut_base(flow).children.mut_iter()
+}
+
+pub trait ImmutableFlowUtils {
+ // Convenience functions
+
+ /// Returns true if this flow is a block or a float flow.
+ fn is_block_like(self) -> bool;
+
+ /// Returns true if this flow is a table flow.
+ fn is_table(self) -> bool;
+
+ /// Returns true if this flow is a table caption flow.
+ fn is_table_caption(self) -> bool;
+
+ /// Returns true if this flow is a proper table child.
+ fn is_proper_table_child(self) -> bool;
+
+ /// Returns true if this flow is a table row flow.
+ fn is_table_row(self) -> bool;
+
+ /// Returns true if this flow is a table cell flow.
+ fn is_table_cell(self) -> bool;
+
+ /// Returns true if this flow is a table colgroup flow.
+ fn is_table_colgroup(self) -> bool;
+
+ /// Returns true if this flow is a table rowgroup flow.
+ fn is_table_rowgroup(self) -> bool;
+
+ /// Returns true if this flow is one of table-related flows.
+ fn is_table_kind(self) -> bool;
+
+ /// Returns true if anonymous flow is needed between this flow and child flow.
+ fn need_anonymous_flow(self, child: &Flow) -> bool;
+
+ /// Generates missing child flow of this flow.
+ fn generate_missing_child_flow(self, node: &ThreadSafeLayoutNode) -> FlowRef;
+
+ /// Returns true if this flow has no children.
+ fn is_leaf(self) -> bool;
+
+ /// Returns the number of children that this flow possesses.
+ fn child_count(self) -> uint;
+
+ /// Return true if this flow is a Block Container.
+ fn is_block_container(self) -> bool;
+
+ /// Returns true if this flow is a block flow.
+ fn is_block_flow(self) -> bool;
+
+ /// Returns true if this flow is an inline flow.
+ fn is_inline_flow(self) -> bool;
+
+ /// Dumps the flow tree for debugging.
+ fn dump(self);
+
+ /// Dumps the flow tree for debugging, with a prefix to indicate that we're at the given level.
+ fn dump_with_level(self, level: uint);
+}
+
+pub trait MutableFlowUtils {
+ // Traversals
+
+ /// Traverses the tree in preorder.
+ fn traverse_preorder<T:PreorderFlowTraversal>(self, traversal: &mut T) -> bool;
+
+ /// Traverses the tree in postorder.
+ fn traverse_postorder<T:PostorderFlowTraversal>(self, traversal: &mut T) -> bool;
+
+ // Mutators
+
+ /// Computes the overflow region for this flow.
+ fn store_overflow(self, _: &LayoutContext);
+
+ /// Builds the display lists for this flow.
+ fn build_display_list(self, layout_context: &LayoutContext);
+}
+
+pub trait MutableOwnedFlowUtils {
+ /// Set absolute descendants for this flow.
+ ///
+ /// Set this flow as the Containing Block for all the absolute descendants.
+ fn set_abs_descendants(&mut self, abs_descendants: AbsDescendants);
+}
+
+#[deriving(Encodable, PartialEq, Show)]
+pub enum FlowClass {
+ BlockFlowClass,
+ InlineFlowClass,
+ TableWrapperFlowClass,
+ TableFlowClass,
+ TableColGroupFlowClass,
+ TableRowGroupFlowClass,
+ TableRowFlowClass,
+ TableCaptionFlowClass,
+ TableCellFlowClass,
+}
+
+/// A top-down traversal.
+pub trait PreorderFlowTraversal {
+ /// The operation to perform. Return true to continue or false to stop.
+ fn process(&mut self, flow: &mut Flow) -> bool;
+
+ /// Returns true if this node should be pruned. If this returns true, we skip the operation
+ /// entirely and do not process any descendant nodes. This is called *before* child nodes are
+ /// visited. The default implementation never prunes any nodes.
+ fn should_prune(&mut self, _flow: &mut Flow) -> bool {
+ false
+ }
+}
+
+/// A bottom-up traversal, with a optional in-order pass.
+pub trait PostorderFlowTraversal {
+ /// The operation to perform. Return true to continue or false to stop.
+ fn process(&mut self, flow: &mut Flow) -> bool;
+
+ /// Returns false if this node must be processed in-order. If this returns false, we skip the
+ /// operation for this node, but continue processing the ancestors. This is called *after*
+ /// child nodes are visited.
+ fn should_process(&mut self, _flow: &mut Flow) -> bool {
+ true
+ }
+
+ /// Returns true if this node should be pruned. If this returns true, we skip the operation
+ /// entirely and do not process any descendant nodes. This is called *before* child nodes are
+ /// visited. The default implementation never prunes any nodes.
+ fn should_prune(&mut self, _flow: &mut Flow) -> bool {
+ false
+ }
+}
+
+/// Flags used in flows, tightly packed to save space.
+#[deriving(Clone, Encodable)]
+pub struct FlowFlags(pub u8);
+
+/// The bitmask of flags that represent the `has_left_floated_descendants` and
+/// `has_right_floated_descendants` fields.
+///
+/// NB: If you update this field, you must update the bitfields below.
+static HAS_FLOATED_DESCENDANTS_BITMASK: u8 = 0b0000_0011;
+
+// Whether this flow has descendants that float left in the same block formatting context.
+bitfield!(FlowFlags, has_left_floated_descendants, set_has_left_floated_descendants, 0b0000_0001)
+
+// Whether this flow has descendants that float right in the same block formatting context.
+bitfield!(FlowFlags, has_right_floated_descendants, set_has_right_floated_descendants, 0b0000_0010)
+
+// Whether this flow is impacted by floats to the left in the same block formatting context (i.e.
+// its block-size depends on some prior flows with `float: left`).
+bitfield!(FlowFlags, impacted_by_left_floats, set_impacted_by_left_floats, 0b0000_0100)
+
+// Whether this flow is impacted by floats to the right in the same block formatting context (i.e.
+// its block-size depends on some prior flows with `float: right`).
+bitfield!(FlowFlags, impacted_by_right_floats, set_impacted_by_right_floats, 0b0000_1000)
+
+/// The bitmask of flags that represent the text alignment field.
+///
+/// NB: If you update this field, you must update the bitfields below.
+static TEXT_ALIGN_BITMASK: u8 = 0b0011_0000;
+
+/// The number of bits we must shift off to handle the text alignment field.
+///
+/// NB: If you update this field, you must update the bitfields below.
+static TEXT_ALIGN_SHIFT: u8 = 4;
+
+// Whether this flow contains a flow that has its own layer within the same absolute containing
+// block.
+bitfield!(FlowFlags,
+ layers_needed_for_descendants,
+ set_layers_needed_for_descendants,
+ 0b0100_0000)
+
+// Whether this flow must have its own layer. Even if this flag is not set, it might get its own
+// layer if it's deemed to be likely to overlap flows with their own layer.
+bitfield!(FlowFlags, needs_layer, set_needs_layer, 0b1000_0000)
+
+impl FlowFlags {
+ /// Creates a new set of flow flags.
+ pub fn new() -> FlowFlags {
+ FlowFlags(0)
+ }
+
+ /// Propagates text alignment flags from an appropriate parent flow per CSS 2.1.
+ ///
+ /// FIXME(#2265, pcwalton): It would be cleaner and faster to make this a derived CSS property
+ /// `-servo-text-align-in-effect`.
+ pub fn propagate_text_alignment_from_parent(&mut self, parent_flags: FlowFlags) {
+ self.set_text_align_override(parent_flags);
+ }
+
+ #[inline]
+ pub fn text_align(self) -> text_align::T {
+ let FlowFlags(ff) = self;
+ FromPrimitive::from_u8((ff & TEXT_ALIGN_BITMASK) >> TEXT_ALIGN_SHIFT as uint).unwrap()
+ }
+
+ #[inline]
+ pub fn set_text_align(&mut self, value: text_align::T) {
+ let FlowFlags(ff) = *self;
+ *self = FlowFlags((ff & !TEXT_ALIGN_BITMASK) | ((value as u8) << TEXT_ALIGN_SHIFT as uint))
+ }
+
+ #[inline]
+ pub fn set_text_align_override(&mut self, parent: FlowFlags) {
+ let FlowFlags(ff) = *self;
+ let FlowFlags(pff) = parent;
+ *self = FlowFlags(ff | (pff & TEXT_ALIGN_BITMASK))
+ }
+
+ #[inline]
+ pub fn union_floated_descendants_flags(&mut self, other: FlowFlags) {
+ let FlowFlags(my_flags) = *self;
+ let FlowFlags(other_flags) = other;
+ *self = FlowFlags(my_flags | (other_flags & HAS_FLOATED_DESCENDANTS_BITMASK))
+ }
+
+ #[inline]
+ pub fn impacted_by_floats(&self) -> bool {
+ self.impacted_by_left_floats() || self.impacted_by_right_floats()
+ }
+}
+
+/// The Descendants of a flow.
+///
+/// Also, details about their position wrt this flow.
+pub struct Descendants {
+ /// Links to every descendant. This must be private because it is unsafe to leak `FlowRef`s to
+ /// layout.
+ descendant_links: Vec<FlowRef>,
+
+ /// Static y offsets of all descendants from the start of this flow box.
+ pub static_b_offsets: Vec<Au>,
+}
+
+impl Descendants {
+ pub fn new() -> Descendants {
+ Descendants {
+ descendant_links: Vec::new(),
+ static_b_offsets: Vec::new(),
+ }
+ }
+
+ pub fn len(&self) -> uint {
+ self.descendant_links.len()
+ }
+
+ pub fn push(&mut self, given_descendant: FlowRef) {
+ self.descendant_links.push(given_descendant);
+ }
+
+ /// Push the given descendants on to the existing descendants.
+ ///
+ /// Ignore any static y offsets, because they are None before layout.
+ pub fn push_descendants(&mut self, given_descendants: Descendants) {
+ for elem in given_descendants.descendant_links.move_iter() {
+ self.descendant_links.push(elem);
+ }
+ }
+
+ /// Return an iterator over the descendant flows.
+ pub fn iter<'a>(&'a mut self) -> DescendantIter<'a> {
+ DescendantIter {
+ iter: self.descendant_links.mut_slice_from(0).mut_iter(),
+ }
+ }
+
+ /// Return an iterator over (descendant, static y offset).
+ pub fn iter_with_offset<'a>(&'a mut self) -> DescendantOffsetIter<'a> {
+ let descendant_iter = DescendantIter {
+ iter: self.descendant_links.mut_slice_from(0).mut_iter(),
+ };
+ descendant_iter.zip(self.static_b_offsets.mut_slice_from(0).mut_iter())
+ }
+}
+
+pub type AbsDescendants = Descendants;
+
+pub struct DescendantIter<'a> {
+ iter: MutItems<'a, FlowRef>,
+}
+
+impl<'a> Iterator<&'a mut Flow> for DescendantIter<'a> {
+ fn next(&mut self) -> Option<&'a mut Flow> {
+ match self.iter.next() {
+ None => None,
+ Some(ref mut flow) => {
+ unsafe {
+ let result: &'a mut Flow = mem::transmute(flow.get_mut());
+ Some(result)
+ }
+ }
+ }
+ }
+}
+
+pub type DescendantOffsetIter<'a> = Zip<DescendantIter<'a>, MutItems<'a, Au>>;
+
+/// Information needed to compute absolute (i.e. viewport-relative) flow positions (not to be
+/// confused with absolutely-positioned flows).
+#[deriving(Encodable)]
+pub struct AbsolutePositionInfo {
+ /// The size of the containing block for relatively-positioned descendants.
+ pub relative_containing_block_size: LogicalSize<Au>,
+ /// The position of the absolute containing block.
+ pub absolute_containing_block_position: Point2D<Au>,
+ /// Whether the absolute containing block forces positioned descendants to be layerized.
+ ///
+ /// FIXME(pcwalton): Move into `FlowFlags`.
+ pub layers_needed_for_positioned_flows: bool,
+}
+
+impl AbsolutePositionInfo {
+ pub fn new(writing_mode: WritingMode) -> AbsolutePositionInfo {
+ // FIXME(pcwalton): The initial relative containing block-size should be equal to the size
+ // of the root layer.
+ AbsolutePositionInfo {
+ relative_containing_block_size: LogicalSize::zero(writing_mode),
+ absolute_containing_block_position: Zero::zero(),
+ layers_needed_for_positioned_flows: false,
+ }
+ }
+}
+
+/// Data common to all flows.
+pub struct BaseFlow {
+ /// NB: Must be the first element.
+ ///
+ /// The necessity of this will disappear once we have dynamically-sized types.
+ ref_count: AtomicUint,
+
+ pub restyle_damage: RestyleDamage,
+
+ /// The children of this flow.
+ pub children: FlowList,
+ pub next_sibling: Link,
+ pub prev_sibling: Link,
+
+ /* layout computations */
+ // TODO: min/pref and position are used during disjoint phases of
+ // layout; maybe combine into a single enum to save space.
+ pub intrinsic_inline_sizes: IntrinsicISizes,
+
+ /// The upper left corner of the box representing this flow, relative to the box representing
+ /// its parent flow.
+ ///
+ /// For absolute flows, this represents the position with respect to its *containing block*.
+ ///
+ /// This does not include margins in the block flow direction, because those can collapse. So
+ /// for the block direction (usually vertical), this represents the *border box*. For the
+ /// inline direction (usually horizontal), this represents the *margin box*.
+ pub position: LogicalRect<Au>,
+
+ /// The amount of overflow of this flow, relative to the containing block. Must include all the
+ /// pixels of all the display list items for correct invalidation.
+ pub overflow: LogicalRect<Au>,
+
+ /// Data used during parallel traversals.
+ ///
+ /// TODO(pcwalton): Group with other transient data to save space.
+ pub parallel: FlowParallelInfo,
+
+ /// The floats next to this flow.
+ pub floats: Floats,
+
+ /// The collapsible margins for this flow, if any.
+ pub collapsible_margins: CollapsibleMargins,
+
+ /// The position of this flow in page coordinates, computed during display list construction.
+ pub abs_position: Point2D<Au>,
+
+ /// Details about descendants with position 'absolute' or 'fixed' for which we are the
+ /// containing block. This is in tree order. This includes any direct children.
+ pub abs_descendants: AbsDescendants,
+
+ /// Offset wrt the nearest positioned ancestor - aka the Containing Block
+ /// for any absolutely positioned elements.
+ pub absolute_static_i_offset: Au,
+
+ /// Offset wrt the Initial Containing Block.
+ pub fixed_static_i_offset: Au,
+
+ /// Reference to the Containing Block, if this flow is absolutely positioned.
+ pub absolute_cb: ContainingBlockLink,
+
+ /// Information needed to compute absolute (i.e. viewport-relative) flow positions (not to be
+ /// confused with absolutely-positioned flows).
+ ///
+ /// FIXME(pcwalton): Merge with `absolute_static_i_offset` and `fixed_static_i_offset` above?
+ pub absolute_position_info: AbsolutePositionInfo,
+
+ /// The unflattened display items for this flow.
+ pub display_list: DisplayList,
+
+ /// Any layers that we're bubbling up, in a linked list.
+ pub layers: DList<RenderLayer>,
+
+ /// Various flags for flows, tightly packed to save space.
+ pub flags: FlowFlags,
+
+ pub writing_mode: WritingMode,
+}
+
+impl<E, S: Encoder<E>> Encodable<S, E> for BaseFlow {
+ fn encode(&self, e: &mut S) -> Result<(), E> {
+ e.emit_struct("base", 0, |e| {
+ try!(e.emit_struct_field("id", 0, |e| self.debug_id().encode(e)))
+ try!(e.emit_struct_field("abs_position", 1, |e| self.abs_position.encode(e)))
+ try!(e.emit_struct_field("intrinsic_inline_sizes", 2, |e| self.intrinsic_inline_sizes.encode(e)))
+ try!(e.emit_struct_field("position", 3, |e| self.position.encode(e)))
+ e.emit_struct_field("children", 4, |e| {
+ e.emit_seq(self.children.len(), |e| {
+ for (i, c) in self.children.iter().enumerate() {
+ try!(e.emit_seq_elt(i, |e| c.encode(e)))
+ }
+ Ok(())
+ })
+
+ })
+ })
+ }
+}
+
+#[unsafe_destructor]
+impl Drop for BaseFlow {
+ fn drop(&mut self) {
+ if self.ref_count.load(SeqCst) != 0 {
+ fail!("Flow destroyed before its ref count hit zero—this is unsafe!")
+ }
+ }
+}
+
+impl BaseFlow {
+ #[inline]
+ pub fn new(node: ThreadSafeLayoutNode) -> BaseFlow {
+ let writing_mode = node.style().writing_mode;
+ BaseFlow {
+ ref_count: AtomicUint::new(1),
+
+ restyle_damage: node.restyle_damage(),
+
+ children: FlowList::new(),
+ next_sibling: None,
+ prev_sibling: None,
+
+ intrinsic_inline_sizes: IntrinsicISizes::new(),
+ position: LogicalRect::zero(writing_mode),
+ overflow: LogicalRect::zero(writing_mode),
+
+ parallel: FlowParallelInfo::new(),
+
+ floats: Floats::new(writing_mode),
+ collapsible_margins: CollapsibleMargins::new(),
+ abs_position: Zero::zero(),
+ abs_descendants: Descendants::new(),
+ absolute_static_i_offset: Au::new(0),
+ fixed_static_i_offset: Au::new(0),
+ absolute_cb: ContainingBlockLink::new(),
+ display_list: DisplayList::new(),
+ layers: DList::new(),
+ absolute_position_info: AbsolutePositionInfo::new(writing_mode),
+
+ flags: FlowFlags::new(),
+ writing_mode: writing_mode,
+ }
+ }
+
+ pub fn child_iter<'a>(&'a mut self) -> MutFlowListIterator<'a> {
+ self.children.mut_iter()
+ }
+
+ pub unsafe fn ref_count<'a>(&'a self) -> &'a AtomicUint {
+ &self.ref_count
+ }
+
+ pub fn debug_id(&self) -> String {
+ format!("{:p}", self as *const _)
+ }
+}
+
+impl<'a> ImmutableFlowUtils for &'a Flow {
+ /// Returns true if this flow is a block or a float flow.
+ fn is_block_like(self) -> bool {
+ match self.class() {
+ BlockFlowClass => true,
+ _ => false,
+ }
+ }
+
+ /// Returns true if this flow is a proper table child.
+ /// 'Proper table child' is defined as table-row flow, table-rowgroup flow,
+ /// table-column-group flow, or table-caption flow.
+ fn is_proper_table_child(self) -> bool {
+ match self.class() {
+ TableRowFlowClass | TableRowGroupFlowClass |
+ TableColGroupFlowClass | TableCaptionFlowClass => true,
+ _ => false,
+ }
+ }
+
+ /// Returns true if this flow is a table row flow.
+ fn is_table_row(self) -> bool {
+ match self.class() {
+ TableRowFlowClass => true,
+ _ => false,
+ }
+ }
+
+ /// Returns true if this flow is a table cell flow.
+ fn is_table_cell(self) -> bool {
+ match self.class() {
+ TableCellFlowClass => true,
+ _ => false,
+ }
+ }
+
+ /// Returns true if this flow is a table colgroup flow.
+ fn is_table_colgroup(self) -> bool {
+ match self.class() {
+ TableColGroupFlowClass => true,
+ _ => false,
+ }
+ }
+
+ /// Returns true if this flow is a table flow.
+ fn is_table(self) -> bool {
+ match self.class() {
+ TableFlowClass => true,
+ _ => false,
+ }
+ }
+
+ /// Returns true if this flow is a table caption flow.
+ fn is_table_caption(self) -> bool {
+ match self.class() {
+ TableCaptionFlowClass => true,
+ _ => false,
+ }
+ }
+
+ /// Returns true if this flow is a table rowgroup flow.
+ fn is_table_rowgroup(self) -> bool {
+ match self.class() {
+ TableRowGroupFlowClass => true,
+ _ => false,
+ }
+ }
+
+ /// Returns true if this flow is one of table-related flows.
+ fn is_table_kind(self) -> bool {
+ match self.class() {
+ TableWrapperFlowClass | TableFlowClass |
+ TableColGroupFlowClass | TableRowGroupFlowClass |
+ TableRowFlowClass | TableCaptionFlowClass | TableCellFlowClass => true,
+ _ => false,
+ }
+ }
+
+ /// Returns true if anonymous flow is needed between this flow and child flow.
+ /// Spec: http://www.w3.org/TR/CSS21/tables.html#anonymous-boxes
+ fn need_anonymous_flow(self, child: &Flow) -> bool {
+ match self.class() {
+ TableFlowClass => !child.is_proper_table_child(),
+ TableRowGroupFlowClass => !child.is_table_row(),
+ TableRowFlowClass => !child.is_table_cell(),
+ _ => false
+ }
+ }
+
+ /// Generates missing child flow of this flow.
+ fn generate_missing_child_flow(self, node: &ThreadSafeLayoutNode) -> FlowRef {
+ let flow = match self.class() {
+ TableFlowClass | TableRowGroupFlowClass => {
+ let fragment = Fragment::new_anonymous_table_fragment(node, TableRowFragment);
+ box TableRowFlow::from_node_and_fragment(node, fragment) as Box<Flow>
+ },
+ TableRowFlowClass => {
+ let fragment = Fragment::new_anonymous_table_fragment(node, TableCellFragment);
+ box TableCellFlow::from_node_and_fragment(node, fragment) as Box<Flow>
+ },
+ _ => {
+ fail!("no need to generate a missing child")
+ }
+ };
+ FlowRef::new(flow)
+ }
+
+ /// Returns true if this flow has no children.
+ fn is_leaf(self) -> bool {
+ base(self).children.len() == 0
+ }
+
+ /// Returns the number of children that this flow possesses.
+ fn child_count(self) -> uint {
+ base(self).children.len()
+ }
+
+ /// Return true if this flow is a Block Container.
+ ///
+ /// Except for table fragments and replaced elements, block-level fragments (`BlockFlow`) are
+ /// also block container fragments.
+ /// Non-replaced inline blocks and non-replaced table cells are also block
+ /// containers.
+ fn is_block_container(self) -> bool {
+ match self.class() {
+ // TODO: Change this when inline-blocks are supported.
+ BlockFlowClass | TableCaptionFlowClass | TableCellFlowClass => {
+ // FIXME: Actually check the type of the node
+ self.child_count() != 0
+ }
+ _ => false,
+ }
+ }
+
+ /// Returns true if this flow is a block flow.
+ fn is_block_flow(self) -> bool {
+ match self.class() {
+ BlockFlowClass => true,
+ _ => false,
+ }
+ }
+
+ /// Returns true if this flow is an inline flow.
+ fn is_inline_flow(self) -> bool {
+ match self.class() {
+ InlineFlowClass => true,
+ _ => false,
+ }
+ }
+
+ /// Dumps the flow tree for debugging.
+ fn dump(self) {
+ self.dump_with_level(0)
+ }
+
+ /// Dumps the flow tree for debugging, with a prefix to indicate that we're at the given level.
+ fn dump_with_level(self, level: uint) {
+ let mut indent = String::new();
+ for _ in range(0, level) {
+ indent.push_str("| ")
+ }
+ debug!("{}+ {}", indent, self.to_string());
+ for kid in imm_child_iter(self) {
+ kid.dump_with_level(level + 1)
+ }
+ }
+}
+
+impl<'a> MutableFlowUtils for &'a mut Flow {
+ /// Traverses the tree in preorder.
+ fn traverse_preorder<T:PreorderFlowTraversal>(self, traversal: &mut T) -> bool {
+ if traversal.should_prune(self) {
+ return true
+ }
+
+ if !traversal.process(self) {
+ return false
+ }
+
+ for kid in child_iter(self) {
+ if !kid.traverse_preorder(traversal) {
+ return false
+ }
+ }
+
+ true
+ }
+
+ /// Traverses the tree in postorder.
+ fn traverse_postorder<T:PostorderFlowTraversal>(self, traversal: &mut T) -> bool {
+ if traversal.should_prune(self) {
+ return true
+ }
+
+ for kid in child_iter(self) {
+ if !kid.traverse_postorder(traversal) {
+ return false
+ }
+ }
+
+ if !traversal.should_process(self) {
+ return true
+ }
+
+ traversal.process(self)
+ }
+
+ /// Calculate and set overflow for current flow.
+ ///
+ /// CSS Section 11.1
+ /// This is the union of rectangles of the flows for which we define the
+ /// Containing Block.
+ ///
+ /// Assumption: This is called in a bottom-up traversal, so kids' overflows have
+ /// already been set.
+ /// Assumption: Absolute descendants have had their overflow calculated.
+ fn store_overflow(self, _: &LayoutContext) {
+ let my_position = mut_base(self).position;
+ let mut overflow = my_position;
+
+ if self.is_block_container() {
+ for kid in child_iter(self) {
+ if kid.is_store_overflow_delayed() {
+ // Absolute flows will be handled by their CB. If we are
+ // their CB, they will show up in `abs_descendants`.
+ continue;
+ }
+ let mut kid_overflow = base(kid).overflow;
+ kid_overflow = kid_overflow.translate(&my_position.start);
+ overflow = overflow.union(&kid_overflow)
+ }
+
+ // FIXME(#2004, pcwalton): This is wrong for `position: fixed`.
+ for descendant_link in mut_base(self).abs_descendants.iter() {
+ let mut kid_overflow = base(descendant_link).overflow;
+ kid_overflow = kid_overflow.translate(&my_position.start);
+ overflow = overflow.union(&kid_overflow)
+ }
+ }
+ mut_base(self).overflow = overflow;
+ }
+
+ /// Push display items for current flow and its descendants onto the appropriate display lists
+ /// of the given stacking context.
+ ///
+ /// Arguments:
+ ///
+ /// * `builder`: The display list builder, which contains information used during the entire
+ /// display list building pass.
+ ///
+ /// * `info`: Per-flow display list building information.
+ fn build_display_list(self, layout_context: &LayoutContext) {
+ debug!("Flow: building display list");
+ match self.class() {
+ BlockFlowClass => self.as_block().build_display_list_block(layout_context),
+ InlineFlowClass => self.as_inline().build_display_list_inline(layout_context),
+ TableWrapperFlowClass => {
+ self.as_table_wrapper().build_display_list_table_wrapper(layout_context)
+ }
+ TableFlowClass => self.as_table().build_display_list_table(layout_context),
+ TableRowGroupFlowClass => {
+ self.as_table_rowgroup().build_display_list_table_rowgroup(layout_context)
+ }
+ TableRowFlowClass => self.as_table_row().build_display_list_table_row(layout_context),
+ TableCaptionFlowClass => {
+ self.as_table_caption().build_display_list_table_caption(layout_context)
+ }
+ TableCellFlowClass => {
+ self.as_table_cell().build_display_list_table_cell(layout_context)
+ }
+ TableColGroupFlowClass => {
+ // Nothing to do here, as column groups don't render.
+ }
+ }
+ }
+}
+
+impl MutableOwnedFlowUtils for FlowRef {
+ /// Set absolute descendants for this flow.
+ ///
+ /// Set yourself as the Containing Block for all the absolute descendants.
+ ///
+ /// This is called during flow construction, so nothing else can be accessing the descendant
+ /// flows. This is enforced by the fact that we have a mutable `FlowRef`, which only flow
+ /// construction is allowed to possess.
+ fn set_abs_descendants(&mut self, abs_descendants: AbsDescendants) {
+ let this = self.clone();
+
+ let block = self.get_mut().as_block();
+ block.base.abs_descendants = abs_descendants;
+ block.base
+ .parallel
+ .children_and_absolute_descendant_count
+ .fetch_add(block.base.abs_descendants.len() as int, Relaxed);
+
+ for descendant_link in block.base.abs_descendants.iter() {
+ let base = mut_base(descendant_link);
+ base.absolute_cb.set(this.clone());
+ }
+ }
+}
+
+/// A link to a flow's containing block.
+///
+/// This cannot safely be a `Flow` pointer because this is a pointer *up* the tree, not *down* the
+/// tree. A pointer up the tree is unsafe during layout because it can be used to access a node
+/// with an immutable reference while that same node is being laid out, causing possible iterator
+/// invalidation and use-after-free.
+///
+/// FIXME(pcwalton): I think this would be better with a borrow flag instead of `unsafe`.
+pub struct ContainingBlockLink {
+ /// The pointer up to the containing block.
+ link: Option<FlowRef>,
+}
+
+impl ContainingBlockLink {
+ fn new() -> ContainingBlockLink {
+ ContainingBlockLink {
+ link: None,
+ }
+ }
+
+ fn set(&mut self, link: FlowRef) {
+ self.link = Some(link)
+ }
+
+ pub unsafe fn get<'a>(&'a mut self) -> &'a mut Option<FlowRef> {
+ &mut self.link
+ }
+
+ #[inline]
+ pub fn generated_containing_block_rect(&mut self) -> LogicalRect<Au> {
+ match self.link {
+ None => fail!("haven't done it"),
+ Some(ref mut link) => link.get_mut().generated_containing_block_rect(),
+ }
+ }
+}
+
diff --git a/components/layout/flow_list.rs b/components/layout/flow_list.rs
new file mode 100644
index 00000000000..4277326a624
--- /dev/null
+++ b/components/layout/flow_list.rs
@@ -0,0 +1,296 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! A variant of `DList` specialized to store `Flow`s without an extra
+//! indirection.
+
+use flow::{Flow, base, mut_base};
+use flow_ref::FlowRef;
+
+use std::kinds::marker::ContravariantLifetime;
+use std::mem;
+use std::ptr;
+use std::raw;
+
+pub type Link = Option<FlowRef>;
+
+
+#[allow(raw_pointer_deriving)]
+pub struct Rawlink<'a> {
+ object: raw::TraitObject,
+ marker: ContravariantLifetime<'a>,
+}
+
+/// Doubly-linked list of Flows.
+///
+/// The forward links are strong references.
+/// The backward links are weak references.
+pub struct FlowList {
+ length: uint,
+ list_head: Link,
+ list_tail: Link,
+}
+
+/// Double-ended FlowList iterator
+pub struct FlowListIterator<'a> {
+ head: &'a Link,
+ nelem: uint,
+}
+
+/// Double-ended mutable FlowList iterator
+pub struct MutFlowListIterator<'a> {
+ head: Rawlink<'a>,
+ nelem: uint,
+}
+
+impl<'a> Rawlink<'a> {
+ /// Like Option::None for Rawlink
+ pub fn none() -> Rawlink<'static> {
+ Rawlink {
+ object: raw::TraitObject {
+ vtable: ptr::mut_null(),
+ data: ptr::mut_null(),
+ },
+ marker: ContravariantLifetime,
+ }
+ }
+
+ /// Like Option::Some for Rawlink
+ pub fn some(n: &Flow) -> Rawlink {
+ unsafe {
+ Rawlink {
+ object: mem::transmute::<&Flow, raw::TraitObject>(n),
+ marker: ContravariantLifetime,
+ }
+ }
+ }
+
+ pub unsafe fn resolve_mut(&self) -> Option<&'a mut Flow> {
+ if self.object.data.is_null() {
+ None
+ } else {
+ Some(mem::transmute_copy::<raw::TraitObject, &mut Flow>(&self.object))
+ }
+ }
+}
+
+/// Set the .prev field on `next`, then return `Some(next)`
+unsafe fn link_with_prev(mut next: FlowRef, prev: Option<FlowRef>) -> Link {
+ mut_base(next.get_mut()).prev_sibling = prev;
+ Some(next)
+}
+
+impl Collection for FlowList {
+ /// O(1)
+ #[inline]
+ fn is_empty(&self) -> bool {
+ self.list_head.is_none()
+ }
+ /// O(1)
+ #[inline]
+ fn len(&self) -> uint {
+ self.length
+ }
+}
+
+// This doesn't quite fit the Deque trait because of the need to switch between
+// &Flow and ~Flow.
+impl FlowList {
+ /// Provide a reference to the front element, or None if the list is empty
+ #[inline]
+ pub fn front<'a>(&'a self) -> Option<&'a Flow> {
+ self.list_head.as_ref().map(|head| head.get())
+ }
+
+ /// Provide a mutable reference to the front element, or None if the list is empty
+ #[inline]
+ pub unsafe fn front_mut<'a>(&'a mut self) -> Option<&'a mut Flow> {
+ self.list_head.as_mut().map(|head| head.get_mut())
+ }
+
+ /// Provide a reference to the back element, or None if the list is empty
+ #[inline]
+ pub fn back<'a>(&'a self) -> Option<&'a Flow> {
+ match self.list_tail {
+ None => None,
+ Some(ref list_tail) => Some(list_tail.get())
+ }
+ }
+
+ /// Provide a mutable reference to the back element, or None if the list is empty
+ #[inline]
+ pub unsafe fn back_mut<'a>(&'a mut self) -> Option<&'a mut Flow> {
+ // Can't use map() due to error:
+ // lifetime of `tail` is too short to guarantee its contents can be safely reborrowed
+ match self.list_tail {
+ None => None,
+ Some(ref mut tail) => {
+ let x: &mut Flow = tail.get_mut();
+ Some(mem::transmute_copy(&x))
+ }
+ }
+ }
+
+ /// Add an element first in the list
+ ///
+ /// O(1)
+ pub fn push_front(&mut self, mut new_head: FlowRef) {
+ unsafe {
+ match self.list_head {
+ None => {
+ self.list_tail = Some(new_head.clone());
+ self.list_head = link_with_prev(new_head, None);
+ }
+ Some(ref mut head) => {
+ mut_base(new_head.get_mut()).prev_sibling = None;
+ mut_base(head.get_mut()).prev_sibling = Some(new_head.clone());
+ mem::swap(head, &mut new_head);
+ mut_base(head.get_mut()).next_sibling = Some(new_head);
+ }
+ }
+ self.length += 1;
+ }
+ }
+
+ /// Remove the first element and return it, or None if the list is empty
+ ///
+ /// O(1)
+ pub fn pop_front(&mut self) -> Option<FlowRef> {
+ self.list_head.take().map(|mut front_node| {
+ self.length -= 1;
+ unsafe {
+ match mut_base(front_node.get_mut()).next_sibling.take() {
+ Some(node) => self.list_head = link_with_prev(node, None),
+ None => self.list_tail = None,
+ }
+ }
+ front_node
+ })
+ }
+
+ /// Add an element last in the list
+ ///
+ /// O(1)
+ pub fn push_back(&mut self, new_tail: FlowRef) {
+ if self.list_tail.is_none() {
+ return self.push_front(new_tail);
+ }
+
+ let old_tail = self.list_tail.clone();
+ self.list_tail = Some(new_tail.clone());
+ let mut tail = (*old_tail.as_ref().unwrap()).clone();
+ let tail_clone = Some(tail.clone());
+ unsafe {
+ mut_base(tail.get_mut()).next_sibling = link_with_prev(new_tail, tail_clone);
+ }
+ self.length += 1;
+ }
+
+ /// Create an empty list
+ #[inline]
+ pub fn new() -> FlowList {
+ FlowList {
+ list_head: None,
+ list_tail: None,
+ length: 0,
+ }
+ }
+
+ /// Provide a forward iterator
+ #[inline]
+ pub fn iter<'a>(&'a self) -> FlowListIterator<'a> {
+ FlowListIterator {
+ nelem: self.len(),
+ head: &self.list_head,
+ }
+ }
+
+ /// Provide a forward iterator with mutable references
+ #[inline]
+ pub fn mut_iter<'a>(&'a mut self) -> MutFlowListIterator<'a> {
+ let len = self.len();
+ let head_raw = match self.list_head {
+ Some(ref mut h) => Rawlink::some(h.get()),
+ None => Rawlink::none(),
+ };
+ MutFlowListIterator {
+ nelem: len,
+ head: head_raw,
+ }
+ }
+}
+
+#[unsafe_destructor]
+impl Drop for FlowList {
+ fn drop(&mut self) {
+ // Dissolve the list in backwards direction
+ // Just dropping the list_head can lead to stack exhaustion
+ // when length is >> 1_000_000
+ let mut tail = mem::replace(&mut self.list_tail, None);
+ loop {
+ let new_tail = match tail {
+ None => break,
+ Some(ref mut prev) => {
+ let prev_base = mut_base(prev.get_mut());
+ prev_base.next_sibling.take();
+ prev_base.prev_sibling.clone()
+ }
+ };
+ tail = new_tail
+ }
+ self.length = 0;
+ self.list_head = None;
+ }
+}
+
+impl<'a> Iterator<&'a Flow> for FlowListIterator<'a> {
+ #[inline]
+ fn next(&mut self) -> Option<&'a Flow> {
+ if self.nelem == 0 {
+ return None;
+ }
+ self.head.as_ref().map(|head| {
+ let head_base = base(head.get());
+ self.nelem -= 1;
+ self.head = &head_base.next_sibling;
+ let ret: &Flow = head.get();
+ ret
+ })
+ }
+
+ #[inline]
+ fn size_hint(&self) -> (uint, Option<uint>) {
+ (self.nelem, Some(self.nelem))
+ }
+}
+
+impl<'a> Iterator<&'a mut Flow> for MutFlowListIterator<'a> {
+ #[inline]
+ fn next(&mut self) -> Option<&'a mut Flow> {
+ if self.nelem == 0 {
+ return None;
+ }
+ unsafe {
+ self.head.resolve_mut().map(|next| {
+ self.nelem -= 1;
+ self.head = match mut_base(next).next_sibling {
+ Some(ref mut node) => {
+ let x: &mut Flow = node.get_mut();
+ // NOTE: transmute needed here to break the link
+ // between x and next so that it is no longer
+ // borrowed.
+ mem::transmute(Rawlink::some(x))
+ }
+ None => Rawlink::none(),
+ };
+ next
+ })
+ }
+ }
+
+ #[inline]
+ fn size_hint(&self) -> (uint, Option<uint>) {
+ (self.nelem, Some(self.nelem))
+ }
+}
diff --git a/components/layout/flow_ref.rs b/components/layout/flow_ref.rs
new file mode 100644
index 00000000000..d90d9ac4cc0
--- /dev/null
+++ b/components/layout/flow_ref.rs
@@ -0,0 +1,84 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/// Reference-counted pointers to flows.
+///
+/// Eventually, with dynamically sized types in Rust, much of this code will be superfluous.
+
+use flow::Flow;
+use flow;
+
+use std::mem;
+use std::ptr;
+use std::raw;
+use std::sync::atomics::SeqCst;
+
+#[unsafe_no_drop_flag]
+pub struct FlowRef {
+ object: raw::TraitObject,
+}
+
+impl FlowRef {
+ pub fn new(mut flow: Box<Flow>) -> FlowRef {
+ unsafe {
+ let result = {
+ let flow_ref: &mut Flow = flow;
+ let object = mem::transmute::<&mut Flow, raw::TraitObject>(flow_ref);
+ FlowRef { object: object }
+ };
+ mem::forget(flow);
+ result
+ }
+ }
+
+ pub fn get<'a>(&'a self) -> &'a Flow {
+ unsafe {
+ mem::transmute_copy::<raw::TraitObject, &'a Flow>(&self.object)
+ }
+ }
+
+ pub fn get_mut<'a>(&'a mut self) -> &'a mut Flow {
+ unsafe {
+ mem::transmute_copy::<raw::TraitObject, &'a mut Flow>(&self.object)
+ }
+ }
+}
+
+impl Drop for FlowRef {
+ fn drop(&mut self) {
+ unsafe {
+ if self.object.vtable.is_null() {
+ return
+ }
+ if flow::base(self.get()).ref_count().fetch_sub(1, SeqCst) > 1 {
+ return
+ }
+ let flow_ref: FlowRef = mem::replace(self, FlowRef {
+ object: raw::TraitObject {
+ vtable: ptr::mut_null(),
+ data: ptr::mut_null(),
+ }
+ });
+ drop(mem::transmute::<raw::TraitObject, Box<Flow>>(flow_ref.object));
+ mem::forget(flow_ref);
+ self.object.vtable = ptr::mut_null();
+ self.object.data = ptr::mut_null();
+ }
+ }
+}
+
+impl Clone for FlowRef {
+ fn clone(&self) -> FlowRef {
+ unsafe {
+ drop(flow::base(self.get()).ref_count().fetch_add(1, SeqCst));
+ FlowRef {
+ object: raw::TraitObject {
+ vtable: self.object.vtable,
+ data: self.object.data,
+ }
+ }
+ }
+ }
+}
+
diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs
new file mode 100644
index 00000000000..191283603b9
--- /dev/null
+++ b/components/layout/fragment.rs
@@ -0,0 +1,1597 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! The `Fragment` type, which represents the leaves of the layout tree.
+
+#![deny(unsafe_block)]
+
+use css::node_style::StyledNode;
+use construct::FlowConstructor;
+use context::LayoutContext;
+use floats::{ClearBoth, ClearLeft, ClearRight, ClearType};
+use flow::Flow;
+use flow;
+use inline::{InlineFragmentContext, InlineMetrics};
+use layout_debug;
+use model::{Auto, IntrinsicISizes, MaybeAuto, Specified, specified};
+use model;
+use text;
+use util::{OpaqueNodeMethods, ToGfxColor};
+use wrapper::{TLayoutNode, ThreadSafeLayoutNode};
+
+use geom::{Point2D, Rect, Size2D, SideOffsets2D};
+use geom::approxeq::ApproxEq;
+use gfx::color::rgb;
+use gfx::display_list::{BackgroundAndBorderLevel, BaseDisplayItem, BorderDisplayItem};
+use gfx::display_list::{BorderDisplayItemClass, ClipDisplayItem, ClipDisplayItemClass};
+use gfx::display_list::{ContentStackingLevel, DisplayItem, DisplayList, ImageDisplayItem};
+use gfx::display_list::{ImageDisplayItemClass, LineDisplayItem};
+use gfx::display_list::{LineDisplayItemClass, OpaqueNode, PseudoDisplayItemClass};
+use gfx::display_list::{SolidColorDisplayItem, SolidColorDisplayItemClass, StackingLevel};
+use gfx::display_list::{TextDisplayItem, TextDisplayItemClass};
+use gfx::display_list::{Upright, SidewaysLeft, SidewaysRight};
+use gfx::font::FontStyle;
+use gfx::text::glyph::CharIndex;
+use gfx::text::text_run::TextRun;
+use serialize::{Encodable, Encoder};
+use servo_msg::constellation_msg::{ConstellationChan, FrameRectMsg, PipelineId, SubpageId};
+use servo_net::image::holder::ImageHolder;
+use servo_net::local_image_cache::LocalImageCache;
+use servo_util::geometry::Au;
+use servo_util::geometry;
+use servo_util::logical_geometry::{LogicalRect, LogicalSize, LogicalMargin};
+use servo_util::range::*;
+use servo_util::namespace;
+use servo_util::smallvec::SmallVec;
+use servo_util::str::is_whitespace;
+use std::fmt;
+use std::from_str::FromStr;
+use std::mem;
+use std::num::Zero;
+use style::{ComputedValues, TElement, TNode, cascade_anonymous, RGBA};
+use style::computed_values::{LengthOrPercentageOrAuto, overflow, LPA_Auto, background_attachment};
+use style::computed_values::{background_repeat, border_style, clear, position, text_align};
+use style::computed_values::{text_decoration, vertical_align, visibility, white_space};
+use sync::{Arc, Mutex};
+use url::Url;
+
+/// Fragments (`struct Fragment`) are the leaves of the layout tree. They cannot position themselves. In
+/// general, fragments do not have a simple correspondence with CSS fragments in the specification:
+///
+/// * Several fragments may correspond to the same CSS box or DOM node. For example, a CSS text box
+/// broken across two lines is represented by two fragments.
+///
+/// * Some CSS fragments are not created at all, such as some anonymous block fragments induced by inline
+/// fragments with block-level sibling fragments. In that case, Servo uses an `InlineFlow` with
+/// `BlockFlow` siblings; the `InlineFlow` is block-level, but not a block container. It is
+/// positioned as if it were a block fragment, but its children are positioned according to inline
+/// flow.
+///
+/// A `GenericFragment` is an empty fragment that contributes only borders, margins, padding, and
+/// backgrounds. It is analogous to a CSS nonreplaced content box.
+///
+/// A fragment's type influences how its styles are interpreted during layout. For example, replaced
+/// content such as images are resized differently from tables, text, or other content. Different
+/// types of fragments may also contain custom data; for example, text fragments contain text.
+///
+/// FIXME(#2260, pcwalton): This can be slimmed down some.
+#[deriving(Clone)]
+pub struct Fragment {
+ /// An opaque reference to the DOM node that this `Fragment` originates from.
+ pub node: OpaqueNode,
+
+ /// The CSS style of this fragment.
+ pub style: Arc<ComputedValues>,
+
+ /// The position of this fragment relative to its owning flow.
+ /// The size includes padding and border, but not margin.
+ pub border_box: LogicalRect<Au>,
+
+ /// The sum of border and padding; i.e. the distance from the edge of the border box to the
+ /// content edge of the fragment.
+ pub border_padding: LogicalMargin<Au>,
+
+ /// The margin of the content box.
+ pub margin: LogicalMargin<Au>,
+
+ /// Info specific to the kind of fragment. Keep this enum small.
+ pub specific: SpecificFragmentInfo,
+
+ /// New-line chracter(\n)'s positions(relative, not absolute)
+ ///
+ /// FIXME(#2260, pcwalton): This is very inefficient; remove.
+ pub new_line_pos: Vec<CharIndex>,
+
+ /// Holds the style context information for fragments
+ /// that are part of an inline formatting context.
+ pub inline_context: Option<InlineFragmentContext>,
+
+ /// A debug ID that is consistent for the life of
+ /// this fragment (via transform etc).
+ pub debug_id: uint,
+}
+
+impl<E, S: Encoder<E>> Encodable<S, E> for Fragment {
+ fn encode(&self, e: &mut S) -> Result<(), E> {
+ e.emit_struct("fragment", 0, |e| {
+ try!(e.emit_struct_field("id", 0, |e| self.debug_id().encode(e)))
+ try!(e.emit_struct_field("border_box", 1, |e| self.border_box.encode(e)))
+ e.emit_struct_field("margin", 2, |e| self.margin.encode(e))
+ })
+ }
+}
+
+/// Info specific to the kind of fragment. Keep this enum small.
+#[deriving(Clone)]
+pub enum SpecificFragmentInfo {
+ GenericFragment,
+ ImageFragment(ImageFragmentInfo),
+ IframeFragment(IframeFragmentInfo),
+ ScannedTextFragment(ScannedTextFragmentInfo),
+ TableFragment,
+ TableCellFragment,
+ TableColumnFragment(TableColumnFragmentInfo),
+ TableRowFragment,
+ TableWrapperFragment,
+ UnscannedTextFragment(UnscannedTextFragmentInfo),
+}
+
+/// A fragment that represents a replaced content image and its accompanying borders, shadows, etc.
+#[deriving(Clone)]
+pub struct ImageFragmentInfo {
+ /// The image held within this fragment.
+ pub image: ImageHolder,
+ pub computed_inline_size: Option<Au>,
+ pub computed_block_size: Option<Au>,
+ pub dom_inline_size: Option<Au>,
+ pub dom_block_size: Option<Au>,
+ pub writing_mode_is_vertical: bool,
+}
+
+impl ImageFragmentInfo {
+ /// Creates a new image fragment from the given URL and local image cache.
+ ///
+ /// FIXME(pcwalton): The fact that image fragments store the cache in the fragment makes little sense to
+ /// me.
+ pub fn new(node: &ThreadSafeLayoutNode,
+ image_url: Url,
+ local_image_cache: Arc<Mutex<LocalImageCache>>)
+ -> ImageFragmentInfo {
+ fn convert_length(node: &ThreadSafeLayoutNode, name: &str) -> Option<Au> {
+ let element = node.as_element();
+ element.get_attr(&namespace::Null, name).and_then(|string| {
+ let n: Option<int> = FromStr::from_str(string);
+ n
+ }).and_then(|pixels| Some(Au::from_px(pixels)))
+ }
+
+ let is_vertical = node.style().writing_mode.is_vertical();
+ let dom_width = convert_length(node, "width");
+ let dom_height = convert_length(node, "height");
+ ImageFragmentInfo {
+ image: ImageHolder::new(image_url, local_image_cache),
+ computed_inline_size: None,
+ computed_block_size: None,
+ dom_inline_size: if is_vertical { dom_height } else { dom_width },
+ dom_block_size: if is_vertical { dom_width } else { dom_height },
+ writing_mode_is_vertical: is_vertical,
+ }
+ }
+
+ /// Returns the calculated inline-size of the image, accounting for the inline-size attribute.
+ pub fn computed_inline_size(&self) -> Au {
+ self.computed_inline_size.expect("image inline_size is not computed yet!")
+ }
+
+ /// Returns the calculated block-size of the image, accounting for the block-size attribute.
+ pub fn computed_block_size(&self) -> Au {
+ self.computed_block_size.expect("image block_size is not computed yet!")
+ }
+
+ /// Returns the original inline-size of the image.
+ pub fn image_inline_size(&mut self) -> Au {
+ let size = self.image.get_size().unwrap_or(Size2D::zero());
+ Au::from_px(if self.writing_mode_is_vertical { size.height } else { size.width })
+ }
+
+ /// Returns the original block-size of the image.
+ pub fn image_block_size(&mut self) -> Au {
+ let size = self.image.get_size().unwrap_or(Size2D::zero());
+ Au::from_px(if self.writing_mode_is_vertical { size.width } else { size.height })
+ }
+
+ // Return used value for inline-size or block-size.
+ //
+ // `dom_length`: inline-size or block-size as specified in the `img` tag.
+ // `style_length`: inline-size as given in the CSS
+ pub fn style_length(style_length: LengthOrPercentageOrAuto,
+ dom_length: Option<Au>,
+ container_inline_size: Au) -> MaybeAuto {
+ match (MaybeAuto::from_style(style_length,container_inline_size),dom_length) {
+ (Specified(length),_) => {
+ Specified(length)
+ },
+ (Auto,Some(length)) => {
+ Specified(length)
+ },
+ (Auto,None) => {
+ Auto
+ }
+ }
+ }
+}
+
+/// A fragment that represents an inline frame (iframe). This stores the pipeline ID so that the size
+/// of this iframe can be communicated via the constellation to the iframe's own layout task.
+#[deriving(Clone)]
+pub struct IframeFragmentInfo {
+ /// The pipeline ID of this iframe.
+ pub pipeline_id: PipelineId,
+ /// The subpage ID of this iframe.
+ pub subpage_id: SubpageId,
+}
+
+impl IframeFragmentInfo {
+ /// Creates the information specific to an iframe fragment.
+ pub fn new(node: &ThreadSafeLayoutNode) -> IframeFragmentInfo {
+ let (pipeline_id, subpage_id) = node.iframe_pipeline_and_subpage_ids();
+ IframeFragmentInfo {
+ pipeline_id: pipeline_id,
+ subpage_id: subpage_id,
+ }
+ }
+}
+
+/// A scanned text fragment represents a single run of text with a distinct style. A `TextFragment`
+/// may be split into two or more fragments across line breaks. Several `TextFragment`s may
+/// correspond to a single DOM text node. Split text fragments are implemented by referring to
+/// subsets of a single `TextRun` object.
+#[deriving(Clone)]
+pub struct ScannedTextFragmentInfo {
+ /// The text run that this represents.
+ pub run: Arc<Box<TextRun>>,
+
+ /// The range within the above text run that this represents.
+ pub range: Range<CharIndex>,
+}
+
+impl ScannedTextFragmentInfo {
+ /// Creates the information specific to a scanned text fragment from a range and a text run.
+ pub fn new(run: Arc<Box<TextRun>>, range: Range<CharIndex>) -> ScannedTextFragmentInfo {
+ ScannedTextFragmentInfo {
+ run: run,
+ range: range,
+ }
+ }
+}
+
+#[deriving(Show)]
+pub struct SplitInfo {
+ // TODO(bjz): this should only need to be a single character index, but both values are
+ // currently needed for splitting in the `inline::try_append_*` functions.
+ pub range: Range<CharIndex>,
+ pub inline_size: Au,
+}
+
+impl SplitInfo {
+ fn new(range: Range<CharIndex>, info: &ScannedTextFragmentInfo) -> SplitInfo {
+ SplitInfo {
+ range: range,
+ inline_size: info.run.advance_for_range(&range),
+ }
+ }
+}
+
+/// Data for an unscanned text fragment. Unscanned text fragments are the results of flow construction that
+/// have not yet had their inline-size determined.
+#[deriving(Clone)]
+pub struct UnscannedTextFragmentInfo {
+ /// The text inside the fragment.
+ pub text: String,
+}
+
+impl UnscannedTextFragmentInfo {
+ /// Creates a new instance of `UnscannedTextFragmentInfo` from the given DOM node.
+ pub fn new(node: &ThreadSafeLayoutNode) -> UnscannedTextFragmentInfo {
+ // FIXME(pcwalton): Don't copy text; atomically reference count it instead.
+ UnscannedTextFragmentInfo {
+ text: node.text(),
+ }
+ }
+
+ /// Creates a new instance of `UnscannedTextFragmentInfo` from the given text.
+ #[inline]
+ pub fn from_text(text: String) -> UnscannedTextFragmentInfo {
+ UnscannedTextFragmentInfo {
+ text: text,
+ }
+ }
+}
+
+/// A fragment that represents a table column.
+#[deriving(Clone)]
+pub struct TableColumnFragmentInfo {
+ /// the number of columns a <col> element should span
+ pub span: Option<int>,
+}
+
+impl TableColumnFragmentInfo {
+ /// Create the information specific to an table column fragment.
+ pub fn new(node: &ThreadSafeLayoutNode) -> TableColumnFragmentInfo {
+ let span = {
+ let element = node.as_element();
+ element.get_attr(&namespace::Null, "span").and_then(|string| {
+ let n: Option<int> = FromStr::from_str(string);
+ n
+ })
+ };
+ TableColumnFragmentInfo {
+ span: span,
+ }
+ }
+}
+
+impl Fragment {
+ /// Constructs a new `Fragment` instance for the given node.
+ ///
+ /// Arguments:
+ ///
+ /// * `constructor`: The flow constructor.
+ ///
+ /// * `node`: The node to create a fragment for.
+ pub fn new(constructor: &mut FlowConstructor, node: &ThreadSafeLayoutNode) -> Fragment {
+ let style = node.style().clone();
+ let writing_mode = style.writing_mode;
+ Fragment {
+ node: OpaqueNodeMethods::from_thread_safe_layout_node(node),
+ style: style,
+ border_box: LogicalRect::zero(writing_mode),
+ border_padding: LogicalMargin::zero(writing_mode),
+ margin: LogicalMargin::zero(writing_mode),
+ specific: constructor.build_specific_fragment_info_for_node(node),
+ new_line_pos: vec!(),
+ inline_context: None,
+ debug_id: layout_debug::generate_unique_debug_id(),
+ }
+ }
+
+ /// Constructs a new `Fragment` instance from a specific info.
+ pub fn new_from_specific_info(node: &ThreadSafeLayoutNode, specific: SpecificFragmentInfo) -> Fragment {
+ let style = node.style().clone();
+ let writing_mode = style.writing_mode;
+ Fragment {
+ node: OpaqueNodeMethods::from_thread_safe_layout_node(node),
+ style: style,
+ border_box: LogicalRect::zero(writing_mode),
+ border_padding: LogicalMargin::zero(writing_mode),
+ margin: LogicalMargin::zero(writing_mode),
+ specific: specific,
+ new_line_pos: vec!(),
+ inline_context: None,
+ debug_id: layout_debug::generate_unique_debug_id(),
+ }
+ }
+
+ /// Constructs a new `Fragment` instance for an anonymous table object.
+ pub fn new_anonymous_table_fragment(node: &ThreadSafeLayoutNode, specific: SpecificFragmentInfo) -> Fragment {
+ // CSS 2.1 § 17.2.1 This is for non-inherited properties on anonymous table fragments
+ // example:
+ //
+ // <div style="display: table">
+ // Foo
+ // </div>
+ //
+ // Anonymous table fragments, TableRowFragment and TableCellFragment, are generated around `Foo`, but it shouldn't inherit the border.
+
+ let node_style = cascade_anonymous(&**node.style());
+ let writing_mode = node_style.writing_mode;
+ Fragment {
+ node: OpaqueNodeMethods::from_thread_safe_layout_node(node),
+ style: Arc::new(node_style),
+ border_box: LogicalRect::zero(writing_mode),
+ border_padding: LogicalMargin::zero(writing_mode),
+ margin: LogicalMargin::zero(writing_mode),
+ specific: specific,
+ new_line_pos: vec!(),
+ inline_context: None,
+ debug_id: layout_debug::generate_unique_debug_id(),
+ }
+ }
+
+ /// Constructs a new `Fragment` instance from an opaque node.
+ pub fn from_opaque_node_and_style(node: OpaqueNode,
+ style: Arc<ComputedValues>,
+ specific: SpecificFragmentInfo)
+ -> Fragment {
+ let writing_mode = style.writing_mode;
+ Fragment {
+ node: node,
+ style: style,
+ border_box: LogicalRect::zero(writing_mode),
+ border_padding: LogicalMargin::zero(writing_mode),
+ margin: LogicalMargin::zero(writing_mode),
+ specific: specific,
+ new_line_pos: vec!(),
+ inline_context: None,
+ debug_id: layout_debug::generate_unique_debug_id(),
+ }
+ }
+
+ /// Returns a debug ID of this fragment. This ID should not be considered stable across multiple
+ /// layouts or fragment manipulations.
+ pub fn debug_id(&self) -> uint {
+ self.debug_id
+ }
+
+ /// Transforms this fragment into another fragment of the given type, with the given size, preserving all
+ /// the other data.
+ pub fn transform(&self, size: LogicalSize<Au>, specific: SpecificFragmentInfo) -> Fragment {
+ Fragment {
+ node: self.node,
+ style: self.style.clone(),
+ border_box: LogicalRect::from_point_size(
+ self.style.writing_mode, self.border_box.start, size),
+ border_padding: self.border_padding,
+ margin: self.margin,
+ specific: specific,
+ new_line_pos: self.new_line_pos.clone(),
+ inline_context: self.inline_context.clone(),
+ debug_id: self.debug_id,
+ }
+ }
+
+ /// Adds a style to the inline context for this fragment. If the inline
+ /// context doesn't exist yet, it will be created.
+ pub fn add_inline_context_style(&mut self, style: Arc<ComputedValues>) {
+ if self.inline_context.is_none() {
+ self.inline_context = Some(InlineFragmentContext::new());
+ }
+ self.inline_context.get_mut_ref().styles.push(style.clone());
+ }
+
+ /// Uses the style only to estimate the intrinsic inline-sizes. These may be modified for text or
+ /// replaced elements.
+ fn style_specified_intrinsic_inline_size(&self) -> IntrinsicISizes {
+ let (use_margins, use_padding) = match self.specific {
+ GenericFragment | IframeFragment(_) | ImageFragment(_) => (true, true),
+ TableFragment | TableCellFragment => (false, true),
+ TableWrapperFragment => (true, false),
+ TableRowFragment => (false, false),
+ ScannedTextFragment(_) | TableColumnFragment(_) | UnscannedTextFragment(_) => {
+ // Styles are irrelevant for these kinds of fragments.
+ return IntrinsicISizes::new()
+ }
+ };
+
+ let style = self.style();
+ let inline_size = MaybeAuto::from_style(style.content_inline_size(), Au::new(0)).specified_or_zero();
+
+ let margin = style.logical_margin();
+ let (margin_inline_start, margin_inline_end) = if use_margins {
+ (MaybeAuto::from_style(margin.inline_start, Au(0)).specified_or_zero(),
+ MaybeAuto::from_style(margin.inline_end, Au(0)).specified_or_zero())
+ } else {
+ (Au(0), Au(0))
+ };
+
+ let padding = style.logical_padding();
+ let (padding_inline_start, padding_inline_end) = if use_padding {
+ (model::specified(padding.inline_start, Au(0)),
+ model::specified(padding.inline_end, Au(0)))
+ } else {
+ (Au(0), Au(0))
+ };
+
+ // FIXME(#2261, pcwalton): This won't work well for inlines: is this OK?
+ let border = self.border_width();
+ let surround_inline_size = margin_inline_start + margin_inline_end + padding_inline_start + padding_inline_end +
+ border.inline_start_end();
+
+ IntrinsicISizes {
+ minimum_inline_size: inline_size,
+ preferred_inline_size: inline_size,
+ surround_inline_size: surround_inline_size,
+ }
+ }
+
+ pub fn calculate_line_height(&self, layout_context: &LayoutContext) -> Au {
+ let font_style = text::computed_style_to_font_style(&*self.style);
+ let font_metrics = text::font_metrics_for_style(layout_context.font_context(), &font_style);
+ text::line_height_from_style(&*self.style, &font_metrics)
+ }
+
+ /// Returns the sum of the inline-sizes of all the borders of this fragment. This is private because
+ /// it should only be called during intrinsic inline-size computation or computation of
+ /// `border_padding`. Other consumers of this information should simply consult that field.
+ #[inline]
+ fn border_width(&self) -> LogicalMargin<Au> {
+ match self.inline_context {
+ None => self.style().logical_border_width(),
+ Some(ref inline_fragment_context) => {
+ let zero = LogicalMargin::zero(self.style.writing_mode);
+ inline_fragment_context.styles.iter().fold(zero, |acc, style| acc + style.logical_border_width())
+ }
+ }
+ }
+
+ /// Computes the border, padding, and vertical margins from the containing block inline-size and the
+ /// style. After this call, the `border_padding` and the vertical direction of the `margin`
+ /// field will be correct.
+ pub fn compute_border_padding_margins(&mut self,
+ containing_block_inline_size: Au) {
+ // Compute vertical margins. Note that this value will be ignored by layout if the style
+ // specifies `auto`.
+ match self.specific {
+ TableFragment | TableCellFragment | TableRowFragment | TableColumnFragment(_) => {
+ self.margin.block_start = Au(0);
+ self.margin.block_end = Au(0)
+ }
+ _ => {
+ // NB: Percentages are relative to containing block inline-size (not block-size) per CSS 2.1.
+ let margin = self.style().logical_margin();
+ self.margin.block_start = MaybeAuto::from_style(margin.block_start, containing_block_inline_size)
+ .specified_or_zero();
+ self.margin.block_end = MaybeAuto::from_style(margin.block_end, containing_block_inline_size)
+ .specified_or_zero()
+ }
+ }
+
+ // Compute border.
+ let border = self.border_width();
+
+ // Compute padding.
+ let padding = match self.specific {
+ TableColumnFragment(_) | TableRowFragment |
+ TableWrapperFragment => LogicalMargin::zero(self.style.writing_mode),
+ _ => {
+ match self.inline_context {
+ None => model::padding_from_style(self.style(), containing_block_inline_size),
+ Some(ref inline_fragment_context) => {
+ let zero = LogicalMargin::zero(self.style.writing_mode);
+ inline_fragment_context.styles.iter()
+ .fold(zero, |acc, style| acc + model::padding_from_style(&**style, Au(0)))
+ }
+ }
+ }
+ };
+
+ self.border_padding = border + padding
+ }
+
+ // Return offset from original position because of `position: relative`.
+ pub fn relative_position(&self,
+ containing_block_size: &LogicalSize<Au>)
+ -> LogicalSize<Au> {
+ fn from_style(style: &ComputedValues, container_size: &LogicalSize<Au>)
+ -> LogicalSize<Au> {
+ let offsets = style.logical_position();
+ let offset_i = if offsets.inline_start != LPA_Auto {
+ MaybeAuto::from_style(offsets.inline_start, container_size.inline).specified_or_zero()
+ } else {
+ -MaybeAuto::from_style(offsets.inline_end, container_size.inline).specified_or_zero()
+ };
+ let offset_b = if offsets.block_start != LPA_Auto {
+ MaybeAuto::from_style(offsets.block_start, container_size.inline).specified_or_zero()
+ } else {
+ -MaybeAuto::from_style(offsets.block_end, container_size.inline).specified_or_zero()
+ };
+ LogicalSize::new(style.writing_mode, offset_i, offset_b)
+ }
+
+ // Go over the ancestor fragments and add all relative offsets (if any).
+ let mut rel_pos = LogicalSize::zero(self.style.writing_mode);
+ match self.inline_context {
+ None => {
+ if self.style().get_box().position == position::relative {
+ rel_pos = rel_pos + from_style(self.style(), containing_block_size);
+ }
+ }
+ Some(ref inline_fragment_context) => {
+ for style in inline_fragment_context.styles.iter() {
+ if style.get_box().position == position::relative {
+ rel_pos = rel_pos + from_style(&**style, containing_block_size);
+ }
+ }
+ },
+ }
+ rel_pos
+ }
+
+ /// Always inline for SCCP.
+ ///
+ /// FIXME(pcwalton): Just replace with the clear type from the style module for speed?
+ #[inline(always)]
+ pub fn clear(&self) -> Option<ClearType> {
+ let style = self.style();
+ match style.get_box().clear {
+ clear::none => None,
+ clear::left => Some(ClearLeft),
+ clear::right => Some(ClearRight),
+ clear::both => Some(ClearBoth),
+ }
+ }
+
+ /// Converts this fragment's computed style to a font style used for rendering.
+ pub fn font_style(&self) -> FontStyle {
+ text::computed_style_to_font_style(self.style())
+ }
+
+ #[inline(always)]
+ pub fn style<'a>(&'a self) -> &'a ComputedValues {
+ &*self.style
+ }
+
+ /// Returns the text alignment of the computed style of the nearest ancestor-or-self `Element`
+ /// node.
+ pub fn text_align(&self) -> text_align::T {
+ self.style().get_inheritedtext().text_align
+ }
+
+ pub fn vertical_align(&self) -> vertical_align::T {
+ self.style().get_box().vertical_align
+ }
+
+ pub fn white_space(&self) -> white_space::T {
+ self.style().get_inheritedtext().white_space
+ }
+
+ /// Returns the text decoration of this fragment, according to the style of the nearest ancestor
+ /// element.
+ ///
+ /// NB: This may not be the actual text decoration, because of the override rules specified in
+ /// CSS 2.1 § 16.3.1. Unfortunately, computing this properly doesn't really fit into Servo's
+ /// model. Therefore, this is a best lower bound approximation, but the end result may actually
+ /// have the various decoration flags turned on afterward.
+ pub fn text_decoration(&self) -> text_decoration::T {
+ self.style().get_text().text_decoration
+ }
+
+ /// Returns the inline-start offset from margin edge to content edge.
+ ///
+ /// FIXME(#2262, pcwalton): I think this method is pretty bogus, because it won't work for
+ /// inlines.
+ pub fn inline_start_offset(&self) -> Au {
+ match self.specific {
+ TableWrapperFragment => self.margin.inline_start,
+ TableFragment | TableCellFragment | TableRowFragment => self.border_padding.inline_start,
+ TableColumnFragment(_) => Au(0),
+ _ => self.margin.inline_start + self.border_padding.inline_start,
+ }
+ }
+
+ /// Returns true if this element can be split. This is true for text fragments.
+ pub fn can_split(&self) -> bool {
+ match self.specific {
+ ScannedTextFragment(..) => true,
+ _ => false,
+ }
+ }
+
+ /// Adds the display items necessary to paint the background of this fragment to the display
+ /// list if necessary.
+ pub fn build_display_list_for_background_if_applicable(&self,
+ style: &ComputedValues,
+ list: &mut DisplayList,
+ layout_context: &LayoutContext,
+ level: StackingLevel,
+ absolute_bounds: &Rect<Au>) {
+ // FIXME: This causes a lot of background colors to be displayed when they are clearly not
+ // needed. We could use display list optimization to clean this up, but it still seems
+ // inefficient. What we really want is something like "nearest ancestor element that
+ // doesn't have a fragment".
+ let background_color = style.resolve_color(style.get_background().background_color);
+ if !background_color.alpha.approx_eq(&0.0) {
+ let display_item = box SolidColorDisplayItem {
+ base: BaseDisplayItem::new(*absolute_bounds, self.node, level),
+ color: background_color.to_gfx_color(),
+ };
+
+ list.push(SolidColorDisplayItemClass(display_item))
+ }
+
+ // The background image is painted on top of the background color.
+ // Implements background image, per spec:
+ // http://www.w3.org/TR/CSS21/colors.html#background
+ let background = style.get_background();
+ let image_url = match background.background_image {
+ None => return,
+ Some(ref image_url) => image_url,
+ };
+
+ let mut holder = ImageHolder::new(image_url.clone(), layout_context.shared.image_cache.clone());
+ let image = match holder.get_image() {
+ None => {
+ // No image data at all? Do nothing.
+ //
+ // TODO: Add some kind of placeholder background image.
+ debug!("(building display list) no background image :(");
+ return
+ }
+ Some(image) => image,
+ };
+ debug!("(building display list) building background image");
+
+ // Adjust bounds for `background-position` and `background-attachment`.
+ let mut bounds = *absolute_bounds;
+ let horizontal_position = model::specified(background.background_position.horizontal,
+ bounds.size.width);
+ let vertical_position = model::specified(background.background_position.vertical,
+ bounds.size.height);
+
+ let clip_display_item;
+ match background.background_attachment {
+ background_attachment::scroll => {
+ clip_display_item = None;
+ bounds.origin.x = bounds.origin.x + horizontal_position;
+ bounds.origin.y = bounds.origin.y + vertical_position;
+ bounds.size.width = bounds.size.width - horizontal_position;
+ bounds.size.height = bounds.size.height - vertical_position;
+ }
+ background_attachment::fixed => {
+ clip_display_item = Some(box ClipDisplayItem {
+ base: BaseDisplayItem::new(bounds, self.node, level),
+ children: DisplayList::new(),
+ });
+
+ bounds = Rect {
+ origin: Point2D(horizontal_position, vertical_position),
+ size: Size2D(bounds.origin.x + bounds.size.width,
+ bounds.origin.y + bounds.size.height),
+ }
+ }
+ }
+
+ // Adjust sizes for `background-repeat`.
+ match background.background_repeat {
+ background_repeat::no_repeat => {
+ bounds.size.width = Au::from_px(image.width as int);
+ bounds.size.height = Au::from_px(image.height as int)
+ }
+ background_repeat::repeat_x => {
+ bounds.size.height = Au::from_px(image.height as int)
+ }
+ background_repeat::repeat_y => {
+ bounds.size.width = Au::from_px(image.width as int)
+ }
+ background_repeat::repeat => {}
+ };
+
+ // Create the image display item.
+ let image_display_item = ImageDisplayItemClass(box ImageDisplayItem {
+ base: BaseDisplayItem::new(bounds, self.node, level),
+ image: image.clone(),
+ stretch_size: Size2D(Au::from_px(image.width as int),
+ Au::from_px(image.height as int)),
+ });
+
+ match clip_display_item {
+ None => list.push(image_display_item),
+ Some(mut clip_display_item) => {
+ clip_display_item.children.push(image_display_item);
+ list.push(ClipDisplayItemClass(clip_display_item))
+ }
+ }
+ }
+
+ /// Adds the display items necessary to paint the borders of this fragment to a display list if
+ /// necessary.
+ pub fn build_display_list_for_borders_if_applicable(&self,
+ style: &ComputedValues,
+ list: &mut DisplayList,
+ abs_bounds: &Rect<Au>,
+ level: StackingLevel) {
+ let border = style.logical_border_width();
+ if border.is_zero() {
+ return
+ }
+
+ let top_color = style.resolve_color(style.get_border().border_top_color);
+ let right_color = style.resolve_color(style.get_border().border_right_color);
+ let bottom_color = style.resolve_color(style.get_border().border_bottom_color);
+ let left_color = style.resolve_color(style.get_border().border_left_color);
+
+ // Append the border to the display list.
+ let border_display_item = box BorderDisplayItem {
+ base: BaseDisplayItem::new(*abs_bounds, self.node, level),
+ border: border.to_physical(style.writing_mode),
+ color: SideOffsets2D::new(top_color.to_gfx_color(),
+ right_color.to_gfx_color(),
+ bottom_color.to_gfx_color(),
+ left_color.to_gfx_color()),
+ style: SideOffsets2D::new(style.get_border().border_top_style,
+ style.get_border().border_right_style,
+ style.get_border().border_bottom_style,
+ style.get_border().border_left_style)
+ };
+
+ list.push(BorderDisplayItemClass(border_display_item))
+ }
+
+ fn build_debug_borders_around_text_fragments(&self,
+ display_list: &mut DisplayList,
+ flow_origin: Point2D<Au>,
+ text_fragment: &ScannedTextFragmentInfo) {
+ // FIXME(#2795): Get the real container size
+ let container_size = Size2D::zero();
+ // Fragment position wrt to the owning flow.
+ let fragment_bounds = self.border_box.to_physical(self.style.writing_mode, container_size);
+ let absolute_fragment_bounds = Rect(
+ fragment_bounds.origin + flow_origin,
+ fragment_bounds.size);
+
+ // Compute the text fragment bounds and draw a border surrounding them.
+ let border_display_item = box BorderDisplayItem {
+ base: BaseDisplayItem::new(absolute_fragment_bounds, self.node, ContentStackingLevel),
+ border: SideOffsets2D::new_all_same(Au::from_px(1)),
+ color: SideOffsets2D::new_all_same(rgb(0, 0, 200)),
+ style: SideOffsets2D::new_all_same(border_style::solid)
+ };
+ display_list.push(BorderDisplayItemClass(border_display_item));
+
+ // Draw a rectangle representing the baselines.
+ let ascent = text_fragment.run.ascent();
+ let mut baseline = self.border_box.clone();
+ baseline.start.b = baseline.start.b + ascent;
+ baseline.size.block = Au(0);
+ let mut baseline = baseline.to_physical(self.style.writing_mode, container_size);
+ baseline.origin = baseline.origin + flow_origin;
+
+ let line_display_item = box LineDisplayItem {
+ base: BaseDisplayItem::new(baseline, self.node, ContentStackingLevel),
+ color: rgb(0, 200, 0),
+ style: border_style::dashed,
+ };
+ display_list.push(LineDisplayItemClass(line_display_item));
+ }
+
+ fn build_debug_borders_around_fragment(&self,
+ display_list: &mut DisplayList,
+ flow_origin: Point2D<Au>) {
+ // FIXME(#2795): Get the real container size
+ let container_size = Size2D::zero();
+ // Fragment position wrt to the owning flow.
+ let fragment_bounds = self.border_box.to_physical(self.style.writing_mode, container_size);
+ let absolute_fragment_bounds = Rect(
+ fragment_bounds.origin + flow_origin,
+ fragment_bounds.size);
+
+ // This prints a debug border around the border of this fragment.
+ let border_display_item = box BorderDisplayItem {
+ base: BaseDisplayItem::new(absolute_fragment_bounds, self.node, ContentStackingLevel),
+ border: SideOffsets2D::new_all_same(Au::from_px(1)),
+ color: SideOffsets2D::new_all_same(rgb(0, 0, 200)),
+ style: SideOffsets2D::new_all_same(border_style::solid)
+ };
+ display_list.push(BorderDisplayItemClass(border_display_item))
+ }
+
+ /// Adds the display items for this fragment to the given stacking context.
+ ///
+ /// Arguments:
+ ///
+ /// * `display_list`: The unflattened display list to add display items to.
+ /// * `layout_context`: The layout context.
+ /// * `dirty`: The dirty rectangle in the coordinate system of the owning flow.
+ /// * `flow_origin`: Position of the origin of the owning flow wrt the display list root flow.
+ pub fn build_display_list(&self,
+ display_list: &mut DisplayList,
+ layout_context: &LayoutContext,
+ flow_origin: Point2D<Au>,
+ background_and_border_level: BackgroundAndBorderLevel)
+ -> ChildDisplayListAccumulator {
+ // FIXME(#2795): Get the real container size
+ let container_size = Size2D::zero();
+ let rect_to_absolute = |logical_rect: LogicalRect<Au>| {
+ let physical_rect = logical_rect.to_physical(self.style.writing_mode, container_size);
+ Rect(physical_rect.origin + flow_origin, physical_rect.size)
+ };
+ // Fragment position wrt to the owning flow.
+ let absolute_fragment_bounds = rect_to_absolute(self.border_box);
+ debug!("Fragment::build_display_list at rel={}, abs={}: {}",
+ self.border_box,
+ absolute_fragment_bounds,
+ self);
+ debug!("Fragment::build_display_list: dirty={}, flow_origin={}",
+ layout_context.shared.dirty,
+ flow_origin);
+
+ let mut accumulator = ChildDisplayListAccumulator::new(self.style(),
+ absolute_fragment_bounds,
+ self.node,
+ ContentStackingLevel);
+ if self.style().get_inheritedbox().visibility != visibility::visible {
+ return accumulator
+ }
+
+ if !absolute_fragment_bounds.intersects(&layout_context.shared.dirty) {
+ debug!("Fragment::build_display_list: Did not intersect...");
+ return accumulator
+ }
+
+ debug!("Fragment::build_display_list: intersected. Adding display item...");
+
+ {
+ let level =
+ StackingLevel::from_background_and_border_level(background_and_border_level);
+
+ // Add a pseudo-display item for content box queries. This is a very bogus thing to do.
+ let base_display_item = box BaseDisplayItem::new(absolute_fragment_bounds, self.node, level);
+ display_list.push(PseudoDisplayItemClass(base_display_item));
+
+ // Add the background to the list, if applicable.
+ match self.inline_context {
+ Some(ref inline_context) => {
+ for style in inline_context.styles.iter().rev() {
+ self.build_display_list_for_background_if_applicable(&**style,
+ display_list,
+ layout_context,
+ level,
+ &absolute_fragment_bounds);
+ }
+ }
+ None => {
+ self.build_display_list_for_background_if_applicable(&*self.style,
+ display_list,
+ layout_context,
+ level,
+ &absolute_fragment_bounds);
+ }
+ }
+
+ // Add a border, if applicable.
+ //
+ // TODO: Outlines.
+ match self.inline_context {
+ Some(ref inline_context) => {
+ for style in inline_context.styles.iter().rev() {
+ self.build_display_list_for_borders_if_applicable(&**style,
+ display_list,
+ &absolute_fragment_bounds,
+ level);
+ }
+ }
+ None => {
+ self.build_display_list_for_borders_if_applicable(&*self.style,
+ display_list,
+ &absolute_fragment_bounds,
+ level);
+ }
+ }
+ }
+
+ let content_box = self.content_box();
+ let absolute_content_box = rect_to_absolute(content_box);
+
+ // Add a clip, if applicable.
+ match self.specific {
+ UnscannedTextFragment(_) => fail!("Shouldn't see unscanned fragments here."),
+ TableColumnFragment(_) => fail!("Shouldn't see table column fragments here."),
+ ScannedTextFragment(ref text_fragment) => {
+ // Create the text display item.
+ let orientation = if self.style.writing_mode.is_vertical() {
+ if self.style.writing_mode.is_sideways_left() {
+ SidewaysLeft
+ } else {
+ SidewaysRight
+ }
+ } else {
+ Upright
+ };
+
+ let metrics = &text_fragment.run.font_metrics;
+ let baseline_origin ={
+ let mut tmp = content_box.start;
+ tmp.b = tmp.b + metrics.ascent;
+ tmp.to_physical(self.style.writing_mode, container_size) + flow_origin
+ };
+
+ let text_display_item = box TextDisplayItem {
+ base: BaseDisplayItem::new(
+ absolute_content_box, self.node, ContentStackingLevel),
+ text_run: text_fragment.run.clone(),
+ range: text_fragment.range,
+ text_color: self.style().get_color().color.to_gfx_color(),
+ orientation: orientation,
+ baseline_origin: baseline_origin,
+ };
+ accumulator.push(display_list, TextDisplayItemClass(text_display_item));
+
+
+ // Create display items for text decoration
+ {
+ let line = |maybe_color: Option<RGBA>, rect: || -> LogicalRect<Au>| {
+ match maybe_color {
+ None => {},
+ Some(color) => {
+ accumulator.push(display_list, SolidColorDisplayItemClass(
+ box SolidColorDisplayItem {
+ base: BaseDisplayItem::new(
+ rect_to_absolute(rect()),
+ self.node, ContentStackingLevel),
+ color: color.to_gfx_color(),
+ }
+ ));
+ }
+ }
+ };
+
+ let text_decorations =
+ self.style().get_inheritedtext()._servo_text_decorations_in_effect;
+ line(text_decorations.underline, || {
+ let mut rect = content_box.clone();
+ rect.start.b = rect.start.b + metrics.ascent - metrics.underline_offset;
+ rect.size.block = metrics.underline_size;
+ rect
+ });
+
+ line(text_decorations.overline, || {
+ let mut rect = content_box.clone();
+ rect.size.block = metrics.underline_size;
+ rect
+ });
+
+ line(text_decorations.line_through, || {
+ let mut rect = content_box.clone();
+ rect.start.b = rect.start.b + metrics.ascent - metrics.strikeout_offset;
+ rect.size.block = metrics.strikeout_size;
+ rect
+ });
+ }
+
+ // Draw debug frames for text bounds.
+ //
+ // FIXME(#2263, pcwalton): This is a bit of an abuse of the logging infrastructure.
+ // We should have a real `SERVO_DEBUG` system.
+ debug!("{:?}", self.build_debug_borders_around_text_fragments(display_list,
+ flow_origin,
+ text_fragment))
+ },
+ GenericFragment | IframeFragment(..) | TableFragment | TableCellFragment | TableRowFragment |
+ TableWrapperFragment => {
+ // FIXME(pcwalton): This is a bit of an abuse of the logging infrastructure. We
+ // should have a real `SERVO_DEBUG` system.
+ debug!("{:?}", self.build_debug_borders_around_fragment(display_list, flow_origin))
+ },
+ ImageFragment(_) => {
+ match self.specific {
+ ImageFragment(ref image_fragment) => {
+ let image_ref = &image_fragment.image;
+ match image_ref.get_image_if_present() {
+ Some(image) => {
+ debug!("(building display list) building image fragment");
+
+ // Place the image into the display list.
+ let image_display_item = box ImageDisplayItem {
+ base: BaseDisplayItem::new(absolute_content_box,
+ self.node,
+ ContentStackingLevel),
+ image: image.clone(),
+ stretch_size: absolute_content_box.size,
+ };
+ accumulator.push(display_list,
+ ImageDisplayItemClass(image_display_item))
+ }
+ None => {
+ // No image data at all? Do nothing.
+ //
+ // TODO: Add some kind of placeholder image.
+ debug!("(building display list) no image :(");
+ }
+ }
+ }
+ _ => fail!("shouldn't get here"),
+ }
+
+ // FIXME(pcwalton): This is a bit of an abuse of the logging
+ // infrastructure. We should have a real `SERVO_DEBUG` system.
+ debug!("{:?}", self.build_debug_borders_around_fragment(display_list, flow_origin))
+ }
+ }
+
+ // If this is an iframe, then send its position and size up to the constellation.
+ //
+ // FIXME(pcwalton): Doing this during display list construction seems potentially
+ // problematic if iframes are outside the area we're computing the display list for, since
+ // they won't be able to reflow at all until the user scrolls to them. Perhaps we should
+ // separate this into two parts: first we should send the size only to the constellation
+ // once that's computed during assign-block-sizes, and second we should should send the origin
+ // to the constellation here during display list construction. This should work because
+ // layout for the iframe only needs to know size, and origin is only relevant if the
+ // iframe is actually going to be displayed.
+ match self.specific {
+ IframeFragment(ref iframe_fragment) => {
+ self.finalize_position_and_size_of_iframe(iframe_fragment, flow_origin, layout_context)
+ }
+ _ => {}
+ }
+
+ accumulator
+ }
+
+ /// Returns the intrinsic inline-sizes of this fragment.
+ pub fn intrinsic_inline_sizes(&mut self)
+ -> IntrinsicISizes {
+ let mut result = self.style_specified_intrinsic_inline_size();
+
+ match self.specific {
+ GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment | TableColumnFragment(_) | TableRowFragment |
+ TableWrapperFragment => {}
+ ImageFragment(ref mut image_fragment_info) => {
+ let image_inline_size = image_fragment_info.image_inline_size();
+ result.minimum_inline_size = geometry::max(result.minimum_inline_size, image_inline_size);
+ result.preferred_inline_size = geometry::max(result.preferred_inline_size, image_inline_size);
+ }
+ ScannedTextFragment(ref text_fragment_info) => {
+ let range = &text_fragment_info.range;
+ let min_line_inline_size = text_fragment_info.run.min_width_for_range(range);
+
+ // See http://dev.w3.org/csswg/css-sizing/#max-content-inline-size.
+ // TODO: Account for soft wrap opportunities.
+ let max_line_inline_size = text_fragment_info.run.metrics_for_range(range).advance_width;
+
+ result.minimum_inline_size = geometry::max(result.minimum_inline_size, min_line_inline_size);
+ result.preferred_inline_size = geometry::max(result.preferred_inline_size, max_line_inline_size);
+ }
+ UnscannedTextFragment(..) => fail!("Unscanned text fragments should have been scanned by now!"),
+ }
+
+ // Take borders and padding for parent inline fragments into account, if necessary.
+ match self.inline_context {
+ None => {}
+ Some(ref context) => {
+ for style in context.styles.iter() {
+ let border_width = style.logical_border_width().inline_start_end();
+ let padding_inline_size = model::padding_from_style(&**style, Au(0)).inline_start_end();
+ result.minimum_inline_size = result.minimum_inline_size + border_width + padding_inline_size;
+ result.preferred_inline_size = result.preferred_inline_size + border_width + padding_inline_size;
+ }
+ }
+ }
+
+ result
+ }
+
+
+ /// TODO: What exactly does this function return? Why is it Au(0) for GenericFragment?
+ pub fn content_inline_size(&self) -> Au {
+ match self.specific {
+ GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment | TableRowFragment |
+ TableWrapperFragment => Au(0),
+ ImageFragment(ref image_fragment_info) => {
+ image_fragment_info.computed_inline_size()
+ }
+ ScannedTextFragment(ref text_fragment_info) => {
+ let (range, run) = (&text_fragment_info.range, &text_fragment_info.run);
+ let text_bounds = run.metrics_for_range(range).bounding_box;
+ text_bounds.size.width
+ }
+ TableColumnFragment(_) => fail!("Table column fragments do not have inline_size"),
+ UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"),
+ }
+ }
+
+ /// Returns, and computes, the block-size of this fragment.
+ pub fn content_block_size(&self, layout_context: &LayoutContext) -> Au {
+ match self.specific {
+ GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment | TableRowFragment |
+ TableWrapperFragment => Au(0),
+ ImageFragment(ref image_fragment_info) => {
+ image_fragment_info.computed_block_size()
+ }
+ ScannedTextFragment(_) => {
+ // Compute the block-size based on the line-block-size and font size.
+ self.calculate_line_height(layout_context)
+ }
+ TableColumnFragment(_) => fail!("Table column fragments do not have block_size"),
+ UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"),
+ }
+ }
+
+ /// Returns the dimensions of the content box.
+ ///
+ /// This is marked `#[inline]` because it is frequently called when only one or two of the
+ /// values are needed and that will save computation.
+ #[inline]
+ pub fn content_box(&self) -> LogicalRect<Au> {
+ self.border_box - self.border_padding
+ }
+
+ /// Find the split of a fragment that includes a new-line character.
+ ///
+ /// A return value of `None` indicates that the fragment is not splittable.
+ /// Otherwise the split information is returned. The right information is
+ /// optional due to the possibility of it being whitespace.
+ //
+ // TODO(bjz): The text run should be removed in the future, but it is currently needed for
+ // the current method of fragment splitting in the `inline::try_append_*` functions.
+ pub fn find_split_info_by_new_line(&self)
+ -> Option<(SplitInfo, Option<SplitInfo>, Arc<Box<TextRun>> /* TODO(bjz): remove */)> {
+ match self.specific {
+ GenericFragment | IframeFragment(_) | ImageFragment(_) | TableFragment | TableCellFragment |
+ TableRowFragment | TableWrapperFragment => None,
+ TableColumnFragment(_) => fail!("Table column fragments do not need to split"),
+ UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"),
+ ScannedTextFragment(ref text_fragment_info) => {
+ let mut new_line_pos = self.new_line_pos.clone();
+ let cur_new_line_pos = new_line_pos.remove(0).unwrap();
+
+ let inline_start_range = Range::new(text_fragment_info.range.begin(), cur_new_line_pos);
+ let inline_end_range = Range::new(text_fragment_info.range.begin() + cur_new_line_pos + CharIndex(1),
+ text_fragment_info.range.length() - (cur_new_line_pos + CharIndex(1)));
+
+ // Left fragment is for inline-start text of first founded new-line character.
+ let inline_start_fragment = SplitInfo::new(inline_start_range, text_fragment_info);
+
+ // Right fragment is for inline-end text of first founded new-line character.
+ let inline_end_fragment = if inline_end_range.length() > CharIndex(0) {
+ Some(SplitInfo::new(inline_end_range, text_fragment_info))
+ } else {
+ None
+ };
+
+ Some((inline_start_fragment, inline_end_fragment, text_fragment_info.run.clone()))
+ }
+ }
+ }
+
+ /// Attempts to find the split positions of a text fragment so that its inline-size is
+ /// no more than `max_inline-size`.
+ ///
+ /// A return value of `None` indicates that the fragment could not be split.
+ /// Otherwise the information pertaining to the split is returned. The inline-start
+ /// and inline-end split information are both optional due to the possibility of
+ /// them being whitespace.
+ //
+ // TODO(bjz): The text run should be removed in the future, but it is currently needed for
+ // the current method of fragment splitting in the `inline::try_append_*` functions.
+ pub fn find_split_info_for_inline_size(&self, start: CharIndex, max_inline_size: Au, starts_line: bool)
+ -> Option<(Option<SplitInfo>, Option<SplitInfo>, Arc<Box<TextRun>> /* TODO(bjz): remove */)> {
+ match self.specific {
+ GenericFragment | IframeFragment(_) | ImageFragment(_) | TableFragment | TableCellFragment |
+ TableRowFragment | TableWrapperFragment => None,
+ TableColumnFragment(_) => fail!("Table column fragments do not have inline_size"),
+ UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"),
+ ScannedTextFragment(ref text_fragment_info) => {
+ let mut pieces_processed_count: uint = 0;
+ let mut remaining_inline_size: Au = max_inline_size;
+ let mut inline_start_range = Range::new(text_fragment_info.range.begin() + start, CharIndex(0));
+ let mut inline_end_range: Option<Range<CharIndex>> = None;
+
+ debug!("split_to_inline_size: splitting text fragment (strlen={}, range={}, avail_inline_size={})",
+ text_fragment_info.run.text.len(),
+ text_fragment_info.range,
+ max_inline_size);
+
+ for (glyphs, offset, slice_range) in text_fragment_info.run.iter_slices_for_range(
+ &text_fragment_info.range) {
+ debug!("split_to_inline_size: considering slice (offset={}, range={}, \
+ remain_inline_size={})",
+ offset,
+ slice_range,
+ remaining_inline_size);
+
+ let metrics = text_fragment_info.run.metrics_for_slice(glyphs, &slice_range);
+ let advance = metrics.advance_width;
+
+ let should_continue;
+ if advance <= remaining_inline_size {
+ should_continue = true;
+
+ if starts_line && pieces_processed_count == 0 && glyphs.is_whitespace() {
+ debug!("split_to_inline_size: case=skipping leading trimmable whitespace");
+ inline_start_range.shift_by(slice_range.length());
+ } else {
+ debug!("split_to_inline_size: case=enlarging span");
+ remaining_inline_size = remaining_inline_size - advance;
+ inline_start_range.extend_by(slice_range.length());
+ }
+ } else {
+ // The advance is more than the remaining inline-size.
+ should_continue = false;
+ let slice_begin = offset + slice_range.begin();
+ let slice_end = offset + slice_range.end();
+
+ if glyphs.is_whitespace() {
+ // If there are still things after the trimmable whitespace, create the
+ // inline-end chunk.
+ if slice_end < text_fragment_info.range.end() {
+ debug!("split_to_inline_size: case=skipping trimmable trailing \
+ whitespace, then split remainder");
+ let inline_end_range_end = text_fragment_info.range.end() - slice_end;
+ inline_end_range = Some(Range::new(slice_end, inline_end_range_end));
+ } else {
+ debug!("split_to_inline_size: case=skipping trimmable trailing \
+ whitespace");
+ }
+ } else if slice_begin < text_fragment_info.range.end() {
+ // There are still some things inline-start over at the end of the line. Create
+ // the inline-end chunk.
+ let inline_end_range_end = text_fragment_info.range.end() - slice_begin;
+ inline_end_range = Some(Range::new(slice_begin, inline_end_range_end));
+ debug!("split_to_inline_size: case=splitting remainder with inline_end range={:?}",
+ inline_end_range);
+ }
+ }
+
+ pieces_processed_count += 1;
+
+ if !should_continue {
+ break
+ }
+ }
+
+ let inline_start_is_some = inline_start_range.length() > CharIndex(0);
+
+ if (pieces_processed_count == 1 || !inline_start_is_some) && !starts_line {
+ None
+ } else {
+ let inline_start = if inline_start_is_some {
+ Some(SplitInfo::new(inline_start_range, text_fragment_info))
+ } else {
+ None
+ };
+ let inline_end = inline_end_range.map(|inline_end_range| SplitInfo::new(inline_end_range, text_fragment_info));
+
+ Some((inline_start, inline_end, text_fragment_info.run.clone()))
+ }
+ }
+ }
+ }
+
+ /// Returns true if this fragment is an unscanned text fragment that consists entirely of whitespace.
+ pub fn is_whitespace_only(&self) -> bool {
+ match self.specific {
+ UnscannedTextFragment(ref text_fragment_info) => is_whitespace(text_fragment_info.text.as_slice()),
+ _ => false,
+ }
+ }
+
+ /// Assigns replaced inline-size, padding, and margins for this fragment only if it is replaced
+ /// content per CSS 2.1 § 10.3.2.
+ pub fn assign_replaced_inline_size_if_necessary(&mut self,
+ container_inline_size: Au) {
+ match self.specific {
+ GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment | TableRowFragment |
+ TableWrapperFragment => return,
+ TableColumnFragment(_) => fail!("Table column fragments do not have inline_size"),
+ UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"),
+ ImageFragment(_) | ScannedTextFragment(_) => {}
+ };
+
+ self.compute_border_padding_margins(container_inline_size);
+
+ let style_inline_size = self.style().content_inline_size();
+ let style_block_size = self.style().content_block_size();
+ let noncontent_inline_size = self.border_padding.inline_start_end();
+
+ match self.specific {
+ ScannedTextFragment(_) => {
+ // Scanned text fragments will have already had their content inline-sizes assigned by this
+ // point.
+ self.border_box.size.inline = self.border_box.size.inline + noncontent_inline_size
+ }
+ ImageFragment(ref mut image_fragment_info) => {
+ // TODO(ksh8281): compute border,margin
+ let inline_size = ImageFragmentInfo::style_length(style_inline_size,
+ image_fragment_info.dom_inline_size,
+ container_inline_size);
+ let block_size = ImageFragmentInfo::style_length(style_block_size,
+ image_fragment_info.dom_block_size,
+ Au(0));
+
+ let inline_size = match (inline_size,block_size) {
+ (Auto, Auto) => image_fragment_info.image_inline_size(),
+ (Auto,Specified(h)) => {
+ let scale = image_fragment_info.
+ image_block_size().to_f32().unwrap() / h.to_f32().unwrap();
+ Au::new((image_fragment_info.image_inline_size().to_f32().unwrap() / scale) as i32)
+ },
+ (Specified(w), _) => w,
+ };
+
+ self.border_box.size.inline = inline_size + noncontent_inline_size;
+ image_fragment_info.computed_inline_size = Some(inline_size);
+ }
+ _ => fail!("this case should have been handled above"),
+ }
+ }
+
+ /// Assign block-size for this fragment if it is replaced content. The inline-size must have been assigned
+ /// first.
+ ///
+ /// Ideally, this should follow CSS 2.1 § 10.6.2.
+ pub fn assign_replaced_block_size_if_necessary(&mut self) {
+ match self.specific {
+ GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment | TableRowFragment |
+ TableWrapperFragment => return,
+ TableColumnFragment(_) => fail!("Table column fragments do not have block_size"),
+ UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"),
+ ImageFragment(_) | ScannedTextFragment(_) => {}
+ }
+
+ let style_inline_size = self.style().content_inline_size();
+ let style_block_size = self.style().content_block_size();
+ let noncontent_block_size = self.border_padding.block_start_end();
+
+ match self.specific {
+ ImageFragment(ref mut image_fragment_info) => {
+ // TODO(ksh8281): compute border,margin,padding
+ let inline_size = image_fragment_info.computed_inline_size();
+ // FIXME(ksh8281): we shouldn't assign block-size this way
+ // we don't know about size of parent's block-size
+ let block_size = ImageFragmentInfo::style_length(style_block_size,
+ image_fragment_info.dom_block_size,
+ Au(0));
+
+ let block_size = match (style_inline_size, image_fragment_info.dom_inline_size, block_size) {
+ (LPA_Auto, None, Auto) => {
+ image_fragment_info.image_block_size()
+ },
+ (_,_,Auto) => {
+ let scale = image_fragment_info.image_inline_size().to_f32().unwrap()
+ / inline_size.to_f32().unwrap();
+ Au::new((image_fragment_info.image_block_size().to_f32().unwrap() / scale) as i32)
+ },
+ (_,_,Specified(h)) => {
+ h
+ }
+ };
+
+ image_fragment_info.computed_block_size = Some(block_size);
+ self.border_box.size.block = block_size + noncontent_block_size
+ }
+ ScannedTextFragment(_) => {
+ // Scanned text fragments' content block-sizes are calculated by the text run scanner
+ // during flow construction.
+ self.border_box.size.block = self.border_box.size.block + noncontent_block_size
+ }
+ _ => fail!("should have been handled above"),
+ }
+ }
+
+ /// Calculates block-size above baseline, depth below baseline, and ascent for this fragment when
+ /// used in an inline formatting context. See CSS 2.1 § 10.8.1.
+ pub fn inline_metrics(&self, layout_context: &LayoutContext) -> InlineMetrics {
+ match self.specific {
+ ImageFragment(ref image_fragment_info) => {
+ let computed_block_size = image_fragment_info.computed_block_size();
+ InlineMetrics {
+ block_size_above_baseline: computed_block_size + self.border_padding.block_start_end(),
+ depth_below_baseline: Au(0),
+ ascent: computed_block_size + self.border_padding.block_end,
+ }
+ }
+ ScannedTextFragment(ref text_fragment) => {
+ // See CSS 2.1 § 10.8.1.
+ let line_height = self.calculate_line_height(layout_context);
+ InlineMetrics::from_font_metrics(&text_fragment.run.font_metrics, line_height)
+ }
+ _ => {
+ InlineMetrics {
+ block_size_above_baseline: self.border_box.size.block,
+ depth_below_baseline: Au(0),
+ ascent: self.border_box.size.block,
+ }
+ }
+ }
+ }
+
+ /// Returns true if this fragment can merge with another adjacent fragment or false otherwise.
+ pub fn can_merge_with_fragment(&self, other: &Fragment) -> bool {
+ match (&self.specific, &other.specific) {
+ (&UnscannedTextFragment(_), &UnscannedTextFragment(_)) => {
+ // FIXME: Should probably use a whitelist of styles that can safely differ (#3165)
+ self.font_style() == other.font_style() &&
+ self.text_decoration() == other.text_decoration() &&
+ self.white_space() == other.white_space()
+ }
+ _ => false,
+ }
+ }
+
+ /// Sends the size and position of this iframe fragment to the constellation. This is out of
+ /// line to guide inlining.
+ #[inline(never)]
+ fn finalize_position_and_size_of_iframe(&self,
+ iframe_fragment: &IframeFragmentInfo,
+ offset: Point2D<Au>,
+ layout_context: &LayoutContext) {
+ let mbp = (self.margin + self.border_padding).to_physical(self.style.writing_mode);
+ let content_size = self.content_box().size.to_physical(self.style.writing_mode);
+
+ let left = offset.x + mbp.left;
+ let top = offset.y + mbp.top;
+ let width = content_size.width;
+ let height = content_size.height;
+ let origin = Point2D(geometry::to_frac_px(left) as f32, geometry::to_frac_px(top) as f32);
+ let size = Size2D(geometry::to_frac_px(width) as f32, geometry::to_frac_px(height) as f32);
+ let rect = Rect(origin, size);
+
+ debug!("finalizing position and size of iframe for {:?},{:?}",
+ iframe_fragment.pipeline_id,
+ iframe_fragment.subpage_id);
+ let msg = FrameRectMsg(iframe_fragment.pipeline_id, iframe_fragment.subpage_id, rect);
+ let ConstellationChan(ref chan) = layout_context.shared.constellation_chan;
+ chan.send(msg)
+ }
+}
+
+impl fmt::Show for Fragment {
+ /// Outputs a debugging string describing this fragment.
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ try!(write!(f, "({} ",
+ match self.specific {
+ GenericFragment => "GenericFragment",
+ IframeFragment(_) => "IframeFragment",
+ ImageFragment(_) => "ImageFragment",
+ ScannedTextFragment(_) => "ScannedTextFragment",
+ TableFragment => "TableFragment",
+ TableCellFragment => "TableCellFragment",
+ TableColumnFragment(_) => "TableColumnFragment",
+ TableRowFragment => "TableRowFragment",
+ TableWrapperFragment => "TableWrapperFragment",
+ UnscannedTextFragment(_) => "UnscannedTextFragment",
+ }));
+ try!(write!(f, "bp {}", self.border_padding));
+ try!(write!(f, " "));
+ try!(write!(f, "m {}", self.margin));
+ write!(f, ")")
+ }
+}
+
+/// An object that accumulates display lists of child flows, applying a clipping rect if necessary.
+pub struct ChildDisplayListAccumulator {
+ clip_display_item: Option<Box<ClipDisplayItem>>,
+}
+
+impl ChildDisplayListAccumulator {
+ /// Creates a `ChildDisplayListAccumulator` from the `overflow` property in the given style.
+ fn new(style: &ComputedValues, bounds: Rect<Au>, node: OpaqueNode, level: StackingLevel)
+ -> ChildDisplayListAccumulator {
+ ChildDisplayListAccumulator {
+ clip_display_item: match style.get_box().overflow {
+ overflow::hidden | overflow::auto | overflow::scroll => {
+ Some(box ClipDisplayItem {
+ base: BaseDisplayItem::new(bounds, node, level),
+ children: DisplayList::new(),
+ })
+ },
+ overflow::visible => None,
+ }
+ }
+ }
+
+ /// Pushes the given display item onto this display list.
+ pub fn push(&mut self, parent_display_list: &mut DisplayList, item: DisplayItem) {
+ match self.clip_display_item {
+ None => parent_display_list.push(item),
+ Some(ref mut clip_display_item) => clip_display_item.children.push(item),
+ }
+ }
+
+ /// Pushes the display items from the given child onto this display list.
+ pub fn push_child(&mut self, parent_display_list: &mut DisplayList, child: &mut Flow) {
+ let kid_display_list = mem::replace(&mut flow::mut_base(child).display_list,
+ DisplayList::new());
+ match self.clip_display_item {
+ None => parent_display_list.push_all_move(kid_display_list),
+ Some(ref mut clip_display_item) => {
+ clip_display_item.children.push_all_move(kid_display_list)
+ }
+ }
+ }
+
+ /// Consumes this accumulator and pushes the clipping item, if any, onto the display list
+ /// associated with the given flow, along with the items in the given display list.
+ pub fn finish(self, parent: &mut Flow, mut display_list: DisplayList) {
+ let ChildDisplayListAccumulator {
+ clip_display_item
+ } = self;
+ match clip_display_item {
+ None => {}
+ Some(clip_display_item) => display_list.push(ClipDisplayItemClass(clip_display_item)),
+ }
+ flow::mut_base(parent).display_list = display_list
+ }
+}
diff --git a/components/layout/incremental.rs b/components/layout/incremental.rs
new file mode 100644
index 00000000000..d04c068b6aa
--- /dev/null
+++ b/components/layout/incremental.rs
@@ -0,0 +1,78 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use style::ComputedValues;
+
+bitflags! {
+ #[doc = "Individual layout actions that may be necessary after restyling."]
+ flags RestyleDamage: int {
+ #[doc = "Repaint the node itself."]
+ #[doc = "Currently unused; need to decide how this propagates."]
+ static Repaint = 0x01,
+
+ #[doc = "Recompute intrinsic inline_sizes (minimum and preferred)."]
+ #[doc = "Propagates down the flow tree because the computation is"]
+ #[doc = "bottom-up."]
+ static BubbleISizes = 0x02,
+
+ #[doc = "Recompute actual inline_sizes and block_sizes."]
+ #[doc = "Propagates up the flow tree because the computation is"]
+ #[doc = "top-down."]
+ static Reflow = 0x04
+ }
+}
+
+impl RestyleDamage {
+ /// Elements of self which should also get set on any ancestor flow.
+ pub fn propagate_up(self) -> RestyleDamage {
+ self & Reflow
+ }
+
+ /// Elements of self which should also get set on any child flows.
+ pub fn propagate_down(self) -> RestyleDamage {
+ self & BubbleISizes
+ }
+}
+
+// NB: We need the braces inside the RHS due to Rust #8012. This particular
+// version of this macro might be safe anyway, but we want to avoid silent
+// breakage on modifications.
+macro_rules! add_if_not_equal(
+ ($old:ident, $new:ident, $damage:ident,
+ [ $($effect:ident),* ], [ $($style_struct_getter:ident.$name:ident),* ]) => ({
+ if $( ($old.$style_struct_getter().$name != $new.$style_struct_getter().$name) )||* {
+ $damage.insert($($effect)|*);
+ }
+ })
+)
+
+pub fn compute_damage(old: &ComputedValues, new: &ComputedValues) -> RestyleDamage {
+ let mut damage = RestyleDamage::empty();
+
+ // This checks every CSS property, as enumerated in
+ // impl<'self> CssComputedStyle<'self>
+ // in src/support/netsurfcss/rust-netsurfcss/netsurfcss.rc.
+
+ // FIXME: We can short-circuit more of this.
+
+ add_if_not_equal!(old, new, damage, [ Repaint ],
+ [ get_color.color, get_background.background_color,
+ get_border.border_top_color, get_border.border_right_color,
+ get_border.border_bottom_color, get_border.border_left_color ]);
+
+ add_if_not_equal!(old, new, damage, [ Repaint, BubbleISizes, Reflow ],
+ [ get_border.border_top_width, get_border.border_right_width,
+ get_border.border_bottom_width, get_border.border_left_width,
+ get_margin.margin_top, get_margin.margin_right,
+ get_margin.margin_bottom, get_margin.margin_left,
+ get_padding.padding_top, get_padding.padding_right,
+ get_padding.padding_bottom, get_padding.padding_left,
+ get_box.position, get_box.width, get_box.height, get_box.float, get_box.display,
+ get_font.font_family, get_font.font_size, get_font.font_style, get_font.font_weight,
+ get_inheritedtext.text_align, get_text.text_decoration, get_inheritedbox.line_height ]);
+
+ // FIXME: test somehow that we checked every CSS property
+
+ damage
+}
diff --git a/components/layout/inline.rs b/components/layout/inline.rs
new file mode 100644
index 00000000000..6c22b25746e
--- /dev/null
+++ b/components/layout/inline.rs
@@ -0,0 +1,1170 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![deny(unsafe_block)]
+
+use css::node_style::StyledNode;
+use context::LayoutContext;
+use floats::{FloatLeft, Floats, PlacementInfo};
+use flow::{BaseFlow, FlowClass, Flow, InlineFlowClass};
+use flow;
+use fragment::{Fragment, ScannedTextFragment, ScannedTextFragmentInfo, SplitInfo};
+use layout_debug;
+use model::IntrinsicISizes;
+use text;
+use wrapper::ThreadSafeLayoutNode;
+
+use collections::{Deque, RingBuf};
+use geom::Rect;
+use gfx::display_list::ContentLevel;
+use gfx::font::FontMetrics;
+use gfx::font_context::FontContext;
+use gfx::text::glyph::CharIndex;
+use servo_util::geometry::Au;
+use servo_util::geometry;
+use servo_util::logical_geometry::{LogicalRect, LogicalSize};
+use servo_util::range;
+use servo_util::range::{EachIndex, Range, RangeIndex, IntRangeIndex};
+use std::fmt;
+use std::mem;
+use std::num;
+use std::u16;
+use style::computed_values::{text_align, vertical_align, white_space};
+use style::ComputedValues;
+use sync::Arc;
+
+/// `Line`s are represented as offsets into the child list, rather than
+/// as an object that "owns" fragments. Choosing a different set of line
+/// breaks requires a new list of offsets, and possibly some splitting and
+/// merging of TextFragments.
+///
+/// A similar list will keep track of the mapping between CSS fragments and
+/// the corresponding fragments in the inline flow.
+///
+/// After line breaks are determined, render fragments in the inline flow may
+/// overlap visually. For example, in the case of nested inline CSS fragments,
+/// outer inlines must be at least as large as the inner inlines, for
+/// purposes of drawing noninherited things like backgrounds, borders,
+/// outlines.
+///
+/// N.B. roc has an alternative design where the list instead consists of
+/// things like "start outer fragment, text, start inner fragment, text, end inner
+/// fragment, text, end outer fragment, text". This seems a little complicated to
+/// serve as the starting point, but the current design doesn't make it
+/// hard to try out that alternative.
+///
+/// Line fragments also contain some metadata used during line breaking. The
+/// green zone is the area that the line can expand to before it collides
+/// with a float or a horizontal wall of the containing block. The block-start
+/// inline-start corner of the green zone is the same as that of the line, but
+/// the green zone can be taller and wider than the line itself.
+#[deriving(Encodable)]
+pub struct Line {
+ /// A range of line indices that describe line breaks.
+ ///
+ /// For example, consider the following HTML and rendered element with
+ /// linebreaks:
+ ///
+ /// ~~~html
+ /// <span>I <span>like truffles, <img></span> yes I do.</span>
+ /// ~~~
+ ///
+ /// ~~~text
+ /// +------------+
+ /// | I like |
+ /// | truffles, |
+ /// | +----+ |
+ /// | | | |
+ /// | +----+ yes |
+ /// | I do. |
+ /// +------------+
+ /// ~~~
+ ///
+ /// The ranges that describe these lines would be:
+ ///
+ /// | [0.0, 1.4) | [1.5, 2.0) | [2.0, 3.4) | [3.4, 4.0) |
+ /// |------------|-------------|-------------|------------|
+ /// | 'I like' | 'truffles,' | '<img> yes' | 'I do.' |
+ pub range: Range<LineIndices>,
+ /// The bounds are the exact position and extents of the line with respect
+ /// to the parent box.
+ ///
+ /// For example, for the HTML below...
+ ///
+ /// ~~~html
+ /// <div><span>I <span>like truffles, <img></span></div>
+ /// ~~~
+ ///
+ /// ...the bounds would be:
+ ///
+ /// ~~~text
+ /// +-----------------------------------------------------------+
+ /// | ^ |
+ /// | | |
+ /// | origin.y |
+ /// | | |
+ /// | v |
+ /// |< - origin.x ->+ - - - - - - - - +---------+---- |
+ /// | | | | ^ |
+ /// | | | <img> | size.block-size |
+ /// | I like truffles, | | v |
+ /// | + - - - - - - - - +---------+---- |
+ /// | | | |
+ /// | |<------ size.inline-size ------->| |
+ /// | |
+ /// | |
+ /// +-----------------------------------------------------------+
+ /// ~~~
+ pub bounds: LogicalRect<Au>,
+ /// The green zone is the greatest extent from wich a line can extend to
+ /// before it collides with a float.
+ ///
+ /// ~~~text
+ /// +-----------------------+
+ /// |::::::::::::::::: |
+ /// |:::::::::::::::::FFFFFF|
+ /// |============:::::FFFFFF|
+ /// |:::::::::::::::::FFFFFF|
+ /// |:::::::::::::::::FFFFFF|
+ /// |::::::::::::::::: |
+ /// | FFFFFFFFF |
+ /// | FFFFFFFFF |
+ /// | FFFFFFFFF |
+ /// | |
+ /// +-----------------------+
+ ///
+ /// === line
+ /// ::: green zone
+ /// FFF float
+ /// ~~~
+ pub green_zone: LogicalSize<Au>
+}
+
+int_range_index! {
+ #[deriving(Encodable)]
+ #[doc = "The index of a fragment in a flattened vector of DOM elements."]
+ struct FragmentIndex(int)
+}
+
+/// A line index consists of two indices: a fragment index that refers to the
+/// index of a DOM fragment within a flattened inline element; and a glyph index
+/// where the 0th glyph refers to the first glyph of that fragment.
+#[deriving(Clone, Encodable, PartialEq, PartialOrd, Eq, Ord, Zero)]
+pub struct LineIndices {
+ /// The index of a fragment into the flattened vector of DOM elements.
+ ///
+ /// For example, given the HTML below:
+ ///
+ /// ~~~html
+ /// <span>I <span>like truffles, <img></span> yes I do.</span>
+ /// ~~~
+ ///
+ /// The fragments would be indexed as follows:
+ ///
+ /// | 0 | 1 | 2 | 3 |
+ /// |------|------------------|---------|--------------|
+ /// | 'I ' | 'like truffles,' | `<img>` | ' yes I do.' |
+ pub fragment_index: FragmentIndex,
+ /// The index of a character in a DOM fragment. Continuous runs of whitespace
+ /// are treated as single characters. Non-breakable DOM fragments such as
+ /// images are treated as having a range length of `1`.
+ ///
+ /// For example, given the HTML below:
+ ///
+ /// ~~~html
+ /// <span>I <span>like truffles, <img></span> yes I do.</span>
+ /// ~~~
+ ///
+ /// The characters would be indexed as follows:
+ ///
+ /// | 0 | 1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
+ /// |---|---|---|---|---|---|---|---|---|---|---|---|----|----|----|----|----|
+ /// | I | | l | i | k | e | | t | r | u | f | f | l | e | s | , | |
+ ///
+ /// | 0 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
+ /// |---------|---|---|---|---|---|---|---|---|---|---|
+ /// | `<img>` | | y | e | s | | I | | d | o | . |
+ pub char_index: CharIndex,
+}
+
+impl RangeIndex for LineIndices {}
+
+impl Add<LineIndices, LineIndices> for LineIndices {
+ fn add(&self, other: &LineIndices) -> LineIndices {
+ // TODO: use debug_assert! after rustc upgrade
+ if cfg!(not(ndebug)) {
+ assert!(other.fragment_index == num::zero() || other.char_index == num::zero(),
+ "Attempted to add {} to {}. Both the fragment_index and \
+ char_index of the RHS are non-zero. This probably was a \
+ mistake!", self, other);
+ }
+ LineIndices {
+ fragment_index: self.fragment_index + other.fragment_index,
+ char_index: self.char_index + other.char_index,
+ }
+ }
+}
+
+impl Sub<LineIndices, LineIndices> for LineIndices {
+ fn sub(&self, other: &LineIndices) -> LineIndices {
+ // TODO: use debug_assert! after rustc upgrade
+ if cfg!(not(ndebug)) {
+ assert!(other.fragment_index == num::zero() || other.char_index == num::zero(),
+ "Attempted to subtract {} from {}. Both the fragment_index \
+ and char_index of the RHS are non-zero. This probably was \
+ a mistake!", self, other);
+ }
+ LineIndices {
+ fragment_index: self.fragment_index - other.fragment_index,
+ char_index: self.char_index - other.char_index,
+ }
+ }
+}
+
+impl Neg<LineIndices> for LineIndices {
+ fn neg(&self) -> LineIndices {
+ // TODO: use debug_assert! after rustc upgrade
+ if cfg!(not(ndebug)) {
+ assert!(self.fragment_index == num::zero() || self.char_index == num::zero(),
+ "Attempted to negate {}. Both the fragment_index and \
+ char_index are non-zero. This probably was a mistake!",
+ self);
+ }
+ LineIndices {
+ fragment_index: -self.fragment_index,
+ char_index: -self.char_index,
+ }
+ }
+}
+
+impl fmt::Show for LineIndices {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}.{}", self.fragment_index, self.char_index)
+ }
+}
+
+pub fn each_fragment_index(range: &Range<LineIndices>) -> EachIndex<int, FragmentIndex> {
+ range::each_index(range.begin().fragment_index, range.end().fragment_index)
+}
+
+pub fn each_char_index(range: &Range<LineIndices>) -> EachIndex<int, CharIndex> {
+ range::each_index(range.begin().char_index, range.end().char_index)
+}
+
+struct LineBreaker {
+ pub floats: Floats,
+ pub new_fragments: Vec<Fragment>,
+ pub work_list: RingBuf<Fragment>,
+ pub pending_line: Line,
+ pub lines: Vec<Line>,
+ pub cur_b: Au, // Current position on the block direction
+}
+
+impl LineBreaker {
+ pub fn new(float_ctx: Floats) -> LineBreaker {
+ LineBreaker {
+ new_fragments: Vec::new(),
+ work_list: RingBuf::new(),
+ pending_line: Line {
+ range: Range::empty(),
+ bounds: LogicalRect::zero(float_ctx.writing_mode),
+ green_zone: LogicalSize::zero(float_ctx.writing_mode)
+ },
+ floats: float_ctx,
+ lines: Vec::new(),
+ cur_b: Au::new(0)
+ }
+ }
+
+ pub fn floats(&mut self) -> Floats {
+ self.floats.clone()
+ }
+
+ fn reset_scanner(&mut self) {
+ debug!("Resetting LineBreaker's state for flow.");
+ self.lines = Vec::new();
+ self.new_fragments = Vec::new();
+ self.cur_b = Au(0);
+ self.reset_line();
+ }
+
+ fn reset_line(&mut self) {
+ self.pending_line.range.reset(num::zero(), num::zero());
+ self.pending_line.bounds = LogicalRect::new(
+ self.floats.writing_mode, Au::new(0), self.cur_b, Au::new(0), Au::new(0));
+ self.pending_line.green_zone = LogicalSize::zero(self.floats.writing_mode)
+ }
+
+ pub fn scan_for_lines(&mut self, flow: &mut InlineFlow, layout_context: &LayoutContext) {
+ self.reset_scanner();
+
+ let mut old_fragments = mem::replace(&mut flow.fragments, InlineFragments::new());
+
+ { // Enter a new scope so that old_fragment_iter's borrow is released
+ let mut old_fragment_iter = old_fragments.fragments.iter();
+ loop {
+ // acquire the next fragment to lay out from work list or fragment list
+ let cur_fragment = if self.work_list.is_empty() {
+ match old_fragment_iter.next() {
+ None => break,
+ Some(fragment) => {
+ debug!("LineBreaker: Working with fragment from flow: b{}",
+ fragment.debug_id());
+ (*fragment).clone()
+ }
+ }
+ } else {
+ let fragment = self.work_list.pop_front().unwrap();
+ debug!("LineBreaker: Working with fragment from work list: b{}",
+ fragment.debug_id());
+ fragment
+ };
+
+ let fragment_was_appended = match cur_fragment.white_space() {
+ white_space::normal => self.try_append_to_line(cur_fragment, flow, layout_context),
+ white_space::pre => self.try_append_to_line_by_new_line(cur_fragment),
+ };
+
+ if !fragment_was_appended {
+ debug!("LineBreaker: Fragment wasn't appended, because line {:u} was full.",
+ self.lines.len());
+ self.flush_current_line();
+ } else {
+ debug!("LineBreaker: appended a fragment to line {:u}", self.lines.len());
+ }
+ }
+
+ if self.pending_line.range.length() > num::zero() {
+ debug!("LineBreaker: Partially full line {:u} inline_start at end of scanning.",
+ self.lines.len());
+ self.flush_current_line();
+ }
+ }
+
+ old_fragments.fragments = mem::replace(&mut self.new_fragments, vec![]);
+ flow.fragments = old_fragments;
+ flow.lines = mem::replace(&mut self.lines, Vec::new());
+ }
+
+ fn flush_current_line(&mut self) {
+ debug!("LineBreaker: Flushing line {:u}: {:?}",
+ self.lines.len(), self.pending_line);
+
+ // clear line and add line mapping
+ debug!("LineBreaker: Saving information for flushed line {:u}.", self.lines.len());
+ self.lines.push(self.pending_line);
+ self.cur_b = self.pending_line.bounds.start.b + self.pending_line.bounds.size.block;
+ self.reset_line();
+ }
+
+ // FIXME(eatkinson): this assumes that the tallest fragment in the line determines the line block-size
+ // This might not be the case with some weird text fonts.
+ fn new_block_size_for_line(&self, new_fragment: &Fragment, layout_context: &LayoutContext) -> Au {
+ let fragment_block_size = new_fragment.content_block_size(layout_context);
+ if fragment_block_size > self.pending_line.bounds.size.block {
+ fragment_block_size
+ } else {
+ self.pending_line.bounds.size.block
+ }
+ }
+
+ /// Computes the position of a line that has only the provided fragment. Returns the bounding
+ /// rect of the line's green zone (whose origin coincides with the line's origin) and the actual
+ /// inline-size of the first fragment after splitting.
+ fn initial_line_placement(&self, first_fragment: &Fragment, ceiling: Au, flow: &InlineFlow)
+ -> (LogicalRect<Au>, Au) {
+ debug!("LineBreaker: Trying to place first fragment of line {}", self.lines.len());
+
+ let first_fragment_size = first_fragment.border_box.size;
+ let splittable = first_fragment.can_split();
+ debug!("LineBreaker: fragment size: {}, splittable: {}", first_fragment_size, splittable);
+
+ // Initally, pretend a splittable fragment has 0 inline-size.
+ // We will move it later if it has nonzero inline-size
+ // and that causes problems.
+ let placement_inline_size = if splittable {
+ Au::new(0)
+ } else {
+ first_fragment_size.inline
+ };
+
+ let info = PlacementInfo {
+ size: LogicalSize::new(
+ self.floats.writing_mode, placement_inline_size, first_fragment_size.block),
+ ceiling: ceiling,
+ max_inline_size: flow.base.position.size.inline,
+ kind: FloatLeft,
+ };
+
+ let line_bounds = self.floats.place_between_floats(&info);
+
+ debug!("LineBreaker: found position for line: {} using placement_info: {:?}",
+ line_bounds,
+ info);
+
+ // Simple case: if the fragment fits, then we can stop here
+ if line_bounds.size.inline > first_fragment_size.inline {
+ debug!("LineBreaker: case=fragment fits");
+ return (line_bounds, first_fragment_size.inline);
+ }
+
+ // If not, but we can't split the fragment, then we'll place
+ // the line here and it will overflow.
+ if !splittable {
+ debug!("LineBreaker: case=line doesn't fit, but is unsplittable");
+ return (line_bounds, first_fragment_size.inline);
+ }
+
+ debug!("LineBreaker: used to call split_to_inline_size here");
+ return (line_bounds, first_fragment_size.inline);
+ }
+
+ /// Performs float collision avoidance. This is called when adding a fragment is going to increase
+ /// the block-size, and because of that we will collide with some floats.
+ ///
+ /// We have two options here:
+ /// 1) Move the entire line so that it doesn't collide any more.
+ /// 2) Break the line and put the new fragment on the next line.
+ ///
+ /// The problem with option 1 is that we might move the line and then wind up breaking anyway,
+ /// which violates the standard.
+ /// But option 2 is going to look weird sometimes.
+ ///
+ /// So we'll try to move the line whenever we can, but break if we have to.
+ ///
+ /// Returns false if and only if we should break the line.
+ fn avoid_floats(&mut self,
+ in_fragment: Fragment,
+ flow: &InlineFlow,
+ new_block_size: Au,
+ line_is_empty: bool)
+ -> bool {
+ debug!("LineBreaker: entering float collision avoider!");
+
+ // First predict where the next line is going to be.
+ let this_line_y = self.pending_line.bounds.start.b;
+ let (next_line, first_fragment_inline_size) = self.initial_line_placement(&in_fragment, this_line_y, flow);
+ let next_green_zone = next_line.size;
+
+ let new_inline_size = self.pending_line.bounds.size.inline + first_fragment_inline_size;
+
+ // Now, see if everything can fit at the new location.
+ if next_green_zone.inline >= new_inline_size && next_green_zone.block >= new_block_size {
+ debug!("LineBreaker: case=adding fragment collides vertically with floats: moving line");
+
+ self.pending_line.bounds.start = next_line.start;
+ self.pending_line.green_zone = next_green_zone;
+
+ assert!(!line_is_empty, "Non-terminating line breaking");
+ self.work_list.push_front(in_fragment);
+ return true
+ }
+
+ debug!("LineBreaker: case=adding fragment collides vertically with floats: breaking line");
+ self.work_list.push_front(in_fragment);
+ false
+ }
+
+ fn try_append_to_line_by_new_line(&mut self, in_fragment: Fragment) -> bool {
+ if in_fragment.new_line_pos.len() == 0 {
+ debug!("LineBreaker: Did not find a new-line character, so pushing the fragment to \
+ the line without splitting.");
+ self.push_fragment_to_line(in_fragment);
+ true
+ } else {
+ debug!("LineBreaker: Found a new-line character, so splitting theline.");
+
+ let (inline_start, inline_end, run) = in_fragment.find_split_info_by_new_line()
+ .expect("LineBreaker: This split case makes no sense!");
+ let writing_mode = self.floats.writing_mode;
+
+ // TODO(bjz): Remove fragment splitting
+ let split_fragment = |split: SplitInfo| {
+ let info = ScannedTextFragmentInfo::new(run.clone(), split.range);
+ let specific = ScannedTextFragment(info);
+ let size = LogicalSize::new(
+ writing_mode, split.inline_size, in_fragment.border_box.size.block);
+ in_fragment.transform(size, specific)
+ };
+
+ debug!("LineBreaker: Pushing the fragment to the inline_start of the new-line character \
+ to the line.");
+ let mut inline_start = split_fragment(inline_start);
+ inline_start.new_line_pos = vec![];
+ self.push_fragment_to_line(inline_start);
+
+ for inline_end in inline_end.move_iter() {
+ debug!("LineBreaker: Deferring the fragment to the inline_end of the new-line \
+ character to the line.");
+ let mut inline_end = split_fragment(inline_end);
+ inline_end.new_line_pos = in_fragment.new_line_pos.clone();
+ self.work_list.push_front(inline_end);
+ }
+ false
+ }
+ }
+
+ /// Tries to append the given fragment to the line, splitting it if necessary. Returns false only if
+ /// we should break the line.
+ fn try_append_to_line(&mut self, in_fragment: Fragment, flow: &InlineFlow, layout_context: &LayoutContext) -> bool {
+ let line_is_empty = self.pending_line.range.length() == num::zero();
+ if line_is_empty {
+ let (line_bounds, _) = self.initial_line_placement(&in_fragment, self.cur_b, flow);
+ self.pending_line.bounds.start = line_bounds.start;
+ self.pending_line.green_zone = line_bounds.size;
+ }
+
+ debug!("LineBreaker: Trying to append fragment to line {:u} (fragment size: {}, green zone: \
+ {}): {}",
+ self.lines.len(),
+ in_fragment.border_box.size,
+ self.pending_line.green_zone,
+ in_fragment);
+
+ let green_zone = self.pending_line.green_zone;
+
+ // NB: At this point, if `green_zone.inline-size < self.pending_line.bounds.size.inline-size` or
+ // `green_zone.block-size < self.pending_line.bounds.size.block-size`, then we committed a line
+ // that overlaps with floats.
+
+ let new_block_size = self.new_block_size_for_line(&in_fragment, layout_context);
+ if new_block_size > green_zone.block {
+ // Uh-oh. Float collision imminent. Enter the float collision avoider
+ return self.avoid_floats(in_fragment, flow, new_block_size, line_is_empty)
+ }
+
+ // If we're not going to overflow the green zone vertically, we might still do so
+ // horizontally. We'll try to place the whole fragment on this line and break somewhere if it
+ // doesn't fit.
+
+ let new_inline_size = self.pending_line.bounds.size.inline + in_fragment.border_box.size.inline;
+ if new_inline_size <= green_zone.inline {
+ debug!("LineBreaker: case=fragment fits without splitting");
+ self.push_fragment_to_line(in_fragment);
+ return true
+ }
+
+ if !in_fragment.can_split() {
+ // TODO(eatkinson, issue #224): Signal that horizontal overflow happened?
+ if line_is_empty {
+ debug!("LineBreaker: case=fragment can't split and line {:u} is empty, so \
+ overflowing.",
+ self.lines.len());
+ self.push_fragment_to_line(in_fragment);
+ return true
+ }
+ }
+
+ let available_inline_size = green_zone.inline - self.pending_line.bounds.size.inline;
+ let split = in_fragment.find_split_info_for_inline_size(CharIndex(0), available_inline_size, line_is_empty);
+ match split.map(|(inline_start, inline_end, run)| {
+ // TODO(bjz): Remove fragment splitting
+ let split_fragment = |split: SplitInfo| {
+ let info = ScannedTextFragmentInfo::new(run.clone(), split.range);
+ let specific = ScannedTextFragment(info);
+ let size = LogicalSize::new(
+ self.floats.writing_mode, split.inline_size, in_fragment.border_box.size.block);
+ in_fragment.transform(size, specific)
+ };
+
+ (inline_start.map(|x| { debug!("LineBreaker: Left split {}", x); split_fragment(x) }),
+ inline_end.map(|x| { debug!("LineBreaker: Right split {}", x); split_fragment(x) }))
+ }) {
+ None => {
+ debug!("LineBreaker: Tried to split unsplittable render fragment! Deferring to next \
+ line. {}", in_fragment);
+ self.work_list.push_front(in_fragment);
+ false
+ },
+ Some((Some(inline_start_fragment), Some(inline_end_fragment))) => {
+ debug!("LineBreaker: Line break found! Pushing inline_start fragment to line and deferring \
+ inline_end fragment to next line.");
+ self.push_fragment_to_line(inline_start_fragment);
+ self.work_list.push_front(inline_end_fragment);
+ true
+ },
+ Some((Some(inline_start_fragment), None)) => {
+ debug!("LineBreaker: Pushing inline_start fragment to line.");
+ self.push_fragment_to_line(inline_start_fragment);
+ true
+ },
+ Some((None, Some(inline_end_fragment))) => {
+ debug!("LineBreaker: Pushing inline_end fragment to line.");
+ self.push_fragment_to_line(inline_end_fragment);
+ true
+ },
+ Some((None, None)) => {
+ error!("LineBreaker: This split case makes no sense!");
+ true
+ },
+ }
+ }
+
+ // An unconditional push
+ fn push_fragment_to_line(&mut self, fragment: Fragment) {
+ debug!("LineBreaker: Pushing fragment {} to line {:u}", fragment.debug_id(), self.lines.len());
+
+ if self.pending_line.range.length() == num::zero() {
+ assert!(self.new_fragments.len() <= (u16::MAX as uint));
+ self.pending_line.range.reset(
+ LineIndices {
+ fragment_index: FragmentIndex(self.new_fragments.len() as int),
+ char_index: CharIndex(0) /* unused for now */,
+ },
+ num::zero()
+ );
+ }
+ self.pending_line.range.extend_by(LineIndices {
+ fragment_index: FragmentIndex(1),
+ char_index: CharIndex(0) /* unused for now */ ,
+ });
+ self.pending_line.bounds.size.inline = self.pending_line.bounds.size.inline +
+ fragment.border_box.size.inline;
+ self.pending_line.bounds.size.block = Au::max(self.pending_line.bounds.size.block,
+ fragment.border_box.size.block);
+ self.new_fragments.push(fragment);
+ }
+}
+
+/// Represents a list of inline fragments, including element ranges.
+#[deriving(Encodable)]
+pub struct InlineFragments {
+ /// The fragments themselves.
+ pub fragments: Vec<Fragment>,
+}
+
+impl InlineFragments {
+ /// Creates an empty set of inline fragments.
+ pub fn new() -> InlineFragments {
+ InlineFragments {
+ fragments: vec![],
+ }
+ }
+
+ /// Returns the number of inline fragments.
+ pub fn len(&self) -> uint {
+ self.fragments.len()
+ }
+
+ /// Returns true if this list contains no fragments and false if it contains at least one fragment.
+ pub fn is_empty(&self) -> bool {
+ self.len() == 0
+ }
+
+ /// Pushes a new inline fragment.
+ pub fn push(&mut self, fragment: &mut Fragment, style: Arc<ComputedValues>) {
+ fragment.add_inline_context_style(style);
+ self.fragments.push(fragment.clone());
+ }
+
+ /// Merges another set of inline fragments with this one.
+ pub fn push_all(&mut self, fragments: InlineFragments) {
+ self.fragments.push_all_move(fragments.fragments);
+ }
+
+ /// A convenience function to return the fragment at a given index.
+ pub fn get<'a>(&'a self, index: uint) -> &'a Fragment {
+ &self.fragments[index]
+ }
+
+ /// A convenience function to return a mutable reference to the fragment at a given index.
+ pub fn get_mut<'a>(&'a mut self, index: uint) -> &'a mut Fragment {
+ self.fragments.get_mut(index)
+ }
+
+ /// Strips ignorable whitespace from the start of a list of fragments.
+ pub fn strip_ignorable_whitespace_from_start(&mut self) {
+ if self.is_empty() { return }; // Fast path
+
+ // FIXME (rust#16151): This can be reverted back to using skip_while once
+ // the upstream bug is fixed.
+ let mut fragments = mem::replace(&mut self.fragments, vec![]).move_iter();
+ let mut new_fragments = Vec::new();
+ let mut skipping = true;
+ for fragment in fragments {
+ if skipping && fragment.is_whitespace_only() {
+ debug!("stripping ignorable whitespace from start");
+ continue
+ }
+
+ skipping = false;
+ new_fragments.push(fragment);
+ }
+
+ self.fragments = new_fragments;
+ }
+
+ /// Strips ignorable whitespace from the end of a list of fragments.
+ pub fn strip_ignorable_whitespace_from_end(&mut self) {
+ if self.is_empty() {
+ return;
+ }
+
+ let mut new_fragments = self.fragments.clone();
+ while new_fragments.len() > 0 && new_fragments.as_slice().last().get_ref().is_whitespace_only() {
+ debug!("stripping ignorable whitespace from end");
+ drop(new_fragments.pop());
+ }
+
+
+ self.fragments = new_fragments;
+ }
+}
+
+/// Flows for inline layout.
+#[deriving(Encodable)]
+pub struct InlineFlow {
+ /// Data common to all flows.
+ pub base: BaseFlow,
+
+ /// A vector of all inline fragments. Several fragments may correspond to one node/element.
+ pub fragments: InlineFragments,
+
+ /// A vector of ranges into fragments that represents line positions. These ranges are disjoint and
+ /// are the result of inline layout. This also includes some metadata used for positioning
+ /// lines.
+ pub lines: Vec<Line>,
+
+ /// The minimum block-size above the baseline for each line, as specified by the line block-size and
+ /// font style.
+ pub minimum_block_size_above_baseline: Au,
+
+ /// The minimum depth below the baseline for each line, as specified by the line block-size and
+ /// font style.
+ pub minimum_depth_below_baseline: Au,
+}
+
+impl InlineFlow {
+ pub fn from_fragments(node: ThreadSafeLayoutNode, fragments: InlineFragments) -> InlineFlow {
+ InlineFlow {
+ base: BaseFlow::new(node),
+ fragments: fragments,
+ lines: Vec::new(),
+ minimum_block_size_above_baseline: Au(0),
+ minimum_depth_below_baseline: Au(0),
+ }
+ }
+
+ pub fn build_display_list_inline(&mut self, layout_context: &LayoutContext) {
+ let size = self.base.position.size.to_physical(self.base.writing_mode);
+ if !Rect(self.base.abs_position, size).intersects(&layout_context.shared.dirty) {
+ return
+ }
+
+ // TODO(#228): Once we form lines and have their cached bounds, we can be smarter and
+ // not recurse on a line if nothing in it can intersect the dirty region.
+ debug!("Flow: building display list for {:u} inline fragments", self.fragments.len());
+
+ for fragment in self.fragments.fragments.mut_iter() {
+ let rel_offset = fragment.relative_position(&self.base
+ .absolute_position_info
+ .relative_containing_block_size);
+ drop(fragment.build_display_list(&mut self.base.display_list,
+ layout_context,
+ self.base.abs_position.add_size(
+ &rel_offset.to_physical(self.base.writing_mode)),
+ ContentLevel));
+ }
+
+ // TODO(#225): Should `inline-block` elements have flows as children of the inline flow or
+ // should the flow be nested inside the fragment somehow?
+
+ // For now, don't traverse the subtree rooted here.
+ }
+
+ /// Returns the distance from the baseline for the logical block-start inline-start corner of this fragment,
+ /// taking into account the value of the CSS `vertical-align` property. Negative values mean
+ /// "toward the logical block-start" and positive values mean "toward the logical block-end".
+ ///
+ /// The extra boolean is set if and only if `biggest_block-start` and/or `biggest_block-end` were updated.
+ /// That is, if the box has a `block-start` or `block-end` value, true is returned.
+ fn distance_from_baseline(fragment: &Fragment,
+ ascent: Au,
+ parent_text_block_start: Au,
+ parent_text_block_end: Au,
+ block_size_above_baseline: &mut Au,
+ depth_below_baseline: &mut Au,
+ largest_block_size_for_top_fragments: &mut Au,
+ largest_block_size_for_bottom_fragments: &mut Au,
+ layout_context: &LayoutContext)
+ -> (Au, bool) {
+ match fragment.vertical_align() {
+ vertical_align::baseline => (-ascent, false),
+ vertical_align::middle => {
+ // TODO: x-block-size value should be used from font info.
+ let xblock_size = Au(0);
+ let fragment_block_size = fragment.content_block_size(layout_context);
+ let offset_block_start = -(xblock_size + fragment_block_size).scale_by(0.5);
+ *block_size_above_baseline = offset_block_start.scale_by(-1.0);
+ *depth_below_baseline = fragment_block_size - *block_size_above_baseline;
+ (offset_block_start, false)
+ },
+ vertical_align::sub => {
+ // TODO: The proper position for subscripts should be used. Lower the baseline to
+ // the proper position for subscripts.
+ let sub_offset = Au(0);
+ (sub_offset - ascent, false)
+ },
+ vertical_align::super_ => {
+ // TODO: The proper position for superscripts should be used. Raise the baseline to
+ // the proper position for superscripts.
+ let super_offset = Au(0);
+ (-super_offset - ascent, false)
+ },
+ vertical_align::text_top => {
+ let fragment_block_size = *block_size_above_baseline + *depth_below_baseline;
+ let prev_depth_below_baseline = *depth_below_baseline;
+ *block_size_above_baseline = parent_text_block_start;
+ *depth_below_baseline = fragment_block_size - *block_size_above_baseline;
+ (*depth_below_baseline - prev_depth_below_baseline - ascent, false)
+ },
+ vertical_align::text_bottom => {
+ let fragment_block_size = *block_size_above_baseline + *depth_below_baseline;
+ let prev_depth_below_baseline = *depth_below_baseline;
+ *depth_below_baseline = parent_text_block_end;
+ *block_size_above_baseline = fragment_block_size - *depth_below_baseline;
+ (*depth_below_baseline - prev_depth_below_baseline - ascent, false)
+ },
+ vertical_align::top => {
+ *largest_block_size_for_top_fragments =
+ Au::max(*largest_block_size_for_top_fragments,
+ *block_size_above_baseline + *depth_below_baseline);
+ let offset_top = *block_size_above_baseline - ascent;
+ (offset_top, true)
+ },
+ vertical_align::bottom => {
+ *largest_block_size_for_bottom_fragments =
+ Au::max(*largest_block_size_for_bottom_fragments,
+ *block_size_above_baseline + *depth_below_baseline);
+ let offset_bottom = -(*depth_below_baseline + ascent);
+ (offset_bottom, true)
+ },
+ vertical_align::Length(length) => (-(length + ascent), false),
+ vertical_align::Percentage(p) => {
+ let line_height = fragment.calculate_line_height(layout_context);
+ let percent_offset = line_height.scale_by(p);
+ (-(percent_offset + ascent), false)
+ }
+ }
+ }
+
+ /// Sets fragment X positions based on alignment for one line.
+ fn set_horizontal_fragment_positions(fragments: &mut InlineFragments,
+ line: &Line,
+ line_align: text_align::T) {
+ // Figure out how much inline-size we have.
+ let slack_inline_size = Au::max(Au(0), line.green_zone.inline - line.bounds.size.inline);
+
+ // Set the fragment x positions based on that alignment.
+ let mut offset_x = line.bounds.start.i;
+ offset_x = offset_x + match line_align {
+ // So sorry, but justified text is more complicated than shuffling line
+ // coordinates.
+ //
+ // TODO(burg, issue #213): Implement `text-align: justify`.
+ text_align::left | text_align::justify => Au(0),
+ text_align::center => slack_inline_size.scale_by(0.5),
+ text_align::right => slack_inline_size,
+ };
+
+ for i in each_fragment_index(&line.range) {
+ let fragment = fragments.get_mut(i.to_uint());
+ let size = fragment.border_box.size;
+ fragment.border_box = LogicalRect::new(
+ fragment.style.writing_mode, offset_x, fragment.border_box.start.b,
+ size.inline, size.block);
+ offset_x = offset_x + size.inline;
+ }
+ }
+
+ /// Computes the minimum ascent and descent for each line. This is done during flow
+ /// construction.
+ ///
+ /// `style` is the style of the block.
+ pub fn compute_minimum_ascent_and_descent(&self,
+ font_context: &mut FontContext,
+ style: &ComputedValues) -> (Au, Au) {
+ let font_style = text::computed_style_to_font_style(style);
+ let font_metrics = text::font_metrics_for_style(font_context, &font_style);
+ let line_height = text::line_height_from_style(style, &font_metrics);
+ let inline_metrics = InlineMetrics::from_font_metrics(&font_metrics, line_height);
+ (inline_metrics.block_size_above_baseline, inline_metrics.depth_below_baseline)
+ }
+}
+
+impl Flow for InlineFlow {
+ fn class(&self) -> FlowClass {
+ InlineFlowClass
+ }
+
+ fn as_immutable_inline<'a>(&'a self) -> &'a InlineFlow {
+ self
+ }
+
+ fn as_inline<'a>(&'a mut self) -> &'a mut InlineFlow {
+ self
+ }
+
+ fn bubble_inline_sizes(&mut self, _: &LayoutContext) {
+ let _scope = layout_debug_scope!("inline::bubble_inline_sizes {:s}", self.base.debug_id());
+
+ let writing_mode = self.base.writing_mode;
+ for kid in self.base.child_iter() {
+ flow::mut_base(kid).floats = Floats::new(writing_mode);
+ }
+
+ let mut intrinsic_inline_sizes = IntrinsicISizes::new();
+ for fragment in self.fragments.fragments.mut_iter() {
+ debug!("Flow: measuring {}", *fragment);
+
+ let fragment_intrinsic_inline_sizes =
+ fragment.intrinsic_inline_sizes();
+ intrinsic_inline_sizes.minimum_inline_size = geometry::max(
+ intrinsic_inline_sizes.minimum_inline_size,
+ fragment_intrinsic_inline_sizes.minimum_inline_size);
+ intrinsic_inline_sizes.preferred_inline_size =
+ intrinsic_inline_sizes.preferred_inline_size +
+ fragment_intrinsic_inline_sizes.preferred_inline_size;
+ }
+
+ self.base.intrinsic_inline_sizes = intrinsic_inline_sizes;
+ }
+
+ /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. When called
+ /// on this context, the context has had its inline-size set by the parent context.
+ fn assign_inline_sizes(&mut self, _: &LayoutContext) {
+ let _scope = layout_debug_scope!("inline::assign_inline_sizes {:s}", self.base.debug_id());
+
+ // Initialize content fragment inline-sizes if they haven't been initialized already.
+ //
+ // TODO: Combine this with `LineBreaker`'s walk in the fragment list, or put this into `Fragment`.
+
+ debug!("InlineFlow::assign_inline_sizes: floats in: {:?}", self.base.floats);
+
+ {
+ let inline_size = self.base.position.size.inline;
+ let this = &mut *self;
+ for fragment in this.fragments.fragments.mut_iter() {
+ fragment.assign_replaced_inline_size_if_necessary(inline_size);
+ }
+ }
+
+ assert!(self.base.children.len() == 0,
+ "InlineFlow: should not have children flows in the current layout implementation.");
+
+ // There are no child contexts, so stop here.
+
+ // TODO(Issue #225): once there are 'inline-block' elements, this won't be
+ // true. In that case, set the InlineBlockFragment's inline-size to the
+ // shrink-to-fit inline-size, perform inline flow, and set the block
+ // flow context's inline-size as the assigned inline-size of the
+ // 'inline-block' fragment that created this flow before recursing.
+ }
+
+ /// Calculate and set the block-size of this flow. See CSS 2.1 § 10.6.1.
+ fn assign_block_size(&mut self, ctx: &LayoutContext) {
+ let _scope = layout_debug_scope!("inline::assign_block_size {:s}", self.base.debug_id());
+
+ // Divide the fragments into lines.
+ //
+ // TODO(#226): Get the CSS `line-block-size` property from the containing block's style to
+ // determine minimum line block-size.
+ //
+ // TODO(#226): Get the CSS `line-block-size` property from each non-replaced inline element to
+ // determine its block-size for computing line block-size.
+ //
+ // TODO(pcwalton): Cache the line scanner?
+ debug!("assign_block_size_inline: floats in: {:?}", self.base.floats);
+
+ // assign block-size for inline fragments
+ for fragment in self.fragments.fragments.mut_iter() {
+ fragment.assign_replaced_block_size_if_necessary();
+ }
+
+ let scanner_floats = self.base.floats.clone();
+ let mut scanner = LineBreaker::new(scanner_floats);
+ scanner.scan_for_lines(self, ctx);
+
+ // All lines use text alignment of the flow.
+ let text_align = self.base.flags.text_align();
+
+ // Now, go through each line and lay out the fragments inside.
+ let mut line_distance_from_flow_block_start = Au(0);
+ for line in self.lines.mut_iter() {
+ // Lay out fragments horizontally.
+ InlineFlow::set_horizontal_fragment_positions(&mut self.fragments, line, text_align);
+
+ // Set the block-start y position of the current line.
+ // `line_height_offset` is updated at the end of the previous loop.
+ line.bounds.start.b = line_distance_from_flow_block_start;
+
+ // Calculate the distance from the baseline to the block-start and block-end of the line.
+ let mut largest_block_size_above_baseline = self.minimum_block_size_above_baseline;
+ let mut largest_depth_below_baseline = self.minimum_depth_below_baseline;
+
+ // Calculate the largest block-size among fragments with 'top' and 'bottom' values
+ // respectively.
+ let (mut largest_block_size_for_top_fragments, mut largest_block_size_for_bottom_fragments) =
+ (Au(0), Au(0));
+
+ for fragment_i in each_fragment_index(&line.range) {
+ let fragment = self.fragments.fragments.get_mut(fragment_i.to_uint());
+
+ let InlineMetrics {
+ block_size_above_baseline: mut block_size_above_baseline,
+ depth_below_baseline: mut depth_below_baseline,
+ ascent
+ } = fragment.inline_metrics(ctx);
+
+ // To calculate text-top and text-bottom value when `vertical-align` is involved,
+ // we should find the top and bottom of the content area of the parent fragment.
+ // "Content area" is defined in CSS 2.1 § 10.6.1.
+ //
+ // TODO: We should extract em-box info from the font size of the parent and
+ // calculate the distances from the baseline to the block-start and the block-end of the
+ // parent's content area.
+
+ // We should calculate the distance from baseline to the top of parent's content
+ // area. But for now we assume it's the font size.
+ //
+ // CSS 2.1 does not state which font to use. Previous versions of the code used
+ // the parent's font; this code uses the current font.
+ let parent_text_top = fragment.style().get_font().font_size;
+
+ // We should calculate the distance from baseline to the bottom of the parent's
+ // content area. But for now we assume it's zero.
+ let parent_text_bottom = Au(0);
+
+ // Calculate the final block-size above the baseline for this fragment.
+ //
+ // The no-update flag decides whether `largest_block-size_for_top_fragments` and
+ // `largest_block-size_for_bottom_fragments` are to be updated or not. This will be set
+ // if and only if the fragment has `vertical-align` set to `top` or `bottom`.
+ let (distance_from_baseline, no_update_flag) =
+ InlineFlow::distance_from_baseline(
+ fragment,
+ ascent,
+ parent_text_top,
+ parent_text_bottom,
+ &mut block_size_above_baseline,
+ &mut depth_below_baseline,
+ &mut largest_block_size_for_top_fragments,
+ &mut largest_block_size_for_bottom_fragments,
+ ctx);
+
+ // Unless the current fragment has `vertical-align` set to `top` or `bottom`,
+ // `largest_block-size_above_baseline` and `largest_depth_below_baseline` are updated.
+ if !no_update_flag {
+ largest_block_size_above_baseline = Au::max(block_size_above_baseline,
+ largest_block_size_above_baseline);
+ largest_depth_below_baseline = Au::max(depth_below_baseline,
+ largest_depth_below_baseline);
+ }
+
+ // Temporarily use `fragment.border_box.start.b` to mean "the distance from the
+ // baseline". We will assign the real value later.
+ fragment.border_box.start.b = distance_from_baseline
+ }
+
+ // Calculate the distance from the baseline to the top of the largest fragment with a
+ // value for `bottom`. Then, if necessary, update `largest_block-size_above_baseline`.
+ largest_block_size_above_baseline =
+ Au::max(largest_block_size_above_baseline,
+ largest_block_size_for_bottom_fragments - largest_depth_below_baseline);
+
+ // Calculate the distance from baseline to the bottom of the largest fragment with a value
+ // for `top`. Then, if necessary, update `largest_depth_below_baseline`.
+ largest_depth_below_baseline =
+ Au::max(largest_depth_below_baseline,
+ largest_block_size_for_top_fragments - largest_block_size_above_baseline);
+
+ // Now, the distance from the logical block-start of the line to the baseline can be
+ // computed as `largest_block-size_above_baseline`.
+ let baseline_distance_from_block_start = largest_block_size_above_baseline;
+
+ // Compute the final positions in the block direction of each fragment. Recall that
+ // `fragment.border_box.start.b` was set to the distance from the baseline above.
+ for fragment_i in each_fragment_index(&line.range) {
+ let fragment = self.fragments.get_mut(fragment_i.to_uint());
+ match fragment.vertical_align() {
+ vertical_align::top => {
+ fragment.border_box.start.b = fragment.border_box.start.b +
+ line_distance_from_flow_block_start
+ }
+ vertical_align::bottom => {
+ fragment.border_box.start.b = fragment.border_box.start.b +
+ line_distance_from_flow_block_start + baseline_distance_from_block_start +
+ largest_depth_below_baseline
+ }
+ _ => {
+ fragment.border_box.start.b = fragment.border_box.start.b +
+ line_distance_from_flow_block_start + baseline_distance_from_block_start
+ }
+ }
+ }
+
+ // This is used to set the block-start y position of the next line in the next loop.
+ line.bounds.size.block = largest_block_size_above_baseline + largest_depth_below_baseline;
+ line_distance_from_flow_block_start = line_distance_from_flow_block_start + line.bounds.size.block;
+ } // End of `lines.each` loop.
+
+ self.base.position.size.block = match self.lines.as_slice().last() {
+ Some(ref last_line) => last_line.bounds.start.b + last_line.bounds.size.block,
+ None => Au::new(0)
+ };
+
+ self.base.floats = scanner.floats();
+ self.base.floats.translate(LogicalSize::new(
+ self.base.writing_mode, Au::new(0), -self.base.position.size.block));
+ }
+}
+
+impl fmt::Show for InlineFlow {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ try!(write!(f, "InlineFlow"));
+ for (i, fragment) in self.fragments.fragments.iter().enumerate() {
+ if i == 0 {
+ try!(write!(f, ": {}", fragment))
+ } else {
+ try!(write!(f, ", {}", fragment))
+ }
+ }
+ Ok(())
+ }
+}
+
+#[deriving(Clone)]
+pub struct InlineFragmentContext {
+ pub styles: Vec<Arc<ComputedValues>>,
+}
+
+impl InlineFragmentContext {
+ pub fn new() -> InlineFragmentContext {
+ InlineFragmentContext {
+ styles: vec!()
+ }
+ }
+}
+
+/// BSize above the baseline, depth below the baseline, and ascent for a fragment. See CSS 2.1 §
+/// 10.8.1.
+pub struct InlineMetrics {
+ pub block_size_above_baseline: Au,
+ pub depth_below_baseline: Au,
+ pub ascent: Au,
+}
+
+impl InlineMetrics {
+ /// Calculates inline metrics from font metrics and line block-size per CSS 2.1 § 10.8.1.
+ #[inline]
+ pub fn from_font_metrics(font_metrics: &FontMetrics, line_height: Au) -> InlineMetrics {
+ let leading = line_height - (font_metrics.ascent + font_metrics.descent);
+ InlineMetrics {
+ block_size_above_baseline: font_metrics.ascent + leading.scale_by(0.5),
+ depth_below_baseline: font_metrics.descent + leading.scale_by(0.5),
+ ascent: font_metrics.ascent,
+ }
+ }
+}
+
diff --git a/components/layout/layout_debug.rs b/components/layout/layout_debug.rs
new file mode 100644
index 00000000000..58db599c9e2
--- /dev/null
+++ b/components/layout/layout_debug.rs
@@ -0,0 +1,126 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Supports writing a trace file created during each layout scope
+//! that can be viewed by an external tool to make layout debugging easier.
+
+#![macro_escape]
+
+use flow_ref::FlowRef;
+use serialize::json;
+use std::cell::RefCell;
+use std::io::File;
+use std::sync::atomics::{AtomicUint, SeqCst, INIT_ATOMIC_UINT};
+
+local_data_key!(state_key: RefCell<State>)
+
+static mut DEBUG_ID_COUNTER: AtomicUint = INIT_ATOMIC_UINT;
+
+pub struct Scope;
+
+#[macro_export]
+macro_rules! layout_debug_scope(
+ ($($arg:tt)*) => (
+ if cfg!(not(ndebug)) {
+ layout_debug::Scope::new(format!($($arg)*))
+ } else {
+ layout_debug::Scope
+ }
+ )
+)
+
+#[deriving(Encodable)]
+struct ScopeData {
+ name: String,
+ pre: String,
+ post: String,
+ children: Vec<Box<ScopeData>>,
+}
+
+impl ScopeData {
+ fn new(name: String, pre: String) -> ScopeData {
+ ScopeData {
+ name: name,
+ pre: pre,
+ post: String::new(),
+ children: vec!(),
+ }
+ }
+}
+
+struct State {
+ flow_root: FlowRef,
+ scope_stack: Vec<Box<ScopeData>>,
+}
+
+/// A layout debugging scope. The entire state of the flow tree
+/// will be output at the beginning and end of this scope.
+impl Scope {
+ pub fn new(name: String) -> Scope {
+ let maybe_refcell = state_key.get();
+ match maybe_refcell {
+ Some(refcell) => {
+ let mut state = refcell.borrow_mut();
+ let flow_trace = json::encode(&state.flow_root.get());
+ let data = box ScopeData::new(name, flow_trace);
+ state.scope_stack.push(data);
+ }
+ None => {}
+ }
+ Scope
+ }
+}
+
+#[cfg(not(ndebug))]
+impl Drop for Scope {
+ fn drop(&mut self) {
+ let maybe_refcell = state_key.get();
+ match maybe_refcell {
+ Some(refcell) => {
+ let mut state = refcell.borrow_mut();
+ let mut current_scope = state.scope_stack.pop().unwrap();
+ current_scope.post = json::encode(&state.flow_root.get());
+ let previous_scope = state.scope_stack.mut_last().unwrap();
+ previous_scope.children.push(current_scope);
+ }
+ None => {}
+ }
+ }
+}
+
+/// Generate a unique ID. This is used for items such as Fragment
+/// which are often reallocated but represent essentially the
+/// same data.
+pub fn generate_unique_debug_id() -> uint {
+ unsafe { DEBUG_ID_COUNTER.fetch_add(1, SeqCst) }
+}
+
+/// Begin a layout debug trace. If this has not been called,
+/// creating debug scopes has no effect.
+pub fn begin_trace(flow_root: FlowRef) {
+ assert!(state_key.get().is_none());
+
+ let flow_trace = json::encode(&flow_root.get());
+ let state = State {
+ scope_stack: vec![box ScopeData::new("root".to_string(), flow_trace)],
+ flow_root: flow_root,
+ };
+ state_key.replace(Some(RefCell::new(state)));
+}
+
+/// End the debug layout trace. This will write the layout
+/// trace to disk in the current directory. The output
+/// file can then be viewed with an external tool.
+pub fn end_trace() {
+ let task_state_cell = state_key.replace(None).unwrap();
+ let mut task_state = task_state_cell.borrow_mut();
+ assert!(task_state.scope_stack.len() == 1);
+ let mut root_scope = task_state.scope_stack.pop().unwrap();
+ root_scope.post = json::encode(&task_state.flow_root.get());
+
+ let result = json::encode(&root_scope);
+ let path = Path::new("layout_trace.json");
+ let mut file = File::create(&path).unwrap();
+ file.write_str(result.as_slice()).unwrap();
+}
diff --git a/components/layout/layout_task.rs b/components/layout/layout_task.rs
new file mode 100644
index 00000000000..c3c463408e6
--- /dev/null
+++ b/components/layout/layout_task.rs
@@ -0,0 +1,1020 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! The layout task. Performs layout on the DOM, builds display lists and sends them to be
+//! rendered.
+
+use css::matching::{ApplicableDeclarations, MatchMethods};
+use css::node_style::StyledNode;
+use construct::{FlowConstructionResult, NoConstructionResult};
+use context::{LayoutContext, SharedLayoutContext};
+use flow::{Flow, ImmutableFlowUtils, MutableFlowUtils, MutableOwnedFlowUtils};
+use flow::{PreorderFlowTraversal, PostorderFlowTraversal};
+use flow;
+use flow_ref::FlowRef;
+use incremental::RestyleDamage;
+use layout_debug;
+use parallel::UnsafeFlow;
+use parallel;
+use util::{LayoutDataAccess, LayoutDataWrapper, OpaqueNodeMethods, ToGfxColor};
+use wrapper::{LayoutNode, TLayoutNode, ThreadSafeLayoutNode};
+
+use collections::dlist::DList;
+use geom::point::Point2D;
+use geom::rect::Rect;
+use geom::size::Size2D;
+use gfx::display_list::{ClipDisplayItemClass, ContentStackingLevel, DisplayItem};
+use gfx::display_list::{DisplayItemIterator, DisplayList, OpaqueNode};
+use gfx::render_task::{RenderInitMsg, RenderChan, RenderLayer};
+use gfx::{render_task, color};
+use layout_traits;
+use layout_traits::{LayoutControlMsg, LayoutTaskFactory};
+use script::dom::bindings::js::JS;
+use script::dom::node::{ElementNodeTypeId, LayoutDataRef, Node};
+use script::dom::element::{HTMLBodyElementTypeId, HTMLHtmlElementTypeId};
+use script::layout_interface::{AddStylesheetMsg, ScriptLayoutChan};
+use script::layout_interface::{TrustedNodeAddress, ContentBoxesResponse, ExitNowMsg};
+use script::layout_interface::{ContentBoxResponse, HitTestResponse, MouseOverResponse};
+use script::layout_interface::{ContentChangedDocumentDamage, LayoutChan, Msg, PrepareToExitMsg};
+use script::layout_interface::{GetRPCMsg, LayoutRPC, ReapLayoutDataMsg, Reflow, UntrustedNodeAddress};
+use script::layout_interface::{ReflowForDisplay, ReflowMsg};
+use script_traits::{SendEventMsg, ReflowEvent, ReflowCompleteMsg, OpaqueScriptLayoutChannel, ScriptControlChan};
+use servo_msg::compositor_msg::Scrollable;
+use servo_msg::constellation_msg::{ConstellationChan, PipelineId, Failure, FailureMsg};
+use servo_net::image_cache_task::{ImageCacheTask, ImageResponseMsg};
+use gfx::font_cache_task::{FontCacheTask};
+use servo_net::local_image_cache::{ImageResponder, LocalImageCache};
+use servo_util::geometry::Au;
+use servo_util::geometry;
+use servo_util::logical_geometry::LogicalPoint;
+use servo_util::opts::Opts;
+use servo_util::smallvec::{SmallVec, SmallVec1};
+use servo_util::time::{TimeProfilerChan, profile};
+use servo_util::time;
+use servo_util::task::spawn_named_with_send_on_failure;
+use servo_util::workqueue::WorkQueue;
+use std::comm::{channel, Sender, Receiver, Select};
+use std::mem;
+use std::ptr;
+use style::{AuthorOrigin, Stylesheet, Stylist};
+use style::iter_font_face_rules;
+use sync::{Arc, Mutex};
+use url::Url;
+
+/// Mutable data belonging to the LayoutTask.
+///
+/// This needs to be protected by a mutex so we can do fast RPCs.
+pub struct LayoutTaskData {
+ /// The local image cache.
+ pub local_image_cache: Arc<Mutex<LocalImageCache>>,
+
+ /// The size of the viewport.
+ pub screen_size: Size2D<Au>,
+
+ /// A cached display list.
+ pub display_list: Option<Arc<DisplayList>>,
+
+ pub stylist: Box<Stylist>,
+
+ /// The workers that we use for parallel operation.
+ pub parallel_traversal: Option<WorkQueue<*const SharedLayoutContext, UnsafeFlow>>,
+
+ /// The dirty rect. Used during display list construction.
+ pub dirty: Rect<Au>,
+}
+
+/// Information needed by the layout task.
+pub struct LayoutTask {
+ /// The ID of the pipeline that we belong to.
+ pub id: PipelineId,
+
+ /// The port on which we receive messages from the script task.
+ pub port: Receiver<Msg>,
+
+ /// The port on which we receive messages from the constellation
+ pub pipeline_port: Receiver<LayoutControlMsg>,
+
+ //// The channel to send messages to ourself.
+ pub chan: LayoutChan,
+
+ /// The channel on which messages can be sent to the constellation.
+ pub constellation_chan: ConstellationChan,
+
+ /// The channel on which messages can be sent to the script task.
+ pub script_chan: ScriptControlChan,
+
+ /// The channel on which messages can be sent to the painting task.
+ pub render_chan: RenderChan,
+
+ /// The channel on which messages can be sent to the time profiler.
+ pub time_profiler_chan: TimeProfilerChan,
+
+ /// The channel on which messages can be sent to the image cache.
+ pub image_cache_task: ImageCacheTask,
+
+ /// Public interface to the font cache task.
+ pub font_cache_task: FontCacheTask,
+
+ /// The command-line options.
+ pub opts: Opts,
+
+ /// A mutex to allow for fast, read-only RPC of layout's internal data
+ /// structures, while still letting the LayoutTask modify them.
+ ///
+ /// All the other elements of this struct are read-only.
+ pub rw_data: Arc<Mutex<LayoutTaskData>>,
+}
+
+/// The damage computation traversal.
+#[deriving(Clone)]
+struct ComputeDamageTraversal;
+
+impl PostorderFlowTraversal for ComputeDamageTraversal {
+ #[inline]
+ fn process(&mut self, flow: &mut Flow) -> bool {
+ let mut damage = flow::base(flow).restyle_damage;
+ for child in flow::child_iter(flow) {
+ damage.insert(flow::base(child).restyle_damage.propagate_up())
+ }
+ flow::mut_base(flow).restyle_damage = damage;
+ true
+ }
+}
+
+/// Propagates restyle damage up and down the tree as appropriate.
+///
+/// FIXME(pcwalton): Merge this with flow tree building and/or other traversals.
+struct PropagateDamageTraversal {
+ all_style_damage: bool,
+}
+
+impl PreorderFlowTraversal for PropagateDamageTraversal {
+ #[inline]
+ fn process(&mut self, flow: &mut Flow) -> bool {
+ if self.all_style_damage {
+ flow::mut_base(flow).restyle_damage.insert(RestyleDamage::all())
+ }
+ debug!("restyle damage = {:?}", flow::base(flow).restyle_damage);
+
+ let prop = flow::base(flow).restyle_damage.propagate_down();
+ if !prop.is_empty() {
+ for kid_ctx in flow::child_iter(flow) {
+ flow::mut_base(kid_ctx).restyle_damage.insert(prop)
+ }
+ }
+ true
+ }
+}
+
+/// The flow tree verification traversal. This is only on in debug builds.
+#[cfg(debug)]
+struct FlowTreeVerificationTraversal;
+
+#[cfg(debug)]
+impl PreorderFlowTraversal for FlowTreeVerificationTraversal {
+ #[inline]
+ fn process(&mut self, flow: &mut Flow) -> bool {
+ let base = flow::base(flow);
+ if !base.flags.is_leaf() && !base.flags.is_nonleaf() {
+ println("flow tree verification failed: flow wasn't a leaf or a nonleaf!");
+ flow.dump();
+ fail!("flow tree verification failed")
+ }
+ true
+ }
+}
+
+/// The bubble-inline-sizes traversal, the first part of layout computation. This computes preferred
+/// and intrinsic inline-sizes and bubbles them up the tree.
+pub struct BubbleISizesTraversal<'a> {
+ pub layout_context: &'a LayoutContext<'a>,
+}
+
+impl<'a> PostorderFlowTraversal for BubbleISizesTraversal<'a> {
+ #[inline]
+ fn process(&mut self, flow: &mut Flow) -> bool {
+ flow.bubble_inline_sizes(self.layout_context);
+ true
+ }
+
+ // FIXME: We can't prune until we start reusing flows
+ /*
+ #[inline]
+ fn should_prune(&mut self, flow: &mut Flow) -> bool {
+ flow::mut_base(flow).restyle_damage.lacks(BubbleISizes)
+ }
+ */
+}
+
+/// The assign-inline-sizes traversal. In Gecko this corresponds to `Reflow`.
+pub struct AssignISizesTraversal<'a> {
+ pub layout_context: &'a LayoutContext<'a>,
+}
+
+impl<'a> PreorderFlowTraversal for AssignISizesTraversal<'a> {
+ #[inline]
+ fn process(&mut self, flow: &mut Flow) -> bool {
+ flow.assign_inline_sizes(self.layout_context);
+ true
+ }
+}
+
+/// The assign-block-sizes-and-store-overflow traversal, the last (and most expensive) part of layout
+/// computation. Determines the final block-sizes for all layout objects, computes positions, and
+/// computes overflow regions. In Gecko this corresponds to `FinishAndStoreOverflow`.
+pub struct AssignBSizesAndStoreOverflowTraversal<'a> {
+ pub layout_context: &'a LayoutContext<'a>,
+}
+
+impl<'a> PostorderFlowTraversal for AssignBSizesAndStoreOverflowTraversal<'a> {
+ #[inline]
+ fn process(&mut self, flow: &mut Flow) -> bool {
+ flow.assign_block_size(self.layout_context);
+ // Skip store-overflow for absolutely positioned flows. That will be
+ // done in a separate traversal.
+ if !flow.is_store_overflow_delayed() {
+ flow.store_overflow(self.layout_context);
+ }
+ true
+ }
+
+ #[inline]
+ fn should_process(&mut self, flow: &mut Flow) -> bool {
+ !flow::base(flow).flags.impacted_by_floats()
+ }
+}
+
+/// The display list construction traversal.
+pub struct BuildDisplayListTraversal<'a> {
+ layout_context: &'a LayoutContext<'a>,
+}
+
+impl<'a> BuildDisplayListTraversal<'a> {
+ #[inline]
+ fn process(&mut self, flow: &mut Flow) {
+ flow.compute_absolute_position();
+
+ for kid in flow::mut_base(flow).child_iter() {
+ if !kid.is_absolutely_positioned() {
+ self.process(kid)
+ }
+ }
+
+ for absolute_descendant_link in flow::mut_base(flow).abs_descendants.iter() {
+ self.process(absolute_descendant_link)
+ }
+
+ flow.build_display_list(self.layout_context)
+ }
+}
+
+struct LayoutImageResponder {
+ id: PipelineId,
+ script_chan: ScriptControlChan,
+}
+
+impl ImageResponder for LayoutImageResponder {
+ fn respond(&self) -> proc(ImageResponseMsg):Send {
+ let id = self.id.clone();
+ let script_chan = self.script_chan.clone();
+ let f: proc(ImageResponseMsg):Send = proc(_) {
+ let ScriptControlChan(chan) = script_chan;
+ drop(chan.send_opt(SendEventMsg(id.clone(), ReflowEvent)))
+ };
+ f
+ }
+}
+
+impl LayoutTaskFactory for LayoutTask {
+ /// Spawns a new layout task.
+ fn create(_phantom: Option<&mut LayoutTask>,
+ id: PipelineId,
+ chan: OpaqueScriptLayoutChannel,
+ pipeline_port: Receiver<LayoutControlMsg>,
+ constellation_chan: ConstellationChan,
+ failure_msg: Failure,
+ script_chan: ScriptControlChan,
+ render_chan: RenderChan,
+ img_cache_task: ImageCacheTask,
+ font_cache_task: FontCacheTask,
+ opts: Opts,
+ time_profiler_chan: TimeProfilerChan,
+ shutdown_chan: Sender<()>) {
+ let ConstellationChan(con_chan) = constellation_chan.clone();
+ spawn_named_with_send_on_failure("LayoutTask", proc() {
+ { // Ensures layout task is destroyed before we send shutdown message
+ let sender = chan.sender();
+ let layout =
+ LayoutTask::new(
+ id,
+ chan.receiver(),
+ LayoutChan(sender),
+ pipeline_port,
+ constellation_chan,
+ script_chan,
+ render_chan,
+ img_cache_task,
+ font_cache_task,
+ &opts,
+ time_profiler_chan);
+ layout.start();
+ }
+ shutdown_chan.send(());
+ }, FailureMsg(failure_msg), con_chan, false);
+ }
+}
+
+impl LayoutTask {
+ /// Creates a new `LayoutTask` structure.
+ fn new(id: PipelineId,
+ port: Receiver<Msg>,
+ chan: LayoutChan,
+ pipeline_port: Receiver<LayoutControlMsg>,
+ constellation_chan: ConstellationChan,
+ script_chan: ScriptControlChan,
+ render_chan: RenderChan,
+ image_cache_task: ImageCacheTask,
+ font_cache_task: FontCacheTask,
+ opts: &Opts,
+ time_profiler_chan: TimeProfilerChan)
+ -> LayoutTask {
+ let local_image_cache = Arc::new(Mutex::new(LocalImageCache::new(image_cache_task.clone())));
+ let screen_size = Size2D(Au(0), Au(0));
+ let parallel_traversal = if opts.layout_threads != 1 {
+ Some(WorkQueue::new("LayoutWorker", opts.layout_threads, ptr::null()))
+ } else {
+ None
+ };
+
+ LayoutTask {
+ id: id,
+ port: port,
+ pipeline_port: pipeline_port,
+ chan: chan,
+ constellation_chan: constellation_chan,
+ script_chan: script_chan,
+ render_chan: render_chan,
+ time_profiler_chan: time_profiler_chan,
+ image_cache_task: image_cache_task.clone(),
+ font_cache_task: font_cache_task,
+ opts: opts.clone(),
+ rw_data: Arc::new(Mutex::new(
+ LayoutTaskData {
+ local_image_cache: local_image_cache,
+ screen_size: screen_size,
+ display_list: None,
+ stylist: box Stylist::new(),
+ parallel_traversal: parallel_traversal,
+ dirty: Rect::zero(),
+ })),
+ }
+ }
+
+ /// Starts listening on the port.
+ fn start(self) {
+ while self.handle_request() {
+ // Loop indefinitely.
+ }
+ }
+
+ // Create a layout context for use in building display lists, hit testing, &c.
+ fn build_shared_layout_context(&self, rw_data: &LayoutTaskData, reflow_root: &LayoutNode, url: &Url) -> SharedLayoutContext {
+ SharedLayoutContext {
+ image_cache: rw_data.local_image_cache.clone(),
+ screen_size: rw_data.screen_size.clone(),
+ constellation_chan: self.constellation_chan.clone(),
+ layout_chan: self.chan.clone(),
+ font_cache_task: self.font_cache_task.clone(),
+ stylist: &*rw_data.stylist,
+ url: (*url).clone(),
+ reflow_root: OpaqueNodeMethods::from_layout_node(reflow_root),
+ opts: self.opts.clone(),
+ dirty: Rect::zero(),
+ }
+ }
+
+ /// Receives and dispatches messages from the script and constellation tasks
+ fn handle_request(&self) -> bool {
+ enum PortToRead {
+ Pipeline,
+ Script,
+ }
+
+ let port_to_read = {
+ let sel = Select::new();
+ let mut port1 = sel.handle(&self.port);
+ let mut port2 = sel.handle(&self.pipeline_port);
+ unsafe {
+ port1.add();
+ port2.add();
+ }
+ let ret = sel.wait();
+ if ret == port1.id() {
+ Script
+ } else if ret == port2.id() {
+ Pipeline
+ } else {
+ fail!("invalid select result");
+ }
+ };
+
+ match port_to_read {
+ Pipeline => match self.pipeline_port.recv() {
+ layout_traits::ExitNowMsg => self.handle_script_request(ExitNowMsg),
+ },
+ Script => {
+ let msg = self.port.recv();
+ self.handle_script_request(msg)
+ }
+ }
+ }
+
+ /// Receives and dispatches messages from the script task.
+ fn handle_script_request(&self, request: Msg) -> bool {
+ match request {
+ AddStylesheetMsg(sheet) => self.handle_add_stylesheet(sheet),
+ GetRPCMsg(response_chan) => {
+ response_chan.send(
+ box LayoutRPCImpl(
+ self.rw_data.clone()) as Box<LayoutRPC + Send>);
+ },
+ ReflowMsg(data) => {
+ profile(time::LayoutPerformCategory, self.time_profiler_chan.clone(), || {
+ self.handle_reflow(&*data);
+ });
+ },
+ ReapLayoutDataMsg(dead_layout_data) => {
+ unsafe {
+ LayoutTask::handle_reap_layout_data(dead_layout_data)
+ }
+ },
+ PrepareToExitMsg(response_chan) => {
+ debug!("layout: PrepareToExitMsg received");
+ self.prepare_to_exit(response_chan);
+ return false
+ },
+ ExitNowMsg => {
+ debug!("layout: ExitNowMsg received");
+ self.exit_now();
+ return false
+ }
+ }
+
+ true
+ }
+
+ /// Enters a quiescent state in which no new messages except for `ReapLayoutDataMsg` will be
+ /// processed until an `ExitNowMsg` is received. A pong is immediately sent on the given
+ /// response channel.
+ fn prepare_to_exit(&self, response_chan: Sender<()>) {
+ response_chan.send(());
+ loop {
+ match self.port.recv() {
+ ReapLayoutDataMsg(dead_layout_data) => {
+ unsafe {
+ LayoutTask::handle_reap_layout_data(dead_layout_data)
+ }
+ }
+ ExitNowMsg => {
+ debug!("layout task is exiting...");
+ self.exit_now();
+ break
+ }
+ _ => {
+ fail!("layout: message that wasn't `ExitNowMsg` received after \
+ `PrepareToExitMsg`")
+ }
+ }
+ }
+ }
+
+ /// Shuts down the layout task now. If there are any DOM nodes left, layout will now (safely)
+ /// crash.
+ fn exit_now(&self) {
+ let (response_chan, response_port) = channel();
+
+ {
+ let mut rw_data = self.rw_data.lock();
+ match rw_data.deref_mut().parallel_traversal {
+ None => {}
+ Some(ref mut traversal) => traversal.shutdown(),
+ }
+ }
+
+ self.render_chan.send(render_task::ExitMsg(Some(response_chan)));
+ response_port.recv()
+ }
+
+ fn handle_add_stylesheet(&self, sheet: Stylesheet) {
+ // Find all font-face rules and notify the font cache of them.
+ // GWTODO: Need to handle unloading web fonts (when we handle unloading stylesheets!)
+ iter_font_face_rules(&sheet, |family, url| {
+ self.font_cache_task.add_web_font(family.to_string(), url.clone());
+ });
+ let mut rw_data = self.rw_data.lock();
+ rw_data.stylist.add_stylesheet(sheet, AuthorOrigin);
+ }
+
+ /// Retrieves the flow tree root from the root node.
+ fn get_layout_root(&self, node: LayoutNode) -> FlowRef {
+ let mut layout_data_ref = node.mutate_layout_data();
+ let result = match &mut *layout_data_ref {
+ &Some(ref mut layout_data) => {
+ mem::replace(&mut layout_data.data.flow_construction_result, NoConstructionResult)
+ }
+ &None => fail!("no layout data for root node"),
+ };
+ let mut flow = match result {
+ FlowConstructionResult(mut flow, abs_descendants) => {
+ // Note: Assuming that the root has display 'static' (as per
+ // CSS Section 9.3.1). Otherwise, if it were absolutely
+ // positioned, it would return a reference to itself in
+ // `abs_descendants` and would lead to a circular reference.
+ // Set Root as CB for any remaining absolute descendants.
+ flow.set_abs_descendants(abs_descendants);
+ flow
+ }
+ _ => fail!("Flow construction didn't result in a flow at the root of the tree!"),
+ };
+ flow.get_mut().mark_as_root();
+ flow
+ }
+
+ /// Performs layout constraint solving.
+ ///
+ /// This corresponds to `Reflow()` in Gecko and `layout()` in WebKit/Blink and should be
+ /// benchmarked against those two. It is marked `#[inline(never)]` to aid profiling.
+ #[inline(never)]
+ fn solve_constraints<'a>(&self,
+ layout_root: &mut Flow,
+ layout_context: &'a LayoutContext<'a>) {
+ let _scope = layout_debug_scope!("solve_constraints");
+
+ if layout_context.shared.opts.bubble_inline_sizes_separately {
+ let mut traversal = BubbleISizesTraversal {
+ layout_context: layout_context,
+ };
+ layout_root.traverse_postorder(&mut traversal);
+ }
+
+ // FIXME(kmc): We want to prune nodes without the Reflow restyle damage
+ // bit, but FloatContext values can't be reused, so we need to
+ // recompute them every time.
+ // NOTE: this currently computes borders, so any pruning should separate that operation
+ // out.
+ {
+ let mut traversal = AssignISizesTraversal {
+ layout_context: layout_context,
+ };
+ layout_root.traverse_preorder(&mut traversal);
+ }
+
+ // FIXME(pcwalton): Prune this pass as well.
+ {
+ let mut traversal = AssignBSizesAndStoreOverflowTraversal {
+ layout_context: layout_context,
+ };
+ layout_root.traverse_postorder(&mut traversal);
+ }
+ }
+
+ /// Performs layout constraint solving in parallel.
+ ///
+ /// This corresponds to `Reflow()` in Gecko and `layout()` in WebKit/Blink and should be
+ /// benchmarked against those two. It is marked `#[inline(never)]` to aid profiling.
+ #[inline(never)]
+ fn solve_constraints_parallel(&self,
+ rw_data: &mut LayoutTaskData,
+ layout_root: &mut FlowRef,
+ shared_layout_context: &SharedLayoutContext) {
+ if shared_layout_context.opts.bubble_inline_sizes_separately {
+ let mut traversal = BubbleISizesTraversal {
+ layout_context: &LayoutContext::new(shared_layout_context),
+ };
+ layout_root.get_mut().traverse_postorder(&mut traversal);
+ }
+
+ match rw_data.parallel_traversal {
+ None => fail!("solve_contraints_parallel() called with no parallel traversal ready"),
+ Some(ref mut traversal) => {
+ // NOTE: this currently computes borders, so any pruning should separate that
+ // operation out.
+ parallel::traverse_flow_tree_preorder(layout_root,
+ self.time_profiler_chan.clone(),
+ shared_layout_context,
+ traversal);
+ }
+ }
+ }
+
+ /// Verifies that every node was either marked as a leaf or as a nonleaf in the flow tree.
+ /// This is only on in debug builds.
+ #[inline(never)]
+ #[cfg(debug)]
+ fn verify_flow_tree(&self, layout_root: &mut FlowRef) {
+ let mut traversal = FlowTreeVerificationTraversal;
+ layout_root.traverse_preorder(&mut traversal);
+ }
+
+ #[cfg(not(debug))]
+ fn verify_flow_tree(&self, _: &mut FlowRef) {
+ }
+
+ /// The high-level routine that performs layout tasks.
+ fn handle_reflow(&self, data: &Reflow) {
+ // FIXME: Isolate this transmutation into a "bridge" module.
+ // FIXME(rust#16366): The following line had to be moved because of a
+ // rustc bug. It should be in the next unsafe block.
+ let mut node: JS<Node> = unsafe { JS::from_trusted_node_address(data.document_root) };
+ let node: &mut LayoutNode = unsafe {
+ mem::transmute(&mut node)
+ };
+
+ debug!("layout: received layout request for: {:s}", data.url.serialize());
+ debug!("layout: damage is {:?}", data.damage);
+ debug!("layout: parsed Node tree");
+ debug!("{:?}", node.dump());
+
+ let mut rw_data = self.rw_data.lock();
+
+ {
+ // Reset the image cache.
+ let mut local_image_cache = rw_data.local_image_cache.lock();
+ local_image_cache.next_round(self.make_on_image_available_cb());
+ }
+
+ // true => Do the reflow with full style damage, because content
+ // changed or the window was resized.
+ let mut all_style_damage = match data.damage.level {
+ ContentChangedDocumentDamage => true,
+ _ => false
+ };
+
+ // TODO: Calculate the "actual viewport":
+ // http://www.w3.org/TR/css-device-adapt/#actual-viewport
+ let viewport_size = data.window_size.initial_viewport;
+
+ let current_screen_size = Size2D(Au::from_frac32_px(viewport_size.width.get()),
+ Au::from_frac32_px(viewport_size.height.get()));
+ if rw_data.screen_size != current_screen_size {
+ all_style_damage = true
+ }
+ rw_data.screen_size = current_screen_size;
+
+ // Create a layout context for use throughout the following passes.
+ let mut shared_layout_ctx = self.build_shared_layout_context(rw_data.deref(), node, &data.url);
+
+ let mut layout_root = profile(time::LayoutStyleRecalcCategory,
+ self.time_profiler_chan.clone(),
+ || {
+ // Perform CSS selector matching and flow construction.
+ let rw_data = rw_data.deref_mut();
+ match rw_data.parallel_traversal {
+ None => {
+ let layout_ctx = LayoutContext::new(&shared_layout_ctx);
+ let mut applicable_declarations = ApplicableDeclarations::new();
+ node.recalc_style_for_subtree(&*rw_data.stylist,
+ &layout_ctx,
+ &mut applicable_declarations,
+ None)
+ }
+ Some(ref mut traversal) => {
+ parallel::recalc_style_for_subtree(node, &mut shared_layout_ctx, traversal)
+ }
+ }
+
+ self.get_layout_root((*node).clone())
+ });
+
+ // Verification of the flow tree, which ensures that all nodes were either marked as leaves
+ // or as non-leaves. This becomes a no-op in release builds. (It is inconsequential to
+ // memory safety but is a useful debugging tool.)
+ self.verify_flow_tree(&mut layout_root);
+
+ if self.opts.trace_layout {
+ layout_debug::begin_trace(layout_root.clone());
+ }
+
+ // Propagate damage.
+ profile(time::LayoutDamagePropagateCategory, self.time_profiler_chan.clone(), || {
+ layout_root.get_mut().traverse_preorder(&mut PropagateDamageTraversal {
+ all_style_damage: all_style_damage
+ });
+ layout_root.get_mut().traverse_postorder(&mut ComputeDamageTraversal.clone());
+ });
+
+ // Perform the primary layout passes over the flow tree to compute the locations of all
+ // the boxes.
+ profile(time::LayoutMainCategory, self.time_profiler_chan.clone(), || {
+ let rw_data = rw_data.deref_mut();
+ match rw_data.parallel_traversal {
+ None => {
+ // Sequential mode.
+ let layout_ctx = LayoutContext::new(&shared_layout_ctx);
+ self.solve_constraints(layout_root.get_mut(), &layout_ctx)
+ }
+ Some(_) => {
+ // Parallel mode.
+ self.solve_constraints_parallel(rw_data, &mut layout_root, &mut shared_layout_ctx)
+ }
+ }
+ });
+
+ // Build the display list if necessary, and send it to the renderer.
+ if data.goal == ReflowForDisplay {
+ let writing_mode = flow::base(layout_root.get()).writing_mode;
+ profile(time::LayoutDispListBuildCategory, self.time_profiler_chan.clone(), || {
+ shared_layout_ctx.dirty = flow::base(layout_root.get()).position.to_physical(
+ writing_mode, rw_data.screen_size);
+ flow::mut_base(layout_root.get_mut()).abs_position =
+ LogicalPoint::zero(writing_mode).to_physical(writing_mode, rw_data.screen_size);
+
+ let rw_data = rw_data.deref_mut();
+ match rw_data.parallel_traversal {
+ None => {
+ let layout_ctx = LayoutContext::new(&shared_layout_ctx);
+ let mut traversal = BuildDisplayListTraversal {
+ layout_context: &layout_ctx,
+ };
+ traversal.process(layout_root.get_mut());
+ }
+ Some(ref mut traversal) => {
+ parallel::build_display_list_for_subtree(&mut layout_root,
+ self.time_profiler_chan.clone(),
+ &mut shared_layout_ctx,
+ traversal);
+ }
+ }
+
+ let root_display_list =
+ mem::replace(&mut flow::mut_base(layout_root.get_mut()).display_list,
+ DisplayList::new());
+ root_display_list.debug();
+ let display_list = Arc::new(root_display_list.flatten(ContentStackingLevel));
+
+ // FIXME(pcwalton): This is really ugly and can't handle overflow: scroll. Refactor
+ // it with extreme prejudice.
+ let mut color = color::rgba(1.0, 1.0, 1.0, 1.0);
+ for child in node.traverse_preorder() {
+ if child.type_id() == Some(ElementNodeTypeId(HTMLHtmlElementTypeId)) ||
+ child.type_id() == Some(ElementNodeTypeId(HTMLBodyElementTypeId)) {
+ let element_bg_color = {
+ let thread_safe_child = ThreadSafeLayoutNode::new(&child);
+ thread_safe_child.style()
+ .resolve_color(thread_safe_child.style()
+ .get_background()
+ .background_color)
+ .to_gfx_color()
+ };
+ match element_bg_color {
+ color::rgba(0., 0., 0., 0.) => {}
+ _ => {
+ color = element_bg_color;
+ break;
+ }
+ }
+ }
+ }
+
+ let root_size = {
+ let root_flow = flow::base(layout_root.get());
+ root_flow.position.size.to_physical(root_flow.writing_mode)
+ };
+ let root_size = Size2D(root_size.width.to_nearest_px() as uint,
+ root_size.height.to_nearest_px() as uint);
+ let render_layer = RenderLayer {
+ id: layout_root.get().layer_id(0),
+ display_list: display_list.clone(),
+ position: Rect(Point2D(0u, 0u), root_size),
+ background_color: color,
+ scroll_policy: Scrollable,
+ };
+
+ rw_data.display_list = Some(display_list.clone());
+
+ // TODO(pcwalton): Eventually, when we have incremental reflow, this will have to
+ // be smarter in order to handle retained layer contents properly from reflow to
+ // reflow.
+ let mut layers = SmallVec1::new();
+ layers.push(render_layer);
+ for layer in mem::replace(&mut flow::mut_base(layout_root.get_mut()).layers,
+ DList::new()).move_iter() {
+ layers.push(layer)
+ }
+
+ debug!("Layout done!");
+
+ self.render_chan.send(RenderInitMsg(layers));
+ });
+ }
+
+ if self.opts.trace_layout {
+ layout_debug::end_trace();
+ }
+
+ // Tell script that we're done.
+ //
+ // FIXME(pcwalton): This should probably be *one* channel, but we can't fix this without
+ // either select or a filtered recv() that only looks for messages of a given type.
+ data.script_join_chan.send(());
+ let ScriptControlChan(ref chan) = data.script_chan;
+ chan.send(ReflowCompleteMsg(self.id, data.id));
+ }
+
+
+ // When images can't be loaded in time to display they trigger
+ // this callback in some task somewhere. This will send a message
+ // to the script task, and ultimately cause the image to be
+ // re-requested. We probably don't need to go all the way back to
+ // the script task for this.
+ fn make_on_image_available_cb(&self) -> Box<ImageResponder+Send> {
+ // This has a crazy signature because the image cache needs to
+ // make multiple copies of the callback, and the dom event
+ // channel is not a copyable type, so this is actually a
+ // little factory to produce callbacks
+ box LayoutImageResponder {
+ id: self.id.clone(),
+ script_chan: self.script_chan.clone(),
+ } as Box<ImageResponder+Send>
+ }
+
+ /// Handles a message to destroy layout data. Layout data must be destroyed on *this* task
+ /// because it contains local managed pointers.
+ unsafe fn handle_reap_layout_data(layout_data: LayoutDataRef) {
+ let mut layout_data_ref = layout_data.borrow_mut();
+ let _: Option<LayoutDataWrapper> = mem::transmute(
+ mem::replace(&mut *layout_data_ref, None));
+ }
+}
+
+struct LayoutRPCImpl(Arc<Mutex<LayoutTaskData>>);
+
+impl LayoutRPC for LayoutRPCImpl {
+ // The neat thing here is that in order to answer the following two queries we only
+ // need to compare nodes for equality. Thus we can safely work only with `OpaqueNode`.
+ fn content_box(&self, node: TrustedNodeAddress) -> ContentBoxResponse {
+ let node: OpaqueNode = OpaqueNodeMethods::from_script_node(node);
+ fn union_boxes_for_node(accumulator: &mut Option<Rect<Au>>,
+ mut iter: DisplayItemIterator,
+ node: OpaqueNode) {
+ for item in iter {
+ union_boxes_for_node(accumulator, item.children(), node);
+ if item.base().node == node {
+ match *accumulator {
+ None => *accumulator = Some(item.base().bounds),
+ Some(ref mut acc) => *acc = acc.union(&item.base().bounds),
+ }
+ }
+ }
+ }
+
+ let mut rect = None;
+ {
+ let &LayoutRPCImpl(ref rw_data) = self;
+ let rw_data = rw_data.lock();
+ match rw_data.display_list {
+ None => fail!("no display list!"),
+ Some(ref display_list) => {
+ union_boxes_for_node(&mut rect, display_list.iter(), node)
+ }
+ }
+ }
+ ContentBoxResponse(rect.unwrap_or(Rect::zero()))
+ }
+
+ /// Requests the dimensions of all the content boxes, as in the `getClientRects()` call.
+ fn content_boxes(&self, node: TrustedNodeAddress) -> ContentBoxesResponse {
+ let node: OpaqueNode = OpaqueNodeMethods::from_script_node(node);
+
+ fn add_boxes_for_node(accumulator: &mut Vec<Rect<Au>>,
+ mut iter: DisplayItemIterator,
+ node: OpaqueNode) {
+ for item in iter {
+ add_boxes_for_node(accumulator, item.children(), node);
+ if item.base().node == node {
+ accumulator.push(item.base().bounds)
+ }
+ }
+ }
+
+ let mut boxes = vec!();
+ {
+ let &LayoutRPCImpl(ref rw_data) = self;
+ let rw_data = rw_data.lock();
+ match rw_data.display_list {
+ None => fail!("no display list!"),
+ Some(ref display_list) => {
+ add_boxes_for_node(&mut boxes, display_list.iter(), node)
+ }
+ }
+ }
+ ContentBoxesResponse(boxes)
+ }
+
+ /// Requests the node containing the point of interest
+ fn hit_test(&self, _: TrustedNodeAddress, point: Point2D<f32>) -> Result<HitTestResponse, ()> {
+ fn hit_test<'a,I:Iterator<&'a DisplayItem>>(x: Au, y: Au, mut iterator: I)
+ -> Option<HitTestResponse> {
+ for item in iterator {
+ match *item {
+ ClipDisplayItemClass(ref cc) => {
+ if geometry::rect_contains_point(cc.base.bounds, Point2D(x, y)) {
+ let ret = hit_test(x, y, cc.children.list.iter().rev());
+ if !ret.is_none() {
+ return ret
+ }
+ }
+ continue
+ }
+ _ => {}
+ }
+
+ let bounds = item.bounds();
+
+ // TODO(tikue): This check should really be performed by a method of
+ // DisplayItem.
+ if x < bounds.origin.x + bounds.size.width &&
+ bounds.origin.x <= x &&
+ y < bounds.origin.y + bounds.size.height &&
+ bounds.origin.y <= y {
+ return Some(HitTestResponse(item.base()
+ .node
+ .to_untrusted_node_address()))
+ }
+ }
+ let ret: Option<HitTestResponse> = None;
+ ret
+ }
+ let (x, y) = (Au::from_frac_px(point.x as f64),
+ Au::from_frac_px(point.y as f64));
+
+ let resp = {
+ let &LayoutRPCImpl(ref rw_data) = self;
+ let rw_data = rw_data.lock();
+ match rw_data.display_list {
+ None => fail!("no display list!"),
+ Some(ref display_list) => hit_test(x, y, display_list.list.iter().rev()),
+ }
+ };
+
+ if resp.is_some() {
+ return Ok(resp.unwrap());
+ }
+ Err(())
+ }
+
+ fn mouse_over(&self, _: TrustedNodeAddress, point: Point2D<f32>) -> Result<MouseOverResponse, ()> {
+ fn mouse_over_test<'a,
+ I:Iterator<&'a DisplayItem>>(
+ x: Au,
+ y: Au,
+ mut iterator: I,
+ result: &mut Vec<UntrustedNodeAddress>) {
+ for item in iterator {
+ match *item {
+ ClipDisplayItemClass(ref cc) => {
+ mouse_over_test(x, y, cc.children.list.iter().rev(), result);
+ }
+ _ => {
+ let bounds = item.bounds();
+
+ // TODO(tikue): This check should really be performed by a method
+ // of DisplayItem.
+ if x < bounds.origin.x + bounds.size.width &&
+ bounds.origin.x <= x &&
+ y < bounds.origin.y + bounds.size.height &&
+ bounds.origin.y <= y {
+ result.push(item.base()
+ .node
+ .to_untrusted_node_address());
+ }
+ }
+ }
+ }
+ }
+
+ let mut mouse_over_list: Vec<UntrustedNodeAddress> = vec!();
+ let (x, y) = (Au::from_frac_px(point.x as f64), Au::from_frac_px(point.y as f64));
+
+ {
+ let &LayoutRPCImpl(ref rw_data) = self;
+ let rw_data = rw_data.lock();
+ match rw_data.display_list {
+ None => fail!("no display list!"),
+ Some(ref display_list) => {
+ mouse_over_test(x,
+ y,
+ display_list.list.iter().rev(),
+ &mut mouse_over_list);
+ }
+ };
+ }
+
+ if mouse_over_list.is_empty() {
+ Err(())
+ } else {
+ Ok(MouseOverResponse(mouse_over_list))
+ }
+ }
+}
diff --git a/components/layout/lib.rs b/components/layout/lib.rs
new file mode 100644
index 00000000000..a782f4e1355
--- /dev/null
+++ b/components/layout/lib.rs
@@ -0,0 +1,68 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![comment = "The Servo Parallel Browser Project"]
+#![license = "MPL"]
+
+#![feature(globs, macro_rules, phase, thread_local, unsafe_destructor)]
+
+#[phase(plugin, link)]
+extern crate log;
+
+extern crate debug;
+
+extern crate geom;
+extern crate gfx;
+extern crate layout_traits;
+extern crate script;
+extern crate script_traits;
+extern crate serialize;
+extern crate style;
+#[phase(plugin)]
+extern crate servo_macros = "macros";
+extern crate servo_net = "net";
+extern crate servo_msg = "msg";
+#[phase(plugin, link)]
+extern crate servo_util = "util";
+
+extern crate collections;
+extern crate green;
+extern crate libc;
+extern crate sync;
+extern crate url;
+
+// Listed first because of macro definitions
+pub mod layout_debug;
+
+pub mod block;
+pub mod construct;
+pub mod context;
+pub mod floats;
+pub mod flow;
+pub mod flow_list;
+pub mod flow_ref;
+pub mod fragment;
+pub mod layout_task;
+pub mod inline;
+pub mod model;
+pub mod parallel;
+pub mod table_wrapper;
+pub mod table;
+pub mod table_caption;
+pub mod table_colgroup;
+pub mod table_rowgroup;
+pub mod table_row;
+pub mod table_cell;
+pub mod text;
+pub mod util;
+pub mod incremental;
+pub mod wrapper;
+pub mod extra;
+
+pub mod css {
+ mod node_util;
+
+ pub mod matching;
+ pub mod node_style;
+}
diff --git a/components/layout/model.rs b/components/layout/model.rs
new file mode 100644
index 00000000000..23647b1b77d
--- /dev/null
+++ b/components/layout/model.rs
@@ -0,0 +1,337 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Borders, padding, and margins.
+
+#![deny(unsafe_block)]
+
+use fragment::Fragment;
+
+use computed = style::computed_values;
+use geom::SideOffsets2D;
+use style::computed_values::{LPA_Auto, LPA_Length, LPA_Percentage, LP_Length, LP_Percentage};
+use style::ComputedValues;
+use servo_util::geometry::Au;
+use servo_util::geometry;
+use servo_util::logical_geometry::LogicalMargin;
+use std::fmt;
+
+/// A collapsible margin. See CSS 2.1 § 8.3.1.
+pub struct AdjoiningMargins {
+ /// The value of the greatest positive margin.
+ pub most_positive: Au,
+
+ /// The actual value (not the absolute value) of the negative margin with the largest absolute
+ /// value. Since this is not the absolute value, this is always zero or negative.
+ pub most_negative: Au,
+}
+
+impl AdjoiningMargins {
+ pub fn new() -> AdjoiningMargins {
+ AdjoiningMargins {
+ most_positive: Au(0),
+ most_negative: Au(0),
+ }
+ }
+
+ pub fn from_margin(margin_value: Au) -> AdjoiningMargins {
+ if margin_value >= Au(0) {
+ AdjoiningMargins {
+ most_positive: margin_value,
+ most_negative: Au(0),
+ }
+ } else {
+ AdjoiningMargins {
+ most_positive: Au(0),
+ most_negative: margin_value,
+ }
+ }
+ }
+
+ pub fn union(&mut self, other: AdjoiningMargins) {
+ self.most_positive = geometry::max(self.most_positive, other.most_positive);
+ self.most_negative = geometry::min(self.most_negative, other.most_negative)
+ }
+
+ pub fn collapse(&self) -> Au {
+ self.most_positive + self.most_negative
+ }
+}
+
+/// Represents the block-start and block-end margins of a flow with collapsible margins. See CSS 2.1 § 8.3.1.
+pub enum CollapsibleMargins {
+ /// Margins may not collapse with this flow.
+ NoCollapsibleMargins(Au, Au),
+
+ /// Both the block-start and block-end margins (specified here in that order) may collapse, but the
+ /// margins do not collapse through this flow.
+ MarginsCollapse(AdjoiningMargins, AdjoiningMargins),
+
+ /// Margins collapse *through* this flow. This means, essentially, that the flow doesn’t
+ /// have any border, padding, or out-of-flow (floating or positioned) content
+ MarginsCollapseThrough(AdjoiningMargins),
+}
+
+impl CollapsibleMargins {
+ pub fn new() -> CollapsibleMargins {
+ NoCollapsibleMargins(Au(0), Au(0))
+ }
+}
+
+enum FinalMarginState {
+ MarginsCollapseThroughFinalMarginState,
+ BottomMarginCollapsesFinalMarginState,
+}
+
+pub struct MarginCollapseInfo {
+ pub state: MarginCollapseState,
+ pub block_start_margin: AdjoiningMargins,
+ pub margin_in: AdjoiningMargins,
+}
+
+impl MarginCollapseInfo {
+ /// TODO(#2012, pcwalton): Remove this method once `fragment` is not an `Option`.
+ pub fn new() -> MarginCollapseInfo {
+ MarginCollapseInfo {
+ state: AccumulatingCollapsibleTopMargin,
+ block_start_margin: AdjoiningMargins::new(),
+ margin_in: AdjoiningMargins::new(),
+ }
+ }
+
+ pub fn initialize_block_start_margin(&mut self,
+ fragment: &Fragment,
+ can_collapse_block_start_margin_with_kids: bool) {
+ if !can_collapse_block_start_margin_with_kids {
+ self.state = AccumulatingMarginIn
+ }
+
+ self.block_start_margin = AdjoiningMargins::from_margin(fragment.margin.block_start)
+ }
+
+ pub fn finish_and_compute_collapsible_margins(mut self,
+ fragment: &Fragment,
+ can_collapse_block_end_margin_with_kids: bool)
+ -> (CollapsibleMargins, Au) {
+ let state = match self.state {
+ AccumulatingCollapsibleTopMargin => {
+ match fragment.style().content_block_size() {
+ LPA_Auto | LPA_Length(Au(0)) | LPA_Percentage(0.) => {
+ match fragment.style().min_block_size() {
+ LP_Length(Au(0)) | LP_Percentage(0.) => {
+ MarginsCollapseThroughFinalMarginState
+ },
+ _ => {
+ // If the fragment has non-zero min-block-size, margins may not
+ // collapse through it.
+ BottomMarginCollapsesFinalMarginState
+ }
+ }
+ },
+ _ => {
+ // If the fragment has an explicitly specified block-size, margins may not
+ // collapse through it.
+ BottomMarginCollapsesFinalMarginState
+ }
+ }
+ }
+ AccumulatingMarginIn => BottomMarginCollapsesFinalMarginState,
+ };
+
+ // Different logic is needed here depending on whether this flow can collapse its block-end
+ // margin with its children.
+ let block_end_margin = fragment.margin.block_end;
+ if !can_collapse_block_end_margin_with_kids {
+ match state {
+ MarginsCollapseThroughFinalMarginState => {
+ let advance = self.block_start_margin.collapse();
+ self.margin_in.union(AdjoiningMargins::from_margin(block_end_margin));
+ (MarginsCollapse(self.block_start_margin, self.margin_in), advance)
+ }
+ BottomMarginCollapsesFinalMarginState => {
+ let advance = self.margin_in.collapse();
+ self.margin_in.union(AdjoiningMargins::from_margin(block_end_margin));
+ (MarginsCollapse(self.block_start_margin, self.margin_in), advance)
+ }
+ }
+ } else {
+ match state {
+ MarginsCollapseThroughFinalMarginState => {
+ self.block_start_margin.union(AdjoiningMargins::from_margin(block_end_margin));
+ (MarginsCollapseThrough(self.block_start_margin), Au(0))
+ }
+ BottomMarginCollapsesFinalMarginState => {
+ self.margin_in.union(AdjoiningMargins::from_margin(block_end_margin));
+ (MarginsCollapse(self.block_start_margin, self.margin_in), Au(0))
+ }
+ }
+ }
+ }
+
+ pub fn current_float_ceiling(&mut self) -> Au {
+ match self.state {
+ AccumulatingCollapsibleTopMargin => self.block_start_margin.collapse(),
+ AccumulatingMarginIn => self.margin_in.collapse(),
+ }
+ }
+
+ /// Adds the child's potentially collapsible block-start margin to the current margin state and
+ /// advances the Y offset by the appropriate amount to handle that margin. Returns the amount
+ /// that should be added to the Y offset during block layout.
+ pub fn advance_block_start_margin(&mut self, child_collapsible_margins: &CollapsibleMargins) -> Au {
+ match (self.state, *child_collapsible_margins) {
+ (AccumulatingCollapsibleTopMargin, NoCollapsibleMargins(block_start, _)) => {
+ self.state = AccumulatingMarginIn;
+ block_start
+ }
+ (AccumulatingCollapsibleTopMargin, MarginsCollapse(block_start, _)) => {
+ self.block_start_margin.union(block_start);
+ self.state = AccumulatingMarginIn;
+ Au(0)
+ }
+ (AccumulatingMarginIn, NoCollapsibleMargins(block_start, _)) => {
+ let previous_margin_value = self.margin_in.collapse();
+ self.margin_in = AdjoiningMargins::new();
+ previous_margin_value + block_start
+ }
+ (AccumulatingMarginIn, MarginsCollapse(block_start, _)) => {
+ self.margin_in.union(block_start);
+ let margin_value = self.margin_in.collapse();
+ self.margin_in = AdjoiningMargins::new();
+ margin_value
+ }
+ (_, MarginsCollapseThrough(_)) => {
+ // For now, we ignore this; this will be handled by `advance_block-end_margin` below.
+ Au(0)
+ }
+ }
+ }
+
+ /// Adds the child's potentially collapsible block-end margin to the current margin state and
+ /// advances the Y offset by the appropriate amount to handle that margin. Returns the amount
+ /// that should be added to the Y offset during block layout.
+ pub fn advance_block_end_margin(&mut self, child_collapsible_margins: &CollapsibleMargins) -> Au {
+ match (self.state, *child_collapsible_margins) {
+ (AccumulatingCollapsibleTopMargin, NoCollapsibleMargins(..)) |
+ (AccumulatingCollapsibleTopMargin, MarginsCollapse(..)) => {
+ // Can't happen because the state will have been replaced with
+ // `AccumulatingMarginIn` above.
+ fail!("should not be accumulating collapsible block_start margins anymore!")
+ }
+ (AccumulatingCollapsibleTopMargin, MarginsCollapseThrough(margin)) => {
+ self.block_start_margin.union(margin);
+ Au(0)
+ }
+ (AccumulatingMarginIn, NoCollapsibleMargins(_, block_end)) => {
+ assert_eq!(self.margin_in.most_positive, Au(0));
+ assert_eq!(self.margin_in.most_negative, Au(0));
+ block_end
+ }
+ (AccumulatingMarginIn, MarginsCollapse(_, block_end)) |
+ (AccumulatingMarginIn, MarginsCollapseThrough(block_end)) => {
+ self.margin_in.union(block_end);
+ Au(0)
+ }
+ }
+ }
+}
+
+pub enum MarginCollapseState {
+ AccumulatingCollapsibleTopMargin,
+ AccumulatingMarginIn,
+}
+
+/// Intrinsic inline-sizes, which consist of minimum and preferred.
+#[deriving(Encodable)]
+pub struct IntrinsicISizes {
+ /// The *minimum inline-size* of the content.
+ pub minimum_inline_size: Au,
+ /// The *preferred inline-size* of the content.
+ pub preferred_inline_size: Au,
+ /// The estimated sum of borders, padding, and margins. Some calculations use this information
+ /// when computing intrinsic inline-sizes.
+ pub surround_inline_size: Au,
+}
+
+impl fmt::Show for IntrinsicISizes {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "min={}, pref={}, surr={}", self.minimum_inline_size, self.preferred_inline_size, self.surround_inline_size)
+ }
+}
+
+impl IntrinsicISizes {
+ pub fn new() -> IntrinsicISizes {
+ IntrinsicISizes {
+ minimum_inline_size: Au(0),
+ preferred_inline_size: Au(0),
+ surround_inline_size: Au(0),
+ }
+ }
+
+ pub fn total_minimum_inline_size(&self) -> Au {
+ self.minimum_inline_size + self.surround_inline_size
+ }
+
+ pub fn total_preferred_inline_size(&self) -> Au {
+ self.preferred_inline_size + self.surround_inline_size
+ }
+}
+
+/// Useful helper data type when computing values for blocks and positioned elements.
+pub enum MaybeAuto {
+ Auto,
+ Specified(Au),
+}
+
+impl MaybeAuto {
+ #[inline]
+ pub fn from_style(length: computed::LengthOrPercentageOrAuto, containing_length: Au)
+ -> MaybeAuto {
+ match length {
+ computed::LPA_Auto => Auto,
+ computed::LPA_Percentage(percent) => Specified(containing_length.scale_by(percent)),
+ computed::LPA_Length(length) => Specified(length)
+ }
+ }
+
+ #[inline]
+ pub fn specified_or_default(&self, default: Au) -> Au {
+ match *self {
+ Auto => default,
+ Specified(value) => value,
+ }
+ }
+
+ #[inline]
+ pub fn specified_or_zero(&self) -> Au {
+ self.specified_or_default(Au::new(0))
+ }
+}
+
+pub fn specified_or_none(length: computed::LengthOrPercentageOrNone, containing_length: Au) -> Option<Au> {
+ match length {
+ computed::LPN_None => None,
+ computed::LPN_Percentage(percent) => Some(containing_length.scale_by(percent)),
+ computed::LPN_Length(length) => Some(length),
+ }
+}
+
+pub fn specified(length: computed::LengthOrPercentage, containing_length: Au) -> Au {
+ match length {
+ computed::LP_Length(length) => length,
+ computed::LP_Percentage(p) => containing_length.scale_by(p)
+ }
+}
+
+#[inline]
+pub fn padding_from_style(style: &ComputedValues, containing_block_inline_size: Au)
+ -> LogicalMargin<Au> {
+ let padding_style = style.get_padding();
+ LogicalMargin::from_physical(style.writing_mode, SideOffsets2D::new(
+ specified(padding_style.padding_top, containing_block_inline_size),
+ specified(padding_style.padding_right, containing_block_inline_size),
+ specified(padding_style.padding_bottom, containing_block_inline_size),
+ specified(padding_style.padding_left, containing_block_inline_size)))
+}
+
diff --git a/components/layout/parallel.rs b/components/layout/parallel.rs
new file mode 100644
index 00000000000..a2786e8ba91
--- /dev/null
+++ b/components/layout/parallel.rs
@@ -0,0 +1,561 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Implements parallel traversals over the DOM and flow trees.
+//!
+//! This code is highly unsafe. Keep this file small and easy to audit.
+
+use css::matching::{ApplicableDeclarations, CannotShare, MatchMethods, StyleWasShared};
+use construct::FlowConstructor;
+use context::{LayoutContext, SharedLayoutContext};
+use extra::LayoutAuxMethods;
+use flow::{Flow, MutableFlowUtils, PreorderFlowTraversal, PostorderFlowTraversal};
+use flow;
+use flow_ref::FlowRef;
+use layout_task::{AssignBSizesAndStoreOverflowTraversal, AssignISizesTraversal};
+use layout_task::{BubbleISizesTraversal};
+use util::{LayoutDataAccess, LayoutDataWrapper, OpaqueNodeMethods};
+use wrapper::{layout_node_to_unsafe_layout_node, layout_node_from_unsafe_layout_node, LayoutNode, PostorderNodeMutTraversal};
+use wrapper::{ThreadSafeLayoutNode, UnsafeLayoutNode};
+
+use gfx::display_list::OpaqueNode;
+use servo_util::time::{TimeProfilerChan, profile};
+use servo_util::time;
+use servo_util::workqueue::{WorkQueue, WorkUnit, WorkerProxy};
+use std::mem;
+use std::ptr;
+use std::sync::atomics::{AtomicInt, Relaxed, SeqCst};
+use style::TNode;
+
+#[allow(dead_code)]
+fn static_assertion(node: UnsafeLayoutNode) {
+ unsafe {
+ let _: UnsafeFlow = ::std::intrinsics::transmute(node);
+ }
+}
+
+/// Vtable + pointer representation of a Flow trait object.
+pub type UnsafeFlow = (uint, uint);
+
+fn null_unsafe_flow() -> UnsafeFlow {
+ (0, 0)
+}
+
+pub fn owned_flow_to_unsafe_flow(flow: *const FlowRef) -> UnsafeFlow {
+ unsafe {
+ mem::transmute_copy(&*flow)
+ }
+}
+
+pub fn mut_owned_flow_to_unsafe_flow(flow: *mut FlowRef) -> UnsafeFlow {
+ unsafe {
+ mem::transmute_copy(&*flow)
+ }
+}
+
+pub fn borrowed_flow_to_unsafe_flow(flow: &Flow) -> UnsafeFlow {
+ unsafe {
+ mem::transmute_copy(&flow)
+ }
+}
+
+pub fn mut_borrowed_flow_to_unsafe_flow(flow: &mut Flow) -> UnsafeFlow {
+ unsafe {
+ mem::transmute_copy(&flow)
+ }
+}
+
+/// Information that we need stored in each DOM node.
+pub struct DomParallelInfo {
+ /// The number of children that still need work done.
+ pub children_count: AtomicInt,
+}
+
+impl DomParallelInfo {
+ pub fn new() -> DomParallelInfo {
+ DomParallelInfo {
+ children_count: AtomicInt::new(0),
+ }
+ }
+}
+
+/// Information that we need stored in each flow.
+pub struct FlowParallelInfo {
+ /// The number of children that still need work done.
+ pub children_count: AtomicInt,
+ /// The number of children and absolute descendants that still need work done.
+ pub children_and_absolute_descendant_count: AtomicInt,
+ /// The address of the parent flow.
+ pub parent: UnsafeFlow,
+}
+
+impl FlowParallelInfo {
+ pub fn new() -> FlowParallelInfo {
+ FlowParallelInfo {
+ children_count: AtomicInt::new(0),
+ children_and_absolute_descendant_count: AtomicInt::new(0),
+ parent: null_unsafe_flow(),
+ }
+ }
+}
+
+/// A parallel bottom-up flow traversal.
+trait ParallelPostorderFlowTraversal : PostorderFlowTraversal {
+ /// Process current flow and potentially traverse its ancestors.
+ ///
+ /// If we are the last child that finished processing, recursively process
+ /// our parent. Else, stop.
+ /// Also, stop at the root (obviously :P).
+ ///
+ /// Thus, if we start with all the leaves of a tree, we end up traversing
+ /// the whole tree bottom-up because each parent will be processed exactly
+ /// once (by the last child that finishes processing).
+ ///
+ /// The only communication between siblings is that they both
+ /// fetch-and-subtract the parent's children count.
+ fn run_parallel(&mut self,
+ mut unsafe_flow: UnsafeFlow,
+ _: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>) {
+ loop {
+ unsafe {
+ // Get a real flow.
+ let flow: &mut FlowRef = mem::transmute(&unsafe_flow);
+
+ // Perform the appropriate traversal.
+ if self.should_process(flow.get_mut()) {
+ self.process(flow.get_mut());
+ }
+
+ let base = flow::mut_base(flow.get_mut());
+
+ // Reset the count of children for the next layout traversal.
+ base.parallel.children_count.store(base.children.len() as int, Relaxed);
+
+ // Possibly enqueue the parent.
+ let unsafe_parent = base.parallel.parent;
+ if unsafe_parent == null_unsafe_flow() {
+ // We're done!
+ break
+ }
+
+ // No, we're not at the root yet. Then are we the last child
+ // of our parent to finish processing? If so, we can continue
+ // on with our parent; otherwise, we've gotta wait.
+ let parent: &mut FlowRef = mem::transmute(&unsafe_parent);
+ let parent_base = flow::mut_base(parent.get_mut());
+ if parent_base.parallel.children_count.fetch_sub(1, SeqCst) == 1 {
+ // We were the last child of our parent. Reflow our parent.
+ unsafe_flow = unsafe_parent
+ } else {
+ // Stop.
+ break
+ }
+ }
+ }
+ }
+}
+
+/// A parallel top-down flow traversal.
+trait ParallelPreorderFlowTraversal : PreorderFlowTraversal {
+ fn run_parallel(&mut self,
+ unsafe_flow: UnsafeFlow,
+ proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>);
+
+ #[inline(always)]
+ fn run_parallel_helper(&mut self,
+ unsafe_flow: UnsafeFlow,
+ proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>,
+ top_down_func: extern "Rust" fn(UnsafeFlow,
+ &mut WorkerProxy<*const SharedLayoutContext,
+ UnsafeFlow>),
+ bottom_up_func: extern "Rust" fn(UnsafeFlow,
+ &mut WorkerProxy<*const SharedLayoutContext,
+ UnsafeFlow>)) {
+ let mut had_children = false;
+ unsafe {
+ // Get a real flow.
+ let flow: &mut FlowRef = mem::transmute(&unsafe_flow);
+
+ // Perform the appropriate traversal.
+ self.process(flow.get_mut());
+
+ // Possibly enqueue the children.
+ for kid in flow::child_iter(flow.get_mut()) {
+ had_children = true;
+ proxy.push(WorkUnit {
+ fun: top_down_func,
+ data: borrowed_flow_to_unsafe_flow(kid),
+ });
+ }
+
+ }
+
+ // If there were no more children, start assigning block-sizes.
+ if !had_children {
+ bottom_up_func(unsafe_flow, proxy)
+ }
+ }
+}
+
+impl<'a> ParallelPostorderFlowTraversal for BubbleISizesTraversal<'a> {}
+
+impl<'a> ParallelPreorderFlowTraversal for AssignISizesTraversal<'a> {
+ fn run_parallel(&mut self,
+ unsafe_flow: UnsafeFlow,
+ proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>) {
+ self.run_parallel_helper(unsafe_flow,
+ proxy,
+ assign_inline_sizes,
+ assign_block_sizes_and_store_overflow)
+ }
+}
+
+impl<'a> ParallelPostorderFlowTraversal for AssignBSizesAndStoreOverflowTraversal<'a> {}
+
+fn recalc_style_for_node(unsafe_layout_node: UnsafeLayoutNode,
+ proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeLayoutNode>) {
+ let shared_layout_context = unsafe { &**proxy.user_data() };
+ let layout_context = LayoutContext::new(shared_layout_context);
+
+ // Get a real layout node.
+ let node: LayoutNode = unsafe {
+ layout_node_from_unsafe_layout_node(&unsafe_layout_node)
+ };
+
+ // Initialize layout data.
+ //
+ // FIXME(pcwalton): Stop allocating here. Ideally this should just be done by the HTML
+ // parser.
+ node.initialize_layout_data(layout_context.shared.layout_chan.clone());
+
+ // Get the parent node.
+ let opaque_node: OpaqueNode = OpaqueNodeMethods::from_layout_node(&node);
+ let parent_opt = if opaque_node == layout_context.shared.reflow_root {
+ None
+ } else {
+ node.parent_node()
+ };
+
+ // First, check to see whether we can share a style with someone.
+ let style_sharing_candidate_cache = layout_context.style_sharing_candidate_cache();
+ let sharing_result = unsafe {
+ node.share_style_if_possible(style_sharing_candidate_cache,
+ parent_opt.clone())
+ };
+
+ // Otherwise, match and cascade selectors.
+ match sharing_result {
+ CannotShare(mut shareable) => {
+ let mut applicable_declarations = ApplicableDeclarations::new();
+
+ if node.is_element() {
+ // Perform the CSS selector matching.
+ let stylist = unsafe { &*layout_context.shared.stylist };
+ node.match_node(stylist, &mut applicable_declarations, &mut shareable);
+ }
+
+ // Perform the CSS cascade.
+ unsafe {
+ node.cascade_node(parent_opt,
+ &applicable_declarations,
+ layout_context.applicable_declarations_cache());
+ }
+
+ // Add ourselves to the LRU cache.
+ if shareable {
+ style_sharing_candidate_cache.insert_if_possible(&node);
+ }
+ }
+ StyleWasShared(index) => style_sharing_candidate_cache.touch(index),
+ }
+
+ // Prepare for flow construction by counting the node's children and storing that count.
+ let mut child_count = 0u;
+ for _ in node.children() {
+ child_count += 1;
+ }
+ if child_count != 0 {
+ let mut layout_data_ref = node.mutate_layout_data();
+ match &mut *layout_data_ref {
+ &Some(ref mut layout_data) => {
+ layout_data.data.parallel.children_count.store(child_count as int, Relaxed)
+ }
+ &None => fail!("no layout data"),
+ }
+ }
+
+ // It's *very* important that this block is in a separate scope to the block above,
+ // to avoid a data race that can occur (github issue #2308). The block above issues
+ // a borrow on the node layout data. That borrow must be dropped before the child
+ // nodes are actually pushed into the work queue. Otherwise, it's possible for a child
+ // node to get into construct_flows() and move up it's parent hierarchy, which can call
+ // borrow on the layout data before it is dropped from the block above.
+ if child_count != 0 {
+ // Enqueue kids.
+ for kid in node.children() {
+ proxy.push(WorkUnit {
+ fun: recalc_style_for_node,
+ data: layout_node_to_unsafe_layout_node(&kid),
+ });
+ }
+ return
+ }
+
+ // If we got here, we're a leaf. Start construction of flows for this node.
+ construct_flows(unsafe_layout_node, &layout_context)
+}
+
+fn construct_flows(mut unsafe_layout_node: UnsafeLayoutNode,
+ layout_context: &LayoutContext) {
+ loop {
+ // Get a real layout node.
+ let node: LayoutNode = unsafe {
+ layout_node_from_unsafe_layout_node(&unsafe_layout_node)
+ };
+
+ // Construct flows for this node.
+ {
+ let mut flow_constructor = FlowConstructor::new(layout_context);
+ flow_constructor.process(&ThreadSafeLayoutNode::new(&node));
+ }
+
+ // Reset the count of children for the next traversal.
+ //
+ // FIXME(pcwalton): Use children().len() when the implementation of that is efficient.
+ let mut child_count = 0u;
+ for _ in node.children() {
+ child_count += 1
+ }
+ {
+ let mut layout_data_ref = node.mutate_layout_data();
+ match &mut *layout_data_ref {
+ &Some(ref mut layout_data) => {
+ layout_data.data.parallel.children_count.store(child_count as int, Relaxed)
+ }
+ &None => fail!("no layout data"),
+ }
+ }
+
+ // If this is the reflow root, we're done.
+ let opaque_node: OpaqueNode = OpaqueNodeMethods::from_layout_node(&node);
+ if layout_context.shared.reflow_root == opaque_node {
+ break
+ }
+
+ // Otherwise, enqueue the parent.
+ match node.parent_node() {
+ Some(parent) => {
+
+ // No, we're not at the root yet. Then are we the last sibling of our parent?
+ // If so, we can continue on with our parent; otherwise, we've gotta wait.
+ unsafe {
+ match *parent.borrow_layout_data_unchecked() {
+ Some(ref parent_layout_data) => {
+ let parent_layout_data: &mut LayoutDataWrapper = mem::transmute(parent_layout_data);
+ if parent_layout_data.data
+ .parallel
+ .children_count
+ .fetch_sub(1, SeqCst) == 1 {
+ // We were the last child of our parent. Construct flows for our
+ // parent.
+ unsafe_layout_node = layout_node_to_unsafe_layout_node(&parent)
+ } else {
+ // Get out of here and find another node to work on.
+ break
+ }
+ }
+ None => fail!("no layout data for parent?!"),
+ }
+ }
+ }
+ None => fail!("no parent and weren't at reflow root?!"),
+ }
+ }
+}
+
+fn assign_inline_sizes(unsafe_flow: UnsafeFlow,
+ proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>) {
+ let shared_layout_context = unsafe { &**proxy.user_data() };
+ let layout_context = LayoutContext::new(shared_layout_context);
+ let mut assign_inline_sizes_traversal = AssignISizesTraversal {
+ layout_context: &layout_context,
+ };
+ assign_inline_sizes_traversal.run_parallel(unsafe_flow, proxy)
+}
+
+fn assign_block_sizes_and_store_overflow(unsafe_flow: UnsafeFlow,
+ proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>) {
+ let shared_layout_context = unsafe { &**proxy.user_data() };
+ let layout_context = LayoutContext::new(shared_layout_context);
+ let mut assign_block_sizes_traversal = AssignBSizesAndStoreOverflowTraversal {
+ layout_context: &layout_context,
+ };
+ assign_block_sizes_traversal.run_parallel(unsafe_flow, proxy)
+}
+
+fn compute_absolute_position(unsafe_flow: UnsafeFlow,
+ proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>) {
+ let mut had_descendants = false;
+ unsafe {
+ // Get a real flow.
+ let flow: &mut FlowRef = mem::transmute(&unsafe_flow);
+
+ // Compute the absolute position for the flow.
+ flow.get_mut().compute_absolute_position();
+
+ // Count the number of absolutely-positioned children, so that we can subtract it from
+ // from `children_and_absolute_descendant_count` to get the number of real children.
+ let mut absolutely_positioned_child_count = 0u;
+ for kid in flow::child_iter(flow.get_mut()) {
+ if kid.is_absolutely_positioned() {
+ absolutely_positioned_child_count += 1;
+ }
+ }
+
+ // Don't enqueue absolutely positioned children.
+ drop(flow::mut_base(flow.get_mut()).parallel
+ .children_and_absolute_descendant_count
+ .fetch_sub(absolutely_positioned_child_count as int,
+ SeqCst));
+
+ // Possibly enqueue the children.
+ for kid in flow::child_iter(flow.get_mut()) {
+ if !kid.is_absolutely_positioned() {
+ had_descendants = true;
+ proxy.push(WorkUnit {
+ fun: compute_absolute_position,
+ data: borrowed_flow_to_unsafe_flow(kid),
+ });
+ }
+ }
+
+ // Possibly enqueue absolute descendants.
+ for absolute_descendant_link in flow::mut_base(flow.get_mut()).abs_descendants.iter() {
+ had_descendants = true;
+ let descendant = absolute_descendant_link;
+ proxy.push(WorkUnit {
+ fun: compute_absolute_position,
+ data: borrowed_flow_to_unsafe_flow(descendant),
+ });
+ }
+
+ // If there were no more descendants, start building the display list.
+ if !had_descendants {
+ build_display_list(mut_owned_flow_to_unsafe_flow(flow),
+ proxy)
+ }
+ }
+}
+
+fn build_display_list(mut unsafe_flow: UnsafeFlow,
+ proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>) {
+ let shared_layout_context = unsafe { &**proxy.user_data() };
+ let layout_context = LayoutContext::new(shared_layout_context);
+
+ loop {
+ unsafe {
+ // Get a real flow.
+ let flow: &mut FlowRef = mem::transmute(&unsafe_flow);
+
+ // Build display lists.
+ flow.get_mut().build_display_list(&layout_context);
+
+ {
+ let base = flow::mut_base(flow.get_mut());
+
+ // Reset the count of children and absolute descendants for the next layout
+ // traversal.
+ let children_and_absolute_descendant_count = base.children.len() +
+ base.abs_descendants.len();
+ base.parallel
+ .children_and_absolute_descendant_count
+ .store(children_and_absolute_descendant_count as int, Relaxed);
+ }
+
+ // Possibly enqueue the parent.
+ let unsafe_parent = if flow.get().is_absolutely_positioned() {
+ match *flow::mut_base(flow.get_mut()).absolute_cb.get() {
+ None => fail!("no absolute containing block for absolutely positioned?!"),
+ Some(ref mut absolute_cb) => {
+ mut_borrowed_flow_to_unsafe_flow(absolute_cb.get_mut())
+ }
+ }
+ } else {
+ flow::mut_base(flow.get_mut()).parallel.parent
+ };
+ if unsafe_parent == null_unsafe_flow() {
+ // We're done!
+ break
+ }
+
+ // No, we're not at the root yet. Then are we the last child
+ // of our parent to finish processing? If so, we can continue
+ // on with our parent; otherwise, we've gotta wait.
+ let parent: &mut FlowRef = mem::transmute(&unsafe_parent);
+ let parent_base = flow::mut_base(parent.get_mut());
+ if parent_base.parallel
+ .children_and_absolute_descendant_count
+ .fetch_sub(1, SeqCst) == 1 {
+ // We were the last child of our parent. Build display lists for our parent.
+ unsafe_flow = unsafe_parent
+ } else {
+ // Stop.
+ break
+ }
+ }
+ }
+}
+
+pub fn recalc_style_for_subtree(root_node: &LayoutNode,
+ shared_layout_context: &SharedLayoutContext,
+ queue: &mut WorkQueue<*const SharedLayoutContext,UnsafeLayoutNode>) {
+ queue.data = shared_layout_context as *const _;
+
+ // Enqueue the root node.
+ queue.push(WorkUnit {
+ fun: recalc_style_for_node,
+ data: layout_node_to_unsafe_layout_node(root_node),
+ });
+
+ queue.run();
+
+ queue.data = ptr::null()
+}
+
+pub fn traverse_flow_tree_preorder(root: &mut FlowRef,
+ time_profiler_chan: TimeProfilerChan,
+ shared_layout_context: &SharedLayoutContext,
+ queue: &mut WorkQueue<*const SharedLayoutContext,UnsafeFlow>) {
+ queue.data = shared_layout_context as *const _;
+
+ profile(time::LayoutParallelWarmupCategory, time_profiler_chan, || {
+ queue.push(WorkUnit {
+ fun: assign_inline_sizes,
+ data: mut_owned_flow_to_unsafe_flow(root),
+ })
+ });
+
+ queue.run();
+
+ queue.data = ptr::null()
+}
+
+pub fn build_display_list_for_subtree(root: &mut FlowRef,
+ time_profiler_chan: TimeProfilerChan,
+ shared_layout_context: &SharedLayoutContext,
+ queue: &mut WorkQueue<*const SharedLayoutContext,UnsafeFlow>) {
+ queue.data = shared_layout_context as *const _;
+
+ profile(time::LayoutParallelWarmupCategory, time_profiler_chan, || {
+ queue.push(WorkUnit {
+ fun: compute_absolute_position,
+ data: mut_owned_flow_to_unsafe_flow(root),
+ })
+ });
+
+ queue.run();
+
+ queue.data = ptr::null()
+}
+
diff --git a/components/layout/table.rs b/components/layout/table.rs
new file mode 100644
index 00000000000..98569d68c95
--- /dev/null
+++ b/components/layout/table.rs
@@ -0,0 +1,324 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! CSS table formatting contexts.
+
+#![deny(unsafe_block)]
+
+use block::{BlockFlow, MarginsMayNotCollapse, ISizeAndMarginsComputer};
+use block::{ISizeConstraintInput, ISizeConstraintSolution};
+use construct::FlowConstructor;
+use context::LayoutContext;
+use floats::FloatKind;
+use flow::{TableFlowClass, FlowClass, Flow, ImmutableFlowUtils};
+use fragment::Fragment;
+use table_wrapper::{TableLayout, FixedLayout, AutoLayout};
+use wrapper::ThreadSafeLayoutNode;
+
+use servo_util::geometry::Au;
+use servo_util::geometry;
+use std::fmt;
+use style::computed_values::table_layout;
+
+/// A table flow corresponded to the table's internal table fragment under a table wrapper flow.
+/// The properties `position`, `float`, and `margin-*` are used on the table wrapper fragment,
+/// not table fragment per CSS 2.1 § 10.5.
+pub struct TableFlow {
+ pub block_flow: BlockFlow,
+
+ /// Column inline-sizes
+ pub col_inline_sizes: Vec<Au>,
+
+ /// Column min inline-sizes.
+ pub col_min_inline_sizes: Vec<Au>,
+
+ /// Column pref inline-sizes.
+ pub col_pref_inline_sizes: Vec<Au>,
+
+ /// Table-layout property
+ pub table_layout: TableLayout,
+}
+
+impl TableFlow {
+ pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode,
+ fragment: Fragment)
+ -> TableFlow {
+ let mut block_flow = BlockFlow::from_node_and_fragment(node, fragment);
+ let table_layout = if block_flow.fragment().style().get_table().table_layout ==
+ table_layout::fixed {
+ FixedLayout
+ } else {
+ AutoLayout
+ };
+ TableFlow {
+ block_flow: block_flow,
+ col_inline_sizes: vec!(),
+ col_min_inline_sizes: vec!(),
+ col_pref_inline_sizes: vec!(),
+ table_layout: table_layout
+ }
+ }
+
+ pub fn from_node(constructor: &mut FlowConstructor,
+ node: &ThreadSafeLayoutNode)
+ -> TableFlow {
+ let mut block_flow = BlockFlow::from_node(constructor, node);
+ let table_layout = if block_flow.fragment().style().get_table().table_layout ==
+ table_layout::fixed {
+ FixedLayout
+ } else {
+ AutoLayout
+ };
+ TableFlow {
+ block_flow: block_flow,
+ col_inline_sizes: vec!(),
+ col_min_inline_sizes: vec!(),
+ col_pref_inline_sizes: vec!(),
+ table_layout: table_layout
+ }
+ }
+
+ pub fn float_from_node(constructor: &mut FlowConstructor,
+ node: &ThreadSafeLayoutNode,
+ float_kind: FloatKind)
+ -> TableFlow {
+ let mut block_flow = BlockFlow::float_from_node(constructor, node, float_kind);
+ let table_layout = if block_flow.fragment().style().get_table().table_layout ==
+ table_layout::fixed {
+ FixedLayout
+ } else {
+ AutoLayout
+ };
+ TableFlow {
+ block_flow: block_flow,
+ col_inline_sizes: vec!(),
+ col_min_inline_sizes: vec!(),
+ col_pref_inline_sizes: vec!(),
+ table_layout: table_layout
+ }
+ }
+
+ /// Update the corresponding value of self_inline-sizes if a value of kid_inline-sizes has larger value
+ /// than one of self_inline-sizes.
+ pub fn update_col_inline_sizes(self_inline_sizes: &mut Vec<Au>, kid_inline_sizes: &Vec<Au>) -> Au {
+ let mut sum_inline_sizes = Au(0);
+ let mut kid_inline_sizes_it = kid_inline_sizes.iter();
+ for self_inline_size in self_inline_sizes.mut_iter() {
+ match kid_inline_sizes_it.next() {
+ Some(kid_inline_size) => {
+ if *self_inline_size < *kid_inline_size {
+ *self_inline_size = *kid_inline_size;
+ }
+ },
+ None => {}
+ }
+ sum_inline_sizes = sum_inline_sizes + *self_inline_size;
+ }
+ sum_inline_sizes
+ }
+
+ /// Assign block-size for table flow.
+ ///
+ /// TODO(#2014, pcwalton): This probably doesn't handle margin collapse right.
+ ///
+ /// inline(always) because this is only ever called by in-order or non-in-order top-level
+ /// methods
+ #[inline(always)]
+ fn assign_block_size_table_base<'a>(&mut self, layout_context: &'a LayoutContext<'a>) {
+ self.block_flow.assign_block_size_block_base(layout_context, MarginsMayNotCollapse);
+ }
+
+ pub fn build_display_list_table(&mut self, layout_context: &LayoutContext) {
+ debug!("build_display_list_table: same process as block flow");
+ self.block_flow.build_display_list_block(layout_context);
+ }
+}
+
+impl Flow for TableFlow {
+ fn class(&self) -> FlowClass {
+ TableFlowClass
+ }
+
+ fn as_table<'a>(&'a mut self) -> &'a mut TableFlow {
+ self
+ }
+
+ fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow {
+ &mut self.block_flow
+ }
+
+ fn col_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<Au> {
+ &mut self.col_inline_sizes
+ }
+
+ fn col_min_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
+ &self.col_min_inline_sizes
+ }
+
+ fn col_pref_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
+ &self.col_pref_inline_sizes
+ }
+
+ /// The specified column inline-sizes are set from column group and the first row for the fixed
+ /// table layout calculation.
+ /// The maximum min/pref inline-sizes of each column are set from the rows for the automatic
+ /// table layout calculation.
+ fn bubble_inline_sizes(&mut self, _: &LayoutContext) {
+ let mut min_inline_size = Au(0);
+ let mut pref_inline_size = Au(0);
+ let mut did_first_row = false;
+
+ for kid in self.block_flow.base.child_iter() {
+ assert!(kid.is_proper_table_child());
+
+ if kid.is_table_colgroup() {
+ self.col_inline_sizes.push_all(kid.as_table_colgroup().inline_sizes.as_slice());
+ self.col_min_inline_sizes = self.col_inline_sizes.clone();
+ self.col_pref_inline_sizes = self.col_inline_sizes.clone();
+ } else if kid.is_table_rowgroup() || kid.is_table_row() {
+ // read column inline-sizes from table-row-group/table-row, and assign
+ // inline-size=0 for the columns not defined in column-group
+ // FIXME: need to read inline-sizes from either table-header-group OR
+ // first table-row
+ match self.table_layout {
+ FixedLayout => {
+ let kid_col_inline_sizes = kid.col_inline_sizes();
+ if !did_first_row {
+ did_first_row = true;
+ let mut child_inline_sizes = kid_col_inline_sizes.iter();
+ for col_inline_size in self.col_inline_sizes.mut_iter() {
+ match child_inline_sizes.next() {
+ Some(child_inline_size) => {
+ if *col_inline_size == Au::new(0) {
+ *col_inline_size = *child_inline_size;
+ }
+ },
+ None => break
+ }
+ }
+ }
+ let num_child_cols = kid_col_inline_sizes.len();
+ let num_cols = self.col_inline_sizes.len();
+ debug!("table until the previous row has {} column(s) and this row has {} column(s)",
+ num_cols, num_child_cols);
+ for i in range(num_cols, num_child_cols) {
+ self.col_inline_sizes.push((*kid_col_inline_sizes)[i]);
+ }
+ },
+ AutoLayout => {
+ min_inline_size = TableFlow::update_col_inline_sizes(&mut self.col_min_inline_sizes, kid.col_min_inline_sizes());
+ pref_inline_size = TableFlow::update_col_inline_sizes(&mut self.col_pref_inline_sizes, kid.col_pref_inline_sizes());
+
+ // update the number of column inline-sizes from table-rows.
+ let num_cols = self.col_min_inline_sizes.len();
+ let num_child_cols = kid.col_min_inline_sizes().len();
+ debug!("table until the previous row has {} column(s) and this row has {} column(s)",
+ num_cols, num_child_cols);
+ for i in range(num_cols, num_child_cols) {
+ self.col_inline_sizes.push(Au::new(0));
+ let new_kid_min = kid.col_min_inline_sizes()[i];
+ self.col_min_inline_sizes.push( new_kid_min );
+ let new_kid_pref = kid.col_pref_inline_sizes()[i];
+ self.col_pref_inline_sizes.push( new_kid_pref );
+ min_inline_size = min_inline_size + new_kid_min;
+ pref_inline_size = pref_inline_size + new_kid_pref;
+ }
+ }
+ }
+ }
+ }
+ self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size = min_inline_size;
+ self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size =
+ geometry::max(min_inline_size, pref_inline_size);
+ }
+
+ /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. When
+ /// called on this context, the context has had its inline-size set by the parent context.
+ fn assign_inline_sizes(&mut self, ctx: &LayoutContext) {
+ debug!("assign_inline_sizes({}): assigning inline_size for flow", "table");
+
+ // The position was set to the containing block by the flow's parent.
+ let containing_block_inline_size = self.block_flow.base.position.size.inline;
+
+ let mut num_unspecified_inline_sizes = 0;
+ let mut total_column_inline_size = Au::new(0);
+ for col_inline_size in self.col_inline_sizes.iter() {
+ if *col_inline_size == Au::new(0) {
+ num_unspecified_inline_sizes += 1;
+ } else {
+ total_column_inline_size = total_column_inline_size.add(col_inline_size);
+ }
+ }
+
+ let inline_size_computer = InternalTable;
+ inline_size_computer.compute_used_inline_size(&mut self.block_flow, ctx, containing_block_inline_size);
+
+ let inline_start_content_edge = self.block_flow.fragment.border_padding.inline_start;
+ let padding_and_borders = self.block_flow.fragment.border_padding.inline_start_end();
+ let content_inline_size = self.block_flow.fragment.border_box.size.inline - padding_and_borders;
+
+ match self.table_layout {
+ FixedLayout => {
+ // In fixed table layout, we distribute extra space among the unspecified columns if there are
+ // any, or among all the columns if all are specified.
+ if (total_column_inline_size < content_inline_size) && (num_unspecified_inline_sizes == 0) {
+ let ratio = content_inline_size.to_f64().unwrap() / total_column_inline_size.to_f64().unwrap();
+ for col_inline_size in self.col_inline_sizes.mut_iter() {
+ *col_inline_size = (*col_inline_size).scale_by(ratio);
+ }
+ } else if num_unspecified_inline_sizes != 0 {
+ let extra_column_inline_size = (content_inline_size - total_column_inline_size) / Au::new(num_unspecified_inline_sizes);
+ for col_inline_size in self.col_inline_sizes.mut_iter() {
+ if *col_inline_size == Au(0) {
+ *col_inline_size = extra_column_inline_size;
+ }
+ }
+ }
+ }
+ _ => {}
+ }
+
+ self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge, content_inline_size, Some(self.col_inline_sizes.clone()));
+ }
+
+ fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
+ debug!("assign_block_size: assigning block_size for table");
+ self.assign_block_size_table_base(ctx);
+ }
+
+ fn compute_absolute_position(&mut self) {
+ self.block_flow.compute_absolute_position()
+ }
+}
+
+impl fmt::Show for TableFlow {
+ /// Outputs a debugging string describing this table flow.
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "TableFlow: {}", self.block_flow)
+ }
+}
+
+/// Table, TableRowGroup, TableRow, TableCell types.
+/// Their inline-sizes are calculated in the same way and do not have margins.
+pub struct InternalTable;
+
+impl ISizeAndMarginsComputer for InternalTable {
+ /// Compute the used value of inline-size, taking care of min-inline-size and max-inline-size.
+ ///
+ /// CSS Section 10.4: Minimum and Maximum inline-sizes
+ fn compute_used_inline_size(&self,
+ block: &mut BlockFlow,
+ ctx: &LayoutContext,
+ parent_flow_inline_size: Au) {
+ let input = self.compute_inline_size_constraint_inputs(block, parent_flow_inline_size, ctx);
+ let solution = self.solve_inline_size_constraints(block, &input);
+ self.set_inline_size_constraint_solutions(block, solution);
+ }
+
+ /// Solve the inline-size and margins constraints for this block flow.
+ fn solve_inline_size_constraints(&self, _: &mut BlockFlow, input: &ISizeConstraintInput)
+ -> ISizeConstraintSolution {
+ ISizeConstraintSolution::new(input.available_inline_size, Au::new(0), Au::new(0))
+ }
+}
diff --git a/components/layout/table_caption.rs b/components/layout/table_caption.rs
new file mode 100644
index 00000000000..8c1dba3e7ca
--- /dev/null
+++ b/components/layout/table_caption.rs
@@ -0,0 +1,73 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! CSS table formatting contexts.
+
+#![deny(unsafe_block)]
+
+use block::BlockFlow;
+use construct::FlowConstructor;
+use context::LayoutContext;
+use flow::{TableCaptionFlowClass, FlowClass, Flow};
+use wrapper::ThreadSafeLayoutNode;
+
+use std::fmt;
+
+/// A table formatting context.
+pub struct TableCaptionFlow {
+ pub block_flow: BlockFlow,
+}
+
+impl TableCaptionFlow {
+ pub fn from_node(constructor: &mut FlowConstructor,
+ node: &ThreadSafeLayoutNode)
+ -> TableCaptionFlow {
+ TableCaptionFlow {
+ block_flow: BlockFlow::from_node(constructor, node)
+ }
+ }
+
+ pub fn build_display_list_table_caption(&mut self, layout_context: &LayoutContext) {
+ debug!("build_display_list_table_caption: same process as block flow");
+ self.block_flow.build_display_list_block(layout_context)
+ }
+}
+
+impl Flow for TableCaptionFlow {
+ fn class(&self) -> FlowClass {
+ TableCaptionFlowClass
+ }
+
+ fn as_table_caption<'a>(&'a mut self) -> &'a mut TableCaptionFlow {
+ self
+ }
+
+ fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow {
+ &mut self.block_flow
+ }
+
+ fn bubble_inline_sizes(&mut self, ctx: &LayoutContext) {
+ self.block_flow.bubble_inline_sizes(ctx);
+ }
+
+ fn assign_inline_sizes(&mut self, ctx: &LayoutContext) {
+ debug!("assign_inline_sizes({}): assigning inline_size for flow", "table_caption");
+ self.block_flow.assign_inline_sizes(ctx);
+ }
+
+ fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
+ debug!("assign_block_size: assigning block_size for table_caption");
+ self.block_flow.assign_block_size(ctx);
+ }
+
+ fn compute_absolute_position(&mut self) {
+ self.block_flow.compute_absolute_position()
+ }
+}
+
+impl fmt::Show for TableCaptionFlow {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "TableCaptionFlow: {}", self.block_flow)
+ }
+}
diff --git a/components/layout/table_cell.rs b/components/layout/table_cell.rs
new file mode 100644
index 00000000000..0011cd29fbc
--- /dev/null
+++ b/components/layout/table_cell.rs
@@ -0,0 +1,121 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! CSS table formatting contexts.
+
+#![deny(unsafe_block)]
+
+use block::{BlockFlow, MarginsMayNotCollapse, ISizeAndMarginsComputer};
+use context::LayoutContext;
+use flow::{TableCellFlowClass, FlowClass, Flow};
+use fragment::Fragment;
+use model::{MaybeAuto};
+use table::InternalTable;
+use wrapper::ThreadSafeLayoutNode;
+
+use servo_util::geometry::Au;
+use std::fmt;
+
+/// A table formatting context.
+pub struct TableCellFlow {
+ /// Data common to all flows.
+ pub block_flow: BlockFlow,
+}
+
+impl TableCellFlow {
+ pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode, fragment: Fragment) -> TableCellFlow {
+ TableCellFlow {
+ block_flow: BlockFlow::from_node_and_fragment(node, fragment)
+ }
+ }
+
+ pub fn fragment<'a>(&'a mut self) -> &'a Fragment {
+ &self.block_flow.fragment
+ }
+
+ pub fn mut_fragment<'a>(&'a mut self) -> &'a mut Fragment {
+ &mut self.block_flow.fragment
+ }
+
+ /// Assign block-size for table-cell flow.
+ ///
+ /// TODO(#2015, pcwalton): This doesn't handle floats right.
+ ///
+ /// inline(always) because this is only ever called by in-order or non-in-order top-level
+ /// methods
+ #[inline(always)]
+ fn assign_block_size_table_cell_base<'a>(&mut self, layout_context: &'a LayoutContext<'a>) {
+ self.block_flow.assign_block_size_block_base(layout_context, MarginsMayNotCollapse)
+ }
+
+ pub fn build_display_list_table_cell(&mut self, layout_context: &LayoutContext) {
+ debug!("build_display_list_table: same process as block flow");
+ self.block_flow.build_display_list_block(layout_context)
+ }
+}
+
+impl Flow for TableCellFlow {
+ fn class(&self) -> FlowClass {
+ TableCellFlowClass
+ }
+
+ fn as_table_cell<'a>(&'a mut self) -> &'a mut TableCellFlow {
+ self
+ }
+
+ fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow {
+ &mut self.block_flow
+ }
+
+ /// Minimum/preferred inline-sizes set by this function are used in automatic table layout calculation.
+ fn bubble_inline_sizes(&mut self, ctx: &LayoutContext) {
+ self.block_flow.bubble_inline_sizes(ctx);
+ let specified_inline_size = MaybeAuto::from_style(self.block_flow.fragment.style().content_inline_size(),
+ Au::new(0)).specified_or_zero();
+ if self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size < specified_inline_size {
+ self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size = specified_inline_size;
+ }
+ if self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size <
+ self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size {
+ self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size =
+ self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size;
+ }
+ }
+
+ /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. When
+ /// called on this context, the context has had its inline-size set by the parent table row.
+ fn assign_inline_sizes(&mut self, ctx: &LayoutContext) {
+ debug!("assign_inline_sizes({}): assigning inline_size for flow", "table_cell");
+
+ // The position was set to the column inline-size by the parent flow, table row flow.
+ let containing_block_inline_size = self.block_flow.base.position.size.inline;
+
+ let inline_size_computer = InternalTable;
+ inline_size_computer.compute_used_inline_size(&mut self.block_flow, ctx, containing_block_inline_size);
+
+ let inline_start_content_edge = self.block_flow.fragment.border_box.start.i +
+ self.block_flow.fragment.border_padding.inline_start;
+ let padding_and_borders = self.block_flow.fragment.border_padding.inline_start_end();
+ let content_inline_size = self.block_flow.fragment.border_box.size.inline - padding_and_borders;
+
+ self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge,
+ content_inline_size,
+ None);
+ }
+
+ fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
+ debug!("assign_block_size: assigning block_size for table_cell");
+ self.assign_block_size_table_cell_base(ctx);
+ }
+
+ fn compute_absolute_position(&mut self) {
+ self.block_flow.compute_absolute_position()
+ }
+}
+
+impl fmt::Show for TableCellFlow {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "TableCellFlow: {}", self.block_flow)
+ }
+}
diff --git a/components/layout/table_colgroup.rs b/components/layout/table_colgroup.rs
new file mode 100644
index 00000000000..270f55970b2
--- /dev/null
+++ b/components/layout/table_colgroup.rs
@@ -0,0 +1,88 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! CSS table formatting contexts.
+
+#![deny(unsafe_block)]
+
+use context::LayoutContext;
+use flow::{BaseFlow, TableColGroupFlowClass, FlowClass, Flow};
+use fragment::{Fragment, TableColumnFragment};
+use model::{MaybeAuto};
+use wrapper::ThreadSafeLayoutNode;
+
+use servo_util::geometry::Au;
+use std::fmt;
+
+/// A table formatting context.
+pub struct TableColGroupFlow {
+ /// Data common to all flows.
+ pub base: BaseFlow,
+
+ /// The associated fragment.
+ pub fragment: Option<Fragment>,
+
+ /// The table column fragments
+ pub cols: Vec<Fragment>,
+
+ /// The specified inline-sizes of table columns
+ pub inline_sizes: Vec<Au>,
+}
+
+impl TableColGroupFlow {
+ pub fn from_node_and_fragments(node: &ThreadSafeLayoutNode,
+ fragment: Fragment,
+ fragments: Vec<Fragment>) -> TableColGroupFlow {
+ TableColGroupFlow {
+ base: BaseFlow::new((*node).clone()),
+ fragment: Some(fragment),
+ cols: fragments,
+ inline_sizes: vec!(),
+ }
+ }
+}
+
+impl Flow for TableColGroupFlow {
+ fn class(&self) -> FlowClass {
+ TableColGroupFlowClass
+ }
+
+ fn as_table_colgroup<'a>(&'a mut self) -> &'a mut TableColGroupFlow {
+ self
+ }
+
+ fn bubble_inline_sizes(&mut self, _: &LayoutContext) {
+ for fragment in self.cols.iter() {
+ // get the specified value from inline-size property
+ let inline_size = MaybeAuto::from_style(fragment.style().content_inline_size(),
+ Au::new(0)).specified_or_zero();
+
+ let span: int = match fragment.specific {
+ TableColumnFragment(col_fragment) => col_fragment.span.unwrap_or(1),
+ _ => fail!("Other fragment come out in TableColGroupFlow. {:?}", fragment.specific)
+ };
+ for _ in range(0, span) {
+ self.inline_sizes.push(inline_size);
+ }
+ }
+ }
+
+ /// Table column inline-sizes are assigned in table flow and propagated to table row or rowgroup flow.
+ /// Therefore, table colgroup flow does not need to assign its inline-size.
+ fn assign_inline_sizes(&mut self, _ctx: &LayoutContext) {
+ }
+
+ /// Table column do not have block-size.
+ fn assign_block_size(&mut self, _ctx: &LayoutContext) {
+ }
+}
+
+impl fmt::Show for TableColGroupFlow {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self.fragment {
+ Some(ref rb) => write!(f, "TableColGroupFlow: {}", rb),
+ None => write!(f, "TableColGroupFlow"),
+ }
+ }
+}
diff --git a/components/layout/table_row.rs b/components/layout/table_row.rs
new file mode 100644
index 00000000000..101f00eb5cc
--- /dev/null
+++ b/components/layout/table_row.rs
@@ -0,0 +1,225 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! CSS table formatting contexts.
+
+#![deny(unsafe_block)]
+
+use block::BlockFlow;
+use block::ISizeAndMarginsComputer;
+use construct::FlowConstructor;
+use context::LayoutContext;
+use flow::{TableRowFlowClass, FlowClass, Flow, ImmutableFlowUtils};
+use flow;
+use fragment::Fragment;
+use table::InternalTable;
+use model::{MaybeAuto, Specified, Auto};
+use wrapper::ThreadSafeLayoutNode;
+
+use servo_util::geometry::Au;
+use servo_util::geometry;
+use std::fmt;
+
+/// A table formatting context.
+pub struct TableRowFlow {
+ pub block_flow: BlockFlow,
+
+ /// Column inline-sizes.
+ pub col_inline_sizes: Vec<Au>,
+
+ /// Column min inline-sizes.
+ pub col_min_inline_sizes: Vec<Au>,
+
+ /// Column pref inline-sizes.
+ pub col_pref_inline_sizes: Vec<Au>,
+}
+
+impl TableRowFlow {
+ pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode,
+ fragment: Fragment)
+ -> TableRowFlow {
+ TableRowFlow {
+ block_flow: BlockFlow::from_node_and_fragment(node, fragment),
+ col_inline_sizes: vec!(),
+ col_min_inline_sizes: vec!(),
+ col_pref_inline_sizes: vec!(),
+ }
+ }
+
+ pub fn from_node(constructor: &mut FlowConstructor,
+ node: &ThreadSafeLayoutNode)
+ -> TableRowFlow {
+ TableRowFlow {
+ block_flow: BlockFlow::from_node(constructor, node),
+ col_inline_sizes: vec!(),
+ col_min_inline_sizes: vec!(),
+ col_pref_inline_sizes: vec!(),
+ }
+ }
+
+ pub fn fragment<'a>(&'a mut self) -> &'a Fragment {
+ &self.block_flow.fragment
+ }
+
+ fn initialize_offsets(&mut self) -> (Au, Au, Au) {
+ // TODO: If border-collapse: collapse, block-start_offset, block-end_offset, and inline-start_offset
+ // should be updated. Currently, they are set as Au(0).
+ (Au(0), Au(0), Au(0))
+ }
+
+ /// Assign block-size for table-row flow.
+ ///
+ /// TODO(pcwalton): This doesn't handle floats and positioned elements right.
+ ///
+ /// inline(always) because this is only ever called by in-order or non-in-order top-level
+ /// methods
+ #[inline(always)]
+ fn assign_block_size_table_row_base<'a>(&mut self, layout_context: &'a LayoutContext<'a>) {
+ let (block_start_offset, _, _) = self.initialize_offsets();
+
+ let /* mut */ cur_y = block_start_offset;
+
+ // Per CSS 2.1 § 17.5.3, find max_y = max( computed `block-size`, minimum block-size of all cells )
+ let mut max_y = Au::new(0);
+ for kid in self.block_flow.base.child_iter() {
+ kid.assign_block_size_for_inorder_child_if_necessary(layout_context);
+
+ {
+ let child_fragment = kid.as_table_cell().fragment();
+ // TODO: Percentage block-size
+ let child_specified_block_size = MaybeAuto::from_style(child_fragment.style().content_block_size(),
+ Au::new(0)).specified_or_zero();
+ max_y =
+ geometry::max(max_y,
+ child_specified_block_size + child_fragment.border_padding.block_start_end());
+ }
+ let child_node = flow::mut_base(kid);
+ child_node.position.start.b = cur_y;
+ max_y = geometry::max(max_y, child_node.position.size.block);
+ }
+
+ let mut block_size = max_y;
+ // TODO: Percentage block-size
+ block_size = match MaybeAuto::from_style(self.block_flow.fragment.style().content_block_size(), Au(0)) {
+ Auto => block_size,
+ Specified(value) => geometry::max(value, block_size)
+ };
+ // cur_y = cur_y + block-size;
+
+ // Assign the block-size of own fragment
+ //
+ // FIXME(pcwalton): Take `cur_y` into account.
+ let mut position = self.block_flow.fragment.border_box;
+ position.size.block = block_size;
+ self.block_flow.fragment.border_box = position;
+ self.block_flow.base.position.size.block = block_size;
+
+ // Assign the block-size of kid fragments, which is the same value as own block-size.
+ for kid in self.block_flow.base.child_iter() {
+ {
+ let kid_fragment = kid.as_table_cell().mut_fragment();
+ let mut position = kid_fragment.border_box;
+ position.size.block = block_size;
+ kid_fragment.border_box = position;
+ }
+ let child_node = flow::mut_base(kid);
+ child_node.position.size.block = block_size;
+ }
+ }
+
+ pub fn build_display_list_table_row(&mut self, layout_context: &LayoutContext) {
+ debug!("build_display_list_table_row: same process as block flow");
+ self.block_flow.build_display_list_block(layout_context)
+ }
+}
+
+impl Flow for TableRowFlow {
+ fn class(&self) -> FlowClass {
+ TableRowFlowClass
+ }
+
+ fn as_table_row<'a>(&'a mut self) -> &'a mut TableRowFlow {
+ self
+ }
+
+ fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow {
+ &mut self.block_flow
+ }
+
+ fn col_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<Au> {
+ &mut self.col_inline_sizes
+ }
+
+ fn col_min_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
+ &self.col_min_inline_sizes
+ }
+
+ fn col_pref_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
+ &self.col_pref_inline_sizes
+ }
+
+ /// Recursively (bottom-up) determines the context's preferred and minimum inline-sizes. When called
+ /// on this context, all child contexts have had their min/pref inline-sizes set. This function must
+ /// decide min/pref inline-sizes based on child context inline-sizes and dimensions of any fragments it is
+ /// responsible for flowing.
+ /// Min/pref inline-sizes set by this function are used in automatic table layout calculation.
+ /// The specified column inline-sizes of children cells are used in fixed table layout calculation.
+ fn bubble_inline_sizes(&mut self, _: &LayoutContext) {
+ let mut min_inline_size = Au(0);
+ let mut pref_inline_size = Au(0);
+ /* find the specified inline_sizes from child table-cell contexts */
+ for kid in self.block_flow.base.child_iter() {
+ assert!(kid.is_table_cell());
+
+ // collect the specified column inline-sizes of cells. These are used in fixed table layout calculation.
+ {
+ let child_fragment = kid.as_table_cell().fragment();
+ let child_specified_inline_size = MaybeAuto::from_style(child_fragment.style().content_inline_size(),
+ Au::new(0)).specified_or_zero();
+ self.col_inline_sizes.push(child_specified_inline_size);
+ }
+
+ // collect min_inline-size & pref_inline-size of children cells for automatic table layout calculation.
+ let child_base = flow::mut_base(kid);
+ self.col_min_inline_sizes.push(child_base.intrinsic_inline_sizes.minimum_inline_size);
+ self.col_pref_inline_sizes.push(child_base.intrinsic_inline_sizes.preferred_inline_size);
+ min_inline_size = min_inline_size + child_base.intrinsic_inline_sizes.minimum_inline_size;
+ pref_inline_size = pref_inline_size + child_base.intrinsic_inline_sizes.preferred_inline_size;
+ }
+ self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size = min_inline_size;
+ self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size = geometry::max(min_inline_size,
+ pref_inline_size);
+ }
+
+ /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. When called
+ /// on this context, the context has had its inline-size set by the parent context.
+ fn assign_inline_sizes(&mut self, ctx: &LayoutContext) {
+ debug!("assign_inline_sizes({}): assigning inline_size for flow", "table_row");
+
+ // The position was set to the containing block by the flow's parent.
+ let containing_block_inline_size = self.block_flow.base.position.size.inline;
+ // FIXME: In case of border-collapse: collapse, inline-start_content_edge should be border-inline-start
+ let inline_start_content_edge = Au::new(0);
+
+ let inline_size_computer = InternalTable;
+ inline_size_computer.compute_used_inline_size(&mut self.block_flow, ctx, containing_block_inline_size);
+
+ self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge, Au(0), Some(self.col_inline_sizes.clone()));
+ }
+
+ fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
+ debug!("assign_block_size: assigning block_size for table_row");
+ self.assign_block_size_table_row_base(ctx);
+ }
+
+ fn compute_absolute_position(&mut self) {
+ self.block_flow.compute_absolute_position()
+ }
+}
+
+impl fmt::Show for TableRowFlow {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "TableRowFlow: {}", self.block_flow.fragment)
+ }
+}
diff --git a/components/layout/table_rowgroup.rs b/components/layout/table_rowgroup.rs
new file mode 100644
index 00000000000..48f9d376af3
--- /dev/null
+++ b/components/layout/table_rowgroup.rs
@@ -0,0 +1,208 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! CSS table formatting contexts.
+
+#![deny(unsafe_block)]
+
+use block::BlockFlow;
+use block::ISizeAndMarginsComputer;
+use construct::FlowConstructor;
+use context::LayoutContext;
+use flow::{TableRowGroupFlowClass, FlowClass, Flow, ImmutableFlowUtils};
+use flow;
+use fragment::Fragment;
+use table::{InternalTable, TableFlow};
+use wrapper::ThreadSafeLayoutNode;
+
+use servo_util::geometry::Au;
+use servo_util::geometry;
+use std::fmt;
+
+/// A table formatting context.
+pub struct TableRowGroupFlow {
+ pub block_flow: BlockFlow,
+
+ /// Column inline-sizes
+ pub col_inline_sizes: Vec<Au>,
+
+ /// Column min inline-sizes.
+ pub col_min_inline_sizes: Vec<Au>,
+
+ /// Column pref inline-sizes.
+ pub col_pref_inline_sizes: Vec<Au>,
+}
+
+impl TableRowGroupFlow {
+ pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode,
+ fragment: Fragment)
+ -> TableRowGroupFlow {
+ TableRowGroupFlow {
+ block_flow: BlockFlow::from_node_and_fragment(node, fragment),
+ col_inline_sizes: vec!(),
+ col_min_inline_sizes: vec!(),
+ col_pref_inline_sizes: vec!(),
+ }
+ }
+
+ pub fn from_node(constructor: &mut FlowConstructor,
+ node: &ThreadSafeLayoutNode)
+ -> TableRowGroupFlow {
+ TableRowGroupFlow {
+ block_flow: BlockFlow::from_node(constructor, node),
+ col_inline_sizes: vec!(),
+ col_min_inline_sizes: vec!(),
+ col_pref_inline_sizes: vec!(),
+ }
+ }
+
+ pub fn fragment<'a>(&'a mut self) -> &'a Fragment {
+ &self.block_flow.fragment
+ }
+
+ fn initialize_offsets(&mut self) -> (Au, Au, Au) {
+ // TODO: If border-collapse: collapse, block-start_offset, block-end_offset, and inline-start_offset
+ // should be updated. Currently, they are set as Au(0).
+ (Au(0), Au(0), Au(0))
+ }
+
+ /// Assign block-size for table-rowgroup flow.
+ ///
+ /// FIXME(pcwalton): This doesn't handle floats right.
+ ///
+ /// inline(always) because this is only ever called by in-order or non-in-order top-level
+ /// methods
+ #[inline(always)]
+ fn assign_block_size_table_rowgroup_base<'a>(&mut self, layout_context: &'a LayoutContext<'a>) {
+ let (block_start_offset, _, _) = self.initialize_offsets();
+
+ let mut cur_y = block_start_offset;
+
+ for kid in self.block_flow.base.child_iter() {
+ kid.assign_block_size_for_inorder_child_if_necessary(layout_context);
+
+ let child_node = flow::mut_base(kid);
+ child_node.position.start.b = cur_y;
+ cur_y = cur_y + child_node.position.size.block;
+ }
+
+ let block_size = cur_y - block_start_offset;
+
+ let mut position = self.block_flow.fragment.border_box;
+ position.size.block = block_size;
+ self.block_flow.fragment.border_box = position;
+ self.block_flow.base.position.size.block = block_size;
+ }
+
+ pub fn build_display_list_table_rowgroup(&mut self, layout_context: &LayoutContext) {
+ debug!("build_display_list_table_rowgroup: same process as block flow");
+ self.block_flow.build_display_list_block(layout_context)
+ }
+}
+
+impl Flow for TableRowGroupFlow {
+ fn class(&self) -> FlowClass {
+ TableRowGroupFlowClass
+ }
+
+ fn as_table_rowgroup<'a>(&'a mut self) -> &'a mut TableRowGroupFlow {
+ self
+ }
+
+ fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow {
+ &mut self.block_flow
+ }
+
+ fn col_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<Au> {
+ &mut self.col_inline_sizes
+ }
+
+ fn col_min_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
+ &self.col_min_inline_sizes
+ }
+
+ fn col_pref_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
+ &self.col_pref_inline_sizes
+ }
+
+ /// Recursively (bottom-up) determines the context's preferred and minimum inline-sizes. When called
+ /// on this context, all child contexts have had their min/pref inline-sizes set. This function must
+ /// decide min/pref inline-sizes based on child context inline-sizes and dimensions of any fragments it is
+ /// responsible for flowing.
+ /// Min/pref inline-sizes set by this function are used in automatic table layout calculation.
+ /// Also, this function finds the specified column inline-sizes from the first row.
+ /// Those are used in fixed table layout calculation
+ fn bubble_inline_sizes(&mut self, _: &LayoutContext) {
+ let mut min_inline_size = Au(0);
+ let mut pref_inline_size = Au(0);
+
+ for kid in self.block_flow.base.child_iter() {
+ assert!(kid.is_table_row());
+
+ // calculate min_inline-size & pref_inline-size for automatic table layout calculation
+ // 'self.col_min_inline-sizes' collects the maximum value of cells' min-inline-sizes for each column.
+ // 'self.col_pref_inline-sizes' collects the maximum value of cells' pref-inline-sizes for each column.
+ if self.col_inline_sizes.is_empty() { // First Row
+ assert!(self.col_min_inline_sizes.is_empty() && self.col_pref_inline_sizes.is_empty());
+ // 'self.col_inline-sizes' collects the specified column inline-sizes from the first table-row for fixed table layout calculation.
+ self.col_inline_sizes = kid.col_inline_sizes().clone();
+ self.col_min_inline_sizes = kid.col_min_inline_sizes().clone();
+ self.col_pref_inline_sizes = kid.col_pref_inline_sizes().clone();
+ } else {
+ min_inline_size = TableFlow::update_col_inline_sizes(&mut self.col_min_inline_sizes, kid.col_min_inline_sizes());
+ pref_inline_size = TableFlow::update_col_inline_sizes(&mut self.col_pref_inline_sizes, kid.col_pref_inline_sizes());
+
+ // update the number of column inline-sizes from table-rows.
+ let num_cols = self.col_inline_sizes.len();
+ let num_child_cols = kid.col_min_inline_sizes().len();
+ for i in range(num_cols, num_child_cols) {
+ self.col_inline_sizes.push(Au::new(0));
+ let new_kid_min = kid.col_min_inline_sizes()[i];
+ self.col_min_inline_sizes.push(kid.col_min_inline_sizes()[i]);
+ let new_kid_pref = kid.col_pref_inline_sizes()[i];
+ self.col_pref_inline_sizes.push(kid.col_pref_inline_sizes()[i]);
+ min_inline_size = min_inline_size + new_kid_min;
+ pref_inline_size = pref_inline_size + new_kid_pref;
+ }
+ }
+ }
+
+ self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size = min_inline_size;
+ self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size = geometry::max(min_inline_size,
+ pref_inline_size);
+ }
+
+ /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. When
+ /// called on this context, the context has had its inline-size set by the parent context.
+ fn assign_inline_sizes(&mut self, ctx: &LayoutContext) {
+ debug!("assign_inline_sizes({}): assigning inline_size for flow", "table_rowgroup");
+
+ // The position was set to the containing block by the flow's parent.
+ let containing_block_inline_size = self.block_flow.base.position.size.inline;
+ // FIXME: In case of border-collapse: collapse, inline-start_content_edge should be
+ // the border width on the inline-start side.
+ let inline_start_content_edge = Au::new(0);
+ let content_inline_size = containing_block_inline_size;
+
+ let inline_size_computer = InternalTable;
+ inline_size_computer.compute_used_inline_size(&mut self.block_flow, ctx, containing_block_inline_size);
+
+ self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge, content_inline_size, Some(self.col_inline_sizes.clone()));
+ }
+
+ fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
+ debug!("assign_block_size: assigning block_size for table_rowgroup");
+ self.assign_block_size_table_rowgroup_base(ctx);
+ }
+
+ fn compute_absolute_position(&mut self) {
+ self.block_flow.compute_absolute_position()
+ }
+}
+
+impl fmt::Show for TableRowGroupFlow {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "TableRowGroupFlow: {}", self.block_flow.fragment)
+ }
+}
diff --git a/components/layout/table_wrapper.rs b/components/layout/table_wrapper.rs
new file mode 100644
index 00000000000..2084ef52bdc
--- /dev/null
+++ b/components/layout/table_wrapper.rs
@@ -0,0 +1,325 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! CSS table formatting contexts.
+
+#![deny(unsafe_block)]
+
+use block::{BlockFlow, MarginsMayNotCollapse, ISizeAndMarginsComputer};
+use block::{ISizeConstraintInput, ISizeConstraintSolution};
+use construct::FlowConstructor;
+use context::LayoutContext;
+use floats::FloatKind;
+use flow::{TableWrapperFlowClass, FlowClass, Flow, ImmutableFlowUtils};
+use fragment::Fragment;
+use model::{Specified, Auto, specified};
+use wrapper::ThreadSafeLayoutNode;
+
+use servo_util::geometry::Au;
+use servo_util::geometry;
+use std::fmt;
+use style::computed_values::table_layout;
+
+pub enum TableLayout {
+ FixedLayout,
+ AutoLayout
+}
+
+/// A table wrapper flow based on a block formatting context.
+pub struct TableWrapperFlow {
+ pub block_flow: BlockFlow,
+
+ /// Column inline-sizes
+ pub col_inline_sizes: Vec<Au>,
+
+ /// Table-layout property
+ pub table_layout: TableLayout,
+}
+
+impl TableWrapperFlow {
+ pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode,
+ fragment: Fragment)
+ -> TableWrapperFlow {
+ let mut block_flow = BlockFlow::from_node_and_fragment(node, fragment);
+ let table_layout = if block_flow.fragment().style().get_table().table_layout ==
+ table_layout::fixed {
+ FixedLayout
+ } else {
+ AutoLayout
+ };
+ TableWrapperFlow {
+ block_flow: block_flow,
+ col_inline_sizes: vec!(),
+ table_layout: table_layout
+ }
+ }
+
+ pub fn from_node(constructor: &mut FlowConstructor,
+ node: &ThreadSafeLayoutNode)
+ -> TableWrapperFlow {
+ let mut block_flow = BlockFlow::from_node(constructor, node);
+ let table_layout = if block_flow.fragment().style().get_table().table_layout ==
+ table_layout::fixed {
+ FixedLayout
+ } else {
+ AutoLayout
+ };
+ TableWrapperFlow {
+ block_flow: block_flow,
+ col_inline_sizes: vec!(),
+ table_layout: table_layout
+ }
+ }
+
+ pub fn float_from_node(constructor: &mut FlowConstructor,
+ node: &ThreadSafeLayoutNode,
+ float_kind: FloatKind)
+ -> TableWrapperFlow {
+ let mut block_flow = BlockFlow::float_from_node(constructor, node, float_kind);
+ let table_layout = if block_flow.fragment().style().get_table().table_layout ==
+ table_layout::fixed {
+ FixedLayout
+ } else {
+ AutoLayout
+ };
+ TableWrapperFlow {
+ block_flow: block_flow,
+ col_inline_sizes: vec!(),
+ table_layout: table_layout
+ }
+ }
+
+ pub fn is_float(&self) -> bool {
+ self.block_flow.float.is_some()
+ }
+
+ /// Assign block-size for table-wrapper flow.
+ /// `Assign block-size` of table-wrapper flow follows a similar process to that of block flow.
+ ///
+ /// inline(always) because this is only ever called by in-order or non-in-order top-level
+ /// methods
+ #[inline(always)]
+ fn assign_block_size_table_wrapper_base<'a>(&mut self, layout_context: &'a LayoutContext<'a>) {
+ self.block_flow.assign_block_size_block_base(layout_context, MarginsMayNotCollapse);
+ }
+
+ pub fn build_display_list_table_wrapper(&mut self, layout_context: &LayoutContext) {
+ debug!("build_display_list_table_wrapper: same process as block flow");
+ self.block_flow.build_display_list_block(layout_context);
+ }
+}
+
+impl Flow for TableWrapperFlow {
+ fn class(&self) -> FlowClass {
+ TableWrapperFlowClass
+ }
+
+ fn as_table_wrapper<'a>(&'a mut self) -> &'a mut TableWrapperFlow {
+ self
+ }
+
+ fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow {
+ &mut self.block_flow
+ }
+
+ /* Recursively (bottom-up) determine the context's preferred and
+ minimum inline_sizes. When called on this context, all child contexts
+ have had their min/pref inline_sizes set. This function must decide
+ min/pref inline_sizes based on child context inline_sizes and dimensions of
+ any fragments it is responsible for flowing. */
+
+ fn bubble_inline_sizes(&mut self, ctx: &LayoutContext) {
+ // get column inline-sizes info from table flow
+ for kid in self.block_flow.base.child_iter() {
+ assert!(kid.is_table_caption() || kid.is_table());
+
+ if kid.is_table() {
+ self.col_inline_sizes.push_all(kid.as_table().col_inline_sizes.as_slice());
+ }
+ }
+
+ self.block_flow.bubble_inline_sizes(ctx);
+ }
+
+ /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. When
+ /// called on this context, the context has had its inline-size set by the parent context.
+ ///
+ /// Dual fragments consume some inline-size first, and the remainder is assigned to all child (block)
+ /// contexts.
+ fn assign_inline_sizes(&mut self, ctx: &LayoutContext) {
+ debug!("assign_inline_sizes({}): assigning inline_size for flow",
+ if self.is_float() {
+ "floated table_wrapper"
+ } else {
+ "table_wrapper"
+ });
+
+ // The position was set to the containing block by the flow's parent.
+ let containing_block_inline_size = self.block_flow.base.position.size.inline;
+
+ let inline_size_computer = TableWrapper;
+ inline_size_computer.compute_used_inline_size_table_wrapper(self, ctx, containing_block_inline_size);
+
+ let inline_start_content_edge = self.block_flow.fragment.border_box.start.i;
+ let content_inline_size = self.block_flow.fragment.border_box.size.inline;
+
+ match self.table_layout {
+ FixedLayout | _ if self.is_float() =>
+ self.block_flow.base.position.size.inline = content_inline_size,
+ _ => {}
+ }
+
+ // In case of fixed layout, column inline-sizes are calculated in table flow.
+ let assigned_col_inline_sizes = match self.table_layout {
+ FixedLayout => None,
+ AutoLayout => Some(self.col_inline_sizes.clone())
+ };
+ self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge, content_inline_size, assigned_col_inline_sizes);
+ }
+
+ fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
+ if self.is_float() {
+ debug!("assign_block_size_float: assigning block_size for floated table_wrapper");
+ self.block_flow.assign_block_size_float(ctx);
+ } else {
+ debug!("assign_block_size: assigning block_size for table_wrapper");
+ self.assign_block_size_table_wrapper_base(ctx);
+ }
+ }
+
+ fn compute_absolute_position(&mut self) {
+ self.block_flow.compute_absolute_position()
+ }
+}
+
+impl fmt::Show for TableWrapperFlow {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ if self.is_float() {
+ write!(f, "TableWrapperFlow(Float): {}", self.block_flow.fragment)
+ } else {
+ write!(f, "TableWrapperFlow: {}", self.block_flow.fragment)
+ }
+ }
+}
+
+struct TableWrapper;
+
+impl TableWrapper {
+ fn compute_used_inline_size_table_wrapper(&self,
+ table_wrapper: &mut TableWrapperFlow,
+ ctx: &LayoutContext,
+ parent_flow_inline_size: Au) {
+ let input = self.compute_inline_size_constraint_inputs_table_wrapper(table_wrapper,
+ parent_flow_inline_size,
+ ctx);
+
+ let solution = self.solve_inline_size_constraints(&mut table_wrapper.block_flow, &input);
+
+ self.set_inline_size_constraint_solutions(&mut table_wrapper.block_flow, solution);
+ self.set_flow_x_coord_if_necessary(&mut table_wrapper.block_flow, solution);
+ }
+
+ fn compute_inline_size_constraint_inputs_table_wrapper(&self,
+ table_wrapper: &mut TableWrapperFlow,
+ parent_flow_inline_size: Au,
+ ctx: &LayoutContext)
+ -> ISizeConstraintInput {
+ let mut input = self.compute_inline_size_constraint_inputs(&mut table_wrapper.block_flow,
+ parent_flow_inline_size,
+ ctx);
+ let computed_inline_size = match table_wrapper.table_layout {
+ FixedLayout => {
+ let fixed_cells_inline_size = table_wrapper.col_inline_sizes.iter().fold(Au(0),
+ |sum, inline_size| sum.add(inline_size));
+
+ let mut computed_inline_size = input.computed_inline_size.specified_or_zero();
+ let style = table_wrapper.block_flow.fragment.style();
+
+ // Get inline-start and inline-end paddings, borders for table.
+ // We get these values from the fragment's style since table_wrapper doesn't have it's own border or padding.
+ // input.available_inline-size is same as containing_block_inline-size in table_wrapper.
+ let padding = style.logical_padding();
+ let border = style.logical_border_width();
+ let padding_and_borders =
+ specified(padding.inline_start, input.available_inline_size) +
+ specified(padding.inline_end, input.available_inline_size) +
+ border.inline_start +
+ border.inline_end;
+ // Compare border-edge inline-sizes. Because fixed_cells_inline-size indicates content-inline-size,
+ // padding and border values are added to fixed_cells_inline-size.
+ computed_inline_size = geometry::max(
+ fixed_cells_inline_size + padding_and_borders, computed_inline_size);
+ computed_inline_size
+ },
+ AutoLayout => {
+ // Automatic table layout is calculated according to CSS 2.1 § 17.5.2.2.
+ let mut cap_min = Au(0);
+ let mut cols_min = Au(0);
+ let mut cols_max = Au(0);
+ let mut col_min_inline_sizes = &vec!();
+ let mut col_pref_inline_sizes = &vec!();
+ for kid in table_wrapper.block_flow.base.child_iter() {
+ if kid.is_table_caption() {
+ cap_min = kid.as_block().base.intrinsic_inline_sizes.minimum_inline_size;
+ } else {
+ assert!(kid.is_table());
+ cols_min = kid.as_block().base.intrinsic_inline_sizes.minimum_inline_size;
+ cols_max = kid.as_block().base.intrinsic_inline_sizes.preferred_inline_size;
+ col_min_inline_sizes = kid.col_min_inline_sizes();
+ col_pref_inline_sizes = kid.col_pref_inline_sizes();
+ }
+ }
+ // 'extra_inline-size': difference between the calculated table inline-size and minimum inline-size
+ // required by all columns. It will be distributed over the columns.
+ let (inline_size, extra_inline_size) = match input.computed_inline_size {
+ Auto => {
+ if input.available_inline_size > geometry::max(cols_max, cap_min) {
+ if cols_max > cap_min {
+ table_wrapper.col_inline_sizes = col_pref_inline_sizes.clone();
+ (cols_max, Au(0))
+ } else {
+ (cap_min, cap_min - cols_min)
+ }
+ } else {
+ let max = if cols_min >= input.available_inline_size && cols_min >= cap_min {
+ table_wrapper.col_inline_sizes = col_min_inline_sizes.clone();
+ cols_min
+ } else {
+ geometry::max(input.available_inline_size, cap_min)
+ };
+ (max, max - cols_min)
+ }
+ },
+ Specified(inline_size) => {
+ let max = if cols_min >= inline_size && cols_min >= cap_min {
+ table_wrapper.col_inline_sizes = col_min_inline_sizes.clone();
+ cols_min
+ } else {
+ geometry::max(inline_size, cap_min)
+ };
+ (max, max - cols_min)
+ }
+ };
+ // The extra inline-size is distributed over the columns
+ if extra_inline_size > Au(0) {
+ let cell_len = table_wrapper.col_inline_sizes.len() as f64;
+ table_wrapper.col_inline_sizes = col_min_inline_sizes.iter().map(|inline_size| {
+ inline_size + extra_inline_size.scale_by(1.0 / cell_len)
+ }).collect();
+ }
+ inline_size
+ }
+ };
+ input.computed_inline_size = Specified(computed_inline_size);
+ input
+ }
+}
+
+impl ISizeAndMarginsComputer for TableWrapper {
+ /// Solve the inline-size and margins constraints for this block flow.
+ fn solve_inline_size_constraints(&self, block: &mut BlockFlow, input: &ISizeConstraintInput)
+ -> ISizeConstraintSolution {
+ self.solve_block_inline_size_constraints(block, input)
+ }
+}
diff --git a/components/layout/text.rs b/components/layout/text.rs
new file mode 100644
index 00000000000..e90272e218a
--- /dev/null
+++ b/components/layout/text.rs
@@ -0,0 +1,327 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Text layout.
+
+#![deny(unsafe_block)]
+
+use flow::Flow;
+use fragment::{Fragment, ScannedTextFragment, ScannedTextFragmentInfo, UnscannedTextFragment};
+
+use gfx::font::{FontMetrics, FontStyle, RunMetrics};
+use gfx::font_context::FontContext;
+use gfx::text::glyph::CharIndex;
+use gfx::text::text_run::TextRun;
+use gfx::text::util::{CompressWhitespaceNewline, transform_text, CompressNone};
+use servo_util::geometry::Au;
+use servo_util::logical_geometry::{LogicalSize, WritingMode};
+use servo_util::range::Range;
+use style::ComputedValues;
+use style::computed_values::{font_family, line_height, text_orientation, white_space};
+use sync::Arc;
+
+struct NewLinePositions {
+ new_line_pos: Vec<CharIndex>,
+}
+
+// A helper function.
+fn can_coalesce_text_nodes(fragments: &[Fragment], left_i: uint, right_i: uint) -> bool {
+ assert!(left_i != right_i);
+ fragments[left_i].can_merge_with_fragment(&fragments[right_i])
+}
+
+/// A stack-allocated object for scanning an inline flow into `TextRun`-containing `TextFragment`s.
+pub struct TextRunScanner {
+ pub clump: Range<CharIndex>,
+}
+
+impl TextRunScanner {
+ pub fn new() -> TextRunScanner {
+ TextRunScanner {
+ clump: Range::empty(),
+ }
+ }
+
+ pub fn scan_for_runs(&mut self, font_context: &mut FontContext, flow: &mut Flow) {
+ {
+ let inline = flow.as_immutable_inline();
+ debug!("TextRunScanner: scanning {:u} fragments for text runs...", inline.fragments.len());
+ }
+
+ let fragments = &mut flow.as_inline().fragments;
+
+ let mut last_whitespace = true;
+ let mut new_fragments = Vec::new();
+ for fragment_i in range(0, fragments.fragments.len()) {
+ debug!("TextRunScanner: considering fragment: {:u}", fragment_i);
+ if fragment_i > 0 && !can_coalesce_text_nodes(fragments.fragments.as_slice(), fragment_i - 1, fragment_i) {
+ last_whitespace = self.flush_clump_to_list(font_context,
+ fragments.fragments.as_slice(),
+ &mut new_fragments,
+ last_whitespace);
+ }
+
+ self.clump.extend_by(CharIndex(1));
+ }
+
+ // Handle remaining clumps.
+ if self.clump.length() > CharIndex(0) {
+ drop(self.flush_clump_to_list(font_context,
+ fragments.fragments.as_slice(),
+ &mut new_fragments,
+ last_whitespace))
+ }
+
+ debug!("TextRunScanner: swapping out fragments.");
+
+ fragments.fragments = new_fragments;
+ }
+
+ /// A "clump" is a range of inline flow leaves that can be merged together into a single
+ /// fragment. Adjacent text with the same style can be merged, and nothing else can.
+ ///
+ /// The flow keeps track of the fragments contained by all non-leaf DOM nodes. This is necessary
+ /// for correct painting order. Since we compress several leaf fragments here, the mapping must
+ /// be adjusted.
+ ///
+ /// FIXME(#2267, pcwalton): Stop cloning fragments. Instead we will need to replace each
+ /// `in_fragment` with some smaller stub.
+ fn flush_clump_to_list(&mut self,
+ font_context: &mut FontContext,
+ in_fragments: &[Fragment],
+ out_fragments: &mut Vec<Fragment>,
+ last_whitespace: bool)
+ -> bool {
+ assert!(self.clump.length() > CharIndex(0));
+
+ debug!("TextRunScanner: flushing fragments in range={}", self.clump);
+ let is_singleton = self.clump.length() == CharIndex(1);
+
+ let is_text_clump = match in_fragments[self.clump.begin().to_uint()].specific {
+ UnscannedTextFragment(_) => true,
+ _ => false,
+ };
+
+ let mut new_whitespace = last_whitespace;
+ match (is_singleton, is_text_clump) {
+ (false, false) => {
+ fail!("WAT: can't coalesce non-text nodes in flush_clump_to_list()!")
+ }
+ (true, false) => {
+ // FIXME(pcwalton): Stop cloning fragments, as above.
+ debug!("TextRunScanner: pushing single non-text fragment in range: {}", self.clump);
+ let new_fragment = in_fragments[self.clump.begin().to_uint()].clone();
+ out_fragments.push(new_fragment)
+ },
+ (true, true) => {
+ let old_fragment = &in_fragments[self.clump.begin().to_uint()];
+ let text = match old_fragment.specific {
+ UnscannedTextFragment(ref text_fragment_info) => &text_fragment_info.text,
+ _ => fail!("Expected an unscanned text fragment!"),
+ };
+
+ let font_style = old_fragment.font_style();
+
+ let compression = match old_fragment.white_space() {
+ white_space::normal => CompressWhitespaceNewline,
+ white_space::pre => CompressNone,
+ };
+
+ let mut new_line_pos = vec![];
+
+ let (transformed_text, whitespace) = transform_text(text.as_slice(),
+ compression,
+ last_whitespace,
+ &mut new_line_pos);
+
+ new_whitespace = whitespace;
+
+ if transformed_text.len() > 0 {
+ // TODO(#177): Text run creation must account for the renderability of text by
+ // font group fonts. This is probably achieved by creating the font group above
+ // and then letting `FontGroup` decide which `Font` to stick into the text run.
+ let fontgroup = font_context.get_layout_font_group_for_style(&font_style);
+ let run = box fontgroup.create_textrun(
+ transformed_text.clone());
+
+ debug!("TextRunScanner: pushing single text fragment in range: {} ({})",
+ self.clump,
+ *text);
+ let range = Range::new(CharIndex(0), run.char_len());
+ let new_metrics = run.metrics_for_range(&range);
+ let bounding_box_size = bounding_box_for_run_metrics(
+ &new_metrics, old_fragment.style.writing_mode);
+ let new_text_fragment_info = ScannedTextFragmentInfo::new(Arc::new(run), range);
+ let mut new_fragment = old_fragment.transform(
+ bounding_box_size, ScannedTextFragment(new_text_fragment_info));
+ new_fragment.new_line_pos = new_line_pos;
+ out_fragments.push(new_fragment)
+ }
+ },
+ (false, true) => {
+ // TODO(#177): Text run creation must account for the renderability of text by
+ // font group fonts. This is probably achieved by creating the font group above
+ // and then letting `FontGroup` decide which `Font` to stick into the text run.
+ let in_fragment = &in_fragments[self.clump.begin().to_uint()];
+ let font_style = in_fragment.font_style();
+ let fontgroup = font_context.get_layout_font_group_for_style(&font_style);
+
+ let compression = match in_fragment.white_space() {
+ white_space::normal => CompressWhitespaceNewline,
+ white_space::pre => CompressNone,
+ };
+
+ let mut new_line_positions: Vec<NewLinePositions> = vec![];
+
+ // First, transform/compress text of all the nodes.
+ let mut last_whitespace_in_clump = new_whitespace;
+ let transformed_strs: Vec<String> = Vec::from_fn(self.clump.length().to_uint(), |i| {
+ let idx = CharIndex(i as int) + self.clump.begin();
+ let in_fragment = match in_fragments[idx.to_uint()].specific {
+ UnscannedTextFragment(ref text_fragment_info) => &text_fragment_info.text,
+ _ => fail!("Expected an unscanned text fragment!"),
+ };
+
+ let mut new_line_pos = vec![];
+
+ let (new_str, new_whitespace) = transform_text(in_fragment.as_slice(),
+ compression,
+ last_whitespace_in_clump,
+ &mut new_line_pos);
+ new_line_positions.push(NewLinePositions { new_line_pos: new_line_pos });
+
+ last_whitespace_in_clump = new_whitespace;
+ new_str
+ });
+ new_whitespace = last_whitespace_in_clump;
+
+ // Next, concatenate all of the transformed strings together, saving the new
+ // character indices.
+ let mut run_str = String::new();
+ let mut new_ranges: Vec<Range<CharIndex>> = vec![];
+ let mut char_total = CharIndex(0);
+ for i in range(0, transformed_strs.len() as int) {
+ let added_chars = CharIndex(transformed_strs[i as uint].as_slice().char_len() as int);
+ new_ranges.push(Range::new(char_total, added_chars));
+ run_str.push_str(transformed_strs[i as uint].as_slice());
+ char_total = char_total + added_chars;
+ }
+
+ // Now create the run.
+ // TextRuns contain a cycle which is usually resolved by the teardown
+ // sequence. If no clump takes ownership, however, it will leak.
+ let clump = self.clump;
+ let run = if clump.length() != CharIndex(0) && run_str.len() > 0 {
+ Some(Arc::new(box TextRun::new(
+ &mut *fontgroup.fonts[0].borrow_mut(),
+ run_str.to_string())))
+ } else {
+ None
+ };
+
+ // Make new fragments with the run and adjusted text indices.
+ debug!("TextRunScanner: pushing fragment(s) in range: {}", self.clump);
+ for i in clump.each_index() {
+ let logical_offset = i - self.clump.begin();
+ let range = new_ranges[logical_offset.to_uint()];
+ if range.length() == CharIndex(0) {
+ debug!("Elided an `UnscannedTextFragment` because it was zero-length after \
+ compression; {}", in_fragments[i.to_uint()]);
+ continue
+ }
+
+ let new_text_fragment_info = ScannedTextFragmentInfo::new(run.get_ref().clone(), range);
+ let old_fragment = &in_fragments[i.to_uint()];
+ let new_metrics = new_text_fragment_info.run.metrics_for_range(&range);
+ let bounding_box_size = bounding_box_for_run_metrics(
+ &new_metrics, old_fragment.style.writing_mode);
+ let mut new_fragment = old_fragment.transform(
+ bounding_box_size, ScannedTextFragment(new_text_fragment_info));
+ new_fragment.new_line_pos = new_line_positions[logical_offset.to_uint()].new_line_pos.clone();
+ out_fragments.push(new_fragment)
+ }
+ }
+ } // End of match.
+
+ let end = self.clump.end(); // FIXME: borrow checker workaround
+ self.clump.reset(end, CharIndex(0));
+
+ new_whitespace
+ } // End of `flush_clump_to_list`.
+}
+
+
+#[inline]
+fn bounding_box_for_run_metrics(metrics: &RunMetrics, writing_mode: WritingMode)
+ -> LogicalSize<Au> {
+
+ // This does nothing, but it will fail to build
+ // when more values are added to the `text-orientation` CSS property.
+ // This will be a reminder to update the code below.
+ let dummy: Option<text_orientation::T> = None;
+ match dummy {
+ Some(text_orientation::sideways_right) |
+ Some(text_orientation::sideways_left) |
+ Some(text_orientation::sideways) |
+ None => {}
+ }
+
+ // In vertical sideways or horizontal upgright text,
+ // the "width" of text metrics is always inline
+ // This will need to be updated when other text orientations are supported.
+ LogicalSize::new(
+ writing_mode,
+ metrics.bounding_box.size.width,
+ metrics.bounding_box.size.height)
+
+}
+
+/// Returns the metrics of the font represented by the given `FontStyle`, respectively.
+///
+/// `#[inline]` because often the caller only needs a few fields from the font metrics.
+#[inline]
+pub fn font_metrics_for_style(font_context: &mut FontContext, font_style: &FontStyle)
+ -> FontMetrics {
+ let fontgroup = font_context.get_layout_font_group_for_style(font_style);
+ fontgroup.fonts[0].borrow().metrics.clone()
+}
+
+/// Converts a computed style to a font style used for rendering.
+///
+/// FIXME(pcwalton): This should not be necessary; just make the font part of the style sharable
+/// with the display list somehow. (Perhaps we should use an ARC.)
+pub fn computed_style_to_font_style(style: &ComputedValues) -> FontStyle {
+ debug!("(font style) start");
+
+ // FIXME: Too much allocation here.
+ let mut font_families = style.get_font().font_family.iter().map(|family| {
+ match *family {
+ font_family::FamilyName(ref name) => (*name).clone(),
+ }
+ });
+ debug!("(font style) font families: `{:?}`", font_families);
+
+ let font_size = style.get_font().font_size.to_f64().unwrap() / 60.0;
+ debug!("(font style) font size: `{:f}px`", font_size);
+
+ FontStyle {
+ pt_size: font_size,
+ weight: style.get_font().font_weight,
+ style: style.get_font().font_style,
+ families: font_families.collect(),
+ }
+}
+
+/// Returns the line block-size needed by the given computed style and font size.
+pub fn line_height_from_style(style: &ComputedValues, metrics: &FontMetrics) -> Au {
+ let font_size = style.get_font().font_size;
+ let from_inline = match style.get_inheritedbox().line_height {
+ line_height::Normal => metrics.line_gap,
+ line_height::Number(l) => font_size.scale_by(l),
+ line_height::Length(l) => l
+ };
+ let minimum = style.get_inheritedbox()._servo_minimum_line_height;
+ Au::max(from_inline, minimum)
+}
+
diff --git a/components/layout/util.rs b/components/layout/util.rs
new file mode 100644
index 00000000000..a0e466e8875
--- /dev/null
+++ b/components/layout/util.rs
@@ -0,0 +1,164 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use construct::{ConstructionResult, NoConstructionResult};
+use incremental::RestyleDamage;
+use parallel::DomParallelInfo;
+use wrapper::{LayoutNode, TLayoutNode, ThreadSafeLayoutNode};
+
+use gfx::display_list::OpaqueNode;
+use gfx;
+use libc::uintptr_t;
+use script::dom::bindings::js::JS;
+use script::dom::bindings::utils::Reflectable;
+use script::dom::node::{Node, SharedLayoutData};
+use script::layout_interface::{LayoutChan, UntrustedNodeAddress, TrustedNodeAddress};
+use std::mem;
+use std::cell::{Ref, RefMut};
+use style::ComputedValues;
+use style;
+use sync::Arc;
+
+/// Data that layout associates with a node.
+pub struct PrivateLayoutData {
+ /// The results of CSS styling for this node's `before` pseudo-element, if any.
+ pub before_style: Option<Arc<ComputedValues>>,
+
+ /// The results of CSS styling for this node's `after` pseudo-element, if any.
+ pub after_style: Option<Arc<ComputedValues>>,
+
+ /// Description of how to account for recent style changes.
+ pub restyle_damage: Option<RestyleDamage>,
+
+ /// The current results of flow construction for this node. This is either a flow or a
+ /// `ConstructionItem`. See comments in `construct.rs` for more details.
+ pub flow_construction_result: ConstructionResult,
+
+ pub before_flow_construction_result: ConstructionResult,
+
+ pub after_flow_construction_result: ConstructionResult,
+
+ /// Information needed during parallel traversals.
+ pub parallel: DomParallelInfo,
+}
+
+impl PrivateLayoutData {
+ /// Creates new layout data.
+ pub fn new() -> PrivateLayoutData {
+ PrivateLayoutData {
+ before_style: None,
+ after_style: None,
+ restyle_damage: None,
+ flow_construction_result: NoConstructionResult,
+ before_flow_construction_result: NoConstructionResult,
+ after_flow_construction_result: NoConstructionResult,
+ parallel: DomParallelInfo::new(),
+ }
+ }
+}
+
+pub struct LayoutDataWrapper {
+ pub chan: Option<LayoutChan>,
+ pub shared_data: SharedLayoutData,
+ pub data: Box<PrivateLayoutData>,
+}
+
+/// A trait that allows access to the layout data of a DOM node.
+pub trait LayoutDataAccess {
+ /// Borrows the layout data without checks.
+ unsafe fn borrow_layout_data_unchecked(&self) -> *const Option<LayoutDataWrapper>;
+ /// Borrows the layout data immutably. Fails on a conflicting borrow.
+ fn borrow_layout_data<'a>(&'a self) -> Ref<'a,Option<LayoutDataWrapper>>;
+ /// Borrows the layout data mutably. Fails on a conflicting borrow.
+ fn mutate_layout_data<'a>(&'a self) -> RefMut<'a,Option<LayoutDataWrapper>>;
+}
+
+impl<'ln> LayoutDataAccess for LayoutNode<'ln> {
+ #[inline(always)]
+ unsafe fn borrow_layout_data_unchecked(&self) -> *const Option<LayoutDataWrapper> {
+ mem::transmute(self.get().layout_data.borrow_unchecked())
+ }
+
+ #[inline(always)]
+ fn borrow_layout_data<'a>(&'a self) -> Ref<'a,Option<LayoutDataWrapper>> {
+ unsafe {
+ mem::transmute(self.get().layout_data.borrow())
+ }
+ }
+
+ #[inline(always)]
+ fn mutate_layout_data<'a>(&'a self) -> RefMut<'a,Option<LayoutDataWrapper>> {
+ unsafe {
+ mem::transmute(self.get().layout_data.borrow_mut())
+ }
+ }
+}
+
+pub trait OpaqueNodeMethods {
+ /// Converts a DOM node (layout view) to an `OpaqueNode`.
+ fn from_layout_node(node: &LayoutNode) -> Self;
+
+ /// Converts a thread-safe DOM node (layout view) to an `OpaqueNode`.
+ fn from_thread_safe_layout_node(node: &ThreadSafeLayoutNode) -> Self;
+
+ /// Converts a DOM node (script view) to an `OpaqueNode`.
+ fn from_script_node(node: TrustedNodeAddress) -> Self;
+
+ /// Converts a DOM node to an `OpaqueNode'.
+ fn from_jsmanaged(node: &JS<Node>) -> Self;
+
+ /// Converts this node to an `UntrustedNodeAddress`. An `UntrustedNodeAddress` is just the type
+ /// of node that script expects to receive in a hit test.
+ fn to_untrusted_node_address(&self) -> UntrustedNodeAddress;
+}
+
+impl OpaqueNodeMethods for OpaqueNode {
+ fn from_layout_node(node: &LayoutNode) -> OpaqueNode {
+ unsafe {
+ OpaqueNodeMethods::from_jsmanaged(node.get_jsmanaged())
+ }
+ }
+
+ fn from_thread_safe_layout_node(node: &ThreadSafeLayoutNode) -> OpaqueNode {
+ unsafe {
+ let abstract_node = node.get_jsmanaged();
+ let ptr: uintptr_t = abstract_node.reflector().get_jsobject() as uint;
+ OpaqueNode(ptr)
+ }
+ }
+
+ fn from_script_node(node: TrustedNodeAddress) -> OpaqueNode {
+ unsafe {
+ OpaqueNodeMethods::from_jsmanaged(&JS::from_trusted_node_address(node))
+ }
+ }
+
+ fn from_jsmanaged(node: &JS<Node>) -> OpaqueNode {
+ unsafe {
+ let ptr: uintptr_t = mem::transmute(node.reflector().get_jsobject());
+ OpaqueNode(ptr)
+ }
+ }
+
+ fn to_untrusted_node_address(&self) -> UntrustedNodeAddress {
+ unsafe {
+ let OpaqueNode(addr) = *self;
+ let addr: UntrustedNodeAddress = mem::transmute(addr);
+ addr
+ }
+ }
+}
+
+/// Allows a CSS color to be converted into a graphics color.
+pub trait ToGfxColor {
+ /// Converts a CSS color to a graphics color.
+ fn to_gfx_color(&self) -> gfx::color::Color;
+}
+
+impl ToGfxColor for style::computed_values::RGBA {
+ fn to_gfx_color(&self) -> gfx::color::Color {
+ gfx::color::rgba(self.red, self.green, self.blue, self.alpha)
+ }
+}
+
diff --git a/components/layout/wrapper.rs b/components/layout/wrapper.rs
new file mode 100644
index 00000000000..d052c263655
--- /dev/null
+++ b/components/layout/wrapper.rs
@@ -0,0 +1,783 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! A safe wrapper for DOM nodes that prevents layout from mutating the DOM, from letting DOM nodes
+//! escape, and from generally doing anything that it isn't supposed to. This is accomplished via
+//! a simple whitelist of allowed operations, along with some lifetime magic to prevent nodes from
+//! escaping.
+//!
+//! As a security wrapper is only as good as its whitelist, be careful when adding operations to
+//! this list. The cardinal rules are:
+//!
+//! 1. Layout is not allowed to mutate the DOM.
+//!
+//! 2. Layout is not allowed to see anything with `JS` in the name, because it could hang
+//! onto these objects and cause use-after-free.
+//!
+//! When implementing wrapper functions, be careful that you do not touch the borrow flags, or you
+//! will race and cause spurious task failure. (Note that I do not believe these races are
+//! exploitable, but they'll result in brokenness nonetheless.)
+//!
+//! Rules of the road for this file:
+//!
+//! * In general, you must not use the `Cast` functions; use explicit checks and `transmute_copy`
+//! instead.
+//!
+//! * You must also not use `.get()`; instead, use `.unsafe_get()`.
+//!
+//! * Do not call any methods on DOM nodes without checking to see whether they use borrow flags.
+//!
+//! o Instead of `get_attr()`, use `.get_attr_val_for_layout()`.
+//!
+//! o Instead of `html_element_in_html_document()`, use
+//! `html_element_in_html_document_for_layout()`.
+
+use css::node_style::StyledNode;
+use util::LayoutDataWrapper;
+
+use script::dom::bindings::codegen::InheritTypes::{HTMLIFrameElementDerived};
+use script::dom::bindings::codegen::InheritTypes::{HTMLImageElementDerived, TextDerived};
+use script::dom::bindings::js::JS;
+use script::dom::element::{Element, HTMLAreaElementTypeId, HTMLAnchorElementTypeId};
+use script::dom::element::{HTMLLinkElementTypeId, LayoutElementHelpers, RawLayoutElementHelpers};
+use script::dom::htmliframeelement::HTMLIFrameElement;
+use script::dom::htmlimageelement::{HTMLImageElement, LayoutHTMLImageElementHelpers};
+use script::dom::node::{DocumentNodeTypeId, ElementNodeTypeId, Node, NodeTypeId};
+use script::dom::node::{LayoutNodeHelpers, RawLayoutNodeHelpers, TextNodeTypeId};
+use script::dom::text::Text;
+use servo_msg::constellation_msg::{PipelineId, SubpageId};
+use servo_util::atom::Atom;
+use servo_util::namespace::Namespace;
+use servo_util::namespace;
+use servo_util::str::is_whitespace;
+use std::cell::{RefCell, Ref, RefMut};
+use std::kinds::marker::ContravariantLifetime;
+use std::mem;
+use style::computed_values::{content, display, white_space};
+use style::{AnyNamespace, AttrSelector, PropertyDeclarationBlock, SpecificNamespace, TElement};
+use style::{TNode};
+use url::Url;
+
+/// Allows some convenience methods on generic layout nodes.
+pub trait TLayoutNode {
+ /// Creates a new layout node with the same lifetime as this layout node.
+ unsafe fn new_with_this_lifetime(&self, node: &JS<Node>) -> Self;
+
+ /// Returns the type ID of this node. Fails if this node is borrowed mutably. Returns `None`
+ /// if this is a pseudo-element; otherwise, returns `Some`.
+ fn type_id(&self) -> Option<NodeTypeId>;
+
+ /// Returns the interior of this node as a `JS`. This is highly unsafe for layout to
+ /// call and as such is marked `unsafe`.
+ unsafe fn get_jsmanaged<'a>(&'a self) -> &'a JS<Node>;
+
+ /// Returns the interior of this node as a `Node`. This is highly unsafe for layout to call
+ /// and as such is marked `unsafe`.
+ unsafe fn get<'a>(&'a self) -> &'a Node {
+ &*self.get_jsmanaged().unsafe_get()
+ }
+
+ fn node_is_element(&self) -> bool {
+ match self.type_id() {
+ Some(ElementNodeTypeId(..)) => true,
+ _ => false
+ }
+ }
+
+ fn node_is_document(&self) -> bool {
+ match self.type_id() {
+ Some(DocumentNodeTypeId(..)) => true,
+ _ => false
+ }
+ }
+
+ /// If this is an image element, returns its URL. If this is not an image element, fails.
+ ///
+ /// FIXME(pcwalton): Don't copy URLs.
+ fn image_url(&self) -> Option<Url> {
+ unsafe {
+ if !self.get().is_htmlimageelement() {
+ fail!("not an image!")
+ }
+ let image_element: JS<HTMLImageElement> = self.get_jsmanaged().transmute_copy();
+ image_element.image().as_ref().map(|url| (*url).clone())
+ }
+ }
+
+ /// If this node is an iframe element, returns its pipeline and subpage IDs. If this node is
+ /// not an iframe element, fails.
+ fn iframe_pipeline_and_subpage_ids(&self) -> (PipelineId, SubpageId) {
+ unsafe {
+ if !self.get().is_htmliframeelement() {
+ fail!("not an iframe element!")
+ }
+ let iframe_element: JS<HTMLIFrameElement> = self.get_jsmanaged().transmute_copy();
+ let size = (*iframe_element.unsafe_get()).size.deref().get().unwrap();
+ (size.pipeline_id, size.subpage_id)
+ }
+ }
+
+ /// If this is a text node, copies out the text. If this is not a text node, fails.
+ ///
+ /// FIXME(pcwalton): Don't copy text. Atomically reference count instead.
+ fn text(&self) -> String;
+
+ /// Returns the first child of this node.
+ fn first_child(&self) -> Option<Self>;
+
+ /// Dumps this node tree, for debugging.
+ fn dump(&self) {
+ // TODO(pcwalton): Reimplement this in a way that's safe for layout to call.
+ }
+}
+
+/// A wrapper so that layout can access only the methods that it should have access to. Layout must
+/// only ever see these and must never see instances of `JS`.
+pub struct LayoutNode<'a> {
+ /// The wrapped node.
+ node: JS<Node>,
+
+ /// Being chained to a ContravariantLifetime prevents `LayoutNode`s from escaping.
+ pub chain: ContravariantLifetime<'a>,
+}
+
+impl<'ln> Clone for LayoutNode<'ln> {
+ fn clone(&self) -> LayoutNode<'ln> {
+ LayoutNode {
+ node: self.node.clone(),
+ chain: self.chain,
+ }
+ }
+}
+
+impl<'a> PartialEq for LayoutNode<'a> {
+ #[inline]
+ fn eq(&self, other: &LayoutNode) -> bool {
+ self.node == other.node
+ }
+}
+
+
+impl<'ln> TLayoutNode for LayoutNode<'ln> {
+ unsafe fn new_with_this_lifetime(&self, node: &JS<Node>) -> LayoutNode<'ln> {
+ LayoutNode {
+ node: node.transmute_copy(),
+ chain: self.chain,
+ }
+ }
+
+ fn type_id(&self) -> Option<NodeTypeId> {
+ unsafe {
+ Some(self.node.type_id_for_layout())
+ }
+ }
+
+ unsafe fn get_jsmanaged<'a>(&'a self) -> &'a JS<Node> {
+ &self.node
+ }
+
+ fn first_child(&self) -> Option<LayoutNode<'ln>> {
+ unsafe {
+ self.get_jsmanaged().first_child_ref().map(|node| self.new_with_this_lifetime(&node))
+ }
+ }
+
+ fn text(&self) -> String {
+ unsafe {
+ if !self.get().is_text() {
+ fail!("not text!")
+ }
+ let text: JS<Text> = self.get_jsmanaged().transmute_copy();
+ (*text.unsafe_get()).characterdata.data.deref().borrow().clone()
+ }
+ }
+}
+
+impl<'ln> LayoutNode<'ln> {
+ /// Creates a new layout node, scoped to the given closure.
+ pub unsafe fn with_layout_node<R>(node: JS<Node>, f: <'a> |LayoutNode<'a>| -> R) -> R {
+ f(LayoutNode {
+ node: node,
+ chain: ContravariantLifetime,
+ })
+ }
+
+ /// Iterates over this node and all its descendants, in preorder.
+ ///
+ /// FIXME(pcwalton): Terribly inefficient. We should use parallelism.
+ pub fn traverse_preorder(&self) -> LayoutTreeIterator<'ln> {
+ let mut nodes = vec!();
+ gather_layout_nodes(self, &mut nodes, false);
+ LayoutTreeIterator::new(nodes)
+ }
+
+ /// Returns an iterator over this node's children.
+ pub fn children(&self) -> LayoutNodeChildrenIterator<'ln> {
+ LayoutNodeChildrenIterator {
+ current_node: self.first_child(),
+ }
+ }
+
+ pub unsafe fn get_jsmanaged<'a>(&'a self) -> &'a JS<Node> {
+ &self.node
+ }
+}
+
+impl<'ln> TNode<LayoutElement<'ln>> for LayoutNode<'ln> {
+ fn parent_node(&self) -> Option<LayoutNode<'ln>> {
+ unsafe {
+ self.node.parent_node_ref().map(|node| self.new_with_this_lifetime(&node))
+ }
+ }
+
+ fn prev_sibling(&self) -> Option<LayoutNode<'ln>> {
+ unsafe {
+ self.node.prev_sibling_ref().map(|node| self.new_with_this_lifetime(&node))
+ }
+ }
+
+ fn next_sibling(&self) -> Option<LayoutNode<'ln>> {
+ unsafe {
+ self.node.next_sibling_ref().map(|node| self.new_with_this_lifetime(&node))
+ }
+ }
+
+ /// If this is an element, accesses the element data. Fails if this is not an element node.
+ #[inline]
+ fn as_element(&self) -> LayoutElement<'ln> {
+ unsafe {
+ assert!(self.node.is_element_for_layout());
+ let elem: JS<Element> = self.node.transmute_copy();
+ let element = &*elem.unsafe_get();
+ LayoutElement {
+ element: mem::transmute(element),
+ }
+ }
+ }
+
+ fn is_element(&self) -> bool {
+ self.node_is_element()
+ }
+
+ fn is_document(&self) -> bool {
+ self.node_is_document()
+ }
+
+ fn match_attr(&self, attr: &AttrSelector, test: |&str| -> bool) -> bool {
+ assert!(self.is_element())
+ let name = if self.is_html_element_in_html_document() {
+ attr.lower_name.as_slice()
+ } else {
+ attr.name.as_slice()
+ };
+ match attr.namespace {
+ SpecificNamespace(ref ns) => {
+ let element = self.as_element();
+ element.get_attr(ns, name)
+ .map_or(false, |attr| test(attr))
+ },
+ // FIXME: https://github.com/mozilla/servo/issues/1558
+ AnyNamespace => false,
+ }
+ }
+
+ fn is_html_element_in_html_document(&self) -> bool {
+ unsafe {
+ self.is_element() && {
+ let element: JS<Element> = self.node.transmute_copy();
+ element.html_element_in_html_document_for_layout()
+ }
+ }
+ }
+}
+
+pub struct LayoutNodeChildrenIterator<'a> {
+ current_node: Option<LayoutNode<'a>>,
+}
+
+impl<'a> Iterator<LayoutNode<'a>> for LayoutNodeChildrenIterator<'a> {
+ fn next(&mut self) -> Option<LayoutNode<'a>> {
+ let node = self.current_node.clone();
+ self.current_node = node.clone().and_then(|node| {
+ node.next_sibling()
+ });
+ node
+ }
+}
+
+// FIXME: Do this without precomputing a vector of refs.
+// Easy for preorder; harder for postorder.
+//
+// FIXME(pcwalton): Parallelism! Eventually this should just be nuked.
+pub struct LayoutTreeIterator<'a> {
+ nodes: Vec<LayoutNode<'a>>,
+ index: uint,
+}
+
+impl<'a> LayoutTreeIterator<'a> {
+ fn new(nodes: Vec<LayoutNode<'a>>) -> LayoutTreeIterator<'a> {
+ LayoutTreeIterator {
+ nodes: nodes,
+ index: 0,
+ }
+ }
+}
+
+impl<'a> Iterator<LayoutNode<'a>> for LayoutTreeIterator<'a> {
+ fn next(&mut self) -> Option<LayoutNode<'a>> {
+ if self.index >= self.nodes.len() {
+ None
+ } else {
+ let v = self.nodes[self.index].clone();
+ self.index += 1;
+ Some(v)
+ }
+ }
+}
+
+/// FIXME(pcwalton): This is super inefficient.
+fn gather_layout_nodes<'a>(cur: &LayoutNode<'a>, refs: &mut Vec<LayoutNode<'a>>, postorder: bool) {
+ if !postorder {
+ refs.push(cur.clone());
+ }
+ for kid in cur.children() {
+ gather_layout_nodes(&kid, refs, postorder)
+ }
+ if postorder {
+ refs.push(cur.clone());
+ }
+}
+
+/// A wrapper around elements that ensures layout can only ever access safe properties.
+pub struct LayoutElement<'le> {
+ element: &'le Element,
+}
+
+impl<'le> LayoutElement<'le> {
+ pub fn style_attribute(&self) -> &'le Option<PropertyDeclarationBlock> {
+ let style: &Option<PropertyDeclarationBlock> = unsafe {
+ let style: &RefCell<Option<PropertyDeclarationBlock>> = self.element.style_attribute.deref();
+ // cast to the direct reference to T placed on the head of RefCell<T>
+ mem::transmute(style)
+ };
+ style
+ }
+}
+
+impl<'le> TElement for LayoutElement<'le> {
+ #[inline]
+ fn get_local_name<'a>(&'a self) -> &'a Atom {
+ &self.element.local_name
+ }
+
+ #[inline]
+ fn get_namespace<'a>(&'a self) -> &'a Namespace {
+ &self.element.namespace
+ }
+
+ #[inline]
+ fn get_attr(&self, namespace: &Namespace, name: &str) -> Option<&'static str> {
+ unsafe { self.element.get_attr_val_for_layout(namespace, name) }
+ }
+
+ fn get_link(&self) -> Option<&'static str> {
+ // FIXME: This is HTML only.
+ match self.element.node.type_id_for_layout() {
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/selectors.html#
+ // selector-link
+ ElementNodeTypeId(HTMLAnchorElementTypeId) |
+ ElementNodeTypeId(HTMLAreaElementTypeId) |
+ ElementNodeTypeId(HTMLLinkElementTypeId) => {
+ unsafe { self.element.get_attr_val_for_layout(&namespace::Null, "href") }
+ }
+ _ => None,
+ }
+ }
+
+ fn get_hover_state(&self) -> bool {
+ unsafe {
+ self.element.node.get_hover_state_for_layout()
+ }
+ }
+
+ #[inline]
+ fn get_id(&self) -> Option<Atom> {
+ unsafe { self.element.get_attr_atom_for_layout(&namespace::Null, "id") }
+ }
+
+ fn get_disabled_state(&self) -> bool {
+ unsafe {
+ self.element.node.get_disabled_state_for_layout()
+ }
+ }
+
+ fn get_enabled_state(&self) -> bool {
+ unsafe {
+ self.element.node.get_enabled_state_for_layout()
+ }
+ }
+}
+
+fn get_content(content_list: &content::T) -> String {
+ match *content_list {
+ content::Content(ref value) => {
+ let iter = &mut value.clone().move_iter().peekable();
+ match iter.next() {
+ Some(content::StringContent(content)) => content,
+ _ => "".to_string(),
+ }
+ }
+ _ => "".to_string(),
+ }
+}
+
+#[deriving(PartialEq, Clone)]
+pub enum PseudoElementType {
+ Normal,
+ Before,
+ After,
+ BeforeBlock,
+ AfterBlock,
+}
+
+/// A thread-safe version of `LayoutNode`, used during flow construction. This type of layout
+/// node does not allow any parents or siblings of nodes to be accessed, to avoid races.
+#[deriving(Clone)]
+pub struct ThreadSafeLayoutNode<'ln> {
+ /// The wrapped node.
+ node: LayoutNode<'ln>,
+
+ pseudo: PseudoElementType,
+}
+
+impl<'ln> TLayoutNode for ThreadSafeLayoutNode<'ln> {
+ /// Creates a new layout node with the same lifetime as this layout node.
+ unsafe fn new_with_this_lifetime(&self, node: &JS<Node>) -> ThreadSafeLayoutNode<'ln> {
+ ThreadSafeLayoutNode {
+ node: LayoutNode {
+ node: node.transmute_copy(),
+ chain: self.node.chain,
+ },
+ pseudo: Normal,
+ }
+ }
+
+ /// Returns `None` if this is a pseudo-element.
+ fn type_id(&self) -> Option<NodeTypeId> {
+ if self.pseudo == Before || self.pseudo == After {
+ return None
+ }
+
+ self.node.type_id()
+ }
+
+ unsafe fn get_jsmanaged<'a>(&'a self) -> &'a JS<Node> {
+ self.node.get_jsmanaged()
+ }
+
+ unsafe fn get<'a>(&'a self) -> &'a Node { // this change.
+ mem::transmute::<*mut Node,&'a Node>(self.get_jsmanaged().unsafe_get())
+ }
+
+ fn first_child(&self) -> Option<ThreadSafeLayoutNode<'ln>> {
+ if self.pseudo == Before || self.pseudo == After {
+ return None
+ }
+
+ if self.has_before_pseudo() {
+ if self.is_block(Before) && self.pseudo == Normal {
+ let pseudo_before_node = self.with_pseudo(BeforeBlock);
+ return Some(pseudo_before_node)
+ } else if self.pseudo == Normal || self.pseudo == BeforeBlock {
+ let pseudo_before_node = self.with_pseudo(Before);
+ return Some(pseudo_before_node)
+ }
+ }
+
+ unsafe {
+ self.get_jsmanaged().first_child_ref().map(|node| self.new_with_this_lifetime(&node))
+ }
+ }
+
+ fn text(&self) -> String {
+ if self.pseudo == Before || self.pseudo == After {
+ let layout_data_ref = self.borrow_layout_data();
+ let node_layout_data_wrapper = layout_data_ref.get_ref();
+
+ if self.pseudo == Before {
+ let before_style = node_layout_data_wrapper.data.before_style.get_ref();
+ return get_content(&before_style.get_box().content)
+ } else {
+ let after_style = node_layout_data_wrapper.data.after_style.get_ref();
+ return get_content(&after_style.get_box().content)
+ }
+ }
+
+ unsafe {
+ if !self.get().is_text() {
+ fail!("not text!")
+ }
+ let text: JS<Text> = self.get_jsmanaged().transmute_copy();
+ (*text.unsafe_get()).characterdata.data.deref().borrow().clone()
+ }
+ }
+}
+
+
+impl<'ln> ThreadSafeLayoutNode<'ln> {
+ /// Creates a new `ThreadSafeLayoutNode` from the given `LayoutNode`.
+ pub fn new<'a>(node: &LayoutNode<'a>) -> ThreadSafeLayoutNode<'a> {
+ ThreadSafeLayoutNode {
+ node: node.clone(),
+ pseudo: Normal,
+ }
+ }
+
+ /// Creates a new `ThreadSafeLayoutNode` for the same `LayoutNode`
+ /// with a different pseudo-element type.
+ fn with_pseudo(&self, pseudo: PseudoElementType) -> ThreadSafeLayoutNode<'ln> {
+ ThreadSafeLayoutNode {
+ node: self.node.clone(),
+ pseudo: pseudo,
+ }
+ }
+
+ /// Returns the next sibling of this node. Unsafe and private because this can lead to races.
+ unsafe fn next_sibling(&self) -> Option<ThreadSafeLayoutNode<'ln>> {
+ if self.pseudo == Before || self.pseudo == BeforeBlock {
+ return self.get_jsmanaged().first_child_ref().map(|node| self.new_with_this_lifetime(&node))
+ }
+
+ self.get_jsmanaged().next_sibling_ref().map(|node| self.new_with_this_lifetime(&node))
+ }
+
+ /// Returns an iterator over this node's children.
+ pub fn children(&self) -> ThreadSafeLayoutNodeChildrenIterator<'ln> {
+ ThreadSafeLayoutNodeChildrenIterator {
+ current_node: self.first_child(),
+ parent_node: Some(self.clone()),
+ }
+ }
+
+ /// If this is an element, accesses the element data. Fails if this is not an element node.
+ #[inline]
+ pub fn as_element(&self) -> ThreadSafeLayoutElement {
+ unsafe {
+ assert!(self.get_jsmanaged().is_element_for_layout());
+ let elem: JS<Element> = self.get_jsmanaged().transmute_copy();
+ let element = elem.unsafe_get();
+ // FIXME(pcwalton): Workaround until Rust gets multiple lifetime parameters on
+ // implementations.
+ ThreadSafeLayoutElement {
+ element: &mut *element,
+ }
+ }
+ }
+
+ pub fn get_pseudo_element_type(&self) -> PseudoElementType {
+ self.pseudo
+ }
+
+ pub fn is_block(&self, kind: PseudoElementType) -> bool {
+ let mut layout_data_ref = self.mutate_layout_data();
+ let node_layout_data_wrapper = layout_data_ref.get_mut_ref();
+
+ let display = match kind {
+ Before | BeforeBlock => {
+ let before_style = node_layout_data_wrapper.data.before_style.get_ref();
+ before_style.get_box().display
+ }
+ After | AfterBlock => {
+ let after_style = node_layout_data_wrapper.data.after_style.get_ref();
+ after_style.get_box().display
+ }
+ Normal => {
+ let after_style = node_layout_data_wrapper.shared_data.style.get_ref();
+ after_style.get_box().display
+ }
+ };
+
+ display == display::block
+ }
+
+ pub fn has_before_pseudo(&self) -> bool {
+ let layout_data_wrapper = self.borrow_layout_data();
+ let layout_data_wrapper_ref = layout_data_wrapper.get_ref();
+ layout_data_wrapper_ref.data.before_style.is_some()
+ }
+
+ pub fn has_after_pseudo(&self) -> bool {
+ let layout_data_wrapper = self.borrow_layout_data();
+ let layout_data_wrapper_ref = layout_data_wrapper.get_ref();
+ layout_data_wrapper_ref.data.after_style.is_some()
+ }
+
+ /// Borrows the layout data immutably. Fails on a conflicting borrow.
+ #[inline(always)]
+ pub fn borrow_layout_data<'a>(&'a self) -> Ref<'a,Option<LayoutDataWrapper>> {
+ unsafe {
+ mem::transmute(self.get().layout_data.borrow())
+ }
+ }
+
+ /// Borrows the layout data mutably. Fails on a conflicting borrow.
+ #[inline(always)]
+ pub fn mutate_layout_data<'a>(&'a self) -> RefMut<'a,Option<LayoutDataWrapper>> {
+ unsafe {
+ mem::transmute(self.get().layout_data.borrow_mut())
+ }
+ }
+
+ /// Traverses the tree in postorder.
+ ///
+ /// TODO(pcwalton): Offer a parallel version with a compatible API.
+ pub fn traverse_postorder_mut<T:PostorderNodeMutTraversal>(&mut self, traversal: &mut T)
+ -> bool {
+ if traversal.should_prune(self) {
+ return true
+ }
+
+ let mut opt_kid = self.first_child();
+ loop {
+ match opt_kid {
+ None => break,
+ Some(mut kid) => {
+ if !kid.traverse_postorder_mut(traversal) {
+ return false
+ }
+ unsafe {
+ opt_kid = kid.next_sibling()
+ }
+ }
+ }
+ }
+
+ traversal.process(self)
+ }
+
+ pub fn is_ignorable_whitespace(&self) -> bool {
+ match self.type_id() {
+ Some(TextNodeTypeId) => {
+ unsafe {
+ let text: JS<Text> = self.get_jsmanaged().transmute_copy();
+ if !is_whitespace((*text.unsafe_get()).characterdata.data.deref().borrow().as_slice()) {
+ return false
+ }
+
+ // NB: See the rules for `white-space` here:
+ //
+ // http://www.w3.org/TR/CSS21/text.html#propdef-white-space
+ //
+ // If you implement other values for this property, you will almost certainly
+ // want to update this check.
+ match self.style().get_inheritedtext().white_space {
+ white_space::normal => true,
+ _ => false,
+ }
+ }
+ }
+ _ => false
+ }
+ }
+}
+
+pub struct ThreadSafeLayoutNodeChildrenIterator<'a> {
+ current_node: Option<ThreadSafeLayoutNode<'a>>,
+ parent_node: Option<ThreadSafeLayoutNode<'a>>,
+}
+
+impl<'a> Iterator<ThreadSafeLayoutNode<'a>> for ThreadSafeLayoutNodeChildrenIterator<'a> {
+ fn next(&mut self) -> Option<ThreadSafeLayoutNode<'a>> {
+ let node = self.current_node.clone();
+
+ match node {
+ Some(ref node) => {
+ if node.pseudo == After || node.pseudo == AfterBlock {
+ return None
+ }
+
+ match self.parent_node {
+ Some(ref parent_node) => {
+ if parent_node.pseudo == Normal {
+ self.current_node = self.current_node.clone().and_then(|node| {
+ unsafe {
+ node.next_sibling()
+ }
+ });
+ } else {
+ self.current_node = None;
+ }
+ }
+ None => {}
+ }
+ }
+ None => {
+ match self.parent_node {
+ Some(ref parent_node) => {
+ if parent_node.has_after_pseudo() {
+ let pseudo_after_node = if parent_node.is_block(After) && parent_node.pseudo == Normal {
+ let pseudo_after_node = parent_node.with_pseudo(AfterBlock);
+ Some(pseudo_after_node)
+ } else if parent_node.pseudo == Normal || parent_node.pseudo == AfterBlock {
+ let pseudo_after_node = parent_node.with_pseudo(After);
+ Some(pseudo_after_node)
+ } else {
+ None
+ };
+ self.current_node = pseudo_after_node;
+ return self.current_node.clone()
+ }
+ }
+ None => {}
+ }
+ }
+ }
+
+ node
+ }
+}
+
+/// A wrapper around elements that ensures layout can only ever access safe properties and cannot
+/// race on elements.
+pub struct ThreadSafeLayoutElement<'le> {
+ element: &'le Element,
+}
+
+impl<'le> ThreadSafeLayoutElement<'le> {
+ #[inline]
+ pub fn get_attr(&self, namespace: &Namespace, name: &str) -> Option<&'static str> {
+ unsafe { self.element.get_attr_val_for_layout(namespace, name) }
+ }
+}
+
+/// A bottom-up, parallelizable traversal.
+pub trait PostorderNodeMutTraversal {
+ /// The operation to perform. Return true to continue or false to stop.
+ fn process<'a>(&'a mut self, node: &ThreadSafeLayoutNode<'a>) -> bool;
+
+ /// Returns true if this node should be pruned. If this returns true, we skip the operation
+ /// entirely and do not process any descendant nodes. This is called *before* child nodes are
+ /// visited. The default implementation never prunes any nodes.
+ fn should_prune<'a>(&'a self, _node: &ThreadSafeLayoutNode<'a>) -> bool {
+ false
+ }
+}
+
+/// Opaque type stored in type-unsafe work queues for parallel layout.
+/// Must be transmutable to and from LayoutNode/ThreadSafeLayoutNode.
+pub type UnsafeLayoutNode = (uint, uint);
+
+pub fn layout_node_to_unsafe_layout_node(node: &LayoutNode) -> UnsafeLayoutNode {
+ unsafe {
+ let ptr: uint = mem::transmute_copy(node);
+ (ptr, 0)
+ }
+}
+
+// FIXME(#3044): This should be updated to use a real lifetime instead of
+// faking one.
+pub unsafe fn layout_node_from_unsafe_layout_node(node: &UnsafeLayoutNode) -> LayoutNode<'static> {
+ let (node, _) = *node;
+ mem::transmute(node)
+}