aboutsummaryrefslogtreecommitdiffstats
path: root/components
diff options
context:
space:
mode:
authorAnthony Ramine <n.oxyde@gmail.com>2019-07-23 15:44:56 +0200
committerAnthony Ramine <n.oxyde@gmail.com>2019-07-31 17:09:16 +0200
commit4846d76e82e2d60875472fb8ea375e22d40a0800 (patch)
tree073ff8aa7ec1c65ad51c2b08bfe55007c6f442e7 /components
parent87e7e3d429f2122ffa9ef016ba5659a3b21be91b (diff)
downloadservo-4846d76e82e2d60875472fb8ea375e22d40a0800.tar.gz
servo-4846d76e82e2d60875472fb8ea375e22d40a0800.zip
Make layout_2020 be layout_2013
Diffstat (limited to 'components')
-rw-r--r--components/layout_2020/Cargo.toml44
-rw-r--r--components/layout_2020/animation.rs211
-rw-r--r--components/layout_2020/block.rs3697
-rw-r--r--components/layout_2020/construct.rs2443
-rw-r--r--components/layout_2020/context.rs201
-rw-r--r--components/layout_2020/data.rs68
-rw-r--r--components/layout_2020/display_list/background.rs336
-rw-r--r--components/layout_2020/display_list/border.rs199
-rw-r--r--components/layout_2020/display_list/builder.rs3024
-rw-r--r--components/layout_2020/display_list/conversions.rs167
-rw-r--r--components/layout_2020/display_list/gradient.rs323
-rw-r--r--components/layout_2020/display_list/items.rs795
-rw-r--r--components/layout_2020/display_list/mod.rs19
-rw-r--r--components/layout_2020/display_list/webrender_helpers.rs321
-rw-r--r--components/layout_2020/flex.rs1128
-rw-r--r--components/layout_2020/floats.rs605
-rw-r--r--components/layout_2020/flow.rs1458
-rw-r--r--components/layout_2020/flow_list.rs197
-rw-r--r--components/layout_2020/flow_ref.rs64
-rw-r--r--components/layout_2020/fragment.rs3455
-rw-r--r--components/layout_2020/generated_content.rs651
-rw-r--r--components/layout_2020/incremental.rs101
-rw-r--r--components/layout_2020/inline.rs2220
-rw-r--r--components/layout_2020/layout_debug.rs124
-rw-r--r--components/layout_2020/lib.rs60
-rw-r--r--components/layout_2020/linked_list.rs21
-rw-r--r--components/layout_2020/list_item.rs323
-rw-r--r--components/layout_2020/model.rs609
-rw-r--r--components/layout_2020/multicol.rs384
-rw-r--r--components/layout_2020/opaque_node.rs19
-rw-r--r--components/layout_2020/parallel.rs232
-rw-r--r--components/layout_2020/persistent_list.rs101
-rw-r--r--components/layout_2020/query.rs1111
-rw-r--r--components/layout_2020/sequential.rs193
-rw-r--r--components/layout_2020/table.rs1378
-rw-r--r--components/layout_2020/table_caption.rs139
-rw-r--r--components/layout_2020/table_cell.rs506
-rw-r--r--components/layout_2020/table_colgroup.rs131
-rw-r--r--components/layout_2020/table_row.rs1158
-rw-r--r--components/layout_2020/table_rowgroup.rs274
-rw-r--r--components/layout_2020/table_wrapper.rs996
-rw-r--r--components/layout_2020/tests/size_of.rs18
-rw-r--r--components/layout_2020/text.rs798
-rw-r--r--components/layout_2020/traversal.rs350
-rw-r--r--components/layout_2020/wrapper.rs174
-rw-r--r--components/layout_thread_2020/Cargo.toml27
-rw-r--r--components/layout_thread_2020/dom_wrapper.rs1550
-rw-r--r--components/layout_thread_2020/lib.rs2148
48 files changed, 34529 insertions, 22 deletions
diff --git a/components/layout_2020/Cargo.toml b/components/layout_2020/Cargo.toml
index bff14135888..daa5170b23a 100644
--- a/components/layout_2020/Cargo.toml
+++ b/components/layout_2020/Cargo.toml
@@ -7,9 +7,53 @@ edition = "2018"
publish = false
[lib]
+name = "layout"
path = "lib.rs"
test = false
doctest = false
[dependencies]
+app_units = "0.7"
+atomic_refcell = "0.1"
+bitflags = "1.0"
+canvas_traits = {path = "../canvas_traits"}
+crossbeam-channel = "0.3"
+embedder_traits = {path = "../embedder_traits"}
+euclid = "0.19"
+fnv = "1.0"
+fxhash = "0.2"
+gfx = {path = "../gfx"}
+gfx_traits = {path = "../gfx_traits"}
+html5ever = "0.23"
+ipc-channel = "0.11"
+libc = "0.2"
+log = "0.4"
+malloc_size_of = { path = "../malloc_size_of" }
+msg = {path = "../msg"}
+net_traits = {path = "../net_traits"}
+num-traits = "0.2"
+ordered-float = "1.0"
+parking_lot = "0.8"
+profile_traits = {path = "../profile_traits"}
+range = {path = "../range"}
+rayon = "1"
+script_layout_interface = {path = "../script_layout_interface"}
+script_traits = {path = "../script_traits"}
+selectors = { path = "../selectors" }
+serde = "1.0"
+servo_arc = {path = "../servo_arc"}
+servo_atoms = {path = "../atoms"}
+servo_geometry = {path = "../geometry"}
+serde_json = "1.0"
+servo_config = {path = "../config"}
+servo_url = {path = "../url"}
+smallvec = { version = "0.6", features = ["std", "union"] }
style = {path = "../style", features = ["servo", "servo-layout-2020"]}
+style_traits = {path = "../style_traits"}
+unicode-bidi = {version = "0.3", features = ["with_serde"]}
+unicode-script = {version = "0.3", features = ["harfbuzz"]}
+webrender_api = {git = "https://github.com/servo/webrender", features = ["ipc"]}
+xi-unicode = "0.1.0"
+
+[dev-dependencies]
+size_of_test = {path = "../size_of_test"}
diff --git a/components/layout_2020/animation.rs b/components/layout_2020/animation.rs
new file mode 100644
index 00000000000..96e4801fa4e
--- /dev/null
+++ b/components/layout_2020/animation.rs
@@ -0,0 +1,211 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+//! CSS transitions and animations.
+
+use crate::context::LayoutContext;
+use crate::display_list::items::OpaqueNode;
+use crate::flow::{Flow, GetBaseFlow};
+use crate::opaque_node::OpaqueNodeMethods;
+use crossbeam_channel::Receiver;
+use fxhash::{FxHashMap, FxHashSet};
+use ipc_channel::ipc::IpcSender;
+use msg::constellation_msg::PipelineId;
+use script_traits::UntrustedNodeAddress;
+use script_traits::{AnimationState, ConstellationControlMsg, LayoutMsg as ConstellationMsg};
+use style::animation::{update_style_for_animation, Animation};
+use style::dom::TElement;
+use style::font_metrics::ServoMetricsProvider;
+use style::selector_parser::RestyleDamage;
+use style::timer::Timer;
+
+/// Processes any new animations that were discovered after style recalculation.
+/// Also expire any old animations that have completed, inserting them into
+/// `expired_animations`.
+pub fn update_animation_state<E>(
+ constellation_chan: &IpcSender<ConstellationMsg>,
+ script_chan: &IpcSender<ConstellationControlMsg>,
+ running_animations: &mut FxHashMap<OpaqueNode, Vec<Animation>>,
+ expired_animations: &mut FxHashMap<OpaqueNode, Vec<Animation>>,
+ mut keys_to_remove: FxHashSet<OpaqueNode>,
+ mut newly_transitioning_nodes: Option<&mut Vec<UntrustedNodeAddress>>,
+ new_animations_receiver: &Receiver<Animation>,
+ pipeline_id: PipelineId,
+ timer: &Timer,
+) where
+ E: TElement,
+{
+ let mut new_running_animations = vec![];
+ while let Ok(animation) = new_animations_receiver.try_recv() {
+ let mut should_push = true;
+ if let Animation::Keyframes(ref node, _, ref name, ref state) = animation {
+ // If the animation was already present in the list for the
+ // node, just update its state, else push the new animation to
+ // run.
+ if let Some(ref mut animations) = running_animations.get_mut(node) {
+ // TODO: This being linear is probably not optimal.
+ for anim in animations.iter_mut() {
+ if let Animation::Keyframes(_, _, ref anim_name, ref mut anim_state) = *anim {
+ if *name == *anim_name {
+ debug!("update_animation_state: Found other animation {}", name);
+ anim_state.update_from_other(&state, timer);
+ should_push = false;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if should_push {
+ new_running_animations.push(animation);
+ }
+ }
+
+ if running_animations.is_empty() && new_running_animations.is_empty() {
+ // Nothing to do. Return early so we don't flood the compositor with
+ // `ChangeRunningAnimationsState` messages.
+ return;
+ }
+
+ let now = timer.seconds();
+ // Expire old running animations.
+ //
+ // TODO: Do not expunge Keyframes animations, since we need that state if
+ // the animation gets re-triggered. Probably worth splitting in two
+ // different maps, or at least using a linked list?
+ for (key, running_animations) in running_animations.iter_mut() {
+ let mut animations_still_running = vec![];
+ for mut running_animation in running_animations.drain(..) {
+ let still_running = !running_animation.is_expired() &&
+ match running_animation {
+ Animation::Transition(_, started_at, ref frame) => {
+ now < started_at + frame.duration
+ },
+ Animation::Keyframes(_, _, _, ref mut state) => {
+ // This animation is still running, or we need to keep
+ // iterating.
+ now < state.started_at + state.duration || state.tick()
+ },
+ };
+
+ debug!(
+ "update_animation_state({:?}): {:?}",
+ still_running, running_animation
+ );
+
+ if still_running {
+ animations_still_running.push(running_animation);
+ continue;
+ }
+
+ if let Animation::Transition(node, _, ref frame) = running_animation {
+ script_chan
+ .send(ConstellationControlMsg::TransitionEnd(
+ node.to_untrusted_node_address(),
+ frame.property_animation.property_name().into(),
+ frame.duration,
+ ))
+ .unwrap();
+ }
+
+ expired_animations
+ .entry(*key)
+ .or_insert_with(Vec::new)
+ .push(running_animation);
+ }
+
+ if animations_still_running.is_empty() {
+ keys_to_remove.insert(*key);
+ } else {
+ *running_animations = animations_still_running
+ }
+ }
+
+ for key in keys_to_remove {
+ running_animations.remove(&key).unwrap();
+ }
+
+ // Add new running animations.
+ for new_running_animation in new_running_animations {
+ if new_running_animation.is_transition() {
+ match newly_transitioning_nodes {
+ Some(ref mut nodes) => {
+ nodes.push(new_running_animation.node().to_untrusted_node_address());
+ },
+ None => {
+ warn!("New transition encountered from compositor-initiated layout.");
+ },
+ }
+ }
+
+ running_animations
+ .entry(*new_running_animation.node())
+ .or_insert_with(Vec::new)
+ .push(new_running_animation)
+ }
+
+ let animation_state = if running_animations.is_empty() {
+ AnimationState::NoAnimationsPresent
+ } else {
+ AnimationState::AnimationsPresent
+ };
+
+ constellation_chan
+ .send(ConstellationMsg::ChangeRunningAnimationsState(
+ pipeline_id,
+ animation_state,
+ ))
+ .unwrap();
+}
+
+/// Recalculates style for a set of animations. This does *not* run with the DOM
+/// lock held. Returns a set of nodes associated with animations that are no longer
+/// valid.
+pub fn recalc_style_for_animations<E>(
+ context: &LayoutContext,
+ flow: &mut dyn Flow,
+ animations: &FxHashMap<OpaqueNode, Vec<Animation>>,
+) -> FxHashSet<OpaqueNode>
+where
+ E: TElement,
+{
+ let mut invalid_nodes = animations.keys().cloned().collect();
+ do_recalc_style_for_animations::<E>(context, flow, animations, &mut invalid_nodes);
+ invalid_nodes
+}
+
+fn do_recalc_style_for_animations<E>(
+ context: &LayoutContext,
+ flow: &mut dyn Flow,
+ animations: &FxHashMap<OpaqueNode, Vec<Animation>>,
+ invalid_nodes: &mut FxHashSet<OpaqueNode>,
+) where
+ E: TElement,
+{
+ let mut damage = RestyleDamage::empty();
+ flow.mutate_fragments(&mut |fragment| {
+ if let Some(ref animations) = animations.get(&fragment.node) {
+ invalid_nodes.remove(&fragment.node);
+ for animation in animations.iter() {
+ let old_style = fragment.style.clone();
+ update_style_for_animation::<E>(
+ &context.style_context,
+ animation,
+ &mut fragment.style,
+ &ServoMetricsProvider,
+ );
+ let difference =
+ RestyleDamage::compute_style_difference(&old_style, &fragment.style);
+ damage |= difference.damage;
+ }
+ }
+ });
+
+ let base = flow.mut_base();
+ base.restyle_damage.insert(damage);
+ for kid in base.children.iter_mut() {
+ do_recalc_style_for_animations::<E>(context, kid, animations, invalid_nodes)
+ }
+}
diff --git a/components/layout_2020/block.rs b/components/layout_2020/block.rs
new file mode 100644
index 00000000000..24128ca2ae7
--- /dev/null
+++ b/components/layout_2020/block.rs
@@ -0,0 +1,3697 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+//! Layout for CSS block-level elements.
+//!
+//! As a terminology note, the term *absolute positioning* here refers to elements with position
+//! `absolute` or `fixed`. The term *positioned element* refers to elements with position
+//! `relative`, `absolute`, and `fixed`. The term *containing block* (occasionally abbreviated as
+//! *CB*) is the containing block for the current flow, which differs from the static containing
+//! block if the flow is absolutely-positioned.
+//!
+//! "CSS 2.1" or "CSS 2.2" refers to the editor's draft of the W3C "Cascading Style Sheets Level 2
+//! Revision 2 (CSS 2.2) Specification" available here:
+//!
+//! http://dev.w3.org/csswg/css2/
+//!
+//! "INTRINSIC" refers to L. David Baron's "More Precise Definitions of Inline Layout and Table
+//! Layout" available here:
+//!
+//! http://dbaron.org/css/intrinsic/
+//!
+//! "CSS-SIZING" refers to the W3C "CSS Intrinsic & Extrinsic Sizing Module Level 3" document
+//! available here:
+//!
+//! http://dev.w3.org/csswg/css-sizing/
+
+use crate::context::LayoutContext;
+use crate::display_list::items::DisplayListSection;
+use crate::display_list::{
+ BorderPaintingMode, DisplayListBuildState, StackingContextCollectionFlags,
+ StackingContextCollectionState,
+};
+use crate::floats::{ClearType, FloatKind, Floats, PlacementInfo};
+use crate::flow::{
+ BaseFlow, EarlyAbsolutePositionInfo, Flow, FlowClass, ForceNonfloatedFlag, GetBaseFlow,
+};
+use crate::flow::{
+ FlowFlags, FragmentationContext, ImmutableFlowUtils, LateAbsolutePositionInfo, OpaqueFlow,
+};
+use crate::flow_list::FlowList;
+use crate::fragment::{
+ CoordinateSystem, Fragment, FragmentBorderBoxIterator, FragmentFlags, Overflow,
+};
+use crate::incremental::RelayoutMode;
+use crate::layout_debug;
+use crate::model::{
+ AdjoiningMargins, CollapsibleMargins, IntrinsicISizes, MarginCollapseInfo, MaybeAuto,
+};
+use crate::sequential;
+use crate::traversal::PreorderFlowTraversal;
+use app_units::{Au, MAX_AU};
+use euclid::{Point2D, Rect, SideOffsets2D, Size2D};
+use gfx_traits::print_tree::PrintTree;
+use serde::{Serialize, Serializer};
+use servo_geometry::MaxRect;
+use std::cmp::{max, min};
+use std::fmt;
+use std::sync::Arc;
+use style::computed_values::box_sizing::T as BoxSizing;
+use style::computed_values::display::T as Display;
+use style::computed_values::float::T as Float;
+use style::computed_values::overflow_x::T as StyleOverflow;
+use style::computed_values::position::T as Position;
+use style::computed_values::text_align::T as TextAlign;
+use style::context::SharedStyleContext;
+use style::logical_geometry::{LogicalMargin, LogicalPoint, LogicalRect, LogicalSize, WritingMode};
+use style::properties::ComputedValues;
+use style::servo::restyle_damage::ServoRestyleDamage;
+use style::values::computed::{LengthPercentageOrAuto, MaxSize, Size};
+
+/// Information specific to floated blocks.
+#[derive(Clone, Serialize)]
+pub struct FloatedBlockInfo {
+ /// The amount of inline size that is available for the float.
+ pub containing_inline_size: Au,
+
+ /// The float ceiling, relative to `BaseFlow::position::cur_b` (i.e. the top part of the border
+ /// box).
+ pub float_ceiling: Au,
+
+ /// Left or right?
+ pub float_kind: FloatKind,
+}
+
+impl FloatedBlockInfo {
+ pub fn new(float_kind: FloatKind) -> FloatedBlockInfo {
+ FloatedBlockInfo {
+ containing_inline_size: Au(0),
+ float_ceiling: Au(0),
+ float_kind: float_kind,
+ }
+ }
+}
+
+/// The solutions for the block-size-and-margins constraint equation.
+#[derive(Clone, Copy)]
+struct BSizeConstraintSolution {
+ block_start: Au,
+ block_size: Au,
+ margin_block_start: Au,
+ margin_block_end: Au,
+}
+
+impl BSizeConstraintSolution {
+ fn new(
+ block_start: Au,
+ block_size: Au,
+ margin_block_start: Au,
+ margin_block_end: Au,
+ ) -> BSizeConstraintSolution {
+ BSizeConstraintSolution {
+ block_start: block_start,
+ 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,
+ ) -> BSizeConstraintSolution {
+ let (block_start, block_size, margin_block_start, margin_block_end) =
+ match (block_start, block_end, block_size) {
+ (MaybeAuto::Auto, MaybeAuto::Auto, MaybeAuto::Auto) => {
+ let margin_block_start = block_start_margin.specified_or_zero();
+ let margin_block_end = block_end_margin.specified_or_zero();
+ // Now it is the same situation as block-start Specified and block-end
+ // and block-size Auto.
+ let block_size = content_block_size;
+ // Use a dummy value for `block_start`, since it has the static position.
+ (Au(0), block_size, margin_block_start, margin_block_end)
+ },
+ (
+ MaybeAuto::Specified(block_start),
+ MaybeAuto::Specified(block_end),
+ MaybeAuto::Specified(block_size),
+ ) => {
+ match (block_start_margin, block_end_margin) {
+ (MaybeAuto::Auto, MaybeAuto::Auto) => {
+ let total_margin_val =
+ available_block_size - block_start - block_end - block_size;
+ (
+ block_start,
+ block_size,
+ total_margin_val.scale_by(0.5),
+ total_margin_val.scale_by(0.5),
+ )
+ },
+ (MaybeAuto::Specified(margin_block_start), MaybeAuto::Auto) => {
+ let sum = block_start + block_end + block_size + margin_block_start;
+ (
+ block_start,
+ block_size,
+ margin_block_start,
+ available_block_size - sum,
+ )
+ },
+ (MaybeAuto::Auto, MaybeAuto::Specified(margin_block_end)) => {
+ let sum = block_start + block_end + block_size + margin_block_end;
+ (
+ block_start,
+ block_size,
+ available_block_size - sum,
+ margin_block_end,
+ )
+ },
+ (
+ MaybeAuto::Specified(margin_block_start),
+ MaybeAuto::Specified(margin_block_end),
+ ) => {
+ // Values are over-constrained. Ignore value for 'block-end'.
+ (
+ block_start,
+ 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
+ (
+ MaybeAuto::Auto,
+ MaybeAuto::Specified(block_end),
+ MaybeAuto::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_size,
+ margin_block_start,
+ margin_block_end,
+ )
+ },
+ (
+ MaybeAuto::Specified(block_start),
+ MaybeAuto::Auto,
+ MaybeAuto::Specified(block_size),
+ ) => {
+ let margin_block_start = block_start_margin.specified_or_zero();
+ let margin_block_end = block_end_margin.specified_or_zero();
+ (
+ block_start,
+ block_size,
+ margin_block_start,
+ margin_block_end,
+ )
+ },
+ (
+ MaybeAuto::Specified(block_start),
+ MaybeAuto::Specified(block_end),
+ MaybeAuto::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,
+ 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.
+ (MaybeAuto::Specified(block_start), MaybeAuto::Auto, MaybeAuto::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;
+ (
+ block_start,
+ block_size,
+ margin_block_start,
+ margin_block_end,
+ )
+ },
+ (MaybeAuto::Auto, MaybeAuto::Specified(block_end), MaybeAuto::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_size,
+ margin_block_start,
+ margin_block_end,
+ )
+ },
+
+ (MaybeAuto::Auto, MaybeAuto::Auto, MaybeAuto::Specified(block_size)) => {
+ let margin_block_start = block_start_margin.specified_or_zero();
+ let margin_block_end = block_end_margin.specified_or_zero();
+ // Use a dummy value for `block_start`, since it has the static position.
+ (Au(0), block_size, margin_block_start, margin_block_end)
+ },
+ };
+
+ BSizeConstraintSolution::new(
+ block_start,
+ 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,
+ ) -> BSizeConstraintSolution {
+ let (block_start, block_size, margin_block_start, margin_block_end) =
+ match (block_start, block_end) {
+ (MaybeAuto::Auto, MaybeAuto::Auto) => {
+ let margin_block_start = block_start_margin.specified_or_zero();
+ let margin_block_end = block_end_margin.specified_or_zero();
+ // Use a dummy value for `block_start`, since it has the static position.
+ (Au(0), block_size, margin_block_start, margin_block_end)
+ },
+ (MaybeAuto::Specified(block_start), MaybeAuto::Specified(block_end)) => {
+ match (block_start_margin, block_end_margin) {
+ (MaybeAuto::Auto, MaybeAuto::Auto) => {
+ let total_margin_val =
+ available_block_size - block_start - block_end - block_size;
+ (
+ block_start,
+ block_size,
+ total_margin_val.scale_by(0.5),
+ total_margin_val.scale_by(0.5),
+ )
+ },
+ (MaybeAuto::Specified(margin_block_start), MaybeAuto::Auto) => {
+ let sum = block_start + block_end + block_size + margin_block_start;
+ (
+ block_start,
+ block_size,
+ margin_block_start,
+ available_block_size - sum,
+ )
+ },
+ (MaybeAuto::Auto, MaybeAuto::Specified(margin_block_end)) => {
+ let sum = block_start + block_end + block_size + margin_block_end;
+ (
+ block_start,
+ block_size,
+ available_block_size - sum,
+ margin_block_end,
+ )
+ },
+ (
+ MaybeAuto::Specified(margin_block_start),
+ MaybeAuto::Specified(margin_block_end),
+ ) => {
+ // Values are over-constrained. Ignore value for 'block-end'.
+ (
+ block_start,
+ block_size,
+ margin_block_start,
+ margin_block_end,
+ )
+ },
+ }
+ },
+
+ // If only one is Auto, solve for it
+ (MaybeAuto::Auto, MaybeAuto::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_size,
+ margin_block_start,
+ margin_block_end,
+ )
+ },
+ (MaybeAuto::Specified(block_start), MaybeAuto::Auto) => {
+ let margin_block_start = block_start_margin.specified_or_zero();
+ let margin_block_end = block_end_margin.specified_or_zero();
+ (
+ block_start,
+ block_size,
+ margin_block_start,
+ margin_block_end,
+ )
+ },
+ };
+ BSizeConstraintSolution::new(
+ block_start,
+ 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.
+pub struct CandidateBSizeIterator {
+ block_size: MaybeAuto,
+ max_block_size: Option<Au>,
+ min_block_size: Au,
+ pub 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(
+ fragment: &Fragment,
+ 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 fragment.style.content_block_size() {
+ Size::Auto => MaybeAuto::Auto,
+ Size::LengthPercentage(ref lp) => {
+ MaybeAuto::from_option(lp.maybe_to_used_value(block_container_block_size))
+ },
+ };
+
+ let max_block_size = match fragment.style.max_block_size() {
+ MaxSize::None => None,
+ MaxSize::LengthPercentage(ref lp) => lp.maybe_to_used_value(block_container_block_size),
+ };
+
+ let min_block_size = match fragment.style.min_block_size() {
+ Size::Auto => MaybeAuto::Auto,
+ Size::LengthPercentage(ref lp) => {
+ MaybeAuto::from_option(lp.maybe_to_used_value(block_container_block_size))
+ },
+ }
+ .specified_or_zero();
+
+ // If the style includes `box-sizing: border-box`, subtract the border and padding.
+ let adjustment_for_box_sizing = match fragment.style.get_position().box_sizing {
+ BoxSizing::BorderBox => fragment.border_padding.block_start_end(),
+ BoxSizing::ContentBox => Au(0),
+ };
+
+ return CandidateBSizeIterator {
+ block_size: block_size.map(|size| adjust(size, adjustment_for_box_sizing)),
+ max_block_size: max_block_size.map(|size| adjust(size, adjustment_for_box_sizing)),
+ min_block_size: adjust(min_block_size, adjustment_for_box_sizing),
+ candidate_value: Au(0),
+ status: CandidateBSizeIteratorStatus::Initial,
+ };
+
+ fn adjust(size: Au, delta: Au) -> Au {
+ max(size - delta, Au(0))
+ }
+ }
+}
+
+impl Iterator for CandidateBSizeIterator {
+ type Item = MaybeAuto;
+ fn next(&mut self) -> Option<MaybeAuto> {
+ self.status = match self.status {
+ CandidateBSizeIteratorStatus::Initial => CandidateBSizeIteratorStatus::Trying,
+ CandidateBSizeIteratorStatus::Trying => match self.max_block_size {
+ Some(max_block_size) if self.candidate_value > max_block_size => {
+ CandidateBSizeIteratorStatus::TryingMax
+ },
+ _ if self.candidate_value < self.min_block_size => {
+ CandidateBSizeIteratorStatus::TryingMin
+ },
+ _ => CandidateBSizeIteratorStatus::Found,
+ },
+ CandidateBSizeIteratorStatus::TryingMax => {
+ if self.candidate_value < self.min_block_size {
+ CandidateBSizeIteratorStatus::TryingMin
+ } else {
+ CandidateBSizeIteratorStatus::Found
+ }
+ },
+ CandidateBSizeIteratorStatus::TryingMin | CandidateBSizeIteratorStatus::Found => {
+ CandidateBSizeIteratorStatus::Found
+ },
+ };
+
+ match self.status {
+ CandidateBSizeIteratorStatus::Trying => Some(self.block_size),
+ CandidateBSizeIteratorStatus::TryingMax => {
+ Some(MaybeAuto::Specified(self.max_block_size.unwrap()))
+ },
+ CandidateBSizeIteratorStatus::TryingMin => {
+ Some(MaybeAuto::Specified(self.min_block_size))
+ },
+ CandidateBSizeIteratorStatus::Found => None,
+ CandidateBSizeIteratorStatus::Initial => panic!(),
+ }
+ }
+}
+
+enum CandidateBSizeIteratorStatus {
+ Initial,
+ Trying,
+ TryingMax,
+ TryingMin,
+ Found,
+}
+
+// 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.
+pub struct AbsoluteAssignBSizesTraversal<'a>(pub &'a SharedStyleContext<'a>);
+
+impl<'a> PreorderFlowTraversal for AbsoluteAssignBSizesTraversal<'a> {
+ #[inline]
+ fn process(&self, flow: &mut dyn Flow) {
+ if !flow.is_block_like() {
+ return;
+ }
+
+ // This flow might not be an absolutely positioned flow if it is the root of the tree.
+ let block = flow.as_mut_block();
+ if !block
+ .base
+ .flags
+ .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED)
+ {
+ return;
+ }
+
+ if !block
+ .base
+ .restyle_damage
+ .intersects(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW)
+ {
+ return;
+ }
+
+ block.calculate_absolute_block_size_and_margins(self.0);
+ }
+}
+
+pub enum BlockType {
+ Replaced,
+ NonReplaced,
+ AbsoluteReplaced,
+ AbsoluteNonReplaced,
+ FloatReplaced,
+ FloatNonReplaced,
+ InlineBlockReplaced,
+ InlineBlockNonReplaced,
+ InlineFlexItem,
+}
+
+#[derive(Clone, PartialEq)]
+pub enum MarginsMayCollapseFlag {
+ MarginsMayCollapse,
+ MarginsMayNotCollapse,
+}
+
+#[derive(Debug, PartialEq)]
+pub enum FormattingContextType {
+ None,
+ Block,
+ Other,
+}
+
+#[allow(unsafe_code)]
+unsafe impl crate::flow::HasBaseFlow for BlockFlow {}
+
+// A block formatting context.
+#[derive(Serialize)]
+#[repr(C)]
+pub struct BlockFlow {
+ /// Data common to all flows.
+ pub base: BaseFlow,
+
+ /// The associated fragment.
+ pub fragment: Fragment,
+
+ /// Additional floating flow members.
+ pub float: Option<Box<FloatedBlockInfo>>,
+
+ /// Various flags.
+ flags: BlockFlowFlags,
+}
+
+bitflags! {
+ struct BlockFlowFlags: u8 {
+ #[doc = "If this is set, then this block flow is the root flow."]
+ const IS_ROOT = 0b0000_0001;
+ #[doc = "If this is set, then this block flow has overflow and it will scroll."]
+ const HAS_SCROLLING_OVERFLOW = 0b0000_0010;
+ }
+}
+
+impl Serialize for BlockFlowFlags {
+ fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+ self.bits().serialize(serializer)
+ }
+}
+
+impl BlockFlow {
+ pub fn from_fragment(fragment: Fragment) -> BlockFlow {
+ BlockFlow::from_fragment_and_float_kind(fragment, None)
+ }
+
+ pub fn from_fragment_and_float_kind(
+ fragment: Fragment,
+ float_kind: Option<FloatKind>,
+ ) -> BlockFlow {
+ let writing_mode = fragment.style().writing_mode;
+ BlockFlow {
+ base: BaseFlow::new(
+ Some(fragment.style()),
+ writing_mode,
+ match float_kind {
+ Some(_) => ForceNonfloatedFlag::FloatIfNecessary,
+ None => ForceNonfloatedFlag::ForceNonfloated,
+ },
+ ),
+ fragment: fragment,
+ float: float_kind.map(|kind| Box::new(FloatedBlockInfo::new(kind))),
+ flags: BlockFlowFlags::empty(),
+ }
+ }
+
+ /// Return the type of this block.
+ ///
+ /// This determines the algorithm used to calculate inline-size, block-size, and the
+ /// relevant margins for this Block.
+ pub fn block_type(&self) -> BlockType {
+ if self
+ .base
+ .flags
+ .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED)
+ {
+ if self.fragment.is_replaced() {
+ BlockType::AbsoluteReplaced
+ } else {
+ BlockType::AbsoluteNonReplaced
+ }
+ } else if self.is_inline_flex_item() {
+ BlockType::InlineFlexItem
+ } else if self.base.flags.is_float() {
+ if self.fragment.is_replaced() {
+ BlockType::FloatReplaced
+ } else {
+ BlockType::FloatNonReplaced
+ }
+ } else if self.is_inline_block_or_inline_flex() {
+ if self.fragment.is_replaced() {
+ BlockType::InlineBlockReplaced
+ } else {
+ BlockType::InlineBlockNonReplaced
+ }
+ } else {
+ if self.fragment.is_replaced() {
+ BlockType::Replaced
+ } else {
+ BlockType::NonReplaced
+ }
+ }
+ }
+
+ /// Compute the actual inline size and position for this block.
+ pub fn compute_used_inline_size(
+ &mut self,
+ shared_context: &SharedStyleContext,
+ containing_block_inline_size: Au,
+ ) {
+ let block_type = self.block_type();
+ match block_type {
+ BlockType::AbsoluteReplaced => {
+ let inline_size_computer = AbsoluteReplaced;
+ inline_size_computer.compute_used_inline_size(
+ self,
+ shared_context,
+ containing_block_inline_size,
+ );
+ },
+ BlockType::AbsoluteNonReplaced => {
+ let inline_size_computer = AbsoluteNonReplaced;
+ inline_size_computer.compute_used_inline_size(
+ self,
+ shared_context,
+ containing_block_inline_size,
+ );
+ },
+ BlockType::FloatReplaced => {
+ let inline_size_computer = FloatReplaced;
+ inline_size_computer.compute_used_inline_size(
+ self,
+ shared_context,
+ containing_block_inline_size,
+ );
+ },
+ BlockType::FloatNonReplaced => {
+ let inline_size_computer = FloatNonReplaced;
+ inline_size_computer.compute_used_inline_size(
+ self,
+ shared_context,
+ containing_block_inline_size,
+ );
+ },
+ BlockType::InlineBlockReplaced => {
+ let inline_size_computer = InlineBlockReplaced;
+ inline_size_computer.compute_used_inline_size(
+ self,
+ shared_context,
+ containing_block_inline_size,
+ );
+ },
+ BlockType::InlineBlockNonReplaced => {
+ let inline_size_computer = InlineBlockNonReplaced;
+ inline_size_computer.compute_used_inline_size(
+ self,
+ shared_context,
+ containing_block_inline_size,
+ );
+ },
+ BlockType::Replaced => {
+ let inline_size_computer = BlockReplaced;
+ inline_size_computer.compute_used_inline_size(
+ self,
+ shared_context,
+ containing_block_inline_size,
+ );
+ },
+ BlockType::NonReplaced => {
+ let inline_size_computer = BlockNonReplaced;
+ inline_size_computer.compute_used_inline_size(
+ self,
+ shared_context,
+ containing_block_inline_size,
+ );
+ },
+ BlockType::InlineFlexItem => {
+ let inline_size_computer = InlineFlexItem;
+ inline_size_computer.compute_used_inline_size(
+ self,
+ shared_context,
+ containing_block_inline_size,
+ );
+ },
+ }
+ }
+
+ /// Return this flow's fragment.
+ pub fn fragment(&mut self) -> &mut Fragment {
+ &mut self.fragment
+ }
+
+ pub fn stacking_relative_border_box(&self, coor: CoordinateSystem) -> Rect<Au> {
+ return self.fragment.stacking_relative_border_box(
+ &self.base.stacking_relative_position,
+ &self
+ .base
+ .early_absolute_position_info
+ .relative_containing_block_size,
+ self.base
+ .early_absolute_position_info
+ .relative_containing_block_mode,
+ coor,
+ );
+ }
+
+ /// Return the size of the containing block for the given immediate absolute descendant of this
+ /// flow.
+ ///
+ /// Right now, this only gets the containing block size for absolutely positioned elements.
+ /// Note: We assume this is called in a top-down traversal, so it is ok to reference the CB.
+ #[inline]
+ pub fn containing_block_size(
+ &self,
+ viewport_size: &Size2D<Au>,
+ descendant: OpaqueFlow,
+ ) -> LogicalSize<Au> {
+ debug_assert!(self
+ .base
+ .flags
+ .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED));
+ if self.is_fixed() || self.is_root() {
+ // 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_size(descendant)
+ }
+ }
+
+ /// 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.
+ pub fn get_shrink_to_fit_inline_size(&self, available_inline_size: Au) -> Au {
+ let content_intrinsic_inline_sizes = self.content_intrinsic_inline_sizes();
+ min(
+ content_intrinsic_inline_sizes.preferred_inline_size,
+ max(
+ content_intrinsic_inline_sizes.minimum_inline_size,
+ available_inline_size,
+ ),
+ )
+ }
+
+ /// 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,
+ shared_context: &SharedStyleContext,
+ ) {
+ if !self.is_root() {
+ return;
+ }
+
+ let (block_start_margin_value, block_end_margin_value) = match self.base.collapsible_margins
+ {
+ CollapsibleMargins::CollapseThrough(_) => {
+ panic!("Margins unexpectedly collapsed through root flow.")
+ },
+ CollapsibleMargins::Collapse(block_start_margin, block_end_margin) => {
+ (block_start_margin.collapse(), block_end_margin.collapse())
+ },
+ CollapsibleMargins::None(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_mut() {
+ let kid_base = kid.mut_base();
+ kid_base.position.start.b = kid_base.position.start.b + block_start_margin_value
+ }
+ }
+
+ // 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 viewport_size = LogicalSize::from_physical(
+ self.fragment.style.writing_mode,
+ shared_context.viewport_size(),
+ );
+ let block_size = max(
+ viewport_size.block,
+ self.fragment.border_box.size.block + block_start_margin_value + block_end_margin_value,
+ );
+
+ self.base.position.size.block = block_size;
+ self.fragment.border_box.size.block = block_size;
+ }
+
+ // FIXME: Record enough info to deal with fragmented decorations.
+ // See https://drafts.csswg.org/css-break/#break-decoration
+ // For borders, this might be `enum FragmentPosition { First, Middle, Last }`
+ fn clone_with_children(&self, new_children: FlowList) -> BlockFlow {
+ BlockFlow {
+ base: self.base.clone_with_children(new_children),
+ fragment: self.fragment.clone(),
+ float: self.float.clone(),
+ ..*self
+ }
+ }
+
+ /// Writes in the size of the relative containing block for children. (This information
+ /// is also needed to handle RTL.)
+ fn propagate_early_absolute_position_info_to_children(&mut self) {
+ for kid in self.base.child_iter_mut() {
+ kid.mut_base().early_absolute_position_info = EarlyAbsolutePositionInfo {
+ relative_containing_block_size: self.fragment.content_box().size,
+ relative_containing_block_mode: self.fragment.style().writing_mode,
+ }
+ }
+ }
+
+ /// Assign block-size for current flow.
+ ///
+ /// * Collapse margins for flow's children and set in-flow child flows' block offsets now that
+ /// we know their block-sizes.
+ /// * Calculate and set the block-size of the current flow.
+ /// * Calculate block-size, vertical margins, and block offset for the flow's box 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.
+ ///
+ /// When `fragmentation_context` is given (not `None`), this should fit as much of the content
+ /// as possible within the available block size.
+ /// If there is more content (that doesn’t fit), this flow is *fragmented*
+ /// with the extra content moved to another fragment (a flow like this one) which is returned.
+ /// See `Flow::fragment`.
+ ///
+ /// The return value is always `None` when `fragmentation_context` is `None`.
+ ///
+ /// `inline(always)` because this is only ever called by in-order or non-in-order top-level
+ /// methods.
+ #[inline(always)]
+ pub fn assign_block_size_block_base(
+ &mut self,
+ layout_context: &LayoutContext,
+ mut fragmentation_context: Option<FragmentationContext>,
+ margins_may_collapse: MarginsMayCollapseFlag,
+ ) -> Option<Arc<dyn Flow>> {
+ let _scope = layout_debug_scope!("assign_block_size_block_base {:x}", self.base.debug_id());
+
+ let mut break_at = None;
+ let content_box = self.fragment.content_box();
+ if self
+ .base
+ .restyle_damage
+ .contains(ServoRestyleDamage::REFLOW)
+ {
+ // 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
+ .base
+ .flags
+ .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) ||
+ margins_may_collapse != MarginsMayCollapseFlag::MarginsMayCollapse
+ {
+ self.base.floats = Floats::new(self.fragment.style.writing_mode);
+ }
+
+ let writing_mode = self.base.floats.writing_mode;
+ self.base.floats.translate(LogicalSize::new(
+ 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 ==
+ MarginsMayCollapseFlag::MarginsMayCollapse &&
+ !self
+ .base
+ .flags
+ .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) &&
+ self.fragment.border_padding.block_start == Au(0);
+ let mut margin_collapse_info = MarginCollapseInfo::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 thread_id = self.base.thread_id;
+ let (mut had_floated_children, mut had_children_with_clearance) = (false, false);
+ for (child_index, kid) in self.base.child_iter_mut().enumerate() {
+ if kid
+ .base()
+ .flags
+ .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED)
+ {
+ // Assume that the *hypothetical box* for an absolute flow starts immediately
+ // after the margin-end border edge of the previous flow.
+ if kid
+ .base()
+ .flags
+ .contains(FlowFlags::BLOCK_POSITION_IS_STATIC)
+ {
+ let previous_bottom_margin = margin_collapse_info.current_float_ceiling();
+
+ kid.mut_base().position.start.b = cur_b +
+ kid.base()
+ .collapsible_margins
+ .block_start_margin_for_noncollapsible_context() +
+ previous_bottom_margin
+ }
+ kid.place_float_if_applicable();
+ if !kid.base().flags.is_float() {
+ kid.assign_block_size_for_inorder_child_if_necessary(
+ layout_context,
+ thread_id,
+ content_box,
+ );
+ }
+
+ // Skip the collapsing and float processing for absolute flow kids and continue
+ // with the next flow.
+ continue;
+ }
+
+ let previous_b = cur_b;
+ if let Some(ctx) = fragmentation_context {
+ let child_ctx = FragmentationContext {
+ available_block_size: ctx.available_block_size - cur_b,
+ this_fragment_is_empty: ctx.this_fragment_is_empty,
+ };
+ if let Some(remaining) = kid.fragment(layout_context, Some(child_ctx)) {
+ break_at = Some((child_index + 1, Some(remaining)));
+ }
+ }
+
+ // Assign block-size now for the child if it might have floats in and we couldn't
+ // before.
+ kid.mut_base().floats = floats.clone();
+ if kid.base().flags.is_float() {
+ had_floated_children = true;
+ kid.mut_base().position.start.b = cur_b;
+ {
+ let kid_block = kid.as_mut_block();
+ let float_ceiling = margin_collapse_info.current_float_ceiling();
+ kid_block.float.as_mut().unwrap().float_ceiling = float_ceiling
+ }
+ kid.place_float_if_applicable();
+
+ let kid_base = kid.mut_base();
+ 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.base().flags.clears_floats() {
+ kid.mut_base().floats = Floats::new(self.fragment.style.writing_mode)
+ }
+
+ // Lay the child out if this was an in-order traversal.
+ let need_to_process_child_floats = kid
+ .assign_block_size_for_inorder_child_if_necessary(
+ layout_context,
+ thread_id,
+ content_box,
+ );
+
+ if !had_children_with_clearance &&
+ floats.is_present() &&
+ (kid.base().flags.contains(FlowFlags::CLEARS_LEFT) ||
+ kid.base().flags.contains(FlowFlags::CLEARS_RIGHT))
+ {
+ had_children_with_clearance = true
+ }
+
+ // Handle any (possibly collapsed) top margin.
+ let delta = margin_collapse_info.advance_block_start_margin(
+ &kid.base().collapsible_margins,
+ !had_children_with_clearance,
+ );
+ translate_including_floats(&mut cur_b, delta, &mut floats);
+
+ // Collapse-through margins should be placed at the top edge,
+ // so we'll handle the delta after the bottom margin is processed
+ if let CollapsibleMargins::CollapseThrough(_) = kid.base().collapsible_margins {
+ cur_b = cur_b - delta;
+ }
+
+ // Clear past the floats that came in, if necessary.
+ let clearance = match (
+ kid.base().flags.contains(FlowFlags::CLEARS_LEFT),
+ kid.base().flags.contains(FlowFlags::CLEARS_RIGHT),
+ ) {
+ (false, false) => Au(0),
+ (true, false) => floats.clearance(ClearType::Left),
+ (false, true) => floats.clearance(ClearType::Right),
+ (true, true) => floats.clearance(ClearType::Both),
+ };
+ translate_including_floats(&mut cur_b, clearance, &mut floats);
+
+ // At this point, `cur_b` is at the border edge of the child.
+ kid.mut_base().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 need_to_process_child_floats {
+ floats = kid.mut_base().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 = kid.mut_base();
+ 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);
+
+ // Collapse-through margin should be placed at the top edge of the flow.
+ let collapse_delta = match kid_base.collapsible_margins {
+ CollapsibleMargins::CollapseThrough(_) => {
+ let delta = margin_collapse_info.current_float_ceiling();
+ cur_b = cur_b + delta;
+ kid_base.position.start.b = kid_base.position.start.b + delta;
+ delta
+ },
+ _ => Au(0),
+ };
+
+ if break_at.is_some() {
+ break;
+ }
+
+ if let Some(ref mut ctx) = fragmentation_context {
+ if cur_b > ctx.available_block_size && !ctx.this_fragment_is_empty {
+ break_at = Some((child_index, None));
+ cur_b = previous_b;
+ break;
+ }
+ ctx.this_fragment_is_empty = false
+ }
+
+ // For consecutive collapse-through flows, their top margin should be calculated
+ // from the same baseline.
+ cur_b = cur_b - collapse_delta;
+ }
+
+ // Add in our block-end margin and compute our collapsible margins.
+ let can_collapse_block_end_margin_with_kids = margins_may_collapse ==
+ MarginsMayCollapseFlag::MarginsMayCollapse &&
+ !self
+ .base
+ .flags
+ .contains(FlowFlags::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,
+ self.base.block_container_explicit_block_size,
+ can_collapse_block_end_margin_with_kids,
+ !had_floated_children,
+ );
+ self.base.collapsible_margins = collapsible_margins;
+ translate_including_floats(&mut cur_b, delta, &mut floats);
+
+ let mut block_size = cur_b - block_start_offset;
+ let is_root = self.is_root();
+
+ if is_root ||
+ self.formatting_context_type() != FormattingContextType::None ||
+ self.base
+ .flags
+ .contains(FlowFlags::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 it as clearance.
+ block_size = block_size + floats.clearance(ClearType::Both);
+ }
+
+ if self
+ .base
+ .flags
+ .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED)
+ {
+ // 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.
+ if is_root {
+ let viewport_size = LogicalSize::from_physical(
+ self.fragment.style.writing_mode,
+ layout_context.shared_context().viewport_size(),
+ );
+ block_size = max(viewport_size.block, block_size)
+ }
+
+ // Store the content block-size for use in calculating the absolute flow's
+ // dimensions later.
+ //
+ // FIXME(pcwalton): This looks not idempotent. Is it?
+ self.fragment.border_box.size.block = block_size;
+ }
+
+ if self
+ .base
+ .flags
+ .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED)
+ {
+ self.propagate_early_absolute_position_info_to_children();
+ return None;
+ }
+
+ // Compute any explicitly-specified block size.
+ // Can't use `for` because we assign to `candidate_block_size_iterator.candidate_value`.
+ let mut candidate_block_size_iterator = CandidateBSizeIterator::new(
+ &self.fragment,
+ self.base.block_container_explicit_block_size,
+ );
+ while let Some(candidate_block_size) = candidate_block_size_iterator.next() {
+ candidate_block_size_iterator.candidate_value = match candidate_block_size {
+ MaybeAuto::Auto => block_size,
+ MaybeAuto::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);
+
+ // Take border and padding into account.
+ 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.propagate_early_absolute_position_info_to_children();
+
+ // Translate the current set of floats back into the parent coordinate system in the
+ // inline direction, and store them in the flow so that flows that come later in the
+ // document can access them.
+ floats.translate(LogicalSize::new(
+ writing_mode,
+ self.fragment.inline_start_offset(),
+ Au(0),
+ ));
+ self.base.floats = floats;
+ self.adjust_fragments_for_collapsed_margins_if_root(layout_context.shared_context());
+ } else {
+ // We don't need to reflow, but we still need to perform in-order traversals if
+ // necessary.
+ let thread_id = self.base.thread_id;
+ for kid in self.base.child_iter_mut() {
+ kid.assign_block_size_for_inorder_child_if_necessary(
+ layout_context,
+ thread_id,
+ content_box,
+ );
+ }
+ }
+
+ if (&*self as &dyn Flow).contains_roots_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 containing block, which may also be an absolute flow.
+ let assign_abs_b_sizes = AbsoluteAssignBSizesTraversal(layout_context.shared_context());
+ assign_abs_b_sizes.traverse_absolute_flows(&mut *self);
+ }
+
+ // Don't remove the dirty bits yet if we're absolutely-positioned, since our final size
+ // has not been calculated yet. (See `calculate_absolute_block_size_and_margins` for that.)
+ // Also don't remove the dirty bits if we're a block formatting context since our inline
+ // size has not yet been computed. (See `assign_inline_position_for_formatting_context()`.)
+ if (self.base.flags.is_float() ||
+ self.formatting_context_type() == FormattingContextType::None) &&
+ !self
+ .base
+ .flags
+ .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED)
+ {
+ self.base
+ .restyle_damage
+ .remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW);
+ self.fragment
+ .restyle_damage
+ .remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW);
+ }
+
+ break_at.and_then(|(i, child_remaining)| {
+ if i == self.base.children.len() && child_remaining.is_none() {
+ None
+ } else {
+ let mut children = self.base.children.split_off(i);
+ if let Some(child) = child_remaining {
+ children.push_front_arc(child);
+ }
+ Some(Arc::new(self.clone_with_children(children)) as Arc<dyn Flow>)
+ }
+ })
+ }
+
+ /// 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 float_info: FloatedBlockInfo = (**self.float.as_ref().unwrap()).clone();
+
+ // Our `position` field accounts for positive margins, but not negative margins. (See
+ // calculation of `extra_inline_size_from_margin` below.) Negative margins must be taken
+ // into account for float placement, however. So we add them in here.
+ let inline_size_for_float_placement =
+ self.base.position.size.inline + min(Au(0), self.fragment.margin.inline_start_end());
+
+ let info = PlacementInfo {
+ size: LogicalSize::new(
+ self.fragment.style.writing_mode,
+ inline_size_for_float_placement,
+ block_size + self.fragment.margin.block_start_end(),
+ )
+ .convert(
+ self.fragment.style.writing_mode,
+ self.base.floats.writing_mode,
+ ),
+ ceiling: clearance + float_info.float_ceiling,
+ max_inline_size: float_info.containing_inline_size,
+ kind: float_info.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);
+
+ // FIXME (mbrubeck) Get the correct container size for self.base.floats;
+ let container_size = Size2D::new(self.base.block_container_inline_size, Au(0));
+
+ // Move in from the margin edge, as per CSS 2.1 § 9.5, floats may not overlap anything on
+ // their margin edges.
+ let float_offset = self
+ .base
+ .floats
+ .last_float_pos()
+ .unwrap()
+ .convert(
+ self.base.floats.writing_mode,
+ self.base.writing_mode,
+ container_size,
+ )
+ .start;
+ let margin_offset = LogicalPoint::new(
+ self.base.writing_mode,
+ Au(0),
+ self.fragment.margin.block_start,
+ );
+
+ let mut origin = LogicalPoint::new(
+ self.base.writing_mode,
+ self.base.position.start.i,
+ self.base.position.start.b,
+ );
+ origin = origin.add_point(&float_offset).add_point(&margin_offset);
+ self.base.position =
+ LogicalRect::from_point_size(self.base.writing_mode, origin, self.base.position.size);
+ }
+
+ pub fn explicit_block_containing_size(
+ &self,
+ shared_context: &SharedStyleContext,
+ ) -> Option<Au> {
+ if self.is_root() || self.is_fixed() {
+ let viewport_size = LogicalSize::from_physical(
+ self.fragment.style.writing_mode,
+ shared_context.viewport_size(),
+ );
+ Some(viewport_size.block)
+ } else if self
+ .base
+ .flags
+ .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) &&
+ self.base.block_container_explicit_block_size.is_none()
+ {
+ self.base
+ .absolute_cb
+ .explicit_block_containing_size(shared_context)
+ } else {
+ self.base.block_container_explicit_block_size
+ }
+ }
+
+ pub fn explicit_block_size(&self, containing_block_size: Option<Au>) -> Option<Au> {
+ let content_block_size = self.fragment.style().content_block_size();
+
+ match content_block_size {
+ Size::Auto => {
+ let container_size = containing_block_size?;
+ let (block_start, block_end) = {
+ let position = self.fragment.style().logical_position();
+ (
+ MaybeAuto::from_style(position.block_start, container_size),
+ MaybeAuto::from_style(position.block_end, container_size),
+ )
+ };
+
+ match (block_start, block_end) {
+ (MaybeAuto::Specified(block_start), MaybeAuto::Specified(block_end)) => {
+ let available_block_size =
+ container_size - self.fragment.border_padding.block_start_end();
+
+ // 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 {
+ LengthPercentageOrAuto::Auto => MaybeAuto::Auto,
+ _ => MaybeAuto::Specified(self.fragment.margin.block_start),
+ };
+ let margin_block_end = match margin.block_end {
+ LengthPercentageOrAuto::Auto => MaybeAuto::Auto,
+ _ => MaybeAuto::Specified(self.fragment.margin.block_end),
+ };
+
+ let margin_block_start = margin_block_start.specified_or_zero();
+ let margin_block_end = margin_block_end.specified_or_zero();
+ let sum = block_start + block_end + margin_block_start + margin_block_end;
+ Some(available_block_size - sum)
+ },
+ (_, _) => None,
+ }
+ },
+ Size::LengthPercentage(ref lp) => lp.maybe_to_used_value(containing_block_size),
+ }
+ }
+
+ fn calculate_absolute_block_size_and_margins(&mut self, shared_context: &SharedStyleContext) {
+ let opaque_self = OpaqueFlow::from_flow(self);
+ let containing_block_block_size = self
+ .containing_block_size(&shared_context.viewport_size(), opaque_self)
+ .block;
+
+ // This is the stored content block-size value from assign-block-size
+ let content_block_size = self.fragment.border_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 {
+ LengthPercentageOrAuto::Auto => MaybeAuto::Auto,
+ _ => MaybeAuto::Specified(self.fragment.margin.block_start),
+ };
+ let margin_block_end = match margin.block_end {
+ LengthPercentageOrAuto::Auto => MaybeAuto::Auto,
+ _ => MaybeAuto::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.fragment.is_replaced() {
+ // 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 -
+ self.fragment.border_padding.block_start_end();
+ 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,
+ ),
+ )
+ } else {
+ let mut candidate_block_size_iterator =
+ CandidateBSizeIterator::new(&self.fragment, Some(containing_block_block_size));
+
+ // Can't use `for` because we assign to
+ // `candidate_block_size_iterator.candidate_value`.
+ while let Some(block_size_used_val) = candidate_block_size_iterator.next() {
+ 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,
+ ),
+ );
+
+ 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);
+
+ if !self
+ .base
+ .flags
+ .contains(FlowFlags::BLOCK_POSITION_IS_STATIC)
+ {
+ self.base.position.start.b = solution.block_start + self.fragment.margin.block_start
+ }
+
+ let block_size = solution.block_size + self.fragment.border_padding.block_start_end();
+
+ self.fragment.border_box.size.block = block_size;
+ self.base.position.size.block = block_size;
+
+ self.base
+ .restyle_damage
+ .remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW);
+ self.fragment
+ .restyle_damage
+ .remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW);
+ }
+
+ /// Compute inline size based using the `block_container_inline_size` set by the parent flow.
+ ///
+ /// This is run in the `AssignISizes` traversal.
+ fn propagate_and_compute_used_inline_size(&mut self, shared_context: &SharedStyleContext) {
+ let containing_block_inline_size = self.base.block_container_inline_size;
+ self.compute_used_inline_size(shared_context, containing_block_inline_size);
+ if self.base.flags.is_float() {
+ self.float.as_mut().unwrap().containing_inline_size = containing_block_inline_size
+ }
+ }
+
+ /// Assigns the computed inline-start content edge and inline-size to all the children of this
+ /// block flow. The given `callback`, if supplied, will be called once per child; it is
+ /// currently used to push down column sizes for tables.
+ ///
+ /// `#[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<F>(
+ &mut self,
+ shared_context: &SharedStyleContext,
+ inline_start_content_edge: Au,
+ inline_end_content_edge: Au,
+ content_inline_size: Au,
+ mut callback: F,
+ ) where
+ F: FnMut(&mut dyn Flow, usize, Au, WritingMode, &mut Au, &mut Au),
+ {
+ let flags = self.base.flags.clone();
+
+ let opaque_self = OpaqueFlow::from_flow(self);
+
+ // Calculate non-auto block size to pass to children.
+ let box_border = match self.fragment.style().get_position().box_sizing {
+ BoxSizing::BorderBox => self.fragment.border_padding.block_start_end(),
+ BoxSizing::ContentBox => Au(0),
+ };
+ let parent_container_size = self.explicit_block_containing_size(shared_context);
+ // https://drafts.csswg.org/css-ui-3/#box-sizing
+ let mut explicit_content_size = self.explicit_block_size(parent_container_size).map(|x| {
+ if x < box_border {
+ Au(0)
+ } else {
+ x - box_border
+ }
+ });
+ if self.is_root() {
+ explicit_content_size = max(parent_container_size, explicit_content_size);
+ }
+ // Calculate containing block inline size.
+ let containing_block_size = if flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) {
+ self.containing_block_size(&shared_context.viewport_size(), opaque_self)
+ .inline
+ } else {
+ content_inline_size
+ };
+ // FIXME (mbrubeck): Get correct mode for absolute containing block
+ let containing_block_mode = self.base.writing_mode;
+
+ let mut inline_start_margin_edge = inline_start_content_edge;
+ let mut inline_end_margin_edge = inline_end_content_edge;
+
+ let mut iterator = self.base.child_iter_mut().enumerate().peekable();
+ while let Some((i, kid)) = iterator.next() {
+ kid.mut_base().block_container_explicit_block_size = explicit_content_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.
+ let kid_mode = kid.base().writing_mode;
+ {
+ // Don't assign positions to children unless they're going to be reflowed.
+ // Otherwise, the position we assign might be incorrect and never fixed up. (Issue
+ // #13704.)
+ //
+ // For instance, floats have their true inline position calculated in
+ // `assign_block_size()`, which won't do anything unless `REFLOW` is set. So, if a
+ // float child does not have `REFLOW` set, we must be careful to avoid touching its
+ // inline position, as no logic will run afterward to set its true value.
+ let kid_base = kid.mut_base();
+ let reflow_damage = if kid_base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED)
+ {
+ ServoRestyleDamage::REFLOW_OUT_OF_FLOW
+ } else {
+ ServoRestyleDamage::REFLOW
+ };
+ if kid_base
+ .flags
+ .contains(FlowFlags::INLINE_POSITION_IS_STATIC) &&
+ kid_base.restyle_damage.contains(reflow_damage)
+ {
+ kid_base.position.start.i =
+ if kid_mode.is_bidi_ltr() == containing_block_mode.is_bidi_ltr() {
+ inline_start_content_edge
+ } else {
+ // The kid's inline 'start' is at the parent's 'end'
+ inline_end_content_edge
+ };
+ }
+ kid_base.block_container_inline_size = content_inline_size;
+ kid_base.block_container_writing_mode = containing_block_mode;
+ }
+
+ // Call the callback to propagate extra inline size information down to the child. This
+ // is currently used for tables.
+ callback(
+ kid,
+ i,
+ content_inline_size,
+ containing_block_mode,
+ &mut inline_start_margin_edge,
+ &mut inline_end_margin_edge,
+ );
+
+ // Per CSS 2.1 § 16.3.1, text alignment propagates to all children in flow.
+ //
+ // TODO(#2265, pcwalton): Do this in the cascade instead.
+ let containing_block_text_align = self.fragment.style().get_inherited_text().text_align;
+ kid.mut_base()
+ .flags
+ .set_text_align(containing_block_text_align);
+
+ // Handle `text-indent` on behalf of any inline children that we have. This is
+ // necessary because any percentages are relative to the containing block, which only
+ // we know.
+ if kid.is_inline_flow() {
+ kid.as_mut_inline().first_line_indentation = self
+ .fragment
+ .style()
+ .get_inherited_text()
+ .text_indent
+ .to_used_value(containing_block_size);
+ }
+ }
+ }
+
+ /// Determines the type of formatting context this is. See the definition of
+ /// `FormattingContextType`.
+ pub fn formatting_context_type(&self) -> FormattingContextType {
+ if self.is_inline_flex_item() || self.is_block_flex_item() {
+ return FormattingContextType::Other;
+ }
+ let style = self.fragment.style();
+ if style.get_box().float != Float::None {
+ return FormattingContextType::Other;
+ }
+ match style.get_box().display {
+ Display::TableCell |
+ Display::TableCaption |
+ Display::TableRowGroup |
+ Display::Table |
+ Display::InlineBlock |
+ Display::Flex => FormattingContextType::Other,
+ _ if style.get_box().overflow_x != StyleOverflow::Visible ||
+ style.get_box().overflow_y != StyleOverflow::Visible ||
+ style.is_multicol() =>
+ {
+ FormattingContextType::Block
+ },
+ _ => FormattingContextType::None,
+ }
+ }
+
+ /// Per CSS 2.1 § 9.5, block formatting contexts' inline widths and positions are affected by
+ /// the presence of floats. This is the part of the assign-heights traversal that computes
+ /// the final inline position and width for such flows.
+ ///
+ /// Note that this is part of the assign-block-sizes traversal, not the assign-inline-sizes
+ /// traversal as one might expect. That is because, in general, float placement cannot occur
+ /// until heights are assigned. To work around this unfortunate circular dependency, by the
+ /// time we get here we have already estimated the width of the block formatting context based
+ /// on the floats we could see at the time of inline-size assignment. The job of this function,
+ /// therefore, is not only to assign the final size but also to perform the layout again for
+ /// this block formatting context if our speculation was wrong.
+ fn assign_inline_position_for_formatting_context(
+ &mut self,
+ layout_context: &LayoutContext,
+ content_box: LogicalRect<Au>,
+ ) {
+ debug_assert_ne!(self.formatting_context_type(), FormattingContextType::None);
+
+ if !self
+ .base
+ .restyle_damage
+ .intersects(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW)
+ {
+ return;
+ }
+
+ // We do this first to avoid recomputing our inline size when we propagate it.
+ self.base
+ .restyle_damage
+ .remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW);
+ self.fragment
+ .restyle_damage
+ .remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW);
+
+ // The code below would completely wreck the layout if run on a flex item, however:
+ // * Flex items are always the children of flex containers.
+ // * Flex containers only contain flex items.
+ // * Floats cannot intrude into flex containers.
+ // * Floats cannot escape flex items.
+ // * Flex items cannot also be floats.
+ // Therefore, a flex item cannot be impacted by a float.
+ // See also: https://www.w3.org/TR/css-flexbox-1/#flex-containers
+ if !self.base.might_have_floats_in() {
+ return;
+ }
+
+ // If you remove the might_have_floats_in conditional, this will go off.
+ debug_assert!(!self.is_inline_flex_item());
+
+ // Compute the available space for us, based on the actual floats.
+ let rect = self.base.floats.available_rect(
+ Au(0),
+ self.fragment.border_box.size.block,
+ content_box.size.inline,
+ );
+ let available_inline_size = if let Some(rect) = rect {
+ // Offset our position by whatever displacement is needed to not impact the floats.
+ // Also, account for margins sliding behind floats.
+ let inline_offset = if self.fragment.margin.inline_start < rect.start.i {
+ // Do not do anything for negative margins; those are handled separately.
+ rect.start.i - max(Au(0), self.fragment.margin.inline_start)
+ } else {
+ Au(0)
+ };
+ self.base.position.start.i = content_box.start.i + inline_offset;
+ // Handle the end margin sliding behind the float.
+ let end = content_box.size.inline - rect.start.i - rect.size.inline;
+ let inline_end_offset = if self.fragment.margin.inline_end < end {
+ end - max(Au(0), self.fragment.margin.inline_end)
+ } else {
+ Au(0)
+ };
+ content_box.size.inline - inline_offset - inline_end_offset
+ } else {
+ content_box.size.inline
+ } - self.fragment.margin.inline_start_end();
+ let max_inline_size = self
+ .fragment
+ .style()
+ .max_inline_size()
+ .to_used_value(self.base.block_container_inline_size)
+ .unwrap_or(MAX_AU);
+ let min_inline_size = self
+ .fragment
+ .style()
+ .min_inline_size()
+ .to_used_value(self.base.block_container_inline_size)
+ .unwrap_or(Au(0));
+ let specified_inline_size = self.fragment.style().content_inline_size();
+ let container_size = self.base.block_container_inline_size;
+ let inline_size = match specified_inline_size.to_used_value(container_size) {
+ Some(size) => match self.fragment.style().get_position().box_sizing {
+ BoxSizing::BorderBox => size,
+ BoxSizing::ContentBox => size + self.fragment.border_padding.inline_start_end(),
+ },
+ None => max(min_inline_size, min(available_inline_size, max_inline_size)),
+ };
+ self.base.position.size.inline = inline_size + self.fragment.margin.inline_start_end();
+
+ // If float speculation failed, fixup our layout, and re-layout all the children.
+ if self.fragment.margin_box_inline_size() != self.base.position.size.inline {
+ debug!("assign_inline_position_for_formatting_context: float speculation failed");
+ // Fix-up our own layout.
+ // We can't just traverse_flow_tree_preorder ourself, because that would re-run
+ // float speculation, instead of acting on the actual results.
+ self.fragment.border_box.size.inline = inline_size;
+ // Assign final-final inline sizes on all our children.
+ self.assign_inline_sizes(layout_context);
+ // Re-run layout on our children.
+ for child in self.base.child_iter_mut() {
+ sequential::reflow(child, layout_context, RelayoutMode::Force);
+ }
+ // Assign our final-final block size.
+ self.assign_block_size(layout_context);
+ }
+
+ debug_assert_eq!(
+ self.fragment.margin_box_inline_size(),
+ self.base.position.size.inline
+ );
+ }
+
+ fn is_inline_block_or_inline_flex(&self) -> bool {
+ self.fragment.style().get_box().display == Display::InlineBlock ||
+ self.fragment.style().get_box().display == Display::InlineFlex
+ }
+
+ /// Computes the content portion (only) of the intrinsic inline sizes of this flow. This is
+ /// used for calculating shrink-to-fit width. Assumes that intrinsic sizes have already been
+ /// computed for this flow.
+ fn content_intrinsic_inline_sizes(&self) -> IntrinsicISizes {
+ let (border_padding, margin) = self.fragment.surrounding_intrinsic_inline_size();
+ IntrinsicISizes {
+ minimum_inline_size: self.base.intrinsic_inline_sizes.minimum_inline_size -
+ border_padding -
+ margin,
+ preferred_inline_size: self.base.intrinsic_inline_sizes.preferred_inline_size -
+ border_padding -
+ margin,
+ }
+ }
+
+ /// Computes intrinsic inline sizes for a block.
+ pub fn bubble_inline_sizes_for_block(&mut self, consult_children: bool) {
+ let _scope = layout_debug_scope!("block::bubble_inline_sizes {:x}", self.base.debug_id());
+
+ let mut flags = self.base.flags;
+ if self.definitely_has_zero_block_size() {
+ // This is kind of a hack for Acid2. But it's a harmless one, because (a) this behavior
+ // is unspecified; (b) it matches the behavior one would intuitively expect, since
+ // floats don't flow around blocks that take up no space in the block direction.
+ flags.remove(FlowFlags::CONTAINS_TEXT_OR_REPLACED_FRAGMENTS);
+ } else if self.fragment.is_text_or_replaced() {
+ flags.insert(FlowFlags::CONTAINS_TEXT_OR_REPLACED_FRAGMENTS);
+ } else {
+ flags.remove(FlowFlags::CONTAINS_TEXT_OR_REPLACED_FRAGMENTS);
+ for kid in self.base.children.iter() {
+ if kid
+ .base()
+ .flags
+ .contains(FlowFlags::CONTAINS_TEXT_OR_REPLACED_FRAGMENTS)
+ {
+ flags.insert(FlowFlags::CONTAINS_TEXT_OR_REPLACED_FRAGMENTS);
+ break;
+ }
+ }
+ }
+
+ // Find the maximum inline-size from children.
+ //
+ // See: https://lists.w3.org/Archives/Public/www-style/2014Nov/0085.html
+ //
+ // FIXME(pcwalton): This doesn't exactly follow that algorithm at the moment.
+ // FIXME(pcwalton): This should consider all float descendants, not just children.
+ let mut computation = self.fragment.compute_intrinsic_inline_sizes();
+ let (mut left_float_width, mut right_float_width) = (Au(0), Au(0));
+ let (mut left_float_width_accumulator, mut right_float_width_accumulator) = (Au(0), Au(0));
+ let mut preferred_inline_size_of_children_without_text_or_replaced_fragments = Au(0);
+ for kid in self.base.child_iter_mut() {
+ if kid
+ .base()
+ .flags
+ .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) ||
+ !consult_children
+ {
+ continue;
+ }
+
+ let child_base = kid.mut_base();
+ let float_kind = child_base.flags.float_kind();
+ computation.content_intrinsic_sizes.minimum_inline_size = max(
+ computation.content_intrinsic_sizes.minimum_inline_size,
+ child_base.intrinsic_inline_sizes.minimum_inline_size,
+ );
+
+ if child_base.flags.contains(FlowFlags::CLEARS_LEFT) {
+ left_float_width = max(left_float_width, left_float_width_accumulator);
+ left_float_width_accumulator = Au(0)
+ }
+ if child_base.flags.contains(FlowFlags::CLEARS_RIGHT) {
+ right_float_width = max(right_float_width, right_float_width_accumulator);
+ right_float_width_accumulator = Au(0)
+ }
+
+ match (
+ float_kind,
+ child_base
+ .flags
+ .contains(FlowFlags::CONTAINS_TEXT_OR_REPLACED_FRAGMENTS),
+ ) {
+ (Float::None, true) => {
+ computation.content_intrinsic_sizes.preferred_inline_size = max(
+ computation.content_intrinsic_sizes.preferred_inline_size,
+ child_base.intrinsic_inline_sizes.preferred_inline_size,
+ );
+ },
+ (Float::None, false) => {
+ preferred_inline_size_of_children_without_text_or_replaced_fragments = max(
+ preferred_inline_size_of_children_without_text_or_replaced_fragments,
+ child_base.intrinsic_inline_sizes.preferred_inline_size,
+ )
+ },
+ (Float::Left, _) => {
+ left_float_width_accumulator = left_float_width_accumulator +
+ child_base.intrinsic_inline_sizes.preferred_inline_size;
+ },
+ (Float::Right, _) => {
+ right_float_width_accumulator = right_float_width_accumulator +
+ child_base.intrinsic_inline_sizes.preferred_inline_size;
+ },
+ }
+ }
+
+ left_float_width = max(left_float_width, left_float_width_accumulator);
+ right_float_width = max(right_float_width, right_float_width_accumulator);
+
+ computation.content_intrinsic_sizes.preferred_inline_size =
+ computation.content_intrinsic_sizes.preferred_inline_size +
+ left_float_width +
+ right_float_width;
+ computation.content_intrinsic_sizes.preferred_inline_size = max(
+ computation.content_intrinsic_sizes.preferred_inline_size,
+ preferred_inline_size_of_children_without_text_or_replaced_fragments,
+ );
+
+ self.base.intrinsic_inline_sizes = computation.finish();
+ self.base.flags = flags
+ }
+
+ pub fn overflow_style_may_require_clip_scroll_node(&self) -> bool {
+ match (
+ self.fragment.style().get_box().overflow_x,
+ self.fragment.style().get_box().overflow_y,
+ ) {
+ (StyleOverflow::Auto, _) |
+ (StyleOverflow::Scroll, _) |
+ (StyleOverflow::Hidden, _) |
+ (_, StyleOverflow::Auto) |
+ (_, StyleOverflow::Scroll) |
+ (_, StyleOverflow::Hidden) => true,
+ (_, _) => false,
+ }
+ }
+
+ pub fn compute_inline_sizes(&mut self, shared_context: &SharedStyleContext) {
+ if !self
+ .base
+ .restyle_damage
+ .intersects(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW)
+ {
+ return;
+ }
+
+ debug!(
+ "assign_inline_sizes({}): assigning inline_size for flow",
+ if self.base.flags.is_float() {
+ "float"
+ } else {
+ "block"
+ }
+ );
+
+ self.base.floats = Floats::new(self.base.writing_mode);
+
+ self.initialize_container_size_for_root(shared_context);
+
+ // Our inline-size was set to the inline-size of the containing block by the flow's parent.
+ // Now compute the real value.
+ self.propagate_and_compute_used_inline_size(shared_context);
+
+ self.guess_inline_size_for_block_formatting_context_if_necessary()
+ }
+
+ /// If this is the root flow, initialize values that would normally be set by the parent.
+ ///
+ /// Should be called during `assign_inline_sizes` for flows that may be the root.
+ pub fn initialize_container_size_for_root(&mut self, shared_context: &SharedStyleContext) {
+ if self.is_root() {
+ debug!("Setting root position");
+ self.base.position.start = LogicalPoint::zero(self.base.writing_mode);
+ self.base.block_container_inline_size =
+ LogicalSize::from_physical(self.base.writing_mode, shared_context.viewport_size())
+ .inline;
+ self.base.block_container_writing_mode = self.base.writing_mode;
+ }
+ }
+
+ fn guess_inline_size_for_block_formatting_context_if_necessary(&mut self) {
+ // We don't need to guess anything unless this is a block formatting context.
+ if self.formatting_context_type() != FormattingContextType::Block {
+ return;
+ }
+
+ // If `max-width` is set, then don't perform this speculation. We guess that the
+ // page set `max-width` in order to avoid hitting floats. The search box on Google
+ // SERPs falls into this category.
+ if self.fragment.style.max_inline_size() != MaxSize::None {
+ return;
+ }
+
+ // At this point, we know we can't precisely 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 left and/or right
+ // floats.
+ let speculated_left_float_size = if self.fragment.margin.inline_start >= Au(0) &&
+ self.base.speculated_float_placement_in.left > self.fragment.margin.inline_start
+ {
+ self.base.speculated_float_placement_in.left - self.fragment.margin.inline_start
+ } else {
+ Au(0)
+ };
+ let speculated_right_float_size = if self.fragment.margin.inline_end >= Au(0) &&
+ self.base.speculated_float_placement_in.right > self.fragment.margin.inline_end
+ {
+ self.base.speculated_float_placement_in.right - self.fragment.margin.inline_end
+ } else {
+ Au(0)
+ };
+ self.fragment.border_box.size.inline = self.fragment.border_box.size.inline -
+ speculated_left_float_size -
+ speculated_right_float_size
+ }
+
+ fn definitely_has_zero_block_size(&self) -> bool {
+ if !self
+ .fragment
+ .style
+ .content_block_size()
+ .is_definitely_zero()
+ {
+ return false;
+ }
+ let border_width = self.fragment.border_width();
+ if border_width.block_start != Au(0) || border_width.block_end != Au(0) {
+ return false;
+ }
+ let padding = self.fragment.style.logical_padding();
+ padding.block_start.is_definitely_zero() && padding.block_end.is_definitely_zero()
+ }
+
+ pub fn is_inline_flex_item(&self) -> bool {
+ self.fragment
+ .flags
+ .contains(FragmentFlags::IS_INLINE_FLEX_ITEM)
+ }
+
+ pub fn is_block_flex_item(&self) -> bool {
+ self.fragment
+ .flags
+ .contains(FragmentFlags::IS_BLOCK_FLEX_ITEM)
+ }
+
+ pub fn mark_scrolling_overflow(&mut self, has_scrolling_overflow: bool) {
+ if has_scrolling_overflow {
+ self.flags.insert(BlockFlowFlags::HAS_SCROLLING_OVERFLOW);
+ } else {
+ self.flags.remove(BlockFlowFlags::HAS_SCROLLING_OVERFLOW);
+ }
+ }
+
+ pub fn has_scrolling_overflow(&self) -> bool {
+ self.flags.contains(BlockFlowFlags::HAS_SCROLLING_OVERFLOW)
+ }
+
+ // Return offset from original position because of `position: sticky`.
+ pub fn sticky_position(&self) -> SideOffsets2D<MaybeAuto> {
+ let containing_block_size = &self
+ .base
+ .early_absolute_position_info
+ .relative_containing_block_size;
+ let writing_mode = self
+ .base
+ .early_absolute_position_info
+ .relative_containing_block_mode;
+ let offsets = self.fragment.style().logical_position();
+ let as_margins = LogicalMargin::new(
+ writing_mode,
+ MaybeAuto::from_style(offsets.block_start, containing_block_size.inline),
+ MaybeAuto::from_style(offsets.inline_end, containing_block_size.inline),
+ MaybeAuto::from_style(offsets.block_end, containing_block_size.inline),
+ MaybeAuto::from_style(offsets.inline_start, containing_block_size.inline),
+ );
+ as_margins.to_physical(writing_mode)
+ }
+
+ pub fn background_border_section(&self) -> DisplayListSection {
+ if self.base.flags.is_float() {
+ DisplayListSection::BackgroundAndBorders
+ } else if self
+ .base
+ .flags
+ .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED)
+ {
+ if self.fragment.establishes_stacking_context() {
+ DisplayListSection::BackgroundAndBorders
+ } else {
+ DisplayListSection::BlockBackgroundsAndBorders
+ }
+ } else {
+ DisplayListSection::BlockBackgroundsAndBorders
+ }
+ }
+}
+
+impl Flow for BlockFlow {
+ fn class(&self) -> FlowClass {
+ FlowClass::Block
+ }
+
+ fn as_mut_block(&mut self) -> &mut BlockFlow {
+ self
+ }
+
+ fn as_block(&self) -> &BlockFlow {
+ self
+ }
+
+ /// Pass 1 of reflow: computes minimum and preferred inline-sizes.
+ ///
+ /// Recursively (bottom-up) determine the flow's minimum and preferred inline-sizes. When
+ /// called on this flow, all child flows have had their minimum and preferred inline-sizes set.
+ /// This function must decide minimum/preferred inline-sizes based on its children's
+ /// inline-sizes and the dimensions of any fragments it is responsible for flowing.
+ fn bubble_inline_sizes(&mut self) {
+ // If this block has a fixed width, just use that for the minimum and preferred width,
+ // rather than bubbling up children inline width.
+ // FIXME(emilio): This should probably be writing-mode-aware.
+ let consult_children = match self.fragment.style().get_position().width {
+ Size::Auto => true,
+ Size::LengthPercentage(ref lp) => lp.maybe_to_used_value(None).is_none(),
+ };
+ self.bubble_inline_sizes_for_block(consult_children);
+ self.fragment
+ .restyle_damage
+ .remove(ServoRestyleDamage::BUBBLE_ISIZES);
+ }
+
+ /// Recursively (top-down) determines the actual inline-size of child contexts and fragments.
+ /// When called on this context, the context has had its inline-size set by the parent context.
+ ///
+ /// Dual fragments consume some inline-size first, and the remainder is assigned to all child
+ /// (block) contexts.
+ fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
+ let _scope = layout_debug_scope!("block::assign_inline_sizes {:x}", self.base.debug_id());
+
+ let shared_context = layout_context.shared_context();
+ self.compute_inline_sizes(shared_context);
+
+ // Move in from the inline-start border edge.
+ let inline_start_content_edge =
+ self.fragment.border_box.start.i + self.fragment.border_padding.inline_start;
+
+ let padding_and_borders = self.fragment.border_padding.inline_start_end();
+
+ // Distance from the inline-end margin edge to the inline-end content edge.
+ let inline_end_content_edge =
+ self.fragment.margin.inline_end + self.fragment.border_padding.inline_end;
+
+ let content_inline_size = self.fragment.border_box.size.inline - padding_and_borders;
+
+ self.propagate_assigned_inline_size_to_children(
+ shared_context,
+ inline_start_content_edge,
+ inline_end_content_edge,
+ content_inline_size,
+ |_, _, _, _, _, _| {},
+ );
+ }
+
+ fn place_float_if_applicable<'a>(&mut self) {
+ if self.base.flags.is_float() {
+ self.place_float();
+ }
+ }
+
+ fn assign_block_size_for_inorder_child_if_necessary(
+ &mut self,
+ layout_context: &LayoutContext,
+ parent_thread_id: u8,
+ content_box: LogicalRect<Au>,
+ ) -> bool {
+ if self.base.flags.is_float() {
+ return false;
+ }
+
+ let is_formatting_context = self.formatting_context_type() != FormattingContextType::None;
+ if !self
+ .base
+ .flags
+ .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) &&
+ is_formatting_context
+ {
+ self.assign_inline_position_for_formatting_context(layout_context, content_box);
+ }
+
+ if (self as &dyn Flow).floats_might_flow_through() {
+ self.base.thread_id = parent_thread_id;
+ if self
+ .base
+ .restyle_damage
+ .intersects(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW)
+ {
+ self.assign_block_size(layout_context);
+ // Don't remove the restyle damage; `assign_block_size` decides whether that is
+ // appropriate (which in the case of e.g. absolutely-positioned flows, it is not).
+ }
+ return true;
+ }
+
+ if is_formatting_context {
+ // If this is a formatting context and definitely did not have floats in, then we must
+ // translate the floats past us.
+ let writing_mode = self.base.floats.writing_mode;
+ let delta = self.base.position.size.block;
+ self.base
+ .floats
+ .translate(LogicalSize::new(writing_mode, Au(0), -delta));
+ return true;
+ }
+
+ false
+ }
+
+ fn assign_block_size(&mut self, ctx: &LayoutContext) {
+ let remaining = Flow::fragment(self, ctx, None);
+ debug_assert!(remaining.is_none());
+ }
+
+ fn fragment(
+ &mut self,
+ layout_context: &LayoutContext,
+ fragmentation_context: Option<FragmentationContext>,
+ ) -> Option<Arc<dyn Flow>> {
+ if self.fragment.is_replaced() {
+ let _scope = layout_debug_scope!(
+ "assign_replaced_block_size_if_necessary {:x}",
+ self.base.debug_id()
+ );
+
+ // Assign block-size for fragment if it is an image fragment.
+ self.fragment.assign_replaced_block_size_if_necessary();
+ if !self
+ .base
+ .flags
+ .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED)
+ {
+ self.base.position.size.block = self.fragment.border_box.size.block;
+ let mut block_start =
+ AdjoiningMargins::from_margin(self.fragment.margin.block_start);
+ let block_end = AdjoiningMargins::from_margin(self.fragment.margin.block_end);
+ if self.fragment.border_box.size.block == Au(0) {
+ block_start.union(block_end);
+ self.base.collapsible_margins =
+ CollapsibleMargins::CollapseThrough(block_start);
+ } else {
+ self.base.collapsible_margins =
+ CollapsibleMargins::Collapse(block_start, block_end);
+ }
+ self.base
+ .restyle_damage
+ .remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW);
+ self.fragment
+ .restyle_damage
+ .remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW);
+ }
+ None
+ } else if self.is_root() ||
+ self.formatting_context_type() != FormattingContextType::None ||
+ self.base.flags.contains(FlowFlags::MARGINS_CANNOT_COLLAPSE)
+ {
+ // Root element margins should never be collapsed according to CSS § 8.3.1.
+ debug!(
+ "assign_block_size: assigning block_size for root flow {:?}",
+ self.base().debug_id()
+ );
+ self.assign_block_size_block_base(
+ layout_context,
+ fragmentation_context,
+ MarginsMayCollapseFlag::MarginsMayNotCollapse,
+ )
+ } else {
+ debug!(
+ "assign_block_size: assigning block_size for block {:?}",
+ self.base().debug_id()
+ );
+ self.assign_block_size_block_base(
+ layout_context,
+ fragmentation_context,
+ MarginsMayCollapseFlag::MarginsMayCollapse,
+ )
+ }
+ }
+
+ fn compute_stacking_relative_position(&mut self, _layout_context: &LayoutContext) {
+ // FIXME (mbrubeck): Get the real container size, taking the container writing mode into
+ // account. Must handle vertical writing modes.
+ let container_size = Size2D::new(self.base.block_container_inline_size, Au(0));
+
+ if self.is_root() {
+ self.base.clip = Rect::max_rect();
+ }
+
+ if self
+ .base
+ .flags
+ .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED)
+ {
+ let position_start = self
+ .base
+ .position
+ .start
+ .to_physical(self.base.writing_mode, container_size);
+
+ // Compute our position relative to the nearest ancestor stacking context. This will be
+ // passed down later as part of containing block details for absolute descendants.
+ let absolute_stacking_relative_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
+ .late_absolute_position_info
+ .stacking_relative_position_of_absolute_containing_block +
+ position_start.to_vector()
+ };
+
+ if !self.base.writing_mode.is_vertical() {
+ if !self
+ .base
+ .flags
+ .contains(FlowFlags::INLINE_POSITION_IS_STATIC)
+ {
+ self.base.stacking_relative_position.x = absolute_stacking_relative_position.x
+ }
+ if !self
+ .base
+ .flags
+ .contains(FlowFlags::BLOCK_POSITION_IS_STATIC)
+ {
+ self.base.stacking_relative_position.y = absolute_stacking_relative_position.y
+ }
+ } else {
+ if !self
+ .base
+ .flags
+ .contains(FlowFlags::INLINE_POSITION_IS_STATIC)
+ {
+ self.base.stacking_relative_position.y = absolute_stacking_relative_position.y
+ }
+ if !self
+ .base
+ .flags
+ .contains(FlowFlags::BLOCK_POSITION_IS_STATIC)
+ {
+ self.base.stacking_relative_position.x = absolute_stacking_relative_position.x
+ }
+ }
+ }
+
+ // 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 established in other circumstances (see `is_absolute_containing_block').
+ let relative_offset = self.fragment.relative_position(
+ &self
+ .base
+ .early_absolute_position_info
+ .relative_containing_block_size,
+ );
+ if self.is_absolute_containing_block() {
+ let border_box_origin =
+ (self.fragment.border_box - self.fragment.style.logical_border_width()).start;
+ self.base
+ .late_absolute_position_info
+ .stacking_relative_position_of_absolute_containing_block =
+ self.base.stacking_relative_position.to_point() +
+ (border_box_origin + relative_offset)
+ .to_physical(self.base.writing_mode, container_size)
+ .to_vector()
+ }
+
+ // Compute absolute position info for children.
+ let stacking_relative_position_of_absolute_containing_block_for_children =
+ if self.fragment.establishes_stacking_context() {
+ let logical_border_width = self.fragment.style().logical_border_width();
+ let position = LogicalPoint::new(
+ self.base.writing_mode,
+ logical_border_width.inline_start,
+ logical_border_width.block_start,
+ );
+ let position = position.to_physical(self.base.writing_mode, container_size);
+
+ // Some blocks establish a stacking context, but not a containing block for
+ // absolutely positioned elements. An example of this might be a block that has
+ // `position: static` and `opacity` set. In these cases, absolutely-positioned
+ // children will not be positioned relative to us but will instead be positioned
+ // relative to our containing block.
+ if self.is_absolute_containing_block() {
+ position
+ } else {
+ position - self.base.stacking_relative_position
+ }
+ } else {
+ self.base
+ .late_absolute_position_info
+ .stacking_relative_position_of_absolute_containing_block
+ };
+ let late_absolute_position_info_for_children = LateAbsolutePositionInfo {
+ stacking_relative_position_of_absolute_containing_block:
+ stacking_relative_position_of_absolute_containing_block_for_children,
+ };
+ let container_size_for_children =
+ self.base.position.size.to_physical(self.base.writing_mode);
+
+ // Compute the origin and clipping rectangle for children.
+ let relative_offset = relative_offset
+ .to_physical(self.base.writing_mode)
+ .to_vector();
+ let is_stacking_context = self.fragment.establishes_stacking_context();
+ let origin_for_children = if is_stacking_context {
+ // We establish a stacking context, so the position of our children is vertically
+ // correct, but has to be adjusted to accommodate horizontal margins. (Note the
+ // calculation involving `position` below and recall that inline-direction flow
+ // positions are relative to the edges of the margin box.)
+ //
+ // FIXME(pcwalton): Is this vertical-writing-direction-safe?
+ let margin = self.fragment.margin.to_physical(self.base.writing_mode);
+ Point2D::new(-margin.left, Au(0))
+ } else {
+ self.base.stacking_relative_position.to_point() + relative_offset
+ };
+
+ // Process children.
+ for kid in self.base.child_iter_mut() {
+ if kid
+ .base()
+ .flags
+ .contains(FlowFlags::INLINE_POSITION_IS_STATIC) ||
+ kid.base()
+ .flags
+ .contains(FlowFlags::BLOCK_POSITION_IS_STATIC)
+ {
+ let kid_base = kid.mut_base();
+ let physical_position = kid_base
+ .position
+ .to_physical(kid_base.writing_mode, container_size_for_children);
+
+ // Set the inline and block positions as necessary.
+ if !kid_base.writing_mode.is_vertical() {
+ if kid_base
+ .flags
+ .contains(FlowFlags::INLINE_POSITION_IS_STATIC)
+ {
+ kid_base.stacking_relative_position.x =
+ origin_for_children.x + physical_position.origin.x
+ }
+ if kid_base.flags.contains(FlowFlags::BLOCK_POSITION_IS_STATIC) {
+ kid_base.stacking_relative_position.y =
+ origin_for_children.y + physical_position.origin.y
+ }
+ } else {
+ if kid_base
+ .flags
+ .contains(FlowFlags::INLINE_POSITION_IS_STATIC)
+ {
+ kid_base.stacking_relative_position.y =
+ origin_for_children.y + physical_position.origin.y
+ }
+ if kid_base.flags.contains(FlowFlags::BLOCK_POSITION_IS_STATIC) {
+ kid_base.stacking_relative_position.x =
+ origin_for_children.x + physical_position.origin.x
+ }
+ }
+ }
+
+ kid.mut_base().late_absolute_position_info = late_absolute_position_info_for_children;
+ }
+ }
+
+ fn mark_as_root(&mut self) {
+ self.flags.insert(BlockFlowFlags::IS_ROOT)
+ }
+
+ fn is_root(&self) -> bool {
+ self.flags.contains(BlockFlowFlags::IS_ROOT)
+ }
+
+ /// The 'position' property of this flow.
+ fn positioning(&self) -> Position {
+ self.fragment.style.get_box().position
+ }
+
+ /// 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_size(&self, _: OpaqueFlow) -> LogicalSize<Au> {
+ (self.fragment.border_box - self.fragment.style().logical_border_width()).size
+ }
+
+ /// Returns true if this flow contains fragments that are roots of an absolute flow tree.
+ fn contains_roots_of_absolute_flow_tree(&self) -> bool {
+ self.contains_relatively_positioned_fragments() ||
+ self.is_root() ||
+ self.fragment.has_filter_transform_or_perspective()
+ }
+
+ /// Returns true if this is an absolute containing block.
+ fn is_absolute_containing_block(&self) -> bool {
+ self.contains_positioned_fragments() || self.fragment.has_filter_transform_or_perspective()
+ }
+
+ fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
+ if self
+ .base
+ .flags
+ .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) &&
+ self.fragment.style().logical_position().inline_start == LengthPercentageOrAuto::Auto &&
+ self.fragment.style().logical_position().inline_end == LengthPercentageOrAuto::Auto
+ {
+ self.base.position.start.i = inline_position
+ }
+ }
+
+ fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
+ if self
+ .base
+ .flags
+ .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) &&
+ self.fragment.style().logical_position().block_start == LengthPercentageOrAuto::Auto &&
+ self.fragment.style().logical_position().block_end == LengthPercentageOrAuto::Auto
+ {
+ self.base.position.start.b = block_position
+ }
+ }
+
+ fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
+ self.collect_stacking_contexts_for_block(state, StackingContextCollectionFlags::empty());
+ }
+
+ fn build_display_list(&mut self, state: &mut DisplayListBuildState) {
+ self.build_display_list_for_block(state, BorderPaintingMode::Separate);
+ }
+
+ fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>) {
+ self.fragment.repair_style(new_style)
+ }
+
+ fn compute_overflow(&self) -> Overflow {
+ let flow_size = self.base.position.size.to_physical(self.base.writing_mode);
+ let overflow = self.fragment.compute_overflow(
+ &flow_size,
+ &self
+ .base
+ .early_absolute_position_info
+ .relative_containing_block_size,
+ );
+ overflow
+ }
+
+ fn iterate_through_fragment_border_boxes(
+ &self,
+ iterator: &mut dyn FragmentBorderBoxIterator,
+ level: i32,
+ stacking_context_position: &Point2D<Au>,
+ ) {
+ if !iterator.should_process(&self.fragment) {
+ return;
+ }
+
+ iterator.process(
+ &self.fragment,
+ level,
+ &self
+ .fragment
+ .stacking_relative_border_box(
+ &self.base.stacking_relative_position,
+ &self
+ .base
+ .early_absolute_position_info
+ .relative_containing_block_size,
+ self.base
+ .early_absolute_position_info
+ .relative_containing_block_mode,
+ CoordinateSystem::Own,
+ )
+ .translate(&stacking_context_position.to_vector()),
+ );
+ }
+
+ fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) {
+ (*mutator)(&mut self.fragment)
+ }
+
+ fn print_extra_flow_children(&self, print_tree: &mut PrintTree) {
+ print_tree.add_item(format!("↑↑ Fragment for block:{:?}", self.fragment));
+ }
+}
+
+impl fmt::Debug for BlockFlow {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "{:?}({:x}) {:?}",
+ self.class(),
+ self.base.debug_id(),
+ self.base
+ )
+ }
+}
+
+/// The inputs for the inline-sizes-and-margins constraint equation.
+#[derive(Clone, Copy, Debug)]
+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 text_align: TextAlign,
+ pub available_inline_size: Au,
+}
+
+impl ISizeConstraintInput {
+ pub fn new(
+ computed_inline_size: MaybeAuto,
+ inline_start_margin: MaybeAuto,
+ inline_end_margin: MaybeAuto,
+ inline_start: MaybeAuto,
+ inline_end: MaybeAuto,
+ text_align: TextAlign,
+ available_inline_size: 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,
+ text_align: text_align,
+ available_inline_size: available_inline_size,
+ }
+ }
+}
+
+/// The solutions for the inline-size-and-margins constraint equation.
+#[derive(Clone, Copy, Debug)]
+pub struct ISizeConstraintSolution {
+ pub inline_start: 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_size: inline_size,
+ margin_inline_start: margin_inline_start,
+ margin_inline_end: margin_inline_end,
+ }
+ }
+
+ fn for_absolute_flow(
+ inline_start: Au,
+ inline_size: Au,
+ margin_inline_start: Au,
+ margin_inline_end: Au,
+ ) -> ISizeConstraintSolution {
+ ISizeConstraintSolution {
+ inline_start: inline_start,
+ 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 {
+ /// Instructs the fragment to compute its border and padding.
+ fn compute_border_and_padding(&self, block: &mut BlockFlow, containing_block_inline_size: Au) {
+ block
+ .fragment
+ .compute_border_and_padding(containing_block_inline_size);
+ }
+
+ /// Compute the inputs for the ISize constraint equation.
+ ///
+ /// This is called only once to compute the initial inputs. For calculations involving
+ /// minimum and maximum inline-size, we don't need to recompute these.
+ fn compute_inline_size_constraint_inputs(
+ &self,
+ block: &mut BlockFlow,
+ parent_flow_inline_size: Au,
+ shared_context: &SharedStyleContext,
+ ) -> ISizeConstraintInput {
+ let containing_block_inline_size =
+ self.containing_block_inline_size(block, parent_flow_inline_size, shared_context);
+
+ block
+ .fragment
+ .compute_block_direction_margins(containing_block_inline_size);
+ block
+ .fragment
+ .compute_inline_direction_margins(containing_block_inline_size);
+ self.compute_border_and_padding(block, containing_block_inline_size);
+
+ let mut computed_inline_size =
+ self.initial_computed_inline_size(block, parent_flow_inline_size, shared_context);
+ let style = block.fragment.style();
+ match (computed_inline_size, style.get_position().box_sizing) {
+ (MaybeAuto::Specified(size), BoxSizing::BorderBox) => {
+ computed_inline_size =
+ MaybeAuto::Specified(size - block.fragment.border_padding.inline_start_end())
+ },
+ (MaybeAuto::Auto, BoxSizing::BorderBox) | (_, BoxSizing::ContentBox) => {},
+ }
+
+ 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();
+ 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),
+ style.get_inherited_text().text_align,
+ available_inline_size,
+ )
+ }
+
+ /// Set the used values for inline-size and margins 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;
+ /// * Inline-start coordinate of this flow's box;
+ /// * Inline-start coordinate of the flow with respect to 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 extra_inline_size_from_margin;
+ {
+ let block_mode = block.base.writing_mode;
+
+ // FIXME (mbrubeck): Get correct containing block for positioned blocks?
+ let container_mode = block.base.block_container_writing_mode;
+ let container_size = block.base.block_container_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.
+ inline_size = solution.inline_size + fragment.border_padding.inline_start_end();
+ fragment.border_box.size.inline = inline_size;
+
+ // Start border edge.
+ // FIXME (mbrubeck): Handle vertical writing modes.
+ fragment.border_box.start.i =
+ if container_mode.is_bidi_ltr() == block_mode.is_bidi_ltr() {
+ fragment.margin.inline_start
+ } else {
+ // The parent's "start" direction is the child's "end" direction.
+ container_size - inline_size - fragment.margin.inline_end
+ };
+
+ // To calculate the total size of this block, we also need to account for any
+ // additional size contribution from positive margins. Negative margins means the block
+ // isn't made larger at all by the margin.
+ extra_inline_size_from_margin =
+ max(Au(0), fragment.margin.inline_start) + max(Au(0), fragment.margin.inline_end);
+ }
+
+ // 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.
+ block.mut_base().position.size.inline = inline_size + extra_inline_size_from_margin;
+ }
+
+ /// Set the inline coordinate of the given flow if it is absolutely positioned.
+ fn set_inline_position_of_flow_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,
+ shared_context: &SharedStyleContext,
+ ) -> MaybeAuto {
+ MaybeAuto::from_option(
+ block
+ .fragment()
+ .style()
+ .content_inline_size()
+ .to_used_value(self.containing_block_inline_size(
+ block,
+ parent_flow_inline_size,
+ shared_context,
+ )),
+ )
+ }
+
+ fn containing_block_inline_size(
+ &self,
+ _: &mut BlockFlow,
+ parent_flow_inline_size: Au,
+ _: &SharedStyleContext,
+ ) -> 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,
+ shared_context: &SharedStyleContext,
+ parent_flow_inline_size: Au,
+ ) {
+ let mut input = self.compute_inline_size_constraint_inputs(
+ block,
+ parent_flow_inline_size,
+ shared_context,
+ );
+
+ let containing_block_inline_size =
+ self.containing_block_inline_size(block, parent_flow_inline_size, shared_context);
+
+ 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 block
+ .fragment()
+ .style()
+ .max_inline_size()
+ .to_used_value(containing_block_inline_size)
+ {
+ Some(max_inline_size) if max_inline_size < solution.inline_size => {
+ input.computed_inline_size = MaybeAuto::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 = block
+ .fragment()
+ .style()
+ .min_inline_size()
+ .to_used_value(containing_block_inline_size)
+ .unwrap_or(Au(0));
+ if computed_min_inline_size > solution.inline_size {
+ input.computed_inline_size = MaybeAuto::Specified(computed_min_inline_size);
+ solution = self.solve_inline_size_constraints(block, &input);
+ }
+
+ self.set_inline_size_constraint_solutions(block, solution);
+ self.set_inline_position_of_flow_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,
+ 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,
+ );
+
+ // Check for direction of parent flow (NOT Containing Block)
+ let block_mode = block.base.writing_mode;
+ let container_mode = block.base.block_container_writing_mode;
+ let block_align = block.base.flags.text_align();
+
+ // FIXME (mbrubeck): Handle vertical writing modes.
+ let parent_has_same_direction = container_mode.is_bidi_ltr() == block_mode.is_bidi_ltr();
+
+ // 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 {
+ MaybeAuto::Auto => (inline_start_margin, inline_end_margin),
+ MaybeAuto::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 {
+ (
+ MaybeAuto::Specified(inline_start),
+ MaybeAuto::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.
+ (
+ MaybeAuto::Specified(margin_start),
+ MaybeAuto::Specified(inline_size),
+ MaybeAuto::Specified(margin_end),
+ ) => {
+ // servo_left, servo_right, and servo_center are used to implement
+ // the "align descendants" rule in HTML5 § 14.2.
+ if block_align == TextAlign::ServoCenter {
+ // Ignore any existing margins, and make the inline-start and
+ // inline-end margins equal.
+ let margin = (available_inline_size - inline_size).scale_by(0.5);
+ (margin, inline_size, margin)
+ } else {
+ let ignore_end_margin = match block_align {
+ TextAlign::ServoLeft => block_mode.is_bidi_ltr(),
+ TextAlign::ServoRight => !block_mode.is_bidi_ltr(),
+ _ => parent_has_same_direction,
+ };
+ if ignore_end_margin {
+ (
+ margin_start,
+ inline_size,
+ available_inline_size - (margin_start + inline_size),
+ )
+ } else {
+ (
+ available_inline_size - (margin_end + inline_size),
+ inline_size,
+ margin_end,
+ )
+ }
+ }
+ },
+ // If exactly one value is 'auto', solve for it
+ (
+ MaybeAuto::Auto,
+ MaybeAuto::Specified(inline_size),
+ MaybeAuto::Specified(margin_end),
+ ) => (
+ available_inline_size - (inline_size + margin_end),
+ inline_size,
+ margin_end,
+ ),
+ (
+ MaybeAuto::Specified(margin_start),
+ MaybeAuto::Auto,
+ MaybeAuto::Specified(margin_end),
+ ) => (
+ margin_start,
+ available_inline_size - (margin_start + margin_end),
+ margin_end,
+ ),
+ (
+ MaybeAuto::Specified(margin_start),
+ MaybeAuto::Specified(inline_size),
+ MaybeAuto::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
+ (MaybeAuto::Auto, MaybeAuto::Auto, MaybeAuto::Specified(margin_end)) => {
+ (Au(0), available_inline_size - margin_end, margin_end)
+ },
+ (MaybeAuto::Specified(margin_start), MaybeAuto::Auto, MaybeAuto::Auto) => {
+ (margin_start, available_inline_size - margin_start, Au(0))
+ },
+ (MaybeAuto::Auto, MaybeAuto::Auto, MaybeAuto::Auto) => {
+ (Au(0), available_inline_size, Au(0))
+ },
+
+ // If inline-start and inline-end margins are auto, they become equal
+ (MaybeAuto::Auto, MaybeAuto::Specified(inline_size), MaybeAuto::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.
+pub struct AbsoluteNonReplaced;
+pub struct AbsoluteReplaced;
+pub struct BlockNonReplaced;
+pub struct BlockReplaced;
+pub struct FloatNonReplaced;
+pub struct FloatReplaced;
+pub struct InlineBlockNonReplaced;
+pub struct InlineBlockReplaced;
+pub struct InlineFlexItem;
+
+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,
+ ..
+ } = input;
+
+ // Check for direction of parent flow (NOT Containing Block)
+ let block_mode = block.base.writing_mode;
+ let container_mode = block.base.block_container_writing_mode;
+
+ // FIXME (mbrubeck): Handle vertical writing modes.
+ let parent_has_same_direction = container_mode.is_bidi_ltr() == block_mode.is_bidi_ltr();
+
+ let (inline_start, inline_size, margin_inline_start, margin_inline_end) =
+ match (inline_start, inline_end, computed_inline_size) {
+ (MaybeAuto::Auto, MaybeAuto::Auto, MaybeAuto::Auto) => {
+ let margin_start = inline_start_margin.specified_or_zero();
+ let margin_end = inline_end_margin.specified_or_zero();
+ // 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 - (margin_start + margin_end),
+ );
+ (Au(0), inline_size, margin_start, margin_end)
+ },
+ (
+ MaybeAuto::Specified(inline_start),
+ MaybeAuto::Specified(inline_end),
+ MaybeAuto::Specified(inline_size),
+ ) => {
+ match (inline_start_margin, inline_end_margin) {
+ (MaybeAuto::Auto, MaybeAuto::Auto) => {
+ let total_margin_val =
+ available_inline_size - inline_start - inline_end - inline_size;
+ if total_margin_val < Au(0) {
+ if parent_has_same_direction {
+ // margin-inline-start becomes 0
+ (inline_start, inline_size, Au(0), total_margin_val)
+ } else {
+ // margin-inline-end becomes 0, because it's toward the parent's
+ // inline-start edge.
+ (inline_start, inline_size, total_margin_val, Au(0))
+ }
+ } else {
+ // Equal margins
+ (
+ inline_start,
+ inline_size,
+ total_margin_val.scale_by(0.5),
+ total_margin_val.scale_by(0.5),
+ )
+ }
+ },
+ (MaybeAuto::Specified(margin_start), MaybeAuto::Auto) => {
+ let sum = inline_start + inline_end + inline_size + margin_start;
+ (
+ inline_start,
+ inline_size,
+ margin_start,
+ available_inline_size - sum,
+ )
+ },
+ (MaybeAuto::Auto, MaybeAuto::Specified(margin_end)) => {
+ let sum = inline_start + inline_end + inline_size + margin_end;
+ (
+ inline_start,
+ inline_size,
+ available_inline_size - sum,
+ margin_end,
+ )
+ },
+ (MaybeAuto::Specified(margin_start), MaybeAuto::Specified(margin_end)) => {
+ // Values are over-constrained.
+ let sum = inline_start + inline_size + margin_start + margin_end;
+ if parent_has_same_direction {
+ // Ignore value for 'inline-end'
+ (inline_start, inline_size, margin_start, margin_end)
+ } else {
+ // Ignore value for '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
+ (
+ MaybeAuto::Auto,
+ MaybeAuto::Specified(inline_end),
+ MaybeAuto::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_size,
+ margin_start,
+ margin_end,
+ )
+ },
+ (
+ MaybeAuto::Specified(inline_start),
+ MaybeAuto::Auto,
+ MaybeAuto::Specified(inline_size),
+ ) => {
+ let margin_start = inline_start_margin.specified_or_zero();
+ let margin_end = inline_end_margin.specified_or_zero();
+ (inline_start, inline_size, margin_start, margin_end)
+ },
+ (
+ MaybeAuto::Specified(inline_start),
+ MaybeAuto::Specified(inline_end),
+ MaybeAuto::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,
+ 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.
+ (MaybeAuto::Specified(inline_start), MaybeAuto::Auto, MaybeAuto::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 - (margin_start + margin_end),
+ );
+ (inline_start, inline_size, margin_start, margin_end)
+ },
+ (MaybeAuto::Auto, MaybeAuto::Specified(inline_end), MaybeAuto::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 - (margin_start + margin_end),
+ );
+ let sum = inline_end + inline_size + margin_start + margin_end;
+ (
+ available_inline_size - sum,
+ inline_size,
+ margin_start,
+ margin_end,
+ )
+ },
+
+ (MaybeAuto::Auto, MaybeAuto::Auto, MaybeAuto::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.
+ (Au(0), inline_size, margin_start, margin_end)
+ },
+ };
+ ISizeConstraintSolution::for_absolute_flow(
+ inline_start,
+ inline_size,
+ margin_inline_start,
+ margin_inline_end,
+ )
+ }
+
+ fn containing_block_inline_size(
+ &self,
+ block: &mut BlockFlow,
+ _: Au,
+ shared_context: &SharedStyleContext,
+ ) -> Au {
+ let opaque_block = OpaqueFlow::from_flow(block);
+ block
+ .containing_block_size(&shared_context.viewport_size(), opaque_block)
+ .inline
+ }
+
+ fn set_inline_position_of_flow_if_necessary(
+ &self,
+ block: &mut BlockFlow,
+ solution: ISizeConstraintSolution,
+ ) {
+ // Set the inline position of the absolute flow wrt to its containing block.
+ if !block
+ .base
+ .flags
+ .contains(FlowFlags::INLINE_POSITION_IS_STATIC)
+ {
+ block.base.position.start.i = solution.inline_start;
+ }
+ }
+}
+
+impl ISizeAndMarginsComputer for AbsoluteReplaced {
+ /// Solve the horizontal constraint equation for absolute replaced elements.
+ ///
+ /// 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,
+ ..
+ } = 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 {
+ MaybeAuto::Specified(w) => w,
+ _ => panic!(
+ "{} {}",
+ "The used value for inline_size for absolute replaced flow",
+ "should have already been calculated by now."
+ ),
+ };
+
+ let (inline_start, inline_size, margin_inline_start, margin_inline_end) =
+ match (inline_start, inline_end) {
+ (MaybeAuto::Auto, MaybeAuto::Auto) => {
+ let margin_start = inline_start_margin.specified_or_zero();
+ let margin_end = inline_end_margin.specified_or_zero();
+ (Au(0), inline_size, margin_start, margin_end)
+ },
+ // If only one is Auto, solve for it
+ (MaybeAuto::Auto, MaybeAuto::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_size,
+ margin_start,
+ margin_end,
+ )
+ },
+ (MaybeAuto::Specified(inline_start), MaybeAuto::Auto) => {
+ let margin_start = inline_start_margin.specified_or_zero();
+ let margin_end = inline_end_margin.specified_or_zero();
+ (inline_start, inline_size, margin_start, margin_end)
+ },
+ (MaybeAuto::Specified(inline_start), MaybeAuto::Specified(inline_end)) => {
+ match (inline_start_margin, inline_end_margin) {
+ (MaybeAuto::Auto, MaybeAuto::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_size, Au(0), total_margin_val)
+ } else {
+ // Equal margins
+ (
+ inline_start,
+ inline_size,
+ total_margin_val.scale_by(0.5),
+ total_margin_val.scale_by(0.5),
+ )
+ }
+ },
+ (MaybeAuto::Specified(margin_start), MaybeAuto::Auto) => {
+ let sum = inline_start + inline_end + inline_size + margin_start;
+ (
+ inline_start,
+ inline_size,
+ margin_start,
+ available_inline_size - sum,
+ )
+ },
+ (MaybeAuto::Auto, MaybeAuto::Specified(margin_end)) => {
+ let sum = inline_start + inline_end + inline_size + margin_end;
+ (
+ inline_start,
+ inline_size,
+ available_inline_size - sum,
+ margin_end,
+ )
+ },
+ (MaybeAuto::Specified(margin_start), MaybeAuto::Specified(margin_end)) => {
+ // Values are over-constrained.
+ // Ignore value for 'inline-end' cos direction is 'ltr'.
+ (inline_start, inline_size, margin_start, margin_end)
+ },
+ }
+ },
+ };
+ ISizeConstraintSolution::for_absolute_flow(
+ inline_start,
+ 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,
+ shared_context: &SharedStyleContext,
+ ) -> MaybeAuto {
+ let opaque_block = OpaqueFlow::from_flow(block);
+ let containing_block_inline_size = block
+ .containing_block_size(&shared_context.viewport_size(), opaque_block)
+ .inline;
+ let container_block_size = block.explicit_block_containing_size(shared_context);
+ let fragment = block.fragment();
+ fragment.assign_replaced_inline_size_if_necessary(
+ containing_block_inline_size,
+ container_block_size,
+ );
+ // For replaced absolute flow, the rest of the constraint solving will
+ // take inline-size to be specified as the value computed here.
+ MaybeAuto::Specified(fragment.content_box().size.inline)
+ }
+
+ fn containing_block_inline_size(
+ &self,
+ block: &mut BlockFlow,
+ _: Au,
+ shared_context: &SharedStyleContext,
+ ) -> Au {
+ let opaque_block = OpaqueFlow::from_flow(block);
+ block
+ .containing_block_size(&shared_context.viewport_size(), opaque_block)
+ .inline
+ }
+
+ fn set_inline_position_of_flow_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 {
+ MaybeAuto::Specified(_) => {},
+ MaybeAuto::Auto => {
+ panic!("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,
+ shared_context: &SharedStyleContext,
+ ) -> MaybeAuto {
+ let container_block_size = block.explicit_block_containing_size(shared_context);
+ let fragment = block.fragment();
+ fragment.assign_replaced_inline_size_if_necessary(
+ parent_flow_inline_size,
+ container_block_size,
+ );
+ // For replaced block flow, the rest of the constraint solving will
+ // take inline-size to be specified as the value computed here.
+ MaybeAuto::Specified(fragment.content_box().size.inline)
+ }
+}
+
+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 {
+ MaybeAuto::Specified(w) => w,
+ MaybeAuto::Auto => {
+ panic!("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,
+ shared_context: &SharedStyleContext,
+ ) -> MaybeAuto {
+ let container_block_size = block.explicit_block_containing_size(shared_context);
+ let fragment = block.fragment();
+ fragment.assign_replaced_inline_size_if_necessary(
+ parent_flow_inline_size,
+ container_block_size,
+ );
+ // For replaced block flow, the rest of the constraint solving will
+ // take inline-size to be specified as the value computed here.
+ MaybeAuto::Specified(fragment.content_box().size.inline)
+ }
+}
+
+impl ISizeAndMarginsComputer for InlineBlockNonReplaced {
+ /// Compute inline-start and inline-end margins and 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,
+ );
+
+ // For inline-blocks, `auto` margins compute to 0.
+ let inline_start_margin = inline_start_margin.specified_or_zero();
+ let inline_end_margin = inline_end_margin.specified_or_zero();
+
+ // If inline-size is set to 'auto', and this is an inline block, use the
+ // shrink to fit algorithm (see CSS 2.1 § 10.3.9)
+ let inline_size = match computed_inline_size {
+ MaybeAuto::Auto => block.get_shrink_to_fit_inline_size(
+ available_inline_size - (inline_start_margin + inline_end_margin),
+ ),
+ MaybeAuto::Specified(inline_size) => inline_size,
+ };
+
+ ISizeConstraintSolution::new(inline_size, inline_start_margin, inline_end_margin)
+ }
+}
+
+impl ISizeAndMarginsComputer for InlineBlockReplaced {
+ /// 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 {
+ debug_assert!(match input.computed_inline_size {
+ MaybeAuto::Specified(_) => true,
+ MaybeAuto::Auto => false,
+ });
+
+ 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,
+ );
+
+ // For inline-blocks, `auto` margins compute to 0.
+ let inline_start_margin = inline_start_margin.specified_or_zero();
+ let inline_end_margin = inline_end_margin.specified_or_zero();
+
+ // If inline-size is set to 'auto', and this is an inline block, use the
+ // shrink to fit algorithm (see CSS 2.1 § 10.3.9)
+ let inline_size = match computed_inline_size {
+ MaybeAuto::Auto => block.get_shrink_to_fit_inline_size(
+ available_inline_size - (inline_start_margin + inline_end_margin),
+ ),
+ MaybeAuto::Specified(inline_size) => inline_size,
+ };
+
+ ISizeConstraintSolution::new(inline_size, inline_start_margin, inline_end_margin)
+ }
+
+ /// 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,
+ shared_context: &SharedStyleContext,
+ ) -> MaybeAuto {
+ let container_block_size = block.explicit_block_containing_size(shared_context);
+ let fragment = block.fragment();
+ fragment.assign_replaced_inline_size_if_necessary(
+ parent_flow_inline_size,
+ container_block_size,
+ );
+ // For replaced block flow, the rest of the constraint solving will
+ // take inline-size to be specified as the value computed here.
+ MaybeAuto::Specified(fragment.content_box().size.inline)
+ }
+}
+
+impl ISizeAndMarginsComputer for InlineFlexItem {
+ // Replace the default method directly to prevent recalculating and setting margins again
+ // which has already been set by its parent.
+ fn compute_used_inline_size(
+ &self,
+ block: &mut BlockFlow,
+ shared_context: &SharedStyleContext,
+ parent_flow_inline_size: Au,
+ ) {
+ let container_block_size = block.explicit_block_containing_size(shared_context);
+ block.fragment.assign_replaced_inline_size_if_necessary(
+ parent_flow_inline_size,
+ container_block_size,
+ );
+ }
+
+ // The used inline size and margins are set by parent flex flow, do nothing here.
+ fn solve_inline_size_constraints(
+ &self,
+ block: &mut BlockFlow,
+ _: &ISizeConstraintInput,
+ ) -> ISizeConstraintSolution {
+ let fragment = block.fragment();
+ ISizeConstraintSolution::new(
+ fragment.border_box.size.inline,
+ fragment.margin.inline_start,
+ fragment.margin.inline_end,
+ )
+ }
+}
diff --git a/components/layout_2020/construct.rs b/components/layout_2020/construct.rs
new file mode 100644
index 00000000000..072413995b2
--- /dev/null
+++ b/components/layout_2020/construct.rs
@@ -0,0 +1,2443 @@
+/* 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 https://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.
+
+use crate::block::BlockFlow;
+use crate::context::{with_thread_local_font_context, LayoutContext};
+use crate::data::{LayoutData, LayoutDataFlags};
+use crate::display_list::items::OpaqueNode;
+use crate::flex::FlexFlow;
+use crate::floats::FloatKind;
+use crate::flow::{AbsoluteDescendants, Flow, FlowClass, GetBaseFlow, ImmutableFlowUtils};
+use crate::flow::{FlowFlags, MutableFlowUtils, MutableOwnedFlowUtils};
+use crate::flow_ref::FlowRef;
+use crate::fragment::{
+ CanvasFragmentInfo, Fragment, FragmentFlags, GeneratedContentInfo, IframeFragmentInfo,
+};
+use crate::fragment::{
+ ImageFragmentInfo, InlineAbsoluteFragmentInfo, InlineAbsoluteHypotheticalFragmentInfo,
+};
+use crate::fragment::{
+ InlineBlockFragmentInfo, MediaFragmentInfo, SpecificFragmentInfo, SvgFragmentInfo,
+};
+use crate::fragment::{
+ TableColumnFragmentInfo, UnscannedTextFragmentInfo, WhitespaceStrippingResult,
+};
+use crate::inline::{InlineFlow, InlineFragmentNodeFlags, InlineFragmentNodeInfo};
+use crate::linked_list::prepend_from;
+use crate::list_item::{ListItemFlow, ListStyleTypeContent};
+use crate::multicol::{MulticolColumnFlow, MulticolFlow};
+use crate::parallel;
+use crate::table::TableFlow;
+use crate::table_caption::TableCaptionFlow;
+use crate::table_cell::TableCellFlow;
+use crate::table_colgroup::TableColGroupFlow;
+use crate::table_row::TableRowFlow;
+use crate::table_rowgroup::TableRowGroupFlow;
+use crate::table_wrapper::TableWrapperFlow;
+use crate::text::TextRunScanner;
+use crate::traversal::PostorderNodeMutTraversal;
+use crate::wrapper::{LayoutNodeLayoutData, TextContent, ThreadSafeLayoutNodeHelpers};
+use crate::ServoArc;
+use script_layout_interface::wrapper_traits::{
+ PseudoElementType, ThreadSafeLayoutElement, ThreadSafeLayoutNode,
+};
+use script_layout_interface::{LayoutElementType, LayoutNodeType};
+use servo_config::opts;
+use servo_url::ServoUrl;
+use std::collections::LinkedList;
+use std::marker::PhantomData;
+use std::mem;
+use std::sync::atomic::Ordering;
+use std::sync::Arc;
+use style::computed_values::caption_side::T as CaptionSide;
+use style::computed_values::display::T as Display;
+use style::computed_values::empty_cells::T as EmptyCells;
+use style::computed_values::float::T as Float;
+use style::computed_values::list_style_position::T as ListStylePosition;
+use style::computed_values::position::T as Position;
+use style::context::SharedStyleContext;
+use style::dom::TElement;
+use style::logical_geometry::Direction;
+use style::properties::ComputedValues;
+use style::selector_parser::{PseudoElement, RestyleDamage};
+use style::servo::restyle_damage::ServoRestyleDamage;
+use style::values::generics::counters::ContentItem;
+use style::values::generics::url::UrlOrNone as ImageUrlOrNone;
+
+/// The results of flow construction for a DOM node.
+#[derive(Clone)]
+pub enum ConstructionResult {
+ /// This node contributes nothing at all (`display: none`). Alternately, this is what newly
+ /// created nodes have their `ConstructionResult` set to.
+ None,
+
+ /// 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 containing block above it.
+ Flow(FlowRef, AbsoluteDescendants),
+
+ /// 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.
+ ConstructionItem(ConstructionItem),
+}
+
+impl ConstructionResult {
+ pub fn get(&mut self) -> ConstructionResult {
+ // FIXME(pcwalton): Stop doing this with inline fragments. Cloning fragments is very
+ // inefficient!
+ (*self).clone()
+ }
+
+ pub fn debug_id(&self) -> usize {
+ match *self {
+ ConstructionResult::None => 0,
+ ConstructionResult::ConstructionItem(_) => 0,
+ ConstructionResult::Flow(ref flow_ref, _) => flow_ref.base().debug_id(),
+ }
+ }
+}
+
+/// 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.
+#[derive(Clone)]
+pub enum ConstructionItem {
+ /// Inline fragments and associated {ib} splits that have not yet found flows.
+ InlineFragments(InlineFragmentsConstructionResult),
+ /// Potentially ignorable whitespace.
+ ///
+ /// FIXME(emilio): How could whitespace have any PseudoElementType other
+ /// than Normal?
+ Whitespace(
+ OpaqueNode,
+ PseudoElementType,
+ ServoArc<ComputedValues>,
+ RestyleDamage,
+ ),
+ /// TableColumn Fragment
+ TableColumnFragment(Fragment),
+}
+
+/// Represents inline fragments and {ib} splits that are bubbling up from an inline.
+#[derive(Clone)]
+pub struct InlineFragmentsConstructionResult {
+ /// Any {ib} splits that we're bubbling up.
+ pub splits: LinkedList<InlineBlockSplit>,
+
+ /// Any fragments that succeed the {ib} splits.
+ pub fragments: IntermediateInlineFragments,
+}
+
+/// 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:
+///
+/// ```html
+/// <span>
+/// A
+/// <div>B</div>
+/// C
+/// </span>
+/// ```
+///
+/// The resulting `ConstructionItem` for the outer `span` will be:
+///
+/// ```rust,ignore
+/// ConstructionItem::InlineFragments(
+/// InlineFragmentsConstructionResult {
+/// splits: linked_list![
+/// InlineBlockSplit {
+/// predecessors: IntermediateInlineFragments {
+/// fragments: linked_list![A],
+/// absolute_descendents: AbsoluteDescendents {
+/// descendant_links: vec![]
+/// },
+/// },
+/// flow: B,
+/// }
+/// ],
+/// fragments: linked_list![C],
+/// },
+/// )
+/// ```
+#[derive(Clone)]
+pub struct InlineBlockSplit {
+ /// The inline fragments that precede the flow.
+ pub predecessors: IntermediateInlineFragments,
+
+ /// The flow that caused this {ib} split.
+ pub flow: FlowRef,
+}
+
+impl InlineBlockSplit {
+ /// Flushes the given accumulator to the new split and makes a new accumulator to hold any
+ /// subsequent fragments.
+ fn new<ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode>(
+ fragment_accumulator: &mut InlineFragmentsAccumulator,
+ node: &ConcreteThreadSafeLayoutNode,
+ style_context: &SharedStyleContext,
+ flow: FlowRef,
+ ) -> InlineBlockSplit {
+ fragment_accumulator.enclosing_node.as_mut().expect(
+ "enclosing_node is None; Are {ib} splits being generated outside of an inline node?"
+ ).flags.remove(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT);
+
+ let split = InlineBlockSplit {
+ predecessors: mem::replace(
+ fragment_accumulator,
+ InlineFragmentsAccumulator::from_inline_node(node, style_context),
+ )
+ .to_intermediate_inline_fragments::<ConcreteThreadSafeLayoutNode>(style_context),
+ flow: flow,
+ };
+
+ fragment_accumulator
+ .enclosing_node
+ .as_mut()
+ .unwrap()
+ .flags
+ .remove(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT);
+
+ split
+ }
+}
+
+/// Holds inline fragments and absolute descendants.
+#[derive(Clone)]
+pub struct IntermediateInlineFragments {
+ /// The list of fragments.
+ pub fragments: LinkedList<Fragment>,
+
+ /// The list of absolute descendants of those inline fragments.
+ pub absolute_descendants: AbsoluteDescendants,
+}
+
+impl IntermediateInlineFragments {
+ fn new() -> IntermediateInlineFragments {
+ IntermediateInlineFragments {
+ fragments: LinkedList::new(),
+ absolute_descendants: AbsoluteDescendants::new(),
+ }
+ }
+
+ fn is_empty(&self) -> bool {
+ self.fragments.is_empty() && self.absolute_descendants.is_empty()
+ }
+
+ fn push_all(&mut self, mut other: IntermediateInlineFragments) {
+ self.fragments.append(&mut other.fragments);
+ self.absolute_descendants
+ .push_descendants(other.absolute_descendants);
+ }
+}
+
+/// Holds inline fragments that we're gathering for children of an inline node.
+struct InlineFragmentsAccumulator {
+ /// The list of fragments.
+ fragments: IntermediateInlineFragments,
+
+ /// Information about the inline box directly enclosing the fragments being gathered, if any.
+ ///
+ /// `inline::InlineFragmentNodeInfo` also stores flags indicating whether a fragment is the
+ /// first and/or last of the corresponding inline box. This `InlineFragmentsAccumulator` may
+ /// represent only one side of an {ib} split, so we store these flags as if it represented only
+ /// one fragment. `to_intermediate_inline_fragments` later splits this hypothetical fragment
+ /// into pieces, leaving the `FIRST_FRAGMENT_OF_ELEMENT` and `LAST_FRAGMENT_OF_ELEMENT` flags,
+ /// if present, on the first and last fragments of the output.
+ enclosing_node: Option<InlineFragmentNodeInfo>,
+
+ /// Restyle damage to use for fragments created in this node.
+ restyle_damage: RestyleDamage,
+
+ /// Bidi control characters to insert before and after these fragments.
+ bidi_control_chars: Option<(&'static str, &'static str)>,
+}
+
+impl InlineFragmentsAccumulator {
+ fn new() -> InlineFragmentsAccumulator {
+ InlineFragmentsAccumulator {
+ fragments: IntermediateInlineFragments::new(),
+ enclosing_node: None,
+ bidi_control_chars: None,
+ restyle_damage: RestyleDamage::empty(),
+ }
+ }
+
+ fn from_inline_node<N>(
+ node: &N,
+ style_context: &SharedStyleContext,
+ ) -> InlineFragmentsAccumulator
+ where
+ N: ThreadSafeLayoutNode,
+ {
+ InlineFragmentsAccumulator {
+ fragments: IntermediateInlineFragments::new(),
+ enclosing_node: Some(InlineFragmentNodeInfo {
+ address: node.opaque(),
+ pseudo: node.get_pseudo_element_type(),
+ style: node.style(style_context),
+ selected_style: node.selected_style(),
+ flags: InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT |
+ InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT,
+ }),
+ bidi_control_chars: None,
+ restyle_damage: node.restyle_damage(),
+ }
+ }
+
+ fn push(&mut self, fragment: Fragment) {
+ self.fragments.fragments.push_back(fragment)
+ }
+
+ fn push_all(&mut self, mut fragments: IntermediateInlineFragments) {
+ self.fragments.fragments.append(&mut fragments.fragments);
+ self.fragments
+ .absolute_descendants
+ .push_descendants(fragments.absolute_descendants);
+ }
+
+ fn to_intermediate_inline_fragments<N>(
+ self,
+ context: &SharedStyleContext,
+ ) -> IntermediateInlineFragments
+ where
+ N: ThreadSafeLayoutNode,
+ {
+ let InlineFragmentsAccumulator {
+ mut fragments,
+ enclosing_node,
+ bidi_control_chars,
+ restyle_damage,
+ } = self;
+ if let Some(mut enclosing_node) = enclosing_node {
+ let fragment_count = fragments.fragments.len();
+ for (index, fragment) in fragments.fragments.iter_mut().enumerate() {
+ let mut enclosing_node = enclosing_node.clone();
+ if index != 0 {
+ enclosing_node
+ .flags
+ .remove(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT)
+ }
+ if index != fragment_count - 1 {
+ enclosing_node
+ .flags
+ .remove(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT)
+ }
+ fragment.add_inline_context_style(enclosing_node);
+ }
+
+ // Control characters are later discarded in transform_text, so they don't affect the
+ // is_first/is_last styles above.
+ enclosing_node.flags.remove(
+ InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT |
+ InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT,
+ );
+
+ if let Some((start, end)) = bidi_control_chars {
+ fragments
+ .fragments
+ .push_front(control_chars_to_fragment::<N::ConcreteElement>(
+ &enclosing_node,
+ context,
+ start,
+ restyle_damage,
+ ));
+ fragments
+ .fragments
+ .push_back(control_chars_to_fragment::<N::ConcreteElement>(
+ &enclosing_node,
+ context,
+ end,
+ restyle_damage,
+ ));
+ }
+ }
+ fragments
+ }
+}
+
+/// An object that knows how to create flows.
+pub struct FlowConstructor<'a, N: ThreadSafeLayoutNode> {
+ /// The layout context.
+ pub layout_context: &'a LayoutContext<'a>,
+ /// Satisfy the compiler about the unused parameters, which we use to improve the ergonomics of
+ /// the ensuing impl {} by removing the need to parameterize all the methods individually.
+ phantom2: PhantomData<N>,
+}
+
+impl<'a, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode>
+ FlowConstructor<'a, ConcreteThreadSafeLayoutNode>
+{
+ /// Creates a new flow constructor.
+ pub fn new(layout_context: &'a LayoutContext<'a>) -> Self {
+ FlowConstructor {
+ layout_context: layout_context,
+ phantom2: PhantomData,
+ }
+ }
+
+ #[inline]
+ fn style_context(&self) -> &SharedStyleContext {
+ self.layout_context.shared_context()
+ }
+
+ #[inline]
+ fn set_flow_construction_result(
+ &self,
+ node: &ConcreteThreadSafeLayoutNode,
+ result: ConstructionResult,
+ ) {
+ node.set_flow_construction_result(result);
+ }
+
+ /// Builds the fragment for the given block or subclass thereof.
+ fn build_fragment_for_block(&self, node: &ConcreteThreadSafeLayoutNode) -> Fragment {
+ let specific_fragment_info = match node.type_id() {
+ Some(LayoutNodeType::Element(LayoutElementType::HTMLIFrameElement)) => {
+ SpecificFragmentInfo::Iframe(IframeFragmentInfo::new(node))
+ },
+ Some(LayoutNodeType::Element(LayoutElementType::HTMLImageElement)) => {
+ let image_info = Box::new(ImageFragmentInfo::new(
+ node.image_url(),
+ node.image_density(),
+ node,
+ &self.layout_context,
+ ));
+ SpecificFragmentInfo::Image(image_info)
+ },
+ Some(LayoutNodeType::Element(LayoutElementType::HTMLMediaElement)) => {
+ let data = node.media_data().unwrap();
+ SpecificFragmentInfo::Media(Box::new(MediaFragmentInfo::new(data)))
+ },
+ Some(LayoutNodeType::Element(LayoutElementType::HTMLObjectElement)) => {
+ let elem = node.as_element().unwrap();
+ let type_and_data = (
+ elem.get_attr(&ns!(), &local_name!("type")),
+ elem.get_attr(&ns!(), &local_name!("data")),
+ );
+ let object_data = match type_and_data {
+ (None, Some(uri)) if is_image_data(uri) => ServoUrl::parse(uri).ok(),
+ _ => None,
+ };
+ let image_info = Box::new(ImageFragmentInfo::new(
+ object_data,
+ None,
+ node,
+ &self.layout_context,
+ ));
+ SpecificFragmentInfo::Image(image_info)
+ },
+ Some(LayoutNodeType::Element(LayoutElementType::HTMLTableElement)) => {
+ SpecificFragmentInfo::TableWrapper
+ },
+ Some(LayoutNodeType::Element(LayoutElementType::HTMLTableColElement)) => {
+ SpecificFragmentInfo::TableColumn(TableColumnFragmentInfo::new(node))
+ },
+ Some(LayoutNodeType::Element(LayoutElementType::HTMLTableCellElement)) => {
+ SpecificFragmentInfo::TableCell
+ },
+ Some(LayoutNodeType::Element(LayoutElementType::HTMLTableRowElement)) |
+ Some(LayoutNodeType::Element(LayoutElementType::HTMLTableSectionElement)) => {
+ SpecificFragmentInfo::TableRow
+ },
+ Some(LayoutNodeType::Element(LayoutElementType::HTMLCanvasElement)) => {
+ let data = node.canvas_data().unwrap();
+ SpecificFragmentInfo::Canvas(Box::new(CanvasFragmentInfo::new(data)))
+ },
+ Some(LayoutNodeType::Element(LayoutElementType::SVGSVGElement)) => {
+ let data = node.svg_data().unwrap();
+ SpecificFragmentInfo::Svg(Box::new(SvgFragmentInfo::new(data)))
+ },
+ _ => {
+ // This includes pseudo-elements.
+ SpecificFragmentInfo::Generic
+ },
+ };
+
+ Fragment::new(node, specific_fragment_info, self.layout_context)
+ }
+
+ /// 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(
+ &mut self,
+ fragment_accumulator: InlineFragmentsAccumulator,
+ flow: &mut FlowRef,
+ absolute_descendants: &mut AbsoluteDescendants,
+ legalizer: &mut Legalizer,
+ node: &ConcreteThreadSafeLayoutNode,
+ ) {
+ let mut fragments = fragment_accumulator
+ .to_intermediate_inline_fragments::<ConcreteThreadSafeLayoutNode>(self.style_context());
+ if fragments.is_empty() {
+ return;
+ };
+
+ strip_ignorable_whitespace_from_start(&mut fragments.fragments);
+ strip_ignorable_whitespace_from_end(&mut fragments.fragments);
+ if fragments.fragments.is_empty() {
+ absolute_descendants.push_descendants(fragments.absolute_descendants);
+ return;
+ }
+
+ // Build a list of all the inline-block fragments before fragments is moved.
+ let mut inline_block_flows = vec![];
+ for fragment in &fragments.fragments {
+ match fragment.specific {
+ SpecificFragmentInfo::InlineBlock(ref info) => {
+ inline_block_flows.push(info.flow_ref.clone())
+ },
+ SpecificFragmentInfo::InlineAbsoluteHypothetical(ref info) => {
+ inline_block_flows.push(info.flow_ref.clone())
+ },
+ SpecificFragmentInfo::InlineAbsolute(ref info) => {
+ inline_block_flows.push(info.flow_ref.clone())
+ },
+ _ => {},
+ }
+ }
+
+ // We must scan for runs before computing minimum ascent and descent because scanning
+ // for runs might collapse so much whitespace away that only hypothetical fragments
+ // remain. In that case the inline flow will compute its ascent and descent to be zero.
+ let scanned_fragments =
+ with_thread_local_font_context(self.layout_context, |font_context| {
+ TextRunScanner::new().scan_for_runs(
+ font_context,
+ mem::replace(&mut fragments.fragments, LinkedList::new()),
+ )
+ });
+ let mut inline_flow_ref = FlowRef::new(Arc::new(InlineFlow::from_fragments(
+ scanned_fragments,
+ node.style(self.style_context()).writing_mode,
+ )));
+
+ // Add all the inline-block fragments as children of the inline flow.
+ for inline_block_flow in &inline_block_flows {
+ inline_flow_ref.add_new_child(inline_block_flow.clone());
+ }
+
+ // Set up absolute descendants as necessary.
+ //
+ // The inline flow itself may need to become the containing block for absolute descendants
+ // in order to handle cases like:
+ //
+ // <div>
+ // <span style="position: relative">
+ // <span style="position: absolute; ..."></span>
+ // </span>
+ // </div>
+ //
+ // See the comment above `flow::AbsoluteDescendantInfo` for more information.
+ inline_flow_ref.take_applicable_absolute_descendants(&mut fragments.absolute_descendants);
+ absolute_descendants.push_descendants(fragments.absolute_descendants);
+
+ {
+ // FIXME(#6503): Use Arc::get_mut().unwrap() here.
+ let inline_flow = FlowRef::deref_mut(&mut inline_flow_ref).as_mut_inline();
+ inline_flow.minimum_line_metrics =
+ with_thread_local_font_context(self.layout_context, |font_context| {
+ inline_flow
+ .minimum_line_metrics(font_context, &node.style(self.style_context()))
+ });
+ }
+
+ inline_flow_ref.finish();
+ legalizer.add_child::<ConcreteThreadSafeLayoutNode::ConcreteElement>(
+ self.style_context(),
+ flow,
+ inline_flow_ref,
+ )
+ }
+
+ fn build_block_flow_using_construction_result_of_child(
+ &mut self,
+ flow: &mut FlowRef,
+ node: &ConcreteThreadSafeLayoutNode,
+ kid: ConcreteThreadSafeLayoutNode,
+ inline_fragment_accumulator: &mut InlineFragmentsAccumulator,
+ abs_descendants: &mut AbsoluteDescendants,
+ legalizer: &mut Legalizer,
+ ) {
+ match kid.get_construction_result() {
+ ConstructionResult::None => {},
+ ConstructionResult::Flow(kid_flow, kid_abs_descendants) => {
+ // If kid_flow is TableCaptionFlow, kid_flow should be added under
+ // TableWrapperFlow.
+ if flow.is_table() && kid_flow.is_table_caption() {
+ let construction_result =
+ ConstructionResult::Flow(kid_flow, AbsoluteDescendants::new());
+ self.set_flow_construction_result(&kid, construction_result)
+ } else {
+ if !kid_flow
+ .base()
+ .flags
+ .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED)
+ {
+ // Flush any inline fragments that we were gathering up. This allows us to
+ // handle {ib} splits.
+ let old_inline_fragment_accumulator = mem::replace(
+ inline_fragment_accumulator,
+ InlineFragmentsAccumulator::new(),
+ );
+ self.flush_inline_fragments_to_flow(
+ old_inline_fragment_accumulator,
+ flow,
+ abs_descendants,
+ legalizer,
+ node,
+ );
+ }
+ legalizer.add_child::<ConcreteThreadSafeLayoutNode::ConcreteElement>(
+ self.style_context(),
+ flow,
+ kid_flow,
+ )
+ }
+ abs_descendants.push_descendants(kid_abs_descendants);
+ },
+ ConstructionResult::ConstructionItem(ConstructionItem::InlineFragments(
+ InlineFragmentsConstructionResult {
+ splits,
+ fragments: successor_fragments,
+ },
+ )) => {
+ // Add any {ib} splits.
+ for split in splits {
+ // Pull apart the {ib} split object and push its predecessor fragments
+ // onto the list.
+ let InlineBlockSplit {
+ predecessors,
+ flow: kid_flow,
+ } = split;
+ inline_fragment_accumulator.push_all(predecessors);
+
+ // Flush any inline fragments that we were gathering up.
+ debug!(
+ "flushing {} inline box(es) to flow A",
+ inline_fragment_accumulator.fragments.fragments.len()
+ );
+ let old_inline_fragment_accumulator = mem::replace(
+ inline_fragment_accumulator,
+ InlineFragmentsAccumulator::new(),
+ );
+ let absolute_descendants =
+ &mut inline_fragment_accumulator.fragments.absolute_descendants;
+ self.flush_inline_fragments_to_flow(
+ old_inline_fragment_accumulator,
+ flow,
+ absolute_descendants,
+ legalizer,
+ node,
+ );
+
+ // Push the flow generated by the {ib} split onto our list of flows.
+ legalizer.add_child::<ConcreteThreadSafeLayoutNode::ConcreteElement>(
+ self.style_context(),
+ flow,
+ kid_flow,
+ )
+ }
+
+ // Add the fragments to the list we're maintaining.
+ inline_fragment_accumulator.push_all(successor_fragments);
+ },
+ ConstructionResult::ConstructionItem(ConstructionItem::Whitespace(
+ whitespace_node,
+ whitespace_pseudo,
+ whitespace_style,
+ whitespace_damage,
+ )) => {
+ // Add whitespace results. They will be stripped out later on when
+ // between block elements, and retained when between inline elements.
+ let fragment_info = SpecificFragmentInfo::UnscannedText(Box::new(
+ UnscannedTextFragmentInfo::new(Box::<str>::from(" "), None),
+ ));
+ let fragment = Fragment::from_opaque_node_and_style(
+ whitespace_node,
+ whitespace_pseudo,
+ whitespace_style,
+ node.selected_style(),
+ whitespace_damage,
+ fragment_info,
+ );
+ inline_fragment_accumulator
+ .fragments
+ .fragments
+ .push_back(fragment);
+ },
+ ConstructionResult::ConstructionItem(ConstructionItem::TableColumnFragment(_)) => {
+ // TODO: Implement anonymous table objects for missing parents
+ // CSS 2.1 § 17.2.1, step 3-2
+ },
+ }
+ }
+
+ /// Constructs a block flow, beginning with the given `initial_fragments` if present and then
+ /// appending the construction results of children to the child list of the block flow. {ib}
+ /// splits and absolutely-positioned descendants are handled correctly.
+ fn build_flow_for_block_starting_with_fragments(
+ &mut self,
+ mut flow: FlowRef,
+ node: &ConcreteThreadSafeLayoutNode,
+ initial_fragments: IntermediateInlineFragments,
+ ) -> ConstructionResult {
+ // Gather up fragments for the inline flows we might need to create.
+ let mut inline_fragment_accumulator = InlineFragmentsAccumulator::new();
+
+ inline_fragment_accumulator
+ .fragments
+ .push_all(initial_fragments);
+
+ // List of absolute descendants, in tree order.
+ let mut abs_descendants = AbsoluteDescendants::new();
+ let mut legalizer = Legalizer::new();
+ let is_media_element_with_widget = node.type_id() ==
+ Some(LayoutNodeType::Element(LayoutElementType::HTMLMediaElement)) &&
+ node.as_element().unwrap().is_shadow_host();
+ if !node.is_replaced_content() || is_media_element_with_widget {
+ for kid in node.children() {
+ if kid.get_pseudo_element_type() != PseudoElementType::Normal {
+ if node.is_replaced_content() {
+ // Replaced elements don't have pseudo-elements per spec.
+ continue;
+ }
+ self.process(&kid);
+ }
+
+ self.build_block_flow_using_construction_result_of_child(
+ &mut flow,
+ node,
+ kid,
+ &mut inline_fragment_accumulator,
+ &mut abs_descendants,
+ &mut legalizer,
+ );
+ }
+ }
+
+ // 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(
+ inline_fragment_accumulator,
+ &mut flow,
+ &mut abs_descendants,
+ &mut legalizer,
+ node,
+ );
+
+ // The flow is done.
+ legalizer.finish(&mut flow);
+ flow.finish();
+
+ // Set up the absolute descendants.
+ if flow.is_absolute_containing_block() {
+ // This is the containing block for all the absolute descendants.
+ flow.set_absolute_descendants(abs_descendants);
+
+ abs_descendants = AbsoluteDescendants::new();
+ if flow
+ .base()
+ .flags
+ .contains(FlowFlags::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());
+ }
+ }
+ ConstructionResult::Flow(flow, abs_descendants)
+ }
+
+ /// Constructs a flow for the given block node and its children. This method creates an
+ /// initial fragment as appropriate and then dispatches to
+ /// `build_flow_for_block_starting_with_fragments`. Currently the following kinds of flows get
+ /// initial content:
+ ///
+ /// * Generated content gets the initial content specified by the `content` attribute of the
+ /// CSS.
+ /// * `<input>` and `<textarea>` elements get their content.
+ ///
+ /// FIXME(pcwalton): It is not clear to me that there isn't a cleaner way to handle
+ /// `<textarea>`.
+ fn build_flow_for_block_like(
+ &mut self,
+ flow: FlowRef,
+ node: &ConcreteThreadSafeLayoutNode,
+ ) -> ConstructionResult {
+ let mut fragments = IntermediateInlineFragments::new();
+ let node_is_input_or_text_area = node.type_id() ==
+ Some(LayoutNodeType::Element(LayoutElementType::HTMLInputElement)) ||
+ node.type_id() ==
+ Some(LayoutNodeType::Element(
+ LayoutElementType::HTMLTextAreaElement,
+ ));
+ if node.get_pseudo_element_type().is_replaced_content() || node_is_input_or_text_area {
+ // A TextArea's text contents are displayed through the input text
+ // box, so don't construct them.
+ if node.type_id() ==
+ Some(LayoutNodeType::Element(
+ LayoutElementType::HTMLTextAreaElement,
+ ))
+ {
+ for kid in node.children() {
+ self.set_flow_construction_result(&kid, ConstructionResult::None)
+ }
+ }
+
+ let context = self.style_context();
+ let mut style = node.style(context);
+ style = context
+ .stylist
+ .style_for_anonymous::<ConcreteThreadSafeLayoutNode::ConcreteElement>(
+ &context.guards,
+ &PseudoElement::ServoText,
+ &style,
+ );
+ if node_is_input_or_text_area {
+ style = context
+ .stylist
+ .style_for_anonymous::<ConcreteThreadSafeLayoutNode::ConcreteElement>(
+ &context.guards,
+ &PseudoElement::ServoInputText,
+ &style,
+ )
+ }
+
+ self.create_fragments_for_node_text_content(&mut fragments, node, &style)
+ }
+ self.build_flow_for_block_starting_with_fragments(flow, node, fragments)
+ }
+
+ /// Pushes fragments appropriate for the content of the given node onto the given list.
+ fn create_fragments_for_node_text_content(
+ &self,
+ fragments: &mut IntermediateInlineFragments,
+ node: &ConcreteThreadSafeLayoutNode,
+ style: &ServoArc<ComputedValues>,
+ ) {
+ // Fast path: If there is no text content, return immediately.
+ let text_content = node.text_content();
+ if text_content.is_empty() {
+ return;
+ }
+
+ let style = (*style).clone();
+ let selected_style = node.selected_style();
+
+ match text_content {
+ TextContent::Text(string) => {
+ let info = Box::new(UnscannedTextFragmentInfo::new(string, node.selection()));
+ let specific_fragment_info = SpecificFragmentInfo::UnscannedText(info);
+ fragments
+ .fragments
+ .push_back(Fragment::from_opaque_node_and_style(
+ node.opaque(),
+ node.get_pseudo_element_type(),
+ style,
+ selected_style,
+ node.restyle_damage(),
+ specific_fragment_info,
+ ))
+ },
+ TextContent::GeneratedContent(content_items) => {
+ for content_item in content_items.into_iter() {
+ let specific_fragment_info = match content_item {
+ ContentItem::String(string) => {
+ let info = Box::new(UnscannedTextFragmentInfo::new(string, None));
+ SpecificFragmentInfo::UnscannedText(info)
+ },
+ content_item => {
+ let content_item =
+ Box::new(GeneratedContentInfo::ContentItem(content_item));
+ SpecificFragmentInfo::GeneratedContent(content_item)
+ },
+ };
+ fragments
+ .fragments
+ .push_back(Fragment::from_opaque_node_and_style(
+ node.opaque(),
+ node.get_pseudo_element_type(),
+ style.clone(),
+ selected_style.clone(),
+ node.restyle_damage(),
+ specific_fragment_info,
+ ))
+ }
+ },
+ }
+ }
+
+ /// 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: &ConcreteThreadSafeLayoutNode,
+ float_kind: Option<FloatKind>,
+ ) -> ConstructionResult {
+ if node.style(self.style_context()).is_multicol() {
+ return self.build_flow_for_multicol(node, float_kind);
+ }
+
+ let fragment = self.build_fragment_for_block(node);
+ let flow = FlowRef::new(Arc::new(BlockFlow::from_fragment_and_float_kind(
+ fragment, float_kind,
+ )));
+ self.build_flow_for_block_like(flow, node)
+ }
+
+ /// Bubbles up {ib} splits.
+ fn accumulate_inline_block_splits(
+ &mut self,
+ splits: LinkedList<InlineBlockSplit>,
+ node: &ConcreteThreadSafeLayoutNode,
+ fragment_accumulator: &mut InlineFragmentsAccumulator,
+ opt_inline_block_splits: &mut LinkedList<InlineBlockSplit>,
+ ) {
+ for split in splits {
+ let InlineBlockSplit {
+ predecessors,
+ flow: kid_flow,
+ } = split;
+ fragment_accumulator.push_all(predecessors);
+
+ opt_inline_block_splits.push_back(InlineBlockSplit::new(
+ fragment_accumulator,
+ node,
+ self.style_context(),
+ kid_flow,
+ ));
+ }
+ }
+
+ /// 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: &ConcreteThreadSafeLayoutNode,
+ ) -> ConstructionResult {
+ let mut opt_inline_block_splits: LinkedList<InlineBlockSplit> = LinkedList::new();
+ let mut fragment_accumulator =
+ InlineFragmentsAccumulator::from_inline_node(node, self.style_context());
+ fragment_accumulator.bidi_control_chars =
+ bidi_control_chars(&node.style(self.style_context()));
+
+ let mut abs_descendants = AbsoluteDescendants::new();
+
+ // Concatenate all the fragments of our kids, creating {ib} splits as necessary.
+ let mut is_empty = true;
+ for kid in node.children() {
+ is_empty = false;
+ if kid.get_pseudo_element_type() != PseudoElementType::Normal {
+ self.process(&kid);
+ }
+ match kid.get_construction_result() {
+ ConstructionResult::None => {},
+ ConstructionResult::Flow(flow, kid_abs_descendants) => {
+ if !flow
+ .base()
+ .flags
+ .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED)
+ {
+ opt_inline_block_splits.push_back(InlineBlockSplit::new(
+ &mut fragment_accumulator,
+ node,
+ self.style_context(),
+ flow,
+ ));
+ abs_descendants.push_descendants(kid_abs_descendants);
+ } else {
+ // Push the absolutely-positioned kid as an inline containing block.
+ let kid_node = flow.as_block().fragment.node;
+ let kid_pseudo = flow.as_block().fragment.pseudo.clone();
+ let kid_style = flow.as_block().fragment.style.clone();
+ let kid_selected_style = flow.as_block().fragment.selected_style.clone();
+ let kid_restyle_damage = flow.as_block().fragment.restyle_damage;
+ let fragment_info = SpecificFragmentInfo::InlineAbsolute(
+ InlineAbsoluteFragmentInfo::new(flow),
+ );
+ fragment_accumulator.push(Fragment::from_opaque_node_and_style(
+ kid_node,
+ kid_pseudo,
+ kid_style,
+ kid_selected_style,
+ kid_restyle_damage,
+ fragment_info,
+ ));
+ fragment_accumulator
+ .fragments
+ .absolute_descendants
+ .push_descendants(kid_abs_descendants);
+ }
+ },
+ ConstructionResult::ConstructionItem(ConstructionItem::InlineFragments(
+ InlineFragmentsConstructionResult {
+ splits,
+ fragments: successors,
+ },
+ )) => {
+ // Bubble up {ib} splits.
+ self.accumulate_inline_block_splits(
+ splits,
+ node,
+ &mut fragment_accumulator,
+ &mut opt_inline_block_splits,
+ );
+
+ // Push residual fragments.
+ fragment_accumulator.push_all(successors);
+ },
+ ConstructionResult::ConstructionItem(ConstructionItem::Whitespace(
+ whitespace_node,
+ whitespace_pseudo,
+ whitespace_style,
+ whitespace_damage,
+ )) => {
+ // Instantiate the whitespace fragment.
+ let fragment_info = SpecificFragmentInfo::UnscannedText(Box::new(
+ UnscannedTextFragmentInfo::new(Box::<str>::from(" "), None),
+ ));
+ let fragment = Fragment::from_opaque_node_and_style(
+ whitespace_node,
+ whitespace_pseudo,
+ whitespace_style,
+ node.selected_style(),
+ whitespace_damage,
+ fragment_info,
+ );
+ fragment_accumulator.fragments.fragments.push_back(fragment)
+ },
+ ConstructionResult::ConstructionItem(ConstructionItem::TableColumnFragment(_)) => {
+ // TODO: Implement anonymous table objects for missing parents
+ // CSS 2.1 § 17.2.1, step 3-2
+ },
+ }
+ }
+
+ let node_style = node.style(self.style_context());
+ if is_empty && has_padding_or_border(&node_style) {
+ // An empty inline box needs at least one fragment to draw its background and borders.
+ let info = SpecificFragmentInfo::UnscannedText(Box::new(
+ UnscannedTextFragmentInfo::new(Box::<str>::from(""), None),
+ ));
+ let fragment = Fragment::from_opaque_node_and_style(
+ node.opaque(),
+ node.get_pseudo_element_type(),
+ node_style.clone(),
+ node.selected_style(),
+ node.restyle_damage(),
+ info,
+ );
+ fragment_accumulator.fragments.fragments.push_back(fragment)
+ }
+
+ // Finally, make a new construction result.
+ if opt_inline_block_splits.len() > 0 ||
+ !fragment_accumulator.fragments.is_empty() ||
+ abs_descendants.len() > 0
+ {
+ fragment_accumulator
+ .fragments
+ .absolute_descendants
+ .push_descendants(abs_descendants);
+
+ // If the node is positioned, then it's the containing block for all absolutely-
+ // positioned descendants.
+ if node_style.get_box().position != Position::Static {
+ fragment_accumulator
+ .fragments
+ .absolute_descendants
+ .mark_as_having_reached_containing_block();
+ }
+
+ let construction_item =
+ ConstructionItem::InlineFragments(InlineFragmentsConstructionResult {
+ splits: opt_inline_block_splits,
+ fragments: fragment_accumulator
+ .to_intermediate_inline_fragments::<ConcreteThreadSafeLayoutNode>(
+ self.style_context(),
+ ),
+ });
+ ConstructionResult::ConstructionItem(construction_item)
+ } else {
+ ConstructionResult::None
+ }
+ }
+
+ /// 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: &ConcreteThreadSafeLayoutNode,
+ ) -> ConstructionResult {
+ for kid in node.children() {
+ self.set_flow_construction_result(&kid, ConstructionResult::None)
+ }
+
+ let context = self.style_context();
+ let style = node.style(context);
+ // If this node is ignorable whitespace, bail out now.
+ if node.is_ignorable_whitespace(context) {
+ return ConstructionResult::ConstructionItem(ConstructionItem::Whitespace(
+ node.opaque(),
+ node.get_pseudo_element_type(),
+ context
+ .stylist
+ .style_for_anonymous::<ConcreteThreadSafeLayoutNode::ConcreteElement>(
+ &context.guards,
+ &PseudoElement::ServoText,
+ &style,
+ ),
+ node.restyle_damage(),
+ ));
+ }
+
+ // If this is generated content, then we need to initialize the accumulator with the
+ // fragment corresponding to that content. Otherwise, just initialize with the ordinary
+ // fragment that needs to be generated for this inline node.
+ let mut fragments = IntermediateInlineFragments::new();
+ match (node.get_pseudo_element_type(), node.type_id()) {
+ (_, Some(LayoutNodeType::Text)) => {
+ let text_style = context
+ .stylist
+ .style_for_anonymous::<ConcreteThreadSafeLayoutNode::ConcreteElement>(
+ &context.guards,
+ &PseudoElement::ServoText,
+ &style,
+ );
+ self.create_fragments_for_node_text_content(&mut fragments, node, &text_style)
+ },
+ (PseudoElementType::Normal, _) => {
+ fragments
+ .fragments
+ .push_back(self.build_fragment_for_block(node));
+ },
+ (_, _) => self.create_fragments_for_node_text_content(&mut fragments, node, &style),
+ }
+
+ let construction_item =
+ ConstructionItem::InlineFragments(InlineFragmentsConstructionResult {
+ splits: LinkedList::new(),
+ fragments: fragments,
+ });
+ ConstructionResult::ConstructionItem(construction_item)
+ }
+
+ /// Build the fragment for an inline-block or inline-flex, based on the `display` flag
+ fn build_fragment_for_inline_block_or_inline_flex(
+ &mut self,
+ node: &ConcreteThreadSafeLayoutNode,
+ display: Display,
+ ) -> ConstructionResult {
+ let block_flow_result = match display {
+ Display::InlineBlock => self.build_flow_for_block(node, None),
+ Display::InlineFlex => self.build_flow_for_flex(node, None),
+ _ => panic!("The flag should be inline-block or inline-flex"),
+ };
+ let (block_flow, abs_descendants) = match block_flow_result {
+ ConstructionResult::Flow(block_flow, abs_descendants) => (block_flow, abs_descendants),
+ _ => unreachable!(),
+ };
+
+ let context = self.style_context();
+ let style = node.style(context);
+ let style = context
+ .stylist
+ .style_for_anonymous::<ConcreteThreadSafeLayoutNode::ConcreteElement>(
+ &context.guards,
+ &PseudoElement::ServoInlineBlockWrapper,
+ &style,
+ );
+ let fragment_info =
+ SpecificFragmentInfo::InlineBlock(InlineBlockFragmentInfo::new(block_flow));
+ let fragment = Fragment::from_opaque_node_and_style(
+ node.opaque(),
+ node.get_pseudo_element_type(),
+ style,
+ node.selected_style(),
+ node.restyle_damage(),
+ fragment_info,
+ );
+
+ let mut fragment_accumulator = InlineFragmentsAccumulator::new();
+ fragment_accumulator.fragments.fragments.push_back(fragment);
+ fragment_accumulator
+ .fragments
+ .absolute_descendants
+ .push_descendants(abs_descendants);
+
+ let construction_item =
+ ConstructionItem::InlineFragments(InlineFragmentsConstructionResult {
+ splits: LinkedList::new(),
+ fragments: fragment_accumulator
+ .to_intermediate_inline_fragments::<ConcreteThreadSafeLayoutNode>(context),
+ });
+ ConstructionResult::ConstructionItem(construction_item)
+ }
+
+ /// This is an annoying case, because the computed `display` value is `block`, but the
+ /// hypothetical box is inline.
+ fn build_fragment_for_absolutely_positioned_inline(
+ &mut self,
+ node: &ConcreteThreadSafeLayoutNode,
+ ) -> ConstructionResult {
+ let block_flow_result = self.build_flow_for_block(node, None);
+ let (block_flow, abs_descendants) = match block_flow_result {
+ ConstructionResult::Flow(block_flow, abs_descendants) => (block_flow, abs_descendants),
+ _ => unreachable!(),
+ };
+
+ let fragment_info = SpecificFragmentInfo::InlineAbsoluteHypothetical(
+ InlineAbsoluteHypotheticalFragmentInfo::new(block_flow),
+ );
+ let style_context = self.style_context();
+ let style = node.style(style_context);
+ let style = style_context
+ .stylist
+ .style_for_anonymous::<ConcreteThreadSafeLayoutNode::ConcreteElement>(
+ &style_context.guards,
+ &PseudoElement::ServoInlineAbsolute,
+ &style,
+ );
+ let fragment = Fragment::from_opaque_node_and_style(
+ node.opaque(),
+ PseudoElementType::Normal,
+ style,
+ node.selected_style(),
+ node.restyle_damage(),
+ fragment_info,
+ );
+
+ let mut fragment_accumulator =
+ InlineFragmentsAccumulator::from_inline_node(node, self.style_context());
+ fragment_accumulator.fragments.fragments.push_back(fragment);
+ fragment_accumulator
+ .fragments
+ .absolute_descendants
+ .push_descendants(abs_descendants);
+
+ let construction_item =
+ ConstructionItem::InlineFragments(InlineFragmentsConstructionResult {
+ splits: LinkedList::new(),
+ fragments: fragment_accumulator
+ .to_intermediate_inline_fragments::<ConcreteThreadSafeLayoutNode>(
+ style_context,
+ ),
+ });
+ ConstructionResult::ConstructionItem(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: &ConcreteThreadSafeLayoutNode,
+ ) -> 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)
+ }
+ }
+
+ /// Places any table captions found under the given table wrapper, if the value of their
+ /// `caption-side` property is equal to the given `side`.
+ fn place_table_caption_under_table_wrapper_on_side(
+ &mut self,
+ table_wrapper_flow: &mut FlowRef,
+ node: &ConcreteThreadSafeLayoutNode,
+ side: CaptionSide,
+ ) {
+ // Only flows that are table captions are matched here.
+ for kid in node.children() {
+ match kid.get_construction_result() {
+ ConstructionResult::Flow(kid_flow, _) => {
+ if kid_flow.is_table_caption() &&
+ kid_flow
+ .as_block()
+ .fragment
+ .style()
+ .get_inherited_table()
+ .caption_side ==
+ side
+ {
+ table_wrapper_flow.add_new_child(kid_flow);
+ }
+ },
+ ConstructionResult::None | ConstructionResult::ConstructionItem(_) => {},
+ }
+ }
+ }
+
+ /// Builds a flow for a node with `column-count` or `column-width` non-`auto`.
+ /// This yields a `MulticolFlow` with a single `MulticolColumnFlow` underneath it.
+ fn build_flow_for_multicol(
+ &mut self,
+ node: &ConcreteThreadSafeLayoutNode,
+ float_kind: Option<FloatKind>,
+ ) -> ConstructionResult {
+ let fragment = Fragment::new(node, SpecificFragmentInfo::Multicol, self.layout_context);
+ let mut flow = FlowRef::new(Arc::new(MulticolFlow::from_fragment(fragment, float_kind)));
+
+ let column_fragment = Fragment::new(
+ node,
+ SpecificFragmentInfo::MulticolColumn,
+ self.layout_context,
+ );
+ let column_flow =
+ FlowRef::new(Arc::new(MulticolColumnFlow::from_fragment(column_fragment)));
+
+ // First populate the column flow with its children.
+ let construction_result = self.build_flow_for_block_like(column_flow, node);
+
+ let mut abs_descendants = AbsoluteDescendants::new();
+
+ if let ConstructionResult::Flow(column_flow, column_abs_descendants) = construction_result {
+ flow.add_new_child(column_flow);
+ abs_descendants.push_descendants(column_abs_descendants);
+ }
+
+ // The flow is done.
+ flow.finish();
+ if flow.is_absolute_containing_block() {
+ // This is the containing block for all the absolute descendants.
+ flow.set_absolute_descendants(abs_descendants);
+
+ abs_descendants = AbsoluteDescendants::new();
+
+ if flow
+ .base()
+ .flags
+ .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED)
+ {
+ // This is now the only absolute flow in the subtree which hasn't yet
+ // reached its containing block.
+ abs_descendants.push(flow.clone());
+ }
+ }
+
+ ConstructionResult::Flow(flow, abs_descendants)
+ }
+
+ /// Builds a flow for a node with `display: table`. This yields a `TableWrapperFlow` with
+ /// possibly other `TableCaptionFlow`s or `TableFlow`s underneath it.
+ fn build_flow_for_table(
+ &mut self,
+ node: &ConcreteThreadSafeLayoutNode,
+ float_value: Float,
+ ) -> ConstructionResult {
+ let mut legalizer = Legalizer::new();
+
+ let table_style;
+ let wrapper_style;
+ {
+ let context = self.style_context();
+ table_style = node.style(context);
+ wrapper_style = context
+ .stylist
+ .style_for_anonymous::<ConcreteThreadSafeLayoutNode::ConcreteElement>(
+ &context.guards,
+ &PseudoElement::ServoTableWrapper,
+ &table_style,
+ );
+ }
+ let wrapper_fragment = Fragment::from_opaque_node_and_style(
+ node.opaque(),
+ PseudoElementType::Normal,
+ wrapper_style,
+ node.selected_style(),
+ node.restyle_damage(),
+ SpecificFragmentInfo::TableWrapper,
+ );
+ let wrapper_float_kind = FloatKind::from_property(float_value);
+ let mut wrapper_flow = FlowRef::new(Arc::new(
+ TableWrapperFlow::from_fragment_and_float_kind(wrapper_fragment, wrapper_float_kind),
+ ));
+
+ let table_fragment = Fragment::new(node, SpecificFragmentInfo::Table, self.layout_context);
+ let table_flow = FlowRef::new(Arc::new(TableFlow::from_fragment(table_fragment)));
+
+ // First populate the table flow with its children.
+ let construction_result = self.build_flow_for_block_like(table_flow, node);
+
+ let mut abs_descendants = AbsoluteDescendants::new();
+
+ // The order of the caption and the table are not necessarily the same order as in the DOM
+ // tree. All caption blocks are placed before or after the table flow, depending on the
+ // value of `caption-side`.
+ self.place_table_caption_under_table_wrapper_on_side(
+ &mut wrapper_flow,
+ node,
+ CaptionSide::Top,
+ );
+
+ if let ConstructionResult::Flow(table_flow, table_abs_descendants) = construction_result {
+ legalizer.add_child::<ConcreteThreadSafeLayoutNode::ConcreteElement>(
+ self.style_context(),
+ &mut wrapper_flow,
+ table_flow,
+ );
+ abs_descendants.push_descendants(table_abs_descendants);
+ }
+
+ // If the value of `caption-side` is `bottom`, place it now.
+ self.place_table_caption_under_table_wrapper_on_side(
+ &mut wrapper_flow,
+ node,
+ CaptionSide::Bottom,
+ );
+
+ // The flow is done.
+ legalizer.finish(&mut wrapper_flow);
+ wrapper_flow.finish();
+
+ if wrapper_flow.is_absolute_containing_block() {
+ // This is the containing block for all the absolute descendants.
+ wrapper_flow.set_absolute_descendants(abs_descendants);
+
+ abs_descendants = AbsoluteDescendants::new();
+
+ if wrapper_flow
+ .base()
+ .flags
+ .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED)
+ {
+ // This is now the only absolute flow in the subtree which hasn't yet
+ // reached its containing block.
+ abs_descendants.push(wrapper_flow.clone());
+ }
+ }
+
+ ConstructionResult::Flow(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: &ConcreteThreadSafeLayoutNode,
+ ) -> ConstructionResult {
+ let fragment = self.build_fragment_for_block(node);
+ let flow = FlowRef::new(Arc::new(TableCaptionFlow::from_fragment(fragment)));
+ self.build_flow_for_block_like(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: &ConcreteThreadSafeLayoutNode,
+ ) -> ConstructionResult {
+ let fragment = Fragment::new(node, SpecificFragmentInfo::TableRow, self.layout_context);
+ let flow = FlowRef::new(Arc::new(TableRowGroupFlow::from_fragment(fragment)));
+ self.build_flow_for_block_like(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: &ConcreteThreadSafeLayoutNode,
+ ) -> ConstructionResult {
+ let fragment = Fragment::new(node, SpecificFragmentInfo::TableRow, self.layout_context);
+ let flow = FlowRef::new(Arc::new(TableRowFlow::from_fragment(fragment)));
+ self.build_flow_for_block_like(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: &ConcreteThreadSafeLayoutNode,
+ ) -> ConstructionResult {
+ let fragment = Fragment::new(node, SpecificFragmentInfo::TableCell, self.layout_context);
+
+ // Determine if the table cell should be hidden. Per CSS 2.1 § 17.6.1.1, this will be true
+ // if the cell has any in-flow elements (even empty ones!) and has `empty-cells` set to
+ // `hide`.
+ let hide = node
+ .style(self.style_context())
+ .get_inherited_table()
+ .empty_cells ==
+ EmptyCells::Hide &&
+ node.children().all(|kid| {
+ let position = kid.style(self.style_context()).get_box().position;
+ !kid.is_content() || position == Position::Absolute || position == Position::Fixed
+ });
+
+ let flow = FlowRef::new(Arc::new(
+ TableCellFlow::from_node_fragment_and_visibility_flag(node, fragment, !hide),
+ ));
+ self.build_flow_for_block_like(flow, node)
+ }
+
+ /// Builds a flow for a node with `display: list-item`. This yields a `ListItemFlow` with
+ /// possibly other `BlockFlow`s or `InlineFlow`s underneath it.
+ fn build_flow_for_list_item(
+ &mut self,
+ node: &ConcreteThreadSafeLayoutNode,
+ flotation: Float,
+ ) -> ConstructionResult {
+ let flotation = FloatKind::from_property(flotation);
+ let marker_fragments = match node.style(self.style_context()).get_list().list_style_image {
+ ImageUrlOrNone::Url(ref url_value) => {
+ let image_info = Box::new(ImageFragmentInfo::new(
+ url_value.url().map(|u| u.clone()),
+ None,
+ node,
+ &self.layout_context,
+ ));
+ vec![Fragment::new(
+ node,
+ SpecificFragmentInfo::Image(image_info),
+ self.layout_context,
+ )]
+ },
+ ImageUrlOrNone::None => match ListStyleTypeContent::from_list_style_type(
+ node.style(self.style_context()).get_list().list_style_type,
+ ) {
+ ListStyleTypeContent::None => Vec::new(),
+ ListStyleTypeContent::StaticText(ch) => {
+ let text = format!("{}\u{a0}", ch);
+ let mut unscanned_marker_fragments = LinkedList::new();
+ unscanned_marker_fragments.push_back(Fragment::new(
+ node,
+ SpecificFragmentInfo::UnscannedText(Box::new(
+ UnscannedTextFragmentInfo::new(Box::<str>::from(text), None),
+ )),
+ self.layout_context,
+ ));
+ let marker_fragments =
+ with_thread_local_font_context(self.layout_context, |mut font_context| {
+ TextRunScanner::new()
+ .scan_for_runs(&mut font_context, unscanned_marker_fragments)
+ });
+ marker_fragments.fragments
+ },
+ ListStyleTypeContent::GeneratedContent(info) => vec![Fragment::new(
+ node,
+ SpecificFragmentInfo::GeneratedContent(info),
+ self.layout_context,
+ )],
+ },
+ };
+
+ // If the list marker is outside, it becomes the special "outside fragment" that list item
+ // flows have. If it's inside, it's just a plain old fragment. Note that this means that
+ // we adopt Gecko's behavior rather than WebKit's when the marker causes an {ib} split,
+ // which has caused some malaise (Bugzilla #36854) but CSS 2.1 § 12.5.1 lets me do it, so
+ // there.
+ let mut initial_fragments = IntermediateInlineFragments::new();
+ let main_fragment = self.build_fragment_for_block(node);
+ let flow = match node
+ .style(self.style_context())
+ .get_list()
+ .list_style_position
+ {
+ ListStylePosition::Outside => Arc::new(ListItemFlow::from_fragments_and_flotation(
+ main_fragment,
+ marker_fragments,
+ flotation,
+ )),
+ ListStylePosition::Inside => {
+ for marker_fragment in marker_fragments {
+ initial_fragments.fragments.push_back(marker_fragment)
+ }
+ Arc::new(ListItemFlow::from_fragments_and_flotation(
+ main_fragment,
+ vec![],
+ flotation,
+ ))
+ },
+ };
+
+ self.build_flow_for_block_starting_with_fragments(
+ FlowRef::new(flow),
+ node,
+ initial_fragments,
+ )
+ }
+
+ /// Creates a fragment for a node with `display: table-column`.
+ fn build_fragments_for_table_column(
+ &mut self,
+ node: &ConcreteThreadSafeLayoutNode,
+ ) -> ConstructionResult {
+ // CSS 2.1 § 17.2.1. Treat all child fragments of a `table-column` as `display: none`.
+ for kid in node.children() {
+ self.set_flow_construction_result(&kid, ConstructionResult::None)
+ }
+
+ let specific = SpecificFragmentInfo::TableColumn(TableColumnFragmentInfo::new(node));
+ let construction_item = ConstructionItem::TableColumnFragment(Fragment::new(
+ node,
+ specific,
+ self.layout_context,
+ ));
+ ConstructionResult::ConstructionItem(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: &ConcreteThreadSafeLayoutNode,
+ ) -> ConstructionResult {
+ let fragment = Fragment::new(
+ node,
+ SpecificFragmentInfo::TableColumn(TableColumnFragmentInfo::new(node)),
+ self.layout_context,
+ );
+ 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`.
+ if let ConstructionResult::ConstructionItem(ConstructionItem::TableColumnFragment(
+ fragment,
+ )) = kid.get_construction_result()
+ {
+ col_fragments.push(fragment)
+ }
+ }
+ if col_fragments.is_empty() {
+ debug!("add SpecificFragmentInfo::TableColumn for empty colgroup");
+ let specific = SpecificFragmentInfo::TableColumn(TableColumnFragmentInfo::new(node));
+ col_fragments.push(Fragment::new(node, specific, self.layout_context));
+ }
+ let mut flow = FlowRef::new(Arc::new(TableColGroupFlow::from_fragments(
+ fragment,
+ col_fragments,
+ )));
+ flow.finish();
+
+ ConstructionResult::Flow(flow, AbsoluteDescendants::new())
+ }
+
+ /// Builds a flow for a node with 'display: flex'.
+ fn build_flow_for_flex(
+ &mut self,
+ node: &ConcreteThreadSafeLayoutNode,
+ float_kind: Option<FloatKind>,
+ ) -> ConstructionResult {
+ let fragment = self.build_fragment_for_block(node);
+ let flow = FlowRef::new(Arc::new(FlexFlow::from_fragment(fragment, float_kind)));
+ self.build_flow_for_block_like(flow, node)
+ }
+
+ /// Attempts to perform incremental repair to account for recent changes to this node. This
+ /// can fail and return false, indicating that flows will need to be reconstructed.
+ ///
+ /// TODO(pcwalton): Add some more fast paths, like toggling `display: none`, adding block kids
+ /// to block parents with no {ib} splits, adding out-of-flow kids, etc.
+ pub fn repair_if_possible(&mut self, node: &ConcreteThreadSafeLayoutNode) -> bool {
+ // We can skip reconstructing the flow if we don't have to reconstruct and none of our kids
+ // did either.
+ //
+ // We visit the kids first and reset their HAS_NEWLY_CONSTRUCTED_FLOW flags after checking
+ // them. NOTE: Make sure not to bail out early before resetting all the flags!
+ let mut need_to_reconstruct = false;
+
+ // If the node has display: none, it's possible that we haven't even
+ // styled the children once, so we need to bailout early here.
+ if node.style(self.style_context()).get_box().clone_display() == Display::None {
+ return false;
+ }
+
+ for kid in node.children() {
+ if kid
+ .flags()
+ .contains(LayoutDataFlags::HAS_NEWLY_CONSTRUCTED_FLOW)
+ {
+ kid.remove_flags(LayoutDataFlags::HAS_NEWLY_CONSTRUCTED_FLOW);
+ need_to_reconstruct = true
+ }
+ }
+
+ if need_to_reconstruct {
+ return false;
+ }
+
+ if node
+ .restyle_damage()
+ .contains(ServoRestyleDamage::RECONSTRUCT_FLOW)
+ {
+ return false;
+ }
+
+ let mut set_has_newly_constructed_flow_flag = false;
+ let result = {
+ let style = node.style(self.style_context());
+
+ if style.can_be_fragmented() || style.is_multicol() {
+ return false;
+ }
+
+ let damage = node.restyle_damage();
+ let mut data = node.mutate_layout_data().unwrap();
+
+ match *node.construction_result_mut(&mut *data) {
+ ConstructionResult::None => true,
+ ConstructionResult::Flow(ref mut flow, _) => {
+ // The node's flow is of the same type and has the same set of children and can
+ // therefore be repaired by simply propagating damage and style to the flow.
+ if !flow.is_block_flow() {
+ return false;
+ }
+
+ let flow = FlowRef::deref_mut(flow);
+ flow.mut_base().restyle_damage.insert(damage);
+ flow.repair_style_and_bubble_inline_sizes(&style);
+ true
+ },
+ ConstructionResult::ConstructionItem(ConstructionItem::InlineFragments(
+ ref mut inline_fragments_construction_result,
+ )) => {
+ if !inline_fragments_construction_result.splits.is_empty() {
+ return false;
+ }
+
+ for fragment in inline_fragments_construction_result
+ .fragments
+ .fragments
+ .iter_mut()
+ {
+ // Only mutate the styles of fragments that represent the dirty node (including
+ // pseudo-element).
+ if fragment.node != node.opaque() {
+ continue;
+ }
+ if fragment.pseudo != node.get_pseudo_element_type() {
+ continue;
+ }
+
+ match fragment.specific {
+ SpecificFragmentInfo::InlineBlock(ref mut inline_block_fragment) => {
+ let flow_ref =
+ FlowRef::deref_mut(&mut inline_block_fragment.flow_ref);
+ flow_ref.mut_base().restyle_damage.insert(damage);
+ // FIXME(pcwalton): Fragment restyle damage too?
+ flow_ref.repair_style_and_bubble_inline_sizes(&style);
+ },
+ SpecificFragmentInfo::InlineAbsoluteHypothetical(
+ ref mut inline_absolute_hypothetical_fragment,
+ ) => {
+ let flow_ref = FlowRef::deref_mut(
+ &mut inline_absolute_hypothetical_fragment.flow_ref,
+ );
+ flow_ref.mut_base().restyle_damage.insert(damage);
+ // FIXME(pcwalton): Fragment restyle damage too?
+ flow_ref.repair_style_and_bubble_inline_sizes(&style);
+ },
+ SpecificFragmentInfo::InlineAbsolute(
+ ref mut inline_absolute_fragment,
+ ) => {
+ let flow_ref =
+ FlowRef::deref_mut(&mut inline_absolute_fragment.flow_ref);
+ flow_ref.mut_base().restyle_damage.insert(damage);
+ // FIXME(pcwalton): Fragment restyle damage too?
+ flow_ref.repair_style_and_bubble_inline_sizes(&style);
+ },
+ SpecificFragmentInfo::ScannedText(_) => {
+ // Text fragments in ConstructionResult haven't been scanned yet
+ unreachable!()
+ },
+ SpecificFragmentInfo::GeneratedContent(_) |
+ SpecificFragmentInfo::UnscannedText(_) => {
+ // We can't repair this unscanned text; we need to update the
+ // scanned text fragments.
+ //
+ // TODO: Add code to find and repair the ScannedText fragments?
+ return false;
+ },
+ _ => {
+ fragment.repair_style(&style);
+ set_has_newly_constructed_flow_flag = true;
+ },
+ }
+ }
+ true
+ },
+ ConstructionResult::ConstructionItem(_) => false,
+ }
+ };
+ if set_has_newly_constructed_flow_flag {
+ node.insert_flags(LayoutDataFlags::HAS_NEWLY_CONSTRUCTED_FLOW);
+ }
+ return result;
+ }
+}
+
+impl<'a, ConcreteThreadSafeLayoutNode> PostorderNodeMutTraversal<ConcreteThreadSafeLayoutNode>
+ for FlowConstructor<'a, ConcreteThreadSafeLayoutNode>
+where
+ ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode,
+{
+ // 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'.
+ fn process(&mut self, node: &ConcreteThreadSafeLayoutNode) {
+ node.insert_flags(LayoutDataFlags::HAS_NEWLY_CONSTRUCTED_FLOW);
+
+ let style = node.style(self.style_context());
+
+ // Bail out if this node is display: none. The style system guarantees
+ // that we don't arrive here for children of those.
+ if style.get_box().display.is_none() {
+ self.set_flow_construction_result(node, ConstructionResult::None);
+ return;
+ }
+
+ // 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.
+ (
+ style.get_box().display,
+ style.get_box().float,
+ style.get_box().position,
+ )
+ },
+ Some(LayoutNodeType::Element(_)) => {
+ let original_display = style.get_box().original_display;
+ // FIXME(emilio, #19771): This munged_display business is pretty
+ // wrong. After we fix this we should be able to unify the
+ // pseudo-element path too.
+ let munged_display = match original_display {
+ Display::Inline | Display::InlineBlock => original_display,
+ _ => style.get_box().display,
+ };
+ (
+ munged_display,
+ style.get_box().float,
+ style.get_box().position,
+ )
+ },
+ Some(LayoutNodeType::Text) => (Display::Inline, Float::None, Position::Static),
+ };
+
+ debug!(
+ "building flow for node: {:?} {:?} {:?} {:?}",
+ display,
+ float,
+ positioning,
+ node.type_id()
+ );
+
+ // Switch on display and floatedness.
+ match (display, float, positioning) {
+ // `display: none` contributes no flow construction result.
+ (Display::None, _, _) => {
+ self.set_flow_construction_result(node, ConstructionResult::None);
+ },
+
+ // Table items contribute table flow construction results.
+ (Display::Table, float_value, _) => {
+ let construction_result = self.build_flow_for_table(node, float_value);
+ self.set_flow_construction_result(node, 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) | (Display::Block, _, Position::Fixed) => {
+ let construction_result = self.build_flow_for_block(node, None);
+ self.set_flow_construction_result(node, construction_result)
+ },
+
+ // List items contribute their own special flows.
+ (Display::ListItem, float_value, _) => {
+ let construction_result = self.build_flow_for_list_item(node, float_value);
+ self.set_flow_construction_result(node, construction_result)
+ },
+
+ // Inline items that are absolutely-positioned contribute inline fragment construction
+ // results with a hypothetical fragment.
+ (Display::Inline, _, Position::Absolute) |
+ (Display::InlineBlock, _, Position::Absolute) => {
+ let construction_result =
+ self.build_fragment_for_absolutely_positioned_inline(node);
+ self.set_flow_construction_result(node, construction_result)
+ },
+
+ // Inline items contribute inline fragment construction results.
+ //
+ // FIXME(pcwalton, #3307): This is not sufficient to handle floated generated content.
+ (Display::Inline, Float::None, _) => {
+ let construction_result = self.build_fragments_for_inline(node);
+ self.set_flow_construction_result(node, construction_result)
+ },
+
+ // Inline-block items contribute inline fragment construction results.
+ (Display::InlineBlock, Float::None, _) => {
+ let construction_result =
+ self.build_fragment_for_inline_block_or_inline_flex(node, Display::InlineBlock);
+ self.set_flow_construction_result(node, construction_result)
+ },
+
+ // Table items contribute table flow construction results.
+ (Display::TableCaption, _, _) => {
+ let construction_result = self.build_flow_for_table_caption(node);
+ self.set_flow_construction_result(node, construction_result)
+ },
+
+ // Table items contribute table flow construction results.
+ (Display::TableColumnGroup, _, _) => {
+ let construction_result = self.build_flow_for_table_colgroup(node);
+ self.set_flow_construction_result(node, construction_result)
+ },
+
+ // Table items contribute table flow construction results.
+ (Display::TableColumn, _, _) => {
+ let construction_result = self.build_fragments_for_table_column(node);
+ self.set_flow_construction_result(node, construction_result)
+ },
+
+ // Table items contribute table flow construction results.
+ (Display::TableRowGroup, _, _) |
+ (Display::TableHeaderGroup, _, _) |
+ (Display::TableFooterGroup, _, _) => {
+ let construction_result = self.build_flow_for_table_rowgroup(node);
+ self.set_flow_construction_result(node, construction_result)
+ },
+
+ // Table items contribute table flow construction results.
+ (Display::TableRow, _, _) => {
+ let construction_result = self.build_flow_for_table_row(node);
+ self.set_flow_construction_result(node, construction_result)
+ },
+
+ // Table items contribute table flow construction results.
+ (Display::TableCell, _, _) => {
+ let construction_result = self.build_flow_for_table_cell(node);
+ self.set_flow_construction_result(node, construction_result)
+ },
+
+ // Flex items contribute flex flow construction results.
+ (Display::Flex, float_value, _) => {
+ let float_kind = FloatKind::from_property(float_value);
+ let construction_result = self.build_flow_for_flex(node, float_kind);
+ self.set_flow_construction_result(node, construction_result)
+ },
+
+ (Display::InlineFlex, _, _) => {
+ let construction_result =
+ self.build_fragment_for_inline_block_or_inline_flex(node, Display::InlineFlex);
+ self.set_flow_construction_result(node, 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_value, _) => {
+ let float_kind = FloatKind::from_property(float_value);
+ let construction_result = self.build_flow_for_block(node, float_kind);
+ self.set_flow_construction_result(node, construction_result)
+ },
+ }
+ }
+}
+
+/// 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;
+
+ fn construction_result_mut(self, layout_data: &mut LayoutData) -> &mut ConstructionResult;
+
+ /// Sets the construction result of a flow.
+ fn set_flow_construction_result(self, result: ConstructionResult);
+
+ /// Returns the construction result for this node.
+ fn get_construction_result(self) -> ConstructionResult;
+}
+
+impl<ConcreteThreadSafeLayoutNode> NodeUtils for ConcreteThreadSafeLayoutNode
+where
+ ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode,
+{
+ fn is_replaced_content(&self) -> bool {
+ match self.type_id() {
+ Some(LayoutNodeType::Text) |
+ Some(LayoutNodeType::Element(LayoutElementType::HTMLImageElement)) |
+ Some(LayoutNodeType::Element(LayoutElementType::HTMLMediaElement)) |
+ Some(LayoutNodeType::Element(LayoutElementType::HTMLIFrameElement)) |
+ Some(LayoutNodeType::Element(LayoutElementType::HTMLCanvasElement)) |
+ Some(LayoutNodeType::Element(LayoutElementType::SVGSVGElement)) => true,
+ Some(LayoutNodeType::Element(LayoutElementType::HTMLObjectElement)) => {
+ let elem = self.as_element().unwrap();
+ let type_and_data = (
+ elem.get_attr(&ns!(), &local_name!("type")),
+ elem.get_attr(&ns!(), &local_name!("data")),
+ );
+ match type_and_data {
+ (None, Some(uri)) => is_image_data(uri),
+ _ => false,
+ }
+ },
+ Some(LayoutNodeType::Element(_)) => false,
+ None => self.get_pseudo_element_type().is_replaced_content(),
+ }
+ }
+
+ fn construction_result_mut(self, data: &mut LayoutData) -> &mut ConstructionResult {
+ match self.get_pseudo_element_type() {
+ PseudoElementType::Before => &mut data.before_flow_construction_result,
+ PseudoElementType::After => &mut data.after_flow_construction_result,
+ PseudoElementType::DetailsSummary => &mut data.details_summary_flow_construction_result,
+ PseudoElementType::DetailsContent => &mut data.details_content_flow_construction_result,
+ PseudoElementType::Normal => &mut data.flow_construction_result,
+ }
+ }
+
+ #[inline(always)]
+ fn set_flow_construction_result(self, result: ConstructionResult) {
+ let mut layout_data = self.mutate_layout_data().unwrap();
+ let dst = self.construction_result_mut(&mut *layout_data);
+ *dst = result;
+ }
+
+ #[inline(always)]
+ fn get_construction_result(self) -> ConstructionResult {
+ let mut layout_data = self.mutate_layout_data().unwrap();
+ self.construction_result_mut(&mut *layout_data).get()
+ }
+}
+
+impl FlowRef {
+ /// Adds a new flow as a child of this flow. Fails if this flow is marked as a leaf.
+ fn add_new_child(&mut self, mut new_child: FlowRef) {
+ {
+ let kid_base = FlowRef::deref_mut(&mut new_child).mut_base();
+ kid_base.parallel.parent = parallel::mut_owned_flow_to_unsafe_flow(self);
+ }
+
+ let base = FlowRef::deref_mut(self).mut_base();
+ base.children.push_back(new_child);
+ let _ = base.parallel.children_count.fetch_add(1, Ordering::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.)
+ fn finish(&mut self) {
+ if !opts::get().bubble_inline_sizes_separately {
+ FlowRef::deref_mut(self).bubble_inline_sizes();
+ FlowRef::deref_mut(self)
+ .mut_base()
+ .restyle_damage
+ .remove(ServoRestyleDamage::BUBBLE_ISIZES);
+ }
+ }
+}
+
+/// Strips ignorable whitespace from the start of a list of fragments.
+pub fn strip_ignorable_whitespace_from_start(this: &mut LinkedList<Fragment>) {
+ if this.is_empty() {
+ return; // Fast path.
+ }
+
+ let mut leading_fragments_consisting_of_solely_bidi_control_characters = LinkedList::new();
+ while !this.is_empty() {
+ match this
+ .front_mut()
+ .as_mut()
+ .unwrap()
+ .strip_leading_whitespace_if_necessary()
+ {
+ WhitespaceStrippingResult::RetainFragment => break,
+ WhitespaceStrippingResult::FragmentContainedOnlyBidiControlCharacters => {
+ leading_fragments_consisting_of_solely_bidi_control_characters
+ .push_back(this.pop_front().unwrap())
+ },
+ WhitespaceStrippingResult::FragmentContainedOnlyWhitespace => {
+ let removed_fragment = this.pop_front().unwrap();
+ if let Some(ref mut remaining_fragment) = this.front_mut() {
+ remaining_fragment.meld_with_prev_inline_fragment(&removed_fragment);
+ }
+ },
+ }
+ }
+ prepend_from(
+ this,
+ &mut leading_fragments_consisting_of_solely_bidi_control_characters,
+ );
+}
+
+/// Strips ignorable whitespace from the end of a list of fragments.
+pub fn strip_ignorable_whitespace_from_end(this: &mut LinkedList<Fragment>) {
+ if this.is_empty() {
+ return;
+ }
+
+ let mut trailing_fragments_consisting_of_solely_bidi_control_characters = LinkedList::new();
+ while !this.is_empty() {
+ match this
+ .back_mut()
+ .as_mut()
+ .unwrap()
+ .strip_trailing_whitespace_if_necessary()
+ {
+ WhitespaceStrippingResult::RetainFragment => break,
+ WhitespaceStrippingResult::FragmentContainedOnlyBidiControlCharacters => {
+ trailing_fragments_consisting_of_solely_bidi_control_characters
+ .push_front(this.pop_back().unwrap())
+ },
+ WhitespaceStrippingResult::FragmentContainedOnlyWhitespace => {
+ let removed_fragment = this.pop_back().unwrap();
+ if let Some(ref mut remaining_fragment) = this.back_mut() {
+ remaining_fragment.meld_with_next_inline_fragment(&removed_fragment);
+ }
+ },
+ }
+ }
+ this.append(&mut trailing_fragments_consisting_of_solely_bidi_control_characters);
+}
+
+/// If the 'unicode-bidi' property has a value other than 'normal', return the bidi control codes
+/// to inject before and after the text content of the element.
+fn bidi_control_chars(style: &ServoArc<ComputedValues>) -> Option<(&'static str, &'static str)> {
+ use style::computed_values::direction::T::*;
+ use style::computed_values::unicode_bidi::T::*;
+
+ let unicode_bidi = style.get_text().unicode_bidi;
+ let direction = style.get_inherited_box().direction;
+
+ // See the table in http://dev.w3.org/csswg/css-writing-modes/#unicode-bidi
+ match (unicode_bidi, direction) {
+ (Normal, _) => None,
+ (Embed, Ltr) => Some(("\u{202A}", "\u{202C}")),
+ (Embed, Rtl) => Some(("\u{202B}", "\u{202C}")),
+ (Isolate, Ltr) => Some(("\u{2066}", "\u{2069}")),
+ (Isolate, Rtl) => Some(("\u{2067}", "\u{2069}")),
+ (BidiOverride, Ltr) => Some(("\u{202D}", "\u{202C}")),
+ (BidiOverride, Rtl) => Some(("\u{202E}", "\u{202C}")),
+ (IsolateOverride, Ltr) => Some(("\u{2068}\u{202D}", "\u{202C}\u{2069}")),
+ (IsolateOverride, Rtl) => Some(("\u{2068}\u{202E}", "\u{202C}\u{2069}")),
+ (Plaintext, _) => Some(("\u{2068}", "\u{2069}")),
+ }
+}
+
+fn control_chars_to_fragment<E>(
+ node: &InlineFragmentNodeInfo,
+ context: &SharedStyleContext,
+ text: &str,
+ restyle_damage: RestyleDamage,
+) -> Fragment
+where
+ E: TElement,
+{
+ let info = SpecificFragmentInfo::UnscannedText(Box::new(UnscannedTextFragmentInfo::new(
+ Box::<str>::from(text),
+ None,
+ )));
+ let text_style = context.stylist.style_for_anonymous::<E>(
+ &context.guards,
+ &PseudoElement::ServoText,
+ &node.style,
+ );
+
+ Fragment::from_opaque_node_and_style(
+ node.address,
+ node.pseudo,
+ text_style,
+ node.selected_style.clone(),
+ restyle_damage,
+ info,
+ )
+}
+
+/// Returns true if this node has non-zero padding or border.
+fn has_padding_or_border(values: &ComputedValues) -> bool {
+ let padding = values.get_padding();
+ let border = values.get_border();
+
+ !padding.padding_top.is_definitely_zero() ||
+ !padding.padding_right.is_definitely_zero() ||
+ !padding.padding_bottom.is_definitely_zero() ||
+ !padding.padding_left.is_definitely_zero() ||
+ border.border_top_width.px() != 0. ||
+ border.border_right_width.px() != 0. ||
+ border.border_bottom_width.px() != 0. ||
+ border.border_left_width.px() != 0.
+}
+
+/// Maintains a stack of anonymous boxes needed to ensure that the flow tree is *legal*. The tree
+/// is legal if it follows the rules in CSS 2.1 § 17.2.1.
+///
+/// As an example, the legalizer makes sure that table row flows contain only table cells. If the
+/// flow constructor attempts to place, say, a block flow directly underneath the table row, the
+/// legalizer generates an anonymous table cell in between to hold the block.
+///
+/// Generally, the flow constructor should use `Legalizer::add_child()` instead of calling
+/// `Flow::add_new_child()` directly. This ensures that the flow tree remains legal at all times
+/// and centralizes the anonymous flow generation logic in one place.
+struct Legalizer {
+ /// A stack of anonymous flows that have yet to be finalized (i.e. that still could acquire new
+ /// children).
+ stack: Vec<FlowRef>,
+}
+
+impl Legalizer {
+ /// Creates a new legalizer.
+ fn new() -> Legalizer {
+ Legalizer { stack: vec![] }
+ }
+
+ /// Makes the `child` flow a new child of `parent`. Anonymous flows are automatically inserted
+ /// to keep the tree legal.
+ fn add_child<E>(
+ &mut self,
+ context: &SharedStyleContext,
+ parent: &mut FlowRef,
+ mut child: FlowRef,
+ ) where
+ E: TElement,
+ {
+ while !self.stack.is_empty() {
+ if self.try_to_add_child::<E>(context, parent, &mut child) {
+ return;
+ }
+ self.flush_top_of_stack(parent)
+ }
+
+ while !self.try_to_add_child::<E>(context, parent, &mut child) {
+ self.push_next_anonymous_flow::<E>(context, parent)
+ }
+ }
+
+ /// Flushes all flows we've been gathering up.
+ fn finish(mut self, parent: &mut FlowRef) {
+ while !self.stack.is_empty() {
+ self.flush_top_of_stack(parent)
+ }
+ }
+
+ /// Attempts to make `child` a child of `parent`. On success, this returns true. If this would
+ /// make the tree illegal, this method does nothing and returns false.
+ ///
+ /// This method attempts to create anonymous blocks in between `parent` and `child` if and only
+ /// if those blocks will only ever have `child` as their sole child. At present, this is only
+ /// true for anonymous block children of flex flows.
+ fn try_to_add_child<E>(
+ &mut self,
+ context: &SharedStyleContext,
+ parent: &mut FlowRef,
+ child: &mut FlowRef,
+ ) -> bool
+ where
+ E: TElement,
+ {
+ let parent = self.stack.last_mut().unwrap_or(parent);
+ let (parent_class, child_class) = (parent.class(), child.class());
+ match (parent_class, child_class) {
+ (FlowClass::TableWrapper, FlowClass::Table) |
+ (FlowClass::Table, FlowClass::TableColGroup) |
+ (FlowClass::Table, FlowClass::TableRowGroup) |
+ (FlowClass::Table, FlowClass::TableRow) |
+ (FlowClass::Table, FlowClass::TableCaption) |
+ (FlowClass::TableRowGroup, FlowClass::TableRow) |
+ (FlowClass::TableRow, FlowClass::TableCell) => {
+ parent.add_new_child((*child).clone());
+ true
+ },
+
+ (FlowClass::TableWrapper, _) |
+ (FlowClass::Table, _) |
+ (FlowClass::TableRowGroup, _) |
+ (FlowClass::TableRow, _) |
+ (_, FlowClass::Table) |
+ (_, FlowClass::TableColGroup) |
+ (_, FlowClass::TableRowGroup) |
+ (_, FlowClass::TableRow) |
+ (_, FlowClass::TableCaption) |
+ (_, FlowClass::TableCell) => false,
+
+ (FlowClass::Flex, FlowClass::Inline) => {
+ FlowRef::deref_mut(child)
+ .mut_base()
+ .flags
+ .insert(FlowFlags::MARGINS_CANNOT_COLLAPSE);
+ let mut block_wrapper = Legalizer::create_anonymous_flow::<E, _>(
+ context,
+ parent,
+ &[PseudoElement::ServoAnonymousBlock],
+ SpecificFragmentInfo::Generic,
+ BlockFlow::from_fragment,
+ );
+
+ {
+ let flag = if parent.as_flex().main_mode() == Direction::Inline {
+ FragmentFlags::IS_INLINE_FLEX_ITEM
+ } else {
+ FragmentFlags::IS_BLOCK_FLEX_ITEM
+ };
+ let block = FlowRef::deref_mut(&mut block_wrapper).as_mut_block();
+ block.base.flags.insert(FlowFlags::MARGINS_CANNOT_COLLAPSE);
+ block.fragment.flags.insert(flag);
+ }
+ block_wrapper.add_new_child((*child).clone());
+ block_wrapper.finish();
+ parent.add_new_child(block_wrapper);
+ true
+ },
+
+ (FlowClass::Flex, _) => {
+ {
+ let flag = if parent.as_flex().main_mode() == Direction::Inline {
+ FragmentFlags::IS_INLINE_FLEX_ITEM
+ } else {
+ FragmentFlags::IS_BLOCK_FLEX_ITEM
+ };
+ let block = FlowRef::deref_mut(child).as_mut_block();
+ block.base.flags.insert(FlowFlags::MARGINS_CANNOT_COLLAPSE);
+ block.fragment.flags.insert(flag);
+ }
+ parent.add_new_child((*child).clone());
+ true
+ },
+
+ _ => {
+ parent.add_new_child((*child).clone());
+ true
+ },
+ }
+ }
+
+ /// Finalizes the flow on the top of the stack.
+ fn flush_top_of_stack(&mut self, parent: &mut FlowRef) {
+ let mut child = self.stack.pop().expect("flush_top_of_stack(): stack empty");
+ child.finish();
+ self.stack.last_mut().unwrap_or(parent).add_new_child(child)
+ }
+
+ /// Adds the anonymous flow that would be necessary to make an illegal child of `parent` legal
+ /// to the stack.
+ fn push_next_anonymous_flow<E>(&mut self, context: &SharedStyleContext, parent: &FlowRef)
+ where
+ E: TElement,
+ {
+ let parent_class = self.stack.last().unwrap_or(parent).class();
+ match parent_class {
+ FlowClass::TableRow => self.push_new_anonymous_flow::<E, _>(
+ context,
+ parent,
+ &[PseudoElement::ServoAnonymousTableCell],
+ SpecificFragmentInfo::TableCell,
+ TableCellFlow::from_fragment,
+ ),
+ FlowClass::Table | FlowClass::TableRowGroup => self.push_new_anonymous_flow::<E, _>(
+ context,
+ parent,
+ &[PseudoElement::ServoAnonymousTableRow],
+ SpecificFragmentInfo::TableRow,
+ TableRowFlow::from_fragment,
+ ),
+ FlowClass::TableWrapper => self.push_new_anonymous_flow::<E, _>(
+ context,
+ parent,
+ &[PseudoElement::ServoAnonymousTable],
+ SpecificFragmentInfo::Table,
+ TableFlow::from_fragment,
+ ),
+ _ => self.push_new_anonymous_flow::<E, _>(
+ context,
+ parent,
+ &[
+ PseudoElement::ServoTableWrapper,
+ PseudoElement::ServoAnonymousTableWrapper,
+ ],
+ SpecificFragmentInfo::TableWrapper,
+ TableWrapperFlow::from_fragment,
+ ),
+ }
+ }
+
+ /// Creates an anonymous flow and pushes it onto the stack.
+ fn push_new_anonymous_flow<E, F>(
+ &mut self,
+ context: &SharedStyleContext,
+ reference: &FlowRef,
+ pseudos: &[PseudoElement],
+ specific_fragment_info: SpecificFragmentInfo,
+ constructor: fn(Fragment) -> F,
+ ) where
+ E: TElement,
+ F: Flow,
+ {
+ let new_flow = Self::create_anonymous_flow::<E, _>(
+ context,
+ reference,
+ pseudos,
+ specific_fragment_info,
+ constructor,
+ );
+ self.stack.push(new_flow)
+ }
+
+ /// Creates a new anonymous flow. The new flow is identical to `reference` except with all
+ /// styles applying to every pseudo-element in `pseudos` applied.
+ ///
+ /// This method invokes the supplied constructor function on the given specific fragment info
+ /// in order to actually generate the flow.
+ fn create_anonymous_flow<E, F>(
+ context: &SharedStyleContext,
+ reference: &FlowRef,
+ pseudos: &[PseudoElement],
+ specific_fragment_info: SpecificFragmentInfo,
+ constructor: fn(Fragment) -> F,
+ ) -> FlowRef
+ where
+ E: TElement,
+ F: Flow,
+ {
+ let reference_block = reference.as_block();
+ let mut new_style = reference_block.fragment.style.clone();
+ for pseudo in pseudos {
+ new_style =
+ context
+ .stylist
+ .style_for_anonymous::<E>(&context.guards, pseudo, &new_style);
+ }
+ let fragment = reference_block
+ .fragment
+ .create_similar_anonymous_fragment(new_style, specific_fragment_info);
+ FlowRef::new(Arc::new(constructor(fragment)))
+ }
+}
+
+pub fn is_image_data(uri: &str) -> bool {
+ static TYPES: &'static [&'static str] =
+ &["data:image/png", "data:image/gif", "data:image/jpeg"];
+ TYPES.iter().any(|&type_| uri.starts_with(type_))
+}
diff --git a/components/layout_2020/context.rs b/components/layout_2020/context.rs
new file mode 100644
index 00000000000..015cc9ac40d
--- /dev/null
+++ b/components/layout_2020/context.rs
@@ -0,0 +1,201 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+//! Data needed by the layout thread.
+
+use crate::display_list::items::{OpaqueNode, WebRenderImageInfo};
+use crate::opaque_node::OpaqueNodeMethods;
+use fnv::FnvHasher;
+use gfx::font_cache_thread::FontCacheThread;
+use gfx::font_context::FontContext;
+use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
+use msg::constellation_msg::PipelineId;
+use net_traits::image_cache::{CanRequestImages, ImageCache, ImageState};
+use net_traits::image_cache::{ImageOrMetadataAvailable, UsePlaceholder};
+use parking_lot::RwLock;
+use script_layout_interface::{PendingImage, PendingImageState};
+use script_traits::Painter;
+use script_traits::UntrustedNodeAddress;
+use servo_atoms::Atom;
+use servo_url::ServoUrl;
+use std::cell::{RefCell, RefMut};
+use std::collections::HashMap;
+use std::hash::BuildHasherDefault;
+use std::sync::{Arc, Mutex};
+use std::thread;
+use style::context::RegisteredSpeculativePainter;
+use style::context::SharedStyleContext;
+
+pub type LayoutFontContext = FontContext<FontCacheThread>;
+
+thread_local!(static FONT_CONTEXT_KEY: RefCell<Option<LayoutFontContext>> = RefCell::new(None));
+
+pub fn with_thread_local_font_context<F, R>(layout_context: &LayoutContext, f: F) -> R
+where
+ F: FnOnce(&mut LayoutFontContext) -> R,
+{
+ FONT_CONTEXT_KEY.with(|k| {
+ let mut font_context = k.borrow_mut();
+ if font_context.is_none() {
+ let font_cache_thread = layout_context.font_cache_thread.lock().unwrap().clone();
+ *font_context = Some(FontContext::new(font_cache_thread));
+ }
+ f(&mut RefMut::map(font_context, |x| x.as_mut().unwrap()))
+ })
+}
+
+pub fn malloc_size_of_persistent_local_context(ops: &mut MallocSizeOfOps) -> usize {
+ FONT_CONTEXT_KEY.with(|r| {
+ if let Some(ref context) = *r.borrow() {
+ context.size_of(ops)
+ } else {
+ 0
+ }
+ })
+}
+
+/// Layout information shared among all workers. This must be thread-safe.
+pub struct LayoutContext<'a> {
+ /// The pipeline id of this LayoutContext.
+ pub id: PipelineId,
+
+ /// Bits shared by the layout and style system.
+ pub style_context: SharedStyleContext<'a>,
+
+ /// Reference to the script thread image cache.
+ pub image_cache: Arc<dyn ImageCache>,
+
+ /// Interface to the font cache thread.
+ pub font_cache_thread: Mutex<FontCacheThread>,
+
+ /// A cache of WebRender image info.
+ pub webrender_image_cache: Arc<
+ RwLock<
+ HashMap<(ServoUrl, UsePlaceholder), WebRenderImageInfo, BuildHasherDefault<FnvHasher>>,
+ >,
+ >,
+
+ /// Paint worklets
+ pub registered_painters: &'a dyn RegisteredPainters,
+
+ /// A list of in-progress image loads to be shared with the script thread.
+ /// A None value means that this layout was not initiated by the script thread.
+ pub pending_images: Option<Mutex<Vec<PendingImage>>>,
+
+ /// A list of nodes that have just initiated a CSS transition.
+ /// A None value means that this layout was not initiated by the script thread.
+ pub newly_transitioning_nodes: Option<Mutex<Vec<UntrustedNodeAddress>>>,
+}
+
+impl<'a> Drop for LayoutContext<'a> {
+ fn drop(&mut self) {
+ if !thread::panicking() {
+ if let Some(ref pending_images) = self.pending_images {
+ assert!(pending_images.lock().unwrap().is_empty());
+ }
+ }
+ }
+}
+
+impl<'a> LayoutContext<'a> {
+ #[inline(always)]
+ pub fn shared_context(&self) -> &SharedStyleContext {
+ &self.style_context
+ }
+
+ pub fn get_or_request_image_or_meta(
+ &self,
+ node: OpaqueNode,
+ url: ServoUrl,
+ use_placeholder: UsePlaceholder,
+ ) -> Option<ImageOrMetadataAvailable> {
+ //XXXjdm For cases where we do not request an image, we still need to
+ // ensure the node gets another script-initiated reflow or it
+ // won't be requested at all.
+ let can_request = if self.pending_images.is_some() {
+ CanRequestImages::Yes
+ } else {
+ CanRequestImages::No
+ };
+
+ // See if the image is already available
+ let result =
+ self.image_cache
+ .find_image_or_metadata(url.clone(), use_placeholder, can_request);
+ match result {
+ Ok(image_or_metadata) => Some(image_or_metadata),
+ // Image failed to load, so just return nothing
+ Err(ImageState::LoadError) => None,
+ // Not yet requested - request image or metadata from the cache
+ Err(ImageState::NotRequested(id)) => {
+ let image = PendingImage {
+ state: PendingImageState::Unrequested(url),
+ node: node.to_untrusted_node_address(),
+ id: id,
+ };
+ self.pending_images
+ .as_ref()
+ .unwrap()
+ .lock()
+ .unwrap()
+ .push(image);
+ None
+ },
+ // Image has been requested, is still pending. Return no image for this paint loop.
+ // When the image loads it will trigger a reflow and/or repaint.
+ Err(ImageState::Pending(id)) => {
+ //XXXjdm if self.pending_images is not available, we should make sure that
+ // this node gets marked dirty again so it gets a script-initiated
+ // reflow that deals with this properly.
+ if let Some(ref pending_images) = self.pending_images {
+ let image = PendingImage {
+ state: PendingImageState::PendingResponse,
+ node: node.to_untrusted_node_address(),
+ id: id,
+ };
+ pending_images.lock().unwrap().push(image);
+ }
+ None
+ },
+ }
+ }
+
+ pub fn get_webrender_image_for_url(
+ &self,
+ node: OpaqueNode,
+ url: ServoUrl,
+ use_placeholder: UsePlaceholder,
+ ) -> Option<WebRenderImageInfo> {
+ if let Some(existing_webrender_image) = self
+ .webrender_image_cache
+ .read()
+ .get(&(url.clone(), use_placeholder))
+ {
+ return Some((*existing_webrender_image).clone());
+ }
+
+ match self.get_or_request_image_or_meta(node, url.clone(), use_placeholder) {
+ Some(ImageOrMetadataAvailable::ImageAvailable(image, _)) => {
+ let image_info = WebRenderImageInfo::from_image(&*image);
+ if image_info.key.is_none() {
+ Some(image_info)
+ } else {
+ let mut webrender_image_cache = self.webrender_image_cache.write();
+ webrender_image_cache.insert((url, use_placeholder), image_info);
+ Some(image_info)
+ }
+ },
+ None | Some(ImageOrMetadataAvailable::MetadataAvailable(_)) => None,
+ }
+ }
+}
+
+/// A registered painter
+pub trait RegisteredPainter: RegisteredSpeculativePainter + Painter {}
+
+/// A set of registered painters
+pub trait RegisteredPainters: Sync {
+ /// Look up a painter
+ fn get(&self, name: &Atom) -> Option<&dyn RegisteredPainter>;
+}
diff --git a/components/layout_2020/data.rs b/components/layout_2020/data.rs
new file mode 100644
index 00000000000..5f6672d1488
--- /dev/null
+++ b/components/layout_2020/data.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 https://mozilla.org/MPL/2.0/. */
+
+use crate::construct::ConstructionResult;
+use atomic_refcell::AtomicRefCell;
+use script_layout_interface::StyleData;
+
+#[repr(C)]
+pub struct StyleAndLayoutData {
+ /// Data accessed by script_layout_interface. This must be first to allow
+ /// casting between StyleAndLayoutData and StyleData.
+ pub style_data: StyleData,
+ /// The layout data associated with a node.
+ pub layout_data: AtomicRefCell<LayoutData>,
+}
+
+impl StyleAndLayoutData {
+ pub fn new() -> Self {
+ Self {
+ style_data: StyleData::new(),
+ layout_data: AtomicRefCell::new(LayoutData::new()),
+ }
+ }
+}
+
+/// Data that layout associates with a node.
+#[repr(C)]
+pub struct LayoutData {
+ /// 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,
+
+ pub details_summary_flow_construction_result: ConstructionResult,
+
+ pub details_content_flow_construction_result: ConstructionResult,
+
+ /// Various flags.
+ pub flags: LayoutDataFlags,
+}
+
+impl LayoutData {
+ /// Creates new layout data.
+ pub fn new() -> LayoutData {
+ Self {
+ flow_construction_result: ConstructionResult::None,
+ before_flow_construction_result: ConstructionResult::None,
+ after_flow_construction_result: ConstructionResult::None,
+ details_summary_flow_construction_result: ConstructionResult::None,
+ details_content_flow_construction_result: ConstructionResult::None,
+ flags: LayoutDataFlags::empty(),
+ }
+ }
+}
+
+bitflags! {
+ pub struct LayoutDataFlags: u8 {
+ #[doc = "Whether a flow has been newly constructed."]
+ const HAS_NEWLY_CONSTRUCTED_FLOW = 0x01;
+ #[doc = "Whether this node has been traversed by layout."]
+ const HAS_BEEN_TRAVERSED = 0x02;
+ }
+}
diff --git a/components/layout_2020/display_list/background.rs b/components/layout_2020/display_list/background.rs
new file mode 100644
index 00000000000..c6fa1691e46
--- /dev/null
+++ b/components/layout_2020/display_list/background.rs
@@ -0,0 +1,336 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+use crate::display_list::border;
+use app_units::Au;
+use euclid::{Point2D, Rect, SideOffsets2D, Size2D};
+use style::computed_values::background_attachment::single_value::T as BackgroundAttachment;
+use style::computed_values::background_clip::single_value::T as BackgroundClip;
+use style::computed_values::background_origin::single_value::T as BackgroundOrigin;
+use style::properties::style_structs::Background;
+use style::values::computed::{BackgroundSize, NonNegativeLengthPercentageOrAuto};
+use style::values::specified::background::BackgroundRepeatKeyword;
+use webrender_api::BorderRadius;
+
+/// Placment information for both image and gradient backgrounds.
+#[derive(Clone, Copy, Debug)]
+pub struct BackgroundPlacement {
+ /// Rendering bounds. The background will start in the uppper-left corner
+ /// and fill the whole area.
+ pub bounds: Rect<Au>,
+ /// Background tile size. Some backgrounds are repeated. These are the
+ /// dimensions of a single image of the background.
+ pub tile_size: Size2D<Au>,
+ /// Spacing between tiles. Some backgrounds are not repeated seamless
+ /// but have seams between them like tiles in real life.
+ pub tile_spacing: Size2D<Au>,
+ /// A clip area. While the background is rendered according to all the
+ /// measures above it is only shown within these bounds.
+ pub clip_rect: Rect<Au>,
+ /// Rounded corners for the clip_rect.
+ pub clip_radii: BorderRadius,
+ /// Whether or not the background is fixed to the viewport.
+ pub fixed: bool,
+}
+
+/// Access element at index modulo the array length.
+///
+/// Obviously it does not work with empty arrays.
+///
+/// This is used for multiple layered background images.
+/// See: https://drafts.csswg.org/css-backgrounds-3/#layering
+pub fn get_cyclic<T>(arr: &[T], index: usize) -> &T {
+ &arr[index % arr.len()]
+}
+
+/// For a given area and an image compute how big the
+/// image should be displayed on the background.
+fn compute_background_image_size(
+ bg_size: BackgroundSize,
+ bounds_size: Size2D<Au>,
+ intrinsic_size: Option<Size2D<Au>>,
+) -> Size2D<Au> {
+ match intrinsic_size {
+ None => match bg_size {
+ BackgroundSize::Cover | BackgroundSize::Contain => bounds_size,
+ BackgroundSize::ExplicitSize { width, height } => Size2D::new(
+ width
+ .to_used_value(bounds_size.width)
+ .unwrap_or(bounds_size.width),
+ height
+ .to_used_value(bounds_size.height)
+ .unwrap_or(bounds_size.height),
+ ),
+ },
+ Some(own_size) => {
+ // If `image_aspect_ratio` < `bounds_aspect_ratio`, the image is tall; otherwise, it is
+ // wide.
+ let image_aspect_ratio = own_size.width.to_f32_px() / own_size.height.to_f32_px();
+ let bounds_aspect_ratio =
+ bounds_size.width.to_f32_px() / bounds_size.height.to_f32_px();
+ match (bg_size, image_aspect_ratio < bounds_aspect_ratio) {
+ (BackgroundSize::Contain, false) | (BackgroundSize::Cover, true) => Size2D::new(
+ bounds_size.width,
+ bounds_size.width.scale_by(image_aspect_ratio.recip()),
+ ),
+ (BackgroundSize::Contain, true) | (BackgroundSize::Cover, false) => Size2D::new(
+ bounds_size.height.scale_by(image_aspect_ratio),
+ bounds_size.height,
+ ),
+ (
+ BackgroundSize::ExplicitSize {
+ width,
+ height: NonNegativeLengthPercentageOrAuto::Auto,
+ },
+ _,
+ ) => {
+ let width = width
+ .to_used_value(bounds_size.width)
+ .unwrap_or(own_size.width);
+ Size2D::new(width, width.scale_by(image_aspect_ratio.recip()))
+ },
+ (
+ BackgroundSize::ExplicitSize {
+ width: NonNegativeLengthPercentageOrAuto::Auto,
+ height,
+ },
+ _,
+ ) => {
+ let height = height
+ .to_used_value(bounds_size.height)
+ .unwrap_or(own_size.height);
+ Size2D::new(height.scale_by(image_aspect_ratio), height)
+ },
+ (BackgroundSize::ExplicitSize { width, height }, _) => Size2D::new(
+ width
+ .to_used_value(bounds_size.width)
+ .unwrap_or(own_size.width),
+ height
+ .to_used_value(bounds_size.height)
+ .unwrap_or(own_size.height),
+ ),
+ }
+ },
+ }
+}
+
+/// Compute a rounded clip rect for the background.
+pub fn clip(
+ bg_clip: BackgroundClip,
+ absolute_bounds: Rect<Au>,
+ border: SideOffsets2D<Au>,
+ border_padding: SideOffsets2D<Au>,
+ border_radii: BorderRadius,
+) -> (Rect<Au>, BorderRadius) {
+ match bg_clip {
+ BackgroundClip::BorderBox => (absolute_bounds, border_radii),
+ BackgroundClip::PaddingBox => (
+ absolute_bounds.inner_rect(border),
+ border::inner_radii(border_radii, border),
+ ),
+ BackgroundClip::ContentBox => (
+ absolute_bounds.inner_rect(border_padding),
+ border::inner_radii(border_radii, border_padding),
+ ),
+ }
+}
+
+/// Determines where to place an element background image or gradient.
+///
+/// Photos have their resolution as intrinsic size while gradients have
+/// no intrinsic size.
+pub fn placement(
+ bg: &Background,
+ viewport_size: Size2D<Au>,
+ absolute_bounds: Rect<Au>,
+ intrinsic_size: Option<Size2D<Au>>,
+ border: SideOffsets2D<Au>,
+ border_padding: SideOffsets2D<Au>,
+ border_radii: BorderRadius,
+ index: usize,
+) -> BackgroundPlacement {
+ let bg_attachment = *get_cyclic(&bg.background_attachment.0, index);
+ let bg_clip = *get_cyclic(&bg.background_clip.0, index);
+ let bg_origin = *get_cyclic(&bg.background_origin.0, index);
+ let bg_position_x = get_cyclic(&bg.background_position_x.0, index);
+ let bg_position_y = get_cyclic(&bg.background_position_y.0, index);
+ let bg_repeat = get_cyclic(&bg.background_repeat.0, index);
+ let bg_size = *get_cyclic(&bg.background_size.0, index);
+
+ let (clip_rect, clip_radii) = clip(
+ bg_clip,
+ absolute_bounds,
+ border,
+ border_padding,
+ border_radii,
+ );
+
+ let mut fixed = false;
+ let mut bounds = match bg_attachment {
+ BackgroundAttachment::Scroll => match bg_origin {
+ BackgroundOrigin::BorderBox => absolute_bounds,
+ BackgroundOrigin::PaddingBox => absolute_bounds.inner_rect(border),
+ BackgroundOrigin::ContentBox => absolute_bounds.inner_rect(border_padding),
+ },
+ BackgroundAttachment::Fixed => {
+ fixed = true;
+ Rect::new(Point2D::origin(), viewport_size)
+ },
+ };
+
+ let mut tile_size = compute_background_image_size(bg_size, bounds.size, intrinsic_size);
+
+ let mut tile_spacing = Size2D::zero();
+ let own_position = bounds.size - tile_size;
+ let pos_x = bg_position_x.to_used_value(own_position.width);
+ let pos_y = bg_position_y.to_used_value(own_position.height);
+ tile_image_axis(
+ bg_repeat.0,
+ &mut bounds.origin.x,
+ &mut bounds.size.width,
+ &mut tile_size.width,
+ &mut tile_spacing.width,
+ pos_x,
+ clip_rect.origin.x,
+ clip_rect.size.width,
+ );
+ tile_image_axis(
+ bg_repeat.1,
+ &mut bounds.origin.y,
+ &mut bounds.size.height,
+ &mut tile_size.height,
+ &mut tile_spacing.height,
+ pos_y,
+ clip_rect.origin.y,
+ clip_rect.size.height,
+ );
+
+ BackgroundPlacement {
+ bounds,
+ tile_size,
+ tile_spacing,
+ clip_rect,
+ clip_radii,
+ fixed,
+ }
+}
+
+fn tile_image_round(
+ position: &mut Au,
+ size: &mut Au,
+ absolute_anchor_origin: Au,
+ image_size: &mut Au,
+) {
+ if *size == Au(0) || *image_size == Au(0) {
+ *position = Au(0);
+ *size = Au(0);
+ return;
+ }
+
+ let number_of_tiles = (size.to_f32_px() / image_size.to_f32_px()).round().max(1.0);
+ *image_size = *size / (number_of_tiles as i32);
+ tile_image(position, size, absolute_anchor_origin, *image_size);
+}
+
+fn tile_image_spaced(
+ position: &mut Au,
+ size: &mut Au,
+ tile_spacing: &mut Au,
+ absolute_anchor_origin: Au,
+ image_size: Au,
+) {
+ if *size == Au(0) || image_size == Au(0) {
+ *position = Au(0);
+ *size = Au(0);
+ *tile_spacing = Au(0);
+ return;
+ }
+
+ // Per the spec, if the space available is not enough for two images, just tile as
+ // normal but only display a single tile.
+ if image_size * 2 >= *size {
+ tile_image(position, size, absolute_anchor_origin, image_size);
+ *tile_spacing = Au(0);
+ *size = image_size;
+ return;
+ }
+
+ // Take the box size, remove room for two tiles on the edges, and then calculate how many
+ // other tiles fit in between them.
+ let size_remaining = *size - (image_size * 2);
+ let num_middle_tiles = (size_remaining.to_f32_px() / image_size.to_f32_px()).floor() as i32;
+
+ // Allocate the remaining space as padding between tiles. background-position is ignored
+ // as per the spec, so the position is just the box origin. We are also ignoring
+ // background-attachment here, which seems unspecced when combined with
+ // background-repeat: space.
+ let space_for_middle_tiles = image_size * num_middle_tiles;
+ *tile_spacing = (size_remaining - space_for_middle_tiles) / (num_middle_tiles + 1);
+}
+
+/// Tile an image
+fn tile_image(position: &mut Au, size: &mut Au, absolute_anchor_origin: Au, image_size: Au) {
+ // Avoid division by zero below!
+ // Images with a zero width or height are not displayed.
+ // Therefore the positions do not matter and can be left unchanged.
+ // NOTE: A possible optimization is not to build
+ // display items in this case at all.
+ if image_size == Au(0) {
+ return;
+ }
+
+ let delta_pixels = absolute_anchor_origin - *position;
+ let image_size_px = image_size.to_f32_px();
+ let tile_count = ((delta_pixels.to_f32_px() + image_size_px - 1.0) / image_size_px).floor();
+ let offset = image_size * (tile_count as i32);
+ let new_position = absolute_anchor_origin - offset;
+ *size = *position - new_position + *size;
+ *position = new_position;
+}
+
+/// For either the x or the y axis ajust various values to account for tiling.
+///
+/// This is done separately for both axes because the repeat keywords may differ.
+fn tile_image_axis(
+ repeat: BackgroundRepeatKeyword,
+ position: &mut Au,
+ size: &mut Au,
+ tile_size: &mut Au,
+ tile_spacing: &mut Au,
+ offset: Au,
+ clip_origin: Au,
+ clip_size: Au,
+) {
+ let absolute_anchor_origin = *position + offset;
+ match repeat {
+ BackgroundRepeatKeyword::NoRepeat => {
+ *position += offset;
+ *size = *tile_size;
+ },
+ BackgroundRepeatKeyword::Repeat => {
+ *position = clip_origin;
+ *size = clip_size;
+ tile_image(position, size, absolute_anchor_origin, *tile_size);
+ },
+ BackgroundRepeatKeyword::Space => {
+ tile_image_spaced(
+ position,
+ size,
+ tile_spacing,
+ absolute_anchor_origin,
+ *tile_size,
+ );
+ let combined_tile_size = *tile_size + *tile_spacing;
+ *position = clip_origin;
+ *size = clip_size;
+ tile_image(position, size, absolute_anchor_origin, combined_tile_size);
+ },
+ BackgroundRepeatKeyword::Round => {
+ tile_image_round(position, size, absolute_anchor_origin, tile_size);
+ *position = clip_origin;
+ *size = clip_size;
+ tile_image(position, size, absolute_anchor_origin, *tile_size);
+ },
+ }
+}
diff --git a/components/layout_2020/display_list/border.rs b/components/layout_2020/display_list/border.rs
new file mode 100644
index 00000000000..fff18fcf6c0
--- /dev/null
+++ b/components/layout_2020/display_list/border.rs
@@ -0,0 +1,199 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+use crate::display_list::ToLayout;
+use app_units::Au;
+use euclid::{Rect, SideOffsets2D, Size2D};
+use style::computed_values::border_image_outset::T as BorderImageOutset;
+use style::properties::style_structs::Border;
+use style::values::computed::NumberOrPercentage;
+use style::values::computed::{BorderCornerRadius, BorderImageWidth};
+use style::values::computed::{BorderImageSideWidth, NonNegativeLengthOrNumber};
+use style::values::generics::rect::Rect as StyleRect;
+use style::values::generics::NonNegative;
+use webrender_api::units::{LayoutSideOffsets, LayoutSize};
+use webrender_api::{BorderRadius, BorderSide, BorderStyle, ColorF, NormalBorder};
+
+/// Computes a border radius size against the containing size.
+///
+/// Note that percentages in `border-radius` are resolved against the relevant
+/// box dimension instead of only against the width per [1]:
+///
+/// > Percentages: Refer to corresponding dimension of the border box.
+///
+/// [1]: https://drafts.csswg.org/css-backgrounds-3/#border-radius
+fn corner_radius(radius: BorderCornerRadius, containing_size: Size2D<Au>) -> Size2D<Au> {
+ let w = radius.0.width().to_used_value(containing_size.width);
+ let h = radius.0.height().to_used_value(containing_size.height);
+ Size2D::new(w, h)
+}
+
+fn scaled_radii(radii: BorderRadius, factor: f32) -> BorderRadius {
+ BorderRadius {
+ top_left: radii.top_left * factor,
+ top_right: radii.top_right * factor,
+ bottom_left: radii.bottom_left * factor,
+ bottom_right: radii.bottom_right * factor,
+ }
+}
+
+fn overlapping_radii(size: LayoutSize, radii: BorderRadius) -> BorderRadius {
+ // No two corners' border radii may add up to more than the length of the edge
+ // between them. To prevent that, all radii are scaled down uniformly.
+ fn scale_factor(radius_a: f32, radius_b: f32, edge_length: f32) -> f32 {
+ let required = radius_a + radius_b;
+
+ if required <= edge_length {
+ 1.0
+ } else {
+ edge_length / required
+ }
+ }
+
+ let top_factor = scale_factor(radii.top_left.width, radii.top_right.width, size.width);
+ let bottom_factor = scale_factor(
+ radii.bottom_left.width,
+ radii.bottom_right.width,
+ size.width,
+ );
+ let left_factor = scale_factor(radii.top_left.height, radii.bottom_left.height, size.height);
+ let right_factor = scale_factor(
+ radii.top_right.height,
+ radii.bottom_right.height,
+ size.height,
+ );
+ let min_factor = top_factor
+ .min(bottom_factor)
+ .min(left_factor)
+ .min(right_factor);
+ if min_factor < 1.0 {
+ scaled_radii(radii, min_factor)
+ } else {
+ radii
+ }
+}
+
+/// Determine the four corner radii of a border.
+///
+/// Radii may either be absolute or relative to the absolute bounds.
+/// Each corner radius has a width and a height which may differ.
+/// Lastly overlapping radii are shrank so they don't collide anymore.
+pub fn radii(abs_bounds: Rect<Au>, border_style: &Border) -> BorderRadius {
+ // TODO(cgaebel): Support border radii even in the case of multiple border widths.
+ // This is an extension of supporting elliptical radii. For now, all percentage
+ // radii will be relative to the width.
+
+ overlapping_radii(
+ abs_bounds.size.to_layout(),
+ BorderRadius {
+ top_left: corner_radius(border_style.border_top_left_radius, abs_bounds.size)
+ .to_layout(),
+ top_right: corner_radius(border_style.border_top_right_radius, abs_bounds.size)
+ .to_layout(),
+ bottom_right: corner_radius(border_style.border_bottom_right_radius, abs_bounds.size)
+ .to_layout(),
+ bottom_left: corner_radius(border_style.border_bottom_left_radius, abs_bounds.size)
+ .to_layout(),
+ },
+ )
+}
+
+/// Calculates radii for the inner side.
+///
+/// Radii usually describe the outer side of a border but for the lines to look nice
+/// the inner radii need to be smaller depending on the line width.
+///
+/// This is used to determine clipping areas.
+pub fn inner_radii(mut radii: BorderRadius, offsets: SideOffsets2D<Au>) -> BorderRadius {
+ fn inner_length(x: f32, offset: Au) -> f32 {
+ 0.0_f32.max(x - offset.to_f32_px())
+ }
+ radii.top_left.width = inner_length(radii.top_left.width, offsets.left);
+ radii.bottom_left.width = inner_length(radii.bottom_left.width, offsets.left);
+
+ radii.top_right.width = inner_length(radii.top_right.width, offsets.right);
+ radii.bottom_right.width = inner_length(radii.bottom_right.width, offsets.right);
+
+ radii.top_left.height = inner_length(radii.top_left.height, offsets.top);
+ radii.top_right.height = inner_length(radii.top_right.height, offsets.top);
+
+ radii.bottom_left.height = inner_length(radii.bottom_left.height, offsets.bottom);
+ radii.bottom_right.height = inner_length(radii.bottom_right.height, offsets.bottom);
+ radii
+}
+
+/// Creates a four-sided border with square corners and uniform color and width.
+pub fn simple(color: ColorF, style: BorderStyle) -> NormalBorder {
+ let side = BorderSide { color, style };
+ NormalBorder {
+ left: side,
+ right: side,
+ top: side,
+ bottom: side,
+ radius: BorderRadius::zero(),
+ do_aa: true,
+ }
+}
+
+fn side_image_outset(outset: NonNegativeLengthOrNumber, border_width: Au) -> Au {
+ match outset {
+ NonNegativeLengthOrNumber::Length(length) => length.into(),
+ NonNegativeLengthOrNumber::Number(factor) => border_width.scale_by(factor.0),
+ }
+}
+
+/// Compute the additional border-image area.
+pub fn image_outset(outset: BorderImageOutset, border: SideOffsets2D<Au>) -> SideOffsets2D<Au> {
+ SideOffsets2D::new(
+ side_image_outset(outset.0, border.top),
+ side_image_outset(outset.1, border.right),
+ side_image_outset(outset.2, border.bottom),
+ side_image_outset(outset.3, border.left),
+ )
+}
+
+fn side_image_width(
+ border_image_width: BorderImageSideWidth,
+ border_width: f32,
+ total_length: Au,
+) -> f32 {
+ match border_image_width {
+ BorderImageSideWidth::LengthPercentage(v) => v.to_used_value(total_length).to_f32_px(),
+ BorderImageSideWidth::Number(x) => border_width * x.0,
+ BorderImageSideWidth::Auto => border_width,
+ }
+}
+
+pub fn image_width(
+ width: &BorderImageWidth,
+ border: LayoutSideOffsets,
+ border_area: Size2D<Au>,
+) -> LayoutSideOffsets {
+ LayoutSideOffsets::new(
+ side_image_width(width.0, border.top, border_area.height),
+ side_image_width(width.1, border.right, border_area.width),
+ side_image_width(width.2, border.bottom, border_area.height),
+ side_image_width(width.3, border.left, border_area.width),
+ )
+}
+
+fn resolve_percentage(value: NonNegative<NumberOrPercentage>, length: i32) -> i32 {
+ match value.0 {
+ NumberOrPercentage::Percentage(p) => (p.0 * length as f32).round() as i32,
+ NumberOrPercentage::Number(n) => n.round() as i32,
+ }
+}
+
+pub fn image_slice(
+ border_image_slice: &StyleRect<NonNegative<NumberOrPercentage>>,
+ width: i32,
+ height: i32,
+) -> SideOffsets2D<i32> {
+ SideOffsets2D::new(
+ resolve_percentage(border_image_slice.0, height),
+ resolve_percentage(border_image_slice.1, width),
+ resolve_percentage(border_image_slice.2, height),
+ resolve_percentage(border_image_slice.3, width),
+ )
+}
diff --git a/components/layout_2020/display_list/builder.rs b/components/layout_2020/display_list/builder.rs
new file mode 100644
index 00000000000..e6ead2505ca
--- /dev/null
+++ b/components/layout_2020/display_list/builder.rs
@@ -0,0 +1,3024 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+//! Builds display lists from flows and fragments.
+//!
+//! Other browser engines sometimes call this "painting", but it is more accurately called display
+//! list building, as the actual painting does not happen here—only deciding *what* we're going to
+//! paint.
+
+use crate::block::BlockFlow;
+use crate::context::LayoutContext;
+use crate::display_list::background::{self, get_cyclic};
+use crate::display_list::border;
+use crate::display_list::gradient;
+use crate::display_list::items::{self, BaseDisplayItem, ClipScrollNode};
+use crate::display_list::items::{ClipScrollNodeIndex, ClipScrollNodeType, ClippingAndScrolling};
+use crate::display_list::items::{ClippingRegion, DisplayItem, DisplayItemMetadata, DisplayList};
+use crate::display_list::items::{CommonDisplayItem, DisplayListSection};
+use crate::display_list::items::{IframeDisplayItem, OpaqueNode};
+use crate::display_list::items::{PopAllTextShadowsDisplayItem, PushTextShadowDisplayItem};
+use crate::display_list::items::{StackingContext, StackingContextType, StickyFrameData};
+use crate::display_list::items::{TextOrientation, WebRenderImageInfo};
+use crate::display_list::ToLayout;
+use crate::flow::{BaseFlow, Flow, FlowFlags};
+use crate::flow_ref::FlowRef;
+use crate::fragment::SpecificFragmentInfo;
+use crate::fragment::{CanvasFragmentSource, CoordinateSystem, Fragment, ScannedTextFragmentInfo};
+use crate::inline::InlineFragmentNodeFlags;
+use crate::model::MaybeAuto;
+use crate::table_cell::CollapsedBordersForCell;
+use app_units::{Au, AU_PER_PX};
+use canvas_traits::canvas::{CanvasMsg, FromLayoutMsg};
+use embedder_traits::Cursor;
+use euclid::{rect, Point2D, Rect, SideOffsets2D, Size2D, TypedRect, TypedSize2D};
+use fnv::FnvHashMap;
+use gfx::text::glyph::ByteIndex;
+use gfx::text::TextRun;
+use gfx_traits::{combine_id_with_fragment_type, FragmentType, StackingContextId};
+use ipc_channel::ipc;
+use msg::constellation_msg::PipelineId;
+use net_traits::image_cache::UsePlaceholder;
+use range::Range;
+use script_traits::IFrameSize;
+use servo_config::opts;
+use servo_geometry::{self, MaxRect};
+use std::default::Default;
+use std::f32;
+use std::mem;
+use std::sync::Arc;
+use style::computed_values::border_style::T as BorderStyle;
+use style::computed_values::overflow_x::T as StyleOverflow;
+use style::computed_values::pointer_events::T as PointerEvents;
+use style::computed_values::position::T as StylePosition;
+use style::computed_values::visibility::T as Visibility;
+use style::logical_geometry::{LogicalMargin, LogicalPoint, LogicalRect};
+use style::properties::{style_structs, ComputedValues};
+use style::servo::restyle_damage::ServoRestyleDamage;
+use style::values::computed::effects::SimpleShadow;
+use style::values::computed::image::{Image, ImageLayer};
+use style::values::computed::{Gradient, LengthOrAuto};
+use style::values::generics::background::BackgroundSize;
+use style::values::generics::image::{GradientKind, PaintWorklet};
+use style::values::specified::ui::CursorKind;
+use style::values::{Either, RGBA};
+use style_traits::ToCss;
+use webrender_api::units::{LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D};
+use webrender_api::{self, BorderDetails, BorderRadius, BorderSide, BoxShadowClipMode, ColorF};
+use webrender_api::{ColorU, ExternalScrollId, FilterOp, GlyphInstance, ImageRendering, LineStyle};
+use webrender_api::{NinePatchBorder, NinePatchBorderSource, NormalBorder};
+use webrender_api::{ScrollSensitivity, StickyOffsetBounds};
+
+static THREAD_TINT_COLORS: [ColorF; 8] = [
+ ColorF {
+ r: 6.0 / 255.0,
+ g: 153.0 / 255.0,
+ b: 198.0 / 255.0,
+ a: 0.7,
+ },
+ ColorF {
+ r: 255.0 / 255.0,
+ g: 212.0 / 255.0,
+ b: 83.0 / 255.0,
+ a: 0.7,
+ },
+ ColorF {
+ r: 116.0 / 255.0,
+ g: 29.0 / 255.0,
+ b: 109.0 / 255.0,
+ a: 0.7,
+ },
+ ColorF {
+ r: 204.0 / 255.0,
+ g: 158.0 / 255.0,
+ b: 199.0 / 255.0,
+ a: 0.7,
+ },
+ ColorF {
+ r: 242.0 / 255.0,
+ g: 46.0 / 255.0,
+ b: 121.0 / 255.0,
+ a: 0.7,
+ },
+ ColorF {
+ r: 116.0 / 255.0,
+ g: 203.0 / 255.0,
+ b: 196.0 / 255.0,
+ a: 0.7,
+ },
+ ColorF {
+ r: 255.0 / 255.0,
+ g: 249.0 / 255.0,
+ b: 201.0 / 255.0,
+ a: 0.7,
+ },
+ ColorF {
+ r: 137.0 / 255.0,
+ g: 196.0 / 255.0,
+ b: 78.0 / 255.0,
+ a: 0.7,
+ },
+];
+
+pub struct InlineNodeBorderInfo {
+ is_first_fragment_of_element: bool,
+ is_last_fragment_of_element: bool,
+}
+
+#[derive(Debug)]
+struct StackingContextInfo {
+ children: Vec<StackingContext>,
+ clip_scroll_nodes: Vec<ClipScrollNodeIndex>,
+ real_stacking_context_id: StackingContextId,
+}
+
+impl StackingContextInfo {
+ fn new(real_stacking_context_id: StackingContextId) -> StackingContextInfo {
+ StackingContextInfo {
+ children: Vec::new(),
+ clip_scroll_nodes: Vec::new(),
+ real_stacking_context_id,
+ }
+ }
+
+ fn take_children(&mut self) -> Vec<StackingContext> {
+ mem::replace(&mut self.children, Vec::new())
+ }
+}
+
+pub struct StackingContextCollectionState {
+ /// The PipelineId of this stacking context collection.
+ pub pipeline_id: PipelineId,
+
+ /// The root of the StackingContext tree.
+ pub root_stacking_context: StackingContext,
+
+ /// StackingContext and ClipScrollNode children for each StackingContext.
+ stacking_context_info: FnvHashMap<StackingContextId, StackingContextInfo>,
+
+ pub clip_scroll_nodes: Vec<ClipScrollNode>,
+
+ /// The current stacking context id, used to keep track of state when building.
+ /// recursively building and processing the display list.
+ pub current_stacking_context_id: StackingContextId,
+
+ /// The current reference frame ClipScrollNodeIndex.
+ pub current_real_stacking_context_id: StackingContextId,
+
+ /// The next stacking context id that we will assign to a stacking context.
+ pub next_stacking_context_id: StackingContextId,
+
+ /// The current reference frame id. This is used to assign items to the parent
+ /// reference frame when we encounter a fixed position stacking context.
+ pub current_parent_reference_frame_id: ClipScrollNodeIndex,
+
+ /// The current clip and scroll info, used to keep track of state when
+ /// recursively building and processing the display list.
+ pub current_clipping_and_scrolling: ClippingAndScrolling,
+
+ /// The clip and scroll info of the first ancestor which defines a containing block.
+ /// This is necessary because absolutely positioned items should be clipped
+ /// by their containing block's scroll root.
+ pub containing_block_clipping_and_scrolling: ClippingAndScrolling,
+
+ /// A stack of clips used to cull display list entries that are outside the
+ /// rendered region.
+ pub clip_stack: Vec<Rect<Au>>,
+
+ /// A stack of clips used to cull display list entries that are outside the
+ /// rendered region, but only collected at containing block boundaries.
+ pub containing_block_clip_stack: Vec<Rect<Au>>,
+
+ /// The flow parent's content box, used to calculate sticky constraints.
+ parent_stacking_relative_content_box: Rect<Au>,
+}
+
+impl StackingContextCollectionState {
+ pub fn new(pipeline_id: PipelineId) -> StackingContextCollectionState {
+ let root_clip_indices =
+ ClippingAndScrolling::simple(ClipScrollNodeIndex::root_scroll_node());
+
+ let mut stacking_context_info = FnvHashMap::default();
+ stacking_context_info.insert(
+ StackingContextId::root(),
+ StackingContextInfo::new(StackingContextId::root()),
+ );
+
+ // We add two empty nodes to represent the WebRender root reference frame and
+ // root scroll nodes. WebRender adds these automatically and we add them here
+ // so that the ids in the array match up with the ones we assign during display
+ // list building. We ignore these two nodes during conversion to WebRender
+ // display lists.
+ let clip_scroll_nodes = vec![ClipScrollNode::placeholder(), ClipScrollNode::placeholder()];
+
+ StackingContextCollectionState {
+ pipeline_id: pipeline_id,
+ root_stacking_context: StackingContext::root(),
+ stacking_context_info,
+ clip_scroll_nodes,
+ current_stacking_context_id: StackingContextId::root(),
+ current_real_stacking_context_id: StackingContextId::root(),
+ next_stacking_context_id: StackingContextId::root().next(),
+ current_parent_reference_frame_id: ClipScrollNodeIndex::root_reference_frame(),
+ current_clipping_and_scrolling: root_clip_indices,
+ containing_block_clipping_and_scrolling: root_clip_indices,
+ clip_stack: Vec::new(),
+ containing_block_clip_stack: Vec::new(),
+ parent_stacking_relative_content_box: Rect::zero(),
+ }
+ }
+
+ fn allocate_stacking_context_info(
+ &mut self,
+ stacking_context_type: StackingContextType,
+ ) -> StackingContextId {
+ let next_stacking_context_id = self.next_stacking_context_id.next();
+ let allocated_id =
+ mem::replace(&mut self.next_stacking_context_id, next_stacking_context_id);
+
+ let real_stacking_context_id = match stacking_context_type {
+ StackingContextType::Real => allocated_id,
+ _ => self.current_real_stacking_context_id,
+ };
+
+ self.stacking_context_info.insert(
+ allocated_id,
+ StackingContextInfo::new(real_stacking_context_id),
+ );
+
+ allocated_id
+ }
+
+ fn add_stacking_context(
+ &mut self,
+ parent_id: StackingContextId,
+ stacking_context: StackingContext,
+ ) {
+ self.stacking_context_info
+ .get_mut(&parent_id)
+ .unwrap()
+ .children
+ .push(stacking_context);
+ }
+
+ fn add_clip_scroll_node(&mut self, clip_scroll_node: ClipScrollNode) -> ClipScrollNodeIndex {
+ let is_placeholder = clip_scroll_node.is_placeholder();
+
+ self.clip_scroll_nodes.push(clip_scroll_node);
+ let index = ClipScrollNodeIndex::new(self.clip_scroll_nodes.len() - 1);
+
+ // If this node is a placeholder node (currently just reference frames), then don't add
+ // it to the stacking context list. Placeholder nodes are created automatically by
+ // WebRender and we don't want to explicitly create them in the display list. The node
+ // is just there to take up a spot in the global list of ClipScrollNodes.
+ if !is_placeholder {
+ // We want the scroll root to be defined before any possible item that could use it,
+ // so we make sure that it is added to the beginning of the parent "real" (non-pseudo)
+ // stacking context. This ensures that item reordering will not result in an item using
+ // the scroll root before it is defined.
+ self.stacking_context_info
+ .get_mut(&self.current_real_stacking_context_id)
+ .unwrap()
+ .clip_scroll_nodes
+ .push(index);
+ }
+
+ index
+ }
+}
+
+pub struct DisplayListBuildState<'a> {
+ /// A LayoutContext reference important for creating WebRender images.
+ pub layout_context: &'a LayoutContext<'a>,
+
+ /// The root of the StackingContext tree.
+ pub root_stacking_context: StackingContext,
+
+ /// StackingContext and ClipScrollNode children for each StackingContext.
+ stacking_context_info: FnvHashMap<StackingContextId, StackingContextInfo>,
+
+ /// A vector of ClipScrollNodes which will be given ids during WebRender DL conversion.
+ pub clip_scroll_nodes: Vec<ClipScrollNode>,
+
+ /// The items in this display list.
+ pub items: FnvHashMap<StackingContextId, Vec<DisplayItem>>,
+
+ /// Whether or not we are processing an element that establishes scrolling overflow. Used
+ /// to determine what ClipScrollNode to place backgrounds and borders into.
+ pub processing_scrolling_overflow_element: bool,
+
+ /// The current stacking context id, used to keep track of state when building.
+ /// recursively building and processing the display list.
+ pub current_stacking_context_id: StackingContextId,
+
+ /// The current clip and scroll info, used to keep track of state when
+ /// recursively building and processing the display list.
+ pub current_clipping_and_scrolling: ClippingAndScrolling,
+
+ /// Vector containing iframe sizes, used to inform the constellation about
+ /// new iframe sizes
+ pub iframe_sizes: Vec<IFrameSize>,
+
+ /// Stores text runs to answer text queries used to place a cursor inside text.
+ pub indexable_text: IndexableText,
+}
+
+impl<'a> DisplayListBuildState<'a> {
+ pub fn new(
+ layout_context: &'a LayoutContext,
+ state: StackingContextCollectionState,
+ ) -> DisplayListBuildState<'a> {
+ DisplayListBuildState {
+ layout_context: layout_context,
+ root_stacking_context: state.root_stacking_context,
+ items: FnvHashMap::default(),
+ stacking_context_info: state.stacking_context_info,
+ clip_scroll_nodes: state.clip_scroll_nodes,
+ processing_scrolling_overflow_element: false,
+ current_stacking_context_id: StackingContextId::root(),
+ current_clipping_and_scrolling: ClippingAndScrolling::simple(
+ ClipScrollNodeIndex::root_scroll_node(),
+ ),
+ iframe_sizes: Vec::new(),
+ indexable_text: IndexableText::default(),
+ }
+ }
+
+ pub fn add_display_item(&mut self, display_item: DisplayItem) {
+ let items = self
+ .items
+ .entry(display_item.stacking_context_id())
+ .or_insert(Vec::new());
+ items.push(display_item);
+ }
+
+ fn add_image_item(&mut self, base: BaseDisplayItem, item: webrender_api::ImageDisplayItem) {
+ if item.stretch_size == LayoutSize::zero() {
+ return;
+ }
+ self.add_display_item(DisplayItem::Image(CommonDisplayItem::new(base, item)))
+ }
+
+ fn parent_clip_scroll_node_index(&self, index: ClipScrollNodeIndex) -> ClipScrollNodeIndex {
+ if index.is_root_scroll_node() {
+ return index;
+ }
+
+ self.clip_scroll_nodes[index.to_index()].parent_index
+ }
+
+ fn is_background_or_border_of_clip_scroll_node(&self, section: DisplayListSection) -> bool {
+ (section == DisplayListSection::BackgroundAndBorders ||
+ section == DisplayListSection::BlockBackgroundsAndBorders) &&
+ self.processing_scrolling_overflow_element
+ }
+
+ pub fn create_base_display_item(
+ &self,
+ clip_rect: Rect<Au>,
+ node: OpaqueNode,
+ cursor: Option<Cursor>,
+ section: DisplayListSection,
+ ) -> BaseDisplayItem {
+ let clipping_and_scrolling = if self.is_background_or_border_of_clip_scroll_node(section) {
+ ClippingAndScrolling::simple(
+ self.parent_clip_scroll_node_index(self.current_clipping_and_scrolling.scrolling),
+ )
+ } else {
+ self.current_clipping_and_scrolling
+ };
+ self.create_base_display_item_with_clipping_and_scrolling(
+ clip_rect,
+ node,
+ cursor,
+ section,
+ clipping_and_scrolling,
+ )
+ }
+
+ fn create_base_display_item_with_clipping_and_scrolling(
+ &self,
+ clip_rect: Rect<Au>,
+ node: OpaqueNode,
+ cursor: Option<Cursor>,
+ section: DisplayListSection,
+ clipping_and_scrolling: ClippingAndScrolling,
+ ) -> BaseDisplayItem {
+ BaseDisplayItem::new(
+ DisplayItemMetadata {
+ node,
+ // Store cursor id in display list.
+ pointing: cursor.map(|x| x as u16),
+ },
+ clip_rect.to_layout(),
+ section,
+ self.current_stacking_context_id,
+ clipping_and_scrolling,
+ )
+ }
+
+ fn add_late_clip_node(&mut self, rect: LayoutRect, radii: BorderRadius) -> ClipScrollNodeIndex {
+ let mut clip = ClippingRegion::from_rect(rect);
+ clip.intersect_with_rounded_rect(rect, radii);
+
+ let node = ClipScrollNode {
+ parent_index: self.current_clipping_and_scrolling.scrolling,
+ clip,
+ content_rect: LayoutRect::zero(), // content_rect isn't important for clips.
+ node_type: ClipScrollNodeType::Clip,
+ };
+
+ // We want the scroll root to be defined before any possible item that could use it,
+ // so we make sure that it is added to the beginning of the parent "real" (non-pseudo)
+ // stacking context. This ensures that item reordering will not result in an item using
+ // the scroll root before it is defined.
+ self.clip_scroll_nodes.push(node);
+ let index = ClipScrollNodeIndex::new(self.clip_scroll_nodes.len() - 1);
+ let real_stacking_context_id =
+ self.stacking_context_info[&self.current_stacking_context_id].real_stacking_context_id;
+ self.stacking_context_info
+ .get_mut(&real_stacking_context_id)
+ .unwrap()
+ .clip_scroll_nodes
+ .push(index);
+
+ index
+ }
+
+ pub fn to_display_list(mut self) -> DisplayList {
+ let mut list = Vec::new();
+ let root_context = mem::replace(&mut self.root_stacking_context, StackingContext::root());
+
+ self.to_display_list_for_stacking_context(&mut list, root_context);
+
+ DisplayList {
+ list: list,
+ clip_scroll_nodes: self.clip_scroll_nodes,
+ }
+ }
+
+ fn to_display_list_for_stacking_context(
+ &mut self,
+ list: &mut Vec<DisplayItem>,
+ stacking_context: StackingContext,
+ ) {
+ let mut child_items = self
+ .items
+ .remove(&stacking_context.id)
+ .unwrap_or(Vec::new());
+ child_items.sort_by(|a, b| a.base().section.cmp(&b.base().section));
+ child_items.reverse();
+
+ let mut info = self
+ .stacking_context_info
+ .remove(&stacking_context.id)
+ .unwrap();
+
+ info.children.sort();
+
+ if stacking_context.context_type != StackingContextType::Real {
+ list.extend(
+ info.clip_scroll_nodes
+ .into_iter()
+ .map(|index| index.to_define_item()),
+ );
+ self.to_display_list_for_items(list, child_items, info.children);
+ } else {
+ let (push_item, pop_item) = stacking_context.to_display_list_items();
+ list.push(push_item);
+ list.extend(
+ info.clip_scroll_nodes
+ .into_iter()
+ .map(|index| index.to_define_item()),
+ );
+ self.to_display_list_for_items(list, child_items, info.children);
+ list.push(pop_item);
+ }
+ }
+
+ fn to_display_list_for_items(
+ &mut self,
+ list: &mut Vec<DisplayItem>,
+ mut child_items: Vec<DisplayItem>,
+ child_stacking_contexts: Vec<StackingContext>,
+ ) {
+ // Properly order display items that make up a stacking context. "Steps" here
+ // refer to the steps in CSS 2.1 Appendix E.
+ // Steps 1 and 2: Borders and background for the root.
+ while child_items.last().map_or(false, |child| {
+ child.section() == DisplayListSection::BackgroundAndBorders
+ }) {
+ list.push(child_items.pop().unwrap());
+ }
+
+ // Step 3: Positioned descendants with negative z-indices.
+ let mut child_stacking_contexts = child_stacking_contexts.into_iter().peekable();
+ while child_stacking_contexts
+ .peek()
+ .map_or(false, |child| child.z_index < 0)
+ {
+ let context = child_stacking_contexts.next().unwrap();
+ self.to_display_list_for_stacking_context(list, context);
+ }
+
+ // Step 4: Block backgrounds and borders.
+ while child_items.last().map_or(false, |child| {
+ child.section() == DisplayListSection::BlockBackgroundsAndBorders
+ }) {
+ list.push(child_items.pop().unwrap());
+ }
+
+ // Step 5: Floats.
+ while child_stacking_contexts.peek().map_or(false, |child| {
+ child.context_type == StackingContextType::PseudoFloat
+ }) {
+ let context = child_stacking_contexts.next().unwrap();
+ self.to_display_list_for_stacking_context(list, context);
+ }
+
+ // Step 6 & 7: Content and inlines that generate stacking contexts.
+ while child_items.last().map_or(false, |child| {
+ child.section() == DisplayListSection::Content
+ }) {
+ list.push(child_items.pop().unwrap());
+ }
+
+ // Step 8 & 9: Positioned descendants with nonnegative, numeric z-indices.
+ for child in child_stacking_contexts {
+ self.to_display_list_for_stacking_context(list, child);
+ }
+
+ // Step 10: Outlines.
+ for item in child_items.drain(..) {
+ list.push(item);
+ }
+ }
+
+ fn clipping_and_scrolling_scope<R, F: FnOnce(&mut Self) -> R>(&mut self, function: F) -> R {
+ let previous_clipping_and_scrolling = self.current_clipping_and_scrolling;
+ let ret = function(self);
+ self.current_clipping_and_scrolling = previous_clipping_and_scrolling;
+ ret
+ }
+}
+
+/// The logical width of an insertion point: at the moment, a one-pixel-wide line.
+const INSERTION_POINT_LOGICAL_WIDTH: Au = Au(1 * AU_PER_PX);
+
+/// Get the border radius for the rectangle inside of a rounded border. This is useful
+/// for building the clip for the content inside the border.
+fn build_border_radius_for_inner_rect(
+ outer_rect: Rect<Au>,
+ style: &ComputedValues,
+) -> BorderRadius {
+ let radii = border::radii(outer_rect, style.get_border());
+ if radii.is_zero() {
+ return radii;
+ }
+
+ // Since we are going to using the inner rectangle (outer rectangle minus
+ // border width), we need to adjust to border radius so that we are smaller
+ // rectangle with the same border curve.
+ let border_widths = style.logical_border_width().to_physical(style.writing_mode);
+ border::inner_radii(radii, border_widths)
+}
+
+impl Fragment {
+ pub fn collect_stacking_contexts_for_blocklike_fragment(
+ &mut self,
+ state: &mut StackingContextCollectionState,
+ ) -> bool {
+ match self.specific {
+ SpecificFragmentInfo::InlineBlock(ref mut block_flow) => {
+ let block_flow = FlowRef::deref_mut(&mut block_flow.flow_ref);
+ block_flow.collect_stacking_contexts(state);
+ true
+ },
+ SpecificFragmentInfo::InlineAbsoluteHypothetical(ref mut block_flow) => {
+ let block_flow = FlowRef::deref_mut(&mut block_flow.flow_ref);
+ block_flow.collect_stacking_contexts(state);
+ true
+ },
+ SpecificFragmentInfo::InlineAbsolute(ref mut block_flow) => {
+ let block_flow = FlowRef::deref_mut(&mut block_flow.flow_ref);
+ block_flow.collect_stacking_contexts(state);
+ true
+ },
+ // FIXME: In the future, if #15144 is fixed we can remove this case. See #18510.
+ SpecificFragmentInfo::TruncatedFragment(ref mut info) => info
+ .full
+ .collect_stacking_contexts_for_blocklike_fragment(state),
+ _ => false,
+ }
+ }
+
+ pub fn create_stacking_context_for_inline_block(
+ &mut self,
+ base: &BaseFlow,
+ state: &mut StackingContextCollectionState,
+ ) -> bool {
+ self.stacking_context_id = state.allocate_stacking_context_info(StackingContextType::Real);
+
+ let established_reference_frame = if self.can_establish_reference_frame() {
+ // WebRender currently creates reference frames automatically, so just add
+ // a placeholder node to allocate a ClipScrollNodeIndex for this reference frame.
+ self.established_reference_frame =
+ Some(state.add_clip_scroll_node(ClipScrollNode::placeholder()));
+ self.established_reference_frame
+ } else {
+ None
+ };
+
+ let current_stacking_context_id = state.current_stacking_context_id;
+ let stacking_context = self.create_stacking_context(
+ self.stacking_context_id,
+ &base,
+ StackingContextType::Real,
+ established_reference_frame,
+ state.current_clipping_and_scrolling,
+ );
+ state.add_stacking_context(current_stacking_context_id, stacking_context);
+ true
+ }
+
+ /// Adds the display items necessary to paint the background of this fragment to the display
+ /// list if necessary.
+ fn build_display_list_for_background_if_applicable(
+ &self,
+ state: &mut DisplayListBuildState,
+ style: &ComputedValues,
+ display_list_section: DisplayListSection,
+ absolute_bounds: Rect<Au>,
+ ) {
+ let background = style.get_background();
+ let background_color = style.resolve_color(background.background_color);
+ // XXXManishearth the below method should ideally use an iterator over
+ // backgrounds
+ self.build_display_list_for_background_if_applicable_with_background(
+ state,
+ style,
+ background,
+ background_color,
+ display_list_section,
+ absolute_bounds,
+ )
+ }
+
+ /// Same as build_display_list_for_background_if_applicable, but lets you
+ /// override the actual background used
+ fn build_display_list_for_background_if_applicable_with_background(
+ &self,
+ state: &mut DisplayListBuildState,
+ style: &ComputedValues,
+ background: &style_structs::Background,
+ background_color: RGBA,
+ display_list_section: DisplayListSection,
+ 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".
+
+ // Quote from CSS Backgrounds and Borders Module Level 3:
+ //
+ // > The background color is clipped according to the background-clip value associated
+ // > with the bottom-most background image layer.
+ let last_background_image_index = background.background_image.0.len() - 1;
+ let color_clip = *get_cyclic(&background.background_clip.0, last_background_image_index);
+ let (bounds, border_radii) = background::clip(
+ color_clip,
+ absolute_bounds,
+ style.logical_border_width().to_physical(style.writing_mode),
+ self.border_padding.to_physical(self.style.writing_mode),
+ border::radii(absolute_bounds, style.get_border()),
+ );
+
+ state.clipping_and_scrolling_scope(|state| {
+ if !border_radii.is_zero() {
+ let clip_id = state.add_late_clip_node(bounds.to_layout(), border_radii);
+ state.current_clipping_and_scrolling = ClippingAndScrolling::simple(clip_id);
+ }
+
+ let base = state.create_base_display_item(
+ bounds,
+ self.node,
+ get_cursor(&style, Cursor::Default),
+ display_list_section,
+ );
+ state.add_display_item(DisplayItem::Rectangle(CommonDisplayItem::new(
+ base,
+ webrender_api::RectangleDisplayItem {
+ color: background_color.to_layout(),
+ common: items::empty_common_item_properties(),
+ },
+ )));
+ });
+
+ // 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();
+ for (i, background_image) in background.background_image.0.iter().enumerate().rev() {
+ let background_image = match *background_image {
+ ImageLayer::None => continue,
+ ImageLayer::Image(ref image) => image,
+ };
+
+ match *background_image {
+ Image::Gradient(ref gradient) => {
+ self.build_display_list_for_background_gradient(
+ state,
+ display_list_section,
+ absolute_bounds,
+ gradient,
+ style,
+ i,
+ );
+ },
+ Image::Url(ref image_url) => {
+ if let Some(url) = image_url.url() {
+ let webrender_image = state.layout_context.get_webrender_image_for_url(
+ self.node,
+ url.clone(),
+ UsePlaceholder::No,
+ );
+ if let Some(webrender_image) = webrender_image {
+ self.build_display_list_for_webrender_image(
+ state,
+ style,
+ display_list_section,
+ absolute_bounds,
+ webrender_image,
+ i,
+ );
+ }
+ }
+ },
+ Image::PaintWorklet(ref paint_worklet) => {
+ let bounding_box = self.border_box - style.logical_border_width();
+ let bounding_box_size = bounding_box.size.to_physical(style.writing_mode);
+ let background_size =
+ get_cyclic(&style.get_background().background_size.0, i).clone();
+ let size = match background_size {
+ BackgroundSize::ExplicitSize { width, height } => Size2D::new(
+ width
+ .to_used_value(bounding_box_size.width)
+ .unwrap_or(bounding_box_size.width),
+ height
+ .to_used_value(bounding_box_size.height)
+ .unwrap_or(bounding_box_size.height),
+ ),
+ _ => bounding_box_size,
+ };
+ let webrender_image = self.get_webrender_image_for_paint_worklet(
+ state,
+ style,
+ paint_worklet,
+ size,
+ );
+ if let Some(webrender_image) = webrender_image {
+ self.build_display_list_for_webrender_image(
+ state,
+ style,
+ display_list_section,
+ absolute_bounds,
+ webrender_image,
+ i,
+ );
+ }
+ },
+ Image::Rect(_) => {
+ // TODO: Implement `-moz-image-rect`
+ },
+ Image::Element(_) => {
+ // TODO: Implement `-moz-element`
+ },
+ }
+ }
+ }
+
+ /// Adds the display items necessary to paint a webrender image of this fragment to the
+ /// appropriate section of the display list.
+ fn build_display_list_for_webrender_image(
+ &self,
+ state: &mut DisplayListBuildState,
+ style: &ComputedValues,
+ display_list_section: DisplayListSection,
+ absolute_bounds: Rect<Au>,
+ webrender_image: WebRenderImageInfo,
+ index: usize,
+ ) {
+ debug!("(building display list) building background image");
+ if webrender_image.key.is_none() {
+ return;
+ }
+
+ let image = Size2D::new(
+ Au::from_px(webrender_image.width as i32),
+ Au::from_px(webrender_image.height as i32),
+ );
+ let placement = background::placement(
+ style.get_background(),
+ state.layout_context.shared_context().viewport_size(),
+ absolute_bounds,
+ Some(image),
+ style.logical_border_width().to_physical(style.writing_mode),
+ self.border_padding.to_physical(self.style.writing_mode),
+ border::radii(absolute_bounds, style.get_border()),
+ index,
+ );
+
+ state.clipping_and_scrolling_scope(|state| {
+ if !placement.clip_radii.is_zero() {
+ let clip_id =
+ state.add_late_clip_node(placement.clip_rect.to_layout(), placement.clip_radii);
+ state.current_clipping_and_scrolling = ClippingAndScrolling::simple(clip_id);
+ }
+
+ // Create the image display item.
+ let base = state.create_base_display_item(
+ placement.clip_rect,
+ self.node,
+ get_cursor(&style, Cursor::Default),
+ display_list_section,
+ );
+
+ debug!("(building display list) adding background image.");
+ state.add_image_item(
+ base,
+ webrender_api::ImageDisplayItem {
+ bounds: placement.bounds.to_f32_px(),
+ common: items::empty_common_item_properties(),
+ image_key: webrender_image.key.unwrap(),
+ stretch_size: placement.tile_size.to_layout(),
+ tile_spacing: placement.tile_spacing.to_layout(),
+ image_rendering: style.get_inherited_box().image_rendering.to_layout(),
+ alpha_type: webrender_api::AlphaType::PremultipliedAlpha,
+ color: webrender_api::ColorF::WHITE,
+ },
+ );
+ });
+ }
+
+ /// Calculates the webrender image for a paint worklet.
+ /// Returns None if the worklet is not registered.
+ /// If the worklet has missing image URLs, it passes them to the image cache for loading.
+ fn get_webrender_image_for_paint_worklet(
+ &self,
+ state: &mut DisplayListBuildState,
+ style: &ComputedValues,
+ paint_worklet: &PaintWorklet,
+ size_in_au: Size2D<Au>,
+ ) -> Option<WebRenderImageInfo> {
+ let device_pixel_ratio = state.layout_context.style_context.device_pixel_ratio();
+ let size_in_px =
+ TypedSize2D::new(size_in_au.width.to_f32_px(), size_in_au.height.to_f32_px());
+
+ // TODO: less copying.
+ let name = paint_worklet.name.clone();
+ let arguments = paint_worklet
+ .arguments
+ .iter()
+ .map(|argument| argument.to_css_string())
+ .collect();
+
+ let draw_result = match state.layout_context.registered_painters.get(&name) {
+ Some(painter) => {
+ debug!(
+ "Drawing a paint image {}({},{}).",
+ name, size_in_px.width, size_in_px.height
+ );
+ let properties = painter
+ .properties()
+ .iter()
+ .filter_map(|(name, id)| id.as_shorthand().err().map(|id| (name, id)))
+ .map(|(name, id)| (name.clone(), style.computed_value_to_string(id)))
+ .collect();
+ painter.draw_a_paint_image(size_in_px, device_pixel_ratio, properties, arguments)
+ },
+ None => {
+ debug!("Worklet {} called before registration.", name);
+ return None;
+ },
+ };
+
+ if let Ok(draw_result) = draw_result {
+ let webrender_image = WebRenderImageInfo {
+ width: draw_result.width,
+ height: draw_result.height,
+ key: draw_result.image_key,
+ };
+
+ for url in draw_result.missing_image_urls.into_iter() {
+ debug!("Requesting missing image URL {}.", url);
+ state.layout_context.get_webrender_image_for_url(
+ self.node,
+ url,
+ UsePlaceholder::No,
+ );
+ }
+ Some(webrender_image)
+ } else {
+ None
+ }
+ }
+
+ /// Adds the display items necessary to paint the background linear gradient of this fragment
+ /// to the appropriate section of the display list.
+ fn build_display_list_for_background_gradient(
+ &self,
+ state: &mut DisplayListBuildState,
+ display_list_section: DisplayListSection,
+ absolute_bounds: Rect<Au>,
+ gradient: &Gradient,
+ style: &ComputedValues,
+ index: usize,
+ ) {
+ let placement = background::placement(
+ style.get_background(),
+ state.layout_context.shared_context().viewport_size(),
+ absolute_bounds,
+ None,
+ style.logical_border_width().to_physical(style.writing_mode),
+ self.border_padding.to_physical(self.style.writing_mode),
+ border::radii(absolute_bounds, style.get_border()),
+ index,
+ );
+
+ state.clipping_and_scrolling_scope(|state| {
+ if !placement.clip_radii.is_zero() {
+ let clip_id =
+ state.add_late_clip_node(placement.clip_rect.to_layout(), placement.clip_radii);
+ state.current_clipping_and_scrolling = ClippingAndScrolling::simple(clip_id);
+ }
+
+ let base = state.create_base_display_item(
+ placement.clip_rect,
+ self.node,
+ get_cursor(&style, Cursor::Default),
+ display_list_section,
+ );
+
+ let display_item = match gradient.kind {
+ GradientKind::Linear(angle_or_corner) => {
+ let (gradient, stops) = gradient::linear(
+ style,
+ placement.tile_size,
+ &gradient.items[..],
+ angle_or_corner,
+ gradient.repeating,
+ );
+ let item = webrender_api::GradientDisplayItem {
+ gradient,
+ bounds: placement.bounds.to_f32_px(),
+ common: items::empty_common_item_properties(),
+ tile_size: placement.tile_size.to_layout(),
+ tile_spacing: placement.tile_spacing.to_layout(),
+ };
+ DisplayItem::Gradient(CommonDisplayItem::with_data(base, item, stops))
+ },
+ GradientKind::Radial(shape, center) => {
+ let (gradient, stops) = gradient::radial(
+ style,
+ placement.tile_size,
+ &gradient.items[..],
+ shape,
+ center,
+ gradient.repeating,
+ );
+ let item = webrender_api::RadialGradientDisplayItem {
+ gradient,
+ bounds: placement.bounds.to_f32_px(),
+ common: items::empty_common_item_properties(),
+ tile_size: placement.tile_size.to_layout(),
+ tile_spacing: placement.tile_spacing.to_layout(),
+ };
+ DisplayItem::RadialGradient(CommonDisplayItem::with_data(base, item, stops))
+ },
+ };
+ state.add_display_item(display_item);
+ });
+ }
+
+ /// Adds the display items necessary to paint the box shadow of this fragment to the display
+ /// list if necessary.
+ fn build_display_list_for_box_shadow_if_applicable(
+ &self,
+ state: &mut DisplayListBuildState,
+ style: &ComputedValues,
+ display_list_section: DisplayListSection,
+ absolute_bounds: Rect<Au>,
+ clip: Rect<Au>,
+ ) {
+ // NB: According to CSS-BACKGROUNDS, box shadows render in *reverse* order (front to back).
+ for box_shadow in style.get_effects().box_shadow.0.iter().rev() {
+ let base = state.create_base_display_item(
+ clip,
+ self.node,
+ get_cursor(&style, Cursor::Default),
+ display_list_section,
+ );
+ let border_radius = border::radii(absolute_bounds, style.get_border());
+ state.add_display_item(DisplayItem::BoxShadow(CommonDisplayItem::new(
+ base,
+ webrender_api::BoxShadowDisplayItem {
+ common: items::empty_common_item_properties(),
+ box_bounds: absolute_bounds.to_layout(),
+ color: style.resolve_color(box_shadow.base.color).to_layout(),
+ offset: LayoutVector2D::new(
+ box_shadow.base.horizontal.px(),
+ box_shadow.base.vertical.px(),
+ ),
+ blur_radius: box_shadow.base.blur.px(),
+ spread_radius: box_shadow.spread.px(),
+ border_radius: border_radius,
+ clip_mode: if box_shadow.inset {
+ BoxShadowClipMode::Inset
+ } else {
+ BoxShadowClipMode::Outset
+ },
+ },
+ )));
+ }
+ }
+
+ /// Adds the display items necessary to paint the borders of this fragment to a display list if
+ /// necessary.
+ fn build_display_list_for_borders_if_applicable(
+ &self,
+ state: &mut DisplayListBuildState,
+ style: &ComputedValues,
+ inline_info: Option<InlineNodeBorderInfo>,
+ border_painting_mode: BorderPaintingMode,
+ mut bounds: Rect<Au>,
+ display_list_section: DisplayListSection,
+ clip: Rect<Au>,
+ ) {
+ let mut border = style.logical_border_width();
+
+ if let Some(inline_info) = inline_info {
+ modify_border_width_for_inline_sides(&mut border, inline_info);
+ }
+
+ match border_painting_mode {
+ BorderPaintingMode::Separate => {},
+ BorderPaintingMode::Collapse(collapsed_borders) => {
+ collapsed_borders.adjust_border_widths_for_painting(&mut border)
+ },
+ BorderPaintingMode::Hidden => return,
+ }
+
+ let border_style_struct = style.get_border();
+ let mut colors = SideOffsets2D::new(
+ border_style_struct.border_top_color,
+ border_style_struct.border_right_color,
+ border_style_struct.border_bottom_color,
+ border_style_struct.border_left_color,
+ );
+ let mut border_style = SideOffsets2D::new(
+ border_style_struct.border_top_style,
+ border_style_struct.border_right_style,
+ border_style_struct.border_bottom_style,
+ border_style_struct.border_left_style,
+ );
+
+ if let BorderPaintingMode::Collapse(collapsed_borders) = border_painting_mode {
+ collapsed_borders.adjust_border_colors_and_styles_for_painting(
+ &mut colors,
+ &mut border_style,
+ style.writing_mode,
+ );
+ }
+
+ // If this border collapses, then we draw outside the boundaries we were given.
+ if let BorderPaintingMode::Collapse(collapsed_borders) = border_painting_mode {
+ collapsed_borders.adjust_border_bounds_for_painting(&mut bounds, style.writing_mode)
+ }
+
+ // Append the border to the display list.
+ let base = state.create_base_display_item(
+ clip,
+ self.node,
+ get_cursor(&style, Cursor::Default),
+ display_list_section,
+ );
+
+ let border_radius = border::radii(bounds, border_style_struct);
+ let border_widths = border.to_physical(style.writing_mode);
+
+ if let ImageLayer::Image(ref image) = border_style_struct.border_image_source {
+ if self
+ .build_display_list_for_border_image(
+ state,
+ style,
+ base.clone(),
+ bounds,
+ image,
+ border_widths,
+ )
+ .is_some()
+ {
+ return;
+ }
+ // Fallback to rendering a solid border.
+ }
+ if border_widths == SideOffsets2D::zero() {
+ return;
+ }
+ let details = BorderDetails::Normal(NormalBorder {
+ left: BorderSide {
+ color: style.resolve_color(colors.left).to_layout(),
+ style: border_style.left.to_layout(),
+ },
+ right: BorderSide {
+ color: style.resolve_color(colors.right).to_layout(),
+ style: border_style.right.to_layout(),
+ },
+ top: BorderSide {
+ color: style.resolve_color(colors.top).to_layout(),
+ style: border_style.top.to_layout(),
+ },
+ bottom: BorderSide {
+ color: style.resolve_color(colors.bottom).to_layout(),
+ style: border_style.bottom.to_layout(),
+ },
+ radius: border_radius,
+ do_aa: true,
+ });
+ state.add_display_item(DisplayItem::Border(CommonDisplayItem::with_data(
+ base,
+ webrender_api::BorderDisplayItem {
+ bounds: bounds.to_layout(),
+ common: items::empty_common_item_properties(),
+ widths: border_widths.to_layout(),
+ details,
+ },
+ Vec::new(),
+ )));
+ }
+
+ /// Add display item for image border.
+ ///
+ /// Returns `Some` if the addition was successful.
+ fn build_display_list_for_border_image(
+ &self,
+ state: &mut DisplayListBuildState,
+ style: &ComputedValues,
+ base: BaseDisplayItem,
+ bounds: Rect<Au>,
+ image: &Image,
+ border_width: SideOffsets2D<Au>,
+ ) -> Option<()> {
+ let border_style_struct = style.get_border();
+ let border_image_outset =
+ border::image_outset(border_style_struct.border_image_outset, border_width);
+ let border_image_area = bounds.outer_rect(border_image_outset).size;
+ let border_image_width = border::image_width(
+ &border_style_struct.border_image_width,
+ border_width.to_layout(),
+ border_image_area,
+ );
+ let border_image_repeat = &border_style_struct.border_image_repeat;
+ let border_image_fill = border_style_struct.border_image_slice.fill;
+ let border_image_slice = &border_style_struct.border_image_slice.offsets;
+
+ let mut stops = Vec::new();
+ let mut width = border_image_area.width.to_px() as u32;
+ let mut height = border_image_area.height.to_px() as u32;
+ let source = match image {
+ Image::Url(ref image_url) => {
+ let url = image_url.url()?;
+ let image = state.layout_context.get_webrender_image_for_url(
+ self.node,
+ url.clone(),
+ UsePlaceholder::No,
+ )?;
+ width = image.width;
+ height = image.height;
+ NinePatchBorderSource::Image(image.key?)
+ },
+ Image::PaintWorklet(ref paint_worklet) => {
+ let image = self.get_webrender_image_for_paint_worklet(
+ state,
+ style,
+ paint_worklet,
+ border_image_area,
+ )?;
+ width = image.width;
+ height = image.height;
+ NinePatchBorderSource::Image(image.key?)
+ },
+ Image::Gradient(ref gradient) => match gradient.kind {
+ GradientKind::Linear(angle_or_corner) => {
+ let (wr_gradient, linear_stops) = gradient::linear(
+ style,
+ border_image_area,
+ &gradient.items[..],
+ angle_or_corner,
+ gradient.repeating,
+ );
+ stops = linear_stops;
+ NinePatchBorderSource::Gradient(wr_gradient)
+ },
+ GradientKind::Radial(shape, center) => {
+ let (wr_gradient, radial_stops) = gradient::radial(
+ style,
+ border_image_area,
+ &gradient.items[..],
+ shape,
+ center,
+ gradient.repeating,
+ );
+ stops = radial_stops;
+ NinePatchBorderSource::RadialGradient(wr_gradient)
+ },
+ },
+ _ => return None,
+ };
+
+ let details = BorderDetails::NinePatch(NinePatchBorder {
+ source,
+ width: width as i32,
+ height: height as i32,
+ slice: border::image_slice(border_image_slice, width as i32, height as i32),
+ fill: border_image_fill,
+ repeat_horizontal: border_image_repeat.0.to_layout(),
+ repeat_vertical: border_image_repeat.1.to_layout(),
+ outset: SideOffsets2D::new(
+ border_image_outset.top.to_f32_px(),
+ border_image_outset.right.to_f32_px(),
+ border_image_outset.bottom.to_f32_px(),
+ border_image_outset.left.to_f32_px(),
+ ),
+ });
+ state.add_display_item(DisplayItem::Border(CommonDisplayItem::with_data(
+ base,
+ webrender_api::BorderDisplayItem {
+ bounds: bounds.to_layout(),
+ common: items::empty_common_item_properties(),
+ widths: border_image_width,
+ details,
+ },
+ stops,
+ )));
+ Some(())
+ }
+
+ /// Adds the display items necessary to paint the outline of this fragment to the display list
+ /// if necessary.
+ fn build_display_list_for_outline_if_applicable(
+ &self,
+ state: &mut DisplayListBuildState,
+ style: &ComputedValues,
+ mut bounds: Rect<Au>,
+ clip: Rect<Au>,
+ ) {
+ use style::values::specified::outline::OutlineStyle;
+
+ let width = Au::from(style.get_outline().outline_width);
+ if width == Au(0) {
+ return;
+ }
+
+ let outline_style = match style.get_outline().outline_style {
+ OutlineStyle::Auto => BorderStyle::Solid,
+ // FIXME(emilio): I don't think this border-style check is
+ // necessary, since border-style: none implies an outline-width of
+ // zero at computed value time.
+ OutlineStyle::BorderStyle(BorderStyle::None) => return,
+ OutlineStyle::BorderStyle(s) => s,
+ };
+
+ // Outlines are not accounted for in the dimensions of the border box, so adjust the
+ // absolute bounds.
+ let offset = width + Au::from(style.get_outline().outline_offset);
+ bounds = bounds.inflate(offset, offset);
+
+ // Append the outline to the display list.
+ let color = style
+ .resolve_color(style.get_outline().outline_color)
+ .to_layout();
+ let base = state.create_base_display_item(
+ clip,
+ self.node,
+ get_cursor(&style, Cursor::Default),
+ DisplayListSection::Outlines,
+ );
+ state.add_display_item(DisplayItem::Border(CommonDisplayItem::with_data(
+ base,
+ webrender_api::BorderDisplayItem {
+ bounds: bounds.to_layout(),
+ common: items::empty_common_item_properties(),
+ widths: SideOffsets2D::new_all_same(width).to_layout(),
+ details: BorderDetails::Normal(border::simple(color, outline_style.to_layout())),
+ },
+ Vec::new(),
+ )));
+ }
+
+ /// Adds display items necessary to draw debug boxes around a scanned text fragment.
+ fn build_debug_borders_around_text_fragments(
+ &self,
+ state: &mut DisplayListBuildState,
+ style: &ComputedValues,
+ stacking_relative_border_box: Rect<Au>,
+ stacking_relative_content_box: Rect<Au>,
+ text_fragment: &ScannedTextFragmentInfo,
+ clip: Rect<Au>,
+ ) {
+ // FIXME(pcwalton, #2795): Get the real container size.
+ let container_size = Size2D::zero();
+
+ // Compute the text fragment bounds and draw a border surrounding them.
+ let base = state.create_base_display_item(
+ clip,
+ self.node,
+ get_cursor(&style, Cursor::Default),
+ DisplayListSection::Content,
+ );
+ state.add_display_item(DisplayItem::Border(CommonDisplayItem::with_data(
+ base,
+ webrender_api::BorderDisplayItem {
+ bounds: stacking_relative_border_box.to_layout(),
+ common: items::empty_common_item_properties(),
+ widths: SideOffsets2D::new_all_same(Au::from_px(1)).to_layout(),
+ details: BorderDetails::Normal(border::simple(
+ ColorU::new(0, 0, 200, 1).into(),
+ webrender_api::BorderStyle::Solid,
+ )),
+ },
+ Vec::new(),
+ )));
+
+ // Draw a rectangle representing the baselines.
+ let mut baseline = LogicalRect::from_physical(
+ self.style.writing_mode,
+ stacking_relative_content_box,
+ container_size,
+ );
+ baseline.start.b = baseline.start.b + text_fragment.run.ascent();
+ baseline.size.block = Au(0);
+ let baseline = baseline.to_physical(self.style.writing_mode, container_size);
+
+ let base = state.create_base_display_item(
+ clip,
+ self.node,
+ get_cursor(&style, Cursor::Default),
+ DisplayListSection::Content,
+ );
+ // TODO(gw): Use a better estimate for wavy line thickness.
+ let area = baseline.to_layout();
+ let wavy_line_thickness = (0.33 * area.size.height).ceil();
+ state.add_display_item(DisplayItem::Line(CommonDisplayItem::new(
+ base,
+ webrender_api::LineDisplayItem {
+ common: items::empty_common_item_properties(),
+ area,
+ orientation: webrender_api::LineOrientation::Horizontal,
+ wavy_line_thickness,
+ color: ColorU::new(0, 200, 0, 1).into(),
+ style: LineStyle::Dashed,
+ },
+ )));
+ }
+
+ /// Adds display items necessary to draw debug boxes around this fragment.
+ fn build_debug_borders_around_fragment(
+ &self,
+ state: &mut DisplayListBuildState,
+ stacking_relative_border_box: Rect<Au>,
+ clip: Rect<Au>,
+ ) {
+ // This prints a debug border around the border of this fragment.
+ let base = state.create_base_display_item(
+ clip,
+ self.node,
+ get_cursor(&self.style, Cursor::Default),
+ DisplayListSection::Content,
+ );
+ state.add_display_item(DisplayItem::Border(CommonDisplayItem::with_data(
+ base,
+ webrender_api::BorderDisplayItem {
+ bounds: stacking_relative_border_box.to_layout(),
+ common: items::empty_common_item_properties(),
+ widths: SideOffsets2D::new_all_same(Au::from_px(1)).to_layout(),
+ details: BorderDetails::Normal(border::simple(
+ ColorU::new(0, 0, 200, 1).into(),
+ webrender_api::BorderStyle::Solid,
+ )),
+ },
+ Vec::new(),
+ )));
+ }
+
+ /// Builds the display items necessary to paint the selection and/or caret for this fragment,
+ /// if any.
+ fn build_display_items_for_selection_if_necessary(
+ &self,
+ state: &mut DisplayListBuildState,
+ stacking_relative_border_box: Rect<Au>,
+ display_list_section: DisplayListSection,
+ ) {
+ let scanned_text_fragment_info = match self.specific {
+ SpecificFragmentInfo::ScannedText(ref scanned_text_fragment_info) => {
+ scanned_text_fragment_info
+ },
+ _ => return,
+ };
+
+ // Draw a highlighted background if the text is selected.
+ //
+ // TODO: Allow non-text fragments to be selected too.
+ if scanned_text_fragment_info.selected() {
+ let style = self.selected_style();
+ let background_color = style.resolve_color(style.get_background().background_color);
+ let base = state.create_base_display_item(
+ stacking_relative_border_box,
+ self.node,
+ get_cursor(&self.style, Cursor::Default),
+ display_list_section,
+ );
+ state.add_display_item(DisplayItem::Rectangle(CommonDisplayItem::new(
+ base,
+ webrender_api::RectangleDisplayItem {
+ common: items::empty_common_item_properties(),
+ color: background_color.to_layout(),
+ },
+ )));
+ }
+
+ // Draw a caret at the insertion point.
+ let insertion_point_index = match scanned_text_fragment_info.insertion_point {
+ Some(insertion_point_index) => insertion_point_index,
+ None => return,
+ };
+ let range = Range::new(
+ scanned_text_fragment_info.range.begin(),
+ insertion_point_index - scanned_text_fragment_info.range.begin(),
+ );
+ let advance = scanned_text_fragment_info.run.advance_for_range(&range);
+
+ let insertion_point_bounds;
+ let cursor;
+ if !self.style.writing_mode.is_vertical() {
+ insertion_point_bounds = rect(
+ stacking_relative_border_box.origin.x + advance,
+ stacking_relative_border_box.origin.y,
+ INSERTION_POINT_LOGICAL_WIDTH,
+ stacking_relative_border_box.size.height,
+ );
+ cursor = Cursor::Text;
+ } else {
+ insertion_point_bounds = rect(
+ stacking_relative_border_box.origin.x,
+ stacking_relative_border_box.origin.y + advance,
+ stacking_relative_border_box.size.width,
+ INSERTION_POINT_LOGICAL_WIDTH,
+ );
+ cursor = Cursor::VerticalText;
+ };
+
+ let base = state.create_base_display_item(
+ insertion_point_bounds,
+ self.node,
+ get_cursor(&self.style, cursor),
+ display_list_section,
+ );
+ state.add_display_item(DisplayItem::Rectangle(CommonDisplayItem::new(
+ base,
+ webrender_api::RectangleDisplayItem {
+ common: items::empty_common_item_properties(),
+ color: self.style().get_inherited_text().color.to_layout(),
+ },
+ )));
+ }
+
+ /// Adds the display items for this fragment to the given display list.
+ ///
+ /// Arguments:
+ ///
+ /// * `state`: The display building state, including the display list currently
+ /// under construction and other metadata useful for constructing it.
+ /// * `dirty`: The dirty rectangle in the coordinate system of the owning flow.
+ /// * `clip`: The region to clip the display items to.
+ /// * `overflow_content_size`: The size of content associated with this fragment
+ /// that must have overflow handling applied to it. For a scrollable block
+ /// flow, it is expected that this is the size of the child boxes.
+ pub fn build_display_list(
+ &mut self,
+ state: &mut DisplayListBuildState,
+ stacking_relative_border_box: Rect<Au>,
+ border_painting_mode: BorderPaintingMode,
+ display_list_section: DisplayListSection,
+ clip: Rect<Au>,
+ overflow_content_size: Option<Size2D<Au>>,
+ ) {
+ let previous_clipping_and_scrolling = state.current_clipping_and_scrolling;
+ if let Some(index) = self.established_reference_frame {
+ state.current_clipping_and_scrolling = ClippingAndScrolling::simple(index);
+ }
+
+ self.restyle_damage.remove(ServoRestyleDamage::REPAINT);
+ self.build_display_list_no_damage(
+ state,
+ stacking_relative_border_box,
+ border_painting_mode,
+ display_list_section,
+ clip,
+ overflow_content_size,
+ );
+
+ state.current_clipping_and_scrolling = previous_clipping_and_scrolling;
+ }
+
+ /// build_display_list, but don't update the restyle damage
+ ///
+ /// Must be paired with a self.restyle_damage.remove(REPAINT) somewhere
+ fn build_display_list_no_damage(
+ &self,
+ state: &mut DisplayListBuildState,
+ stacking_relative_border_box: Rect<Au>,
+ border_painting_mode: BorderPaintingMode,
+ display_list_section: DisplayListSection,
+ clip: Rect<Au>,
+ overflow_content_size: Option<Size2D<Au>>,
+ ) {
+ if self.style().get_inherited_box().visibility != Visibility::Visible {
+ return;
+ }
+
+ debug!(
+ "Fragment::build_display_list at rel={:?}, abs={:?}: {:?}",
+ self.border_box, stacking_relative_border_box, self
+ );
+
+ // Check the clip rect. If there's nothing to render at all, don't even construct display
+ // list items.
+ let empty_rect = !clip.intersects(&stacking_relative_border_box);
+ if self.is_primary_fragment() && !empty_rect {
+ // Add shadows, background, borders, and outlines, if applicable.
+ if let Some(ref inline_context) = self.inline_context {
+ for node in inline_context.nodes.iter().rev() {
+ self.build_display_list_for_background_if_applicable(
+ state,
+ &*node.style,
+ display_list_section,
+ stacking_relative_border_box,
+ );
+
+ self.build_display_list_for_box_shadow_if_applicable(
+ state,
+ &*node.style,
+ display_list_section,
+ stacking_relative_border_box,
+ clip,
+ );
+
+ self.build_display_list_for_borders_if_applicable(
+ state,
+ &*node.style,
+ Some(InlineNodeBorderInfo {
+ is_first_fragment_of_element: node
+ .flags
+ .contains(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT),
+ is_last_fragment_of_element: node
+ .flags
+ .contains(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT),
+ }),
+ border_painting_mode,
+ stacking_relative_border_box,
+ display_list_section,
+ clip,
+ );
+
+ // FIXME(emilio): Why does outline not do the same width
+ // fixup as border?
+ self.build_display_list_for_outline_if_applicable(
+ state,
+ &*node.style,
+ stacking_relative_border_box,
+ clip,
+ );
+ }
+ }
+
+ if !self.is_scanned_text_fragment() {
+ self.build_display_list_for_background_if_applicable(
+ state,
+ &*self.style,
+ display_list_section,
+ stacking_relative_border_box,
+ );
+
+ self.build_display_list_for_box_shadow_if_applicable(
+ state,
+ &*self.style,
+ display_list_section,
+ stacking_relative_border_box,
+ clip,
+ );
+
+ self.build_display_list_for_borders_if_applicable(
+ state,
+ &*self.style,
+ /* inline_node_info = */ None,
+ border_painting_mode,
+ stacking_relative_border_box,
+ display_list_section,
+ clip,
+ );
+
+ self.build_display_list_for_outline_if_applicable(
+ state,
+ &*self.style,
+ stacking_relative_border_box,
+ clip,
+ );
+ }
+ }
+
+ if self.is_primary_fragment() {
+ // Paint the selection point if necessary. Even an empty text fragment may have an
+ // insertion point, so we do this even if `empty_rect` is true.
+ self.build_display_items_for_selection_if_necessary(
+ state,
+ stacking_relative_border_box,
+ display_list_section,
+ );
+ }
+
+ if empty_rect {
+ return;
+ }
+
+ debug!("Fragment::build_display_list: intersected. Adding display item...");
+
+ if let Some(content_size) = overflow_content_size {
+ // Create a transparent rectangle for hit-testing purposes that exists in front
+ // of this fragment's background but behind its content. This ensures that any
+ // hit tests inside the content box but not on actual content target the current
+ // scrollable ancestor.
+ let content_size = TypedRect::new(stacking_relative_border_box.origin, content_size);
+ let base = state.create_base_display_item_with_clipping_and_scrolling(
+ content_size,
+ self.node,
+ // FIXME(emilio): Why does this ignore pointer-events?
+ get_cursor(&self.style, Cursor::Default).or(Some(Cursor::Default)),
+ display_list_section,
+ state.current_clipping_and_scrolling,
+ );
+ state.add_display_item(DisplayItem::Rectangle(CommonDisplayItem::new(
+ base,
+ webrender_api::RectangleDisplayItem {
+ common: items::empty_common_item_properties(),
+ color: ColorF::TRANSPARENT,
+ },
+ )));
+ }
+
+ // Create special per-fragment-type display items.
+ state.clipping_and_scrolling_scope(|state| {
+ self.build_fragment_type_specific_display_items(
+ state,
+ stacking_relative_border_box,
+ clip,
+ );
+ });
+
+ if opts::get().show_debug_fragment_borders {
+ self.build_debug_borders_around_fragment(state, stacking_relative_border_box, clip)
+ }
+ }
+
+ /// A helper method that `build_display_list` calls to create per-fragment-type display items.
+ fn build_fragment_type_specific_display_items(
+ &self,
+ state: &mut DisplayListBuildState,
+ stacking_relative_border_box: Rect<Au>,
+ clip: Rect<Au>,
+ ) {
+ // Compute the context box position relative to the parent stacking context.
+ let stacking_relative_content_box =
+ self.stacking_relative_content_box(stacking_relative_border_box);
+
+ let create_base_display_item = |state: &mut DisplayListBuildState| {
+ // Adjust the clipping region as necessary to account for `border-radius`.
+ let radii =
+ build_border_radius_for_inner_rect(stacking_relative_border_box, &self.style);
+
+ if !radii.is_zero() {
+ let clip_id =
+ state.add_late_clip_node(stacking_relative_border_box.to_layout(), radii);
+ state.current_clipping_and_scrolling = ClippingAndScrolling::simple(clip_id);
+ }
+
+ state.create_base_display_item(
+ stacking_relative_border_box,
+ self.node,
+ get_cursor(&self.style, Cursor::Default),
+ DisplayListSection::Content,
+ )
+ };
+
+ match self.specific {
+ SpecificFragmentInfo::TruncatedFragment(ref truncated_fragment)
+ if truncated_fragment.text_info.is_some() =>
+ {
+ let text_fragment = truncated_fragment.text_info.as_ref().unwrap();
+ // Create the main text display item.
+ self.build_display_list_for_text_fragment(
+ state,
+ &text_fragment,
+ stacking_relative_content_box,
+ &self.style.get_inherited_text().text_shadow.0,
+ clip,
+ );
+
+ if opts::get().show_debug_fragment_borders {
+ self.build_debug_borders_around_text_fragments(
+ state,
+ self.style(),
+ stacking_relative_border_box,
+ stacking_relative_content_box,
+ &text_fragment,
+ clip,
+ );
+ }
+ }
+ SpecificFragmentInfo::ScannedText(ref text_fragment) => {
+ // Create the main text display item.
+ self.build_display_list_for_text_fragment(
+ state,
+ &text_fragment,
+ stacking_relative_content_box,
+ &self.style.get_inherited_text().text_shadow.0,
+ clip,
+ );
+
+ if opts::get().show_debug_fragment_borders {
+ self.build_debug_borders_around_text_fragments(
+ state,
+ self.style(),
+ stacking_relative_border_box,
+ stacking_relative_content_box,
+ &text_fragment,
+ clip,
+ );
+ }
+ },
+ SpecificFragmentInfo::Generic |
+ SpecificFragmentInfo::GeneratedContent(..) |
+ SpecificFragmentInfo::Table |
+ SpecificFragmentInfo::TableCell |
+ SpecificFragmentInfo::TableRow |
+ SpecificFragmentInfo::TableWrapper |
+ SpecificFragmentInfo::Multicol |
+ SpecificFragmentInfo::MulticolColumn |
+ SpecificFragmentInfo::InlineBlock(_) |
+ SpecificFragmentInfo::InlineAbsoluteHypothetical(_) |
+ SpecificFragmentInfo::InlineAbsolute(_) |
+ SpecificFragmentInfo::TruncatedFragment(_) |
+ SpecificFragmentInfo::Svg(_) => {
+ if opts::get().show_debug_fragment_borders {
+ self.build_debug_borders_around_fragment(
+ state,
+ stacking_relative_border_box,
+ clip,
+ );
+ }
+ },
+ SpecificFragmentInfo::Iframe(ref fragment_info) => {
+ if !stacking_relative_content_box.is_empty() {
+ let browsing_context_id = match fragment_info.browsing_context_id {
+ Some(browsing_context_id) => browsing_context_id,
+ None => return warn!("No browsing context id for iframe."),
+ };
+ let pipeline_id = match fragment_info.pipeline_id {
+ Some(pipeline_id) => pipeline_id,
+ None => return warn!("No pipeline id for iframe {}.", browsing_context_id),
+ };
+
+ let base = create_base_display_item(state);
+ let bounds = stacking_relative_content_box.to_layout();
+ let item = DisplayItem::Iframe(Box::new(IframeDisplayItem {
+ base,
+ bounds,
+ iframe: pipeline_id,
+ }));
+
+ // XXXjdm: This sleight-of-hand to convert LayoutRect -> Size2D<CSSPixel>
+ // looks bogus.
+ let size = Size2D::new(bounds.size.width, bounds.size.height);
+ state.iframe_sizes.push(IFrameSize {
+ id: browsing_context_id,
+ size: TypedSize2D::from_untyped(&size),
+ });
+
+ state.add_display_item(item);
+ }
+ },
+ SpecificFragmentInfo::Image(ref image_fragment) => {
+ // Place the image into the display list.
+ if let Some(ref image) = image_fragment.image {
+ if let Some(id) = image.id {
+ let base = create_base_display_item(state);
+ state.add_image_item(
+ base,
+ webrender_api::ImageDisplayItem {
+ bounds: stacking_relative_content_box.to_layout(),
+ common: items::empty_common_item_properties(),
+ image_key: id,
+ stretch_size: stacking_relative_content_box.size.to_layout(),
+ tile_spacing: LayoutSize::zero(),
+ image_rendering: self
+ .style
+ .get_inherited_box()
+ .image_rendering
+ .to_layout(),
+ alpha_type: webrender_api::AlphaType::PremultipliedAlpha,
+ color: webrender_api::ColorF::WHITE,
+ },
+ );
+ }
+ }
+ },
+ SpecificFragmentInfo::Media(ref fragment_info) => {
+ if let Some((ref image_key, _, _)) = fragment_info.current_frame {
+ let base = create_base_display_item(state);
+ state.add_image_item(
+ base,
+ webrender_api::ImageDisplayItem {
+ bounds: stacking_relative_content_box.to_layout(),
+ common: items::empty_common_item_properties(),
+ image_key: *image_key,
+ stretch_size: stacking_relative_border_box.size.to_layout(),
+ tile_spacing: LayoutSize::zero(),
+ image_rendering: ImageRendering::Auto,
+ alpha_type: webrender_api::AlphaType::PremultipliedAlpha,
+ color: webrender_api::ColorF::WHITE,
+ },
+ );
+ }
+ },
+ SpecificFragmentInfo::Canvas(ref canvas_fragment_info) => {
+ let image_key = match canvas_fragment_info.source {
+ CanvasFragmentSource::WebGL(image_key) => image_key,
+ CanvasFragmentSource::Image(ref ipc_renderer) => match *ipc_renderer {
+ Some(ref ipc_renderer) => {
+ let ipc_renderer = ipc_renderer.lock().unwrap();
+ let (sender, receiver) = ipc::channel().unwrap();
+ ipc_renderer
+ .send(CanvasMsg::FromLayout(
+ FromLayoutMsg::SendData(sender),
+ canvas_fragment_info.canvas_id.clone(),
+ ))
+ .unwrap();
+ receiver.recv().unwrap().image_key
+ },
+ None => return,
+ },
+ };
+
+ let base = create_base_display_item(state);
+ let display_item = webrender_api::ImageDisplayItem {
+ bounds: stacking_relative_border_box.to_layout(),
+ common: items::empty_common_item_properties(),
+ image_key,
+ stretch_size: stacking_relative_content_box.size.to_layout(),
+ tile_spacing: LayoutSize::zero(),
+ image_rendering: ImageRendering::Auto,
+ alpha_type: webrender_api::AlphaType::PremultipliedAlpha,
+ color: webrender_api::ColorF::WHITE,
+ };
+
+ state.add_image_item(base, display_item);
+ },
+ SpecificFragmentInfo::UnscannedText(_) => {
+ panic!("Shouldn't see unscanned fragments here.")
+ },
+ SpecificFragmentInfo::TableColumn(_) => {
+ panic!("Shouldn't see table column fragments here.")
+ },
+ }
+ }
+
+ /// Creates a stacking context for associated fragment.
+ fn create_stacking_context(
+ &self,
+ id: StackingContextId,
+ base_flow: &BaseFlow,
+ context_type: StackingContextType,
+ established_reference_frame: Option<ClipScrollNodeIndex>,
+ parent_clipping_and_scrolling: ClippingAndScrolling,
+ ) -> StackingContext {
+ let border_box = self.stacking_relative_border_box(
+ &base_flow.stacking_relative_position,
+ &base_flow
+ .early_absolute_position_info
+ .relative_containing_block_size,
+ base_flow
+ .early_absolute_position_info
+ .relative_containing_block_mode,
+ CoordinateSystem::Parent,
+ );
+ // First, compute the offset of our border box (including relative positioning)
+ // from our flow origin, since that is what `BaseFlow::overflow` is relative to.
+ let border_box_offset = border_box
+ .translate(&-base_flow.stacking_relative_position)
+ .origin;
+ // Then, using that, compute our overflow region relative to our border box.
+ let overflow = base_flow
+ .overflow
+ .paint
+ .translate(&-border_box_offset.to_vector());
+
+ // Create the filter pipeline.
+ let effects = self.style().get_effects();
+ let mut filters: Vec<FilterOp> = effects.filter.0.iter().map(ToLayout::to_layout).collect();
+ if effects.opacity != 1.0 {
+ filters.push(FilterOp::Opacity(effects.opacity.into(), effects.opacity));
+ }
+
+ StackingContext::new(
+ id,
+ context_type,
+ border_box.to_layout(),
+ overflow.to_layout(),
+ self.effective_z_index(),
+ self.style().get_box()._servo_top_layer,
+ filters,
+ self.style().get_effects().mix_blend_mode.to_layout(),
+ self.transform_matrix(&border_box),
+ self.style().get_used_transform_style().to_layout(),
+ self.perspective_matrix(&border_box),
+ parent_clipping_and_scrolling,
+ established_reference_frame,
+ )
+ }
+
+ /// Creates the text display item for one text fragment. This can be called multiple times for
+ /// one fragment if there are text shadows.
+ ///
+ /// `text_shadow` will be `Some` if this is rendering a shadow.
+ fn build_display_list_for_text_fragment(
+ &self,
+ state: &mut DisplayListBuildState,
+ text_fragment: &ScannedTextFragmentInfo,
+ stacking_relative_content_box: Rect<Au>,
+ text_shadows: &[SimpleShadow],
+ clip: Rect<Au>,
+ ) {
+ // NB: The order for painting text components (CSS Text Decoration Module Level 3) is:
+ // shadows, underline, overline, text, text-emphasis, and then line-through.
+
+ // TODO(emilio): Allow changing more properties by ::selection
+ // Paint the text with the color as described in its styling.
+ let text_color = if text_fragment.selected() {
+ self.selected_style().get_inherited_text().color
+ } else {
+ self.style().get_inherited_text().color
+ };
+
+ // Determine the orientation and cursor to use.
+ let (_orientation, cursor) = if self.style.writing_mode.is_vertical() {
+ // TODO: Distinguish between 'sideways-lr' and 'sideways-rl' writing modes in CSS
+ // Writing Modes Level 4.
+ (TextOrientation::SidewaysRight, Cursor::VerticalText)
+ } else {
+ (TextOrientation::Upright, Cursor::Text)
+ };
+
+ // Compute location of the baseline.
+ //
+ // FIXME(pcwalton): Get the real container size.
+ let container_size = Size2D::zero();
+ let metrics = &text_fragment.run.font_metrics;
+ let baseline_origin = stacking_relative_content_box.origin +
+ LogicalPoint::new(self.style.writing_mode, Au(0), metrics.ascent)
+ .to_physical(self.style.writing_mode, container_size)
+ .to_vector();
+
+ // Base item for all text/shadows
+ let base = state.create_base_display_item(
+ clip,
+ self.node,
+ get_cursor(&self.style, cursor),
+ DisplayListSection::Content,
+ );
+
+ // NB: According to CSS-BACKGROUNDS, text shadows render in *reverse* order (front
+ // to back).
+
+ // Shadows
+ for shadow in text_shadows.iter().rev() {
+ state.add_display_item(DisplayItem::PushTextShadow(Box::new(
+ PushTextShadowDisplayItem {
+ base: base.clone(),
+ shadow: webrender_api::Shadow {
+ offset: LayoutVector2D::new(shadow.horizontal.px(), shadow.vertical.px()),
+ color: self.style.resolve_color(shadow.color).to_layout(),
+ blur_radius: shadow.blur.px(),
+ },
+ },
+ )));
+ }
+
+ // Create display items for text decorations.
+ let text_decorations = self.style().get_inherited_text().text_decorations_in_effect;
+
+ let logical_stacking_relative_content_box = LogicalRect::from_physical(
+ self.style.writing_mode,
+ stacking_relative_content_box,
+ container_size,
+ );
+
+ // Underline
+ if text_decorations.underline {
+ let mut stacking_relative_box = logical_stacking_relative_content_box;
+ stacking_relative_box.start.b = logical_stacking_relative_content_box.start.b +
+ metrics.ascent -
+ metrics.underline_offset;
+ stacking_relative_box.size.block = metrics.underline_size;
+ self.build_display_list_for_text_decoration(
+ state,
+ &text_color,
+ &stacking_relative_box,
+ clip,
+ );
+ }
+
+ // Overline
+ if text_decorations.overline {
+ let mut stacking_relative_box = logical_stacking_relative_content_box;
+ stacking_relative_box.size.block = metrics.underline_size;
+ self.build_display_list_for_text_decoration(
+ state,
+ &text_color,
+ &stacking_relative_box,
+ clip,
+ );
+ }
+
+ // Text
+ let glyphs = convert_text_run_to_glyphs(
+ text_fragment.run.clone(),
+ text_fragment.range,
+ baseline_origin,
+ );
+ if !glyphs.is_empty() {
+ let indexable_text = IndexableTextItem {
+ origin: stacking_relative_content_box.origin,
+ text_run: text_fragment.run.clone(),
+ range: text_fragment.range,
+ baseline_origin,
+ };
+ state.indexable_text.insert(self.node, indexable_text);
+
+ state.add_display_item(DisplayItem::Text(CommonDisplayItem::with_data(
+ base.clone(),
+ webrender_api::TextDisplayItem {
+ bounds: stacking_relative_content_box.to_layout(),
+ common: items::empty_common_item_properties(),
+ font_key: text_fragment.run.font_key,
+ color: text_color.to_layout(),
+ glyph_options: None,
+ },
+ glyphs,
+ )));
+ }
+
+ // TODO(#17715): emit text-emphasis marks here.
+ // (just push another TextDisplayItem?)
+
+ // Line-Through
+ if text_decorations.line_through {
+ let mut stacking_relative_box = logical_stacking_relative_content_box;
+ stacking_relative_box.start.b =
+ stacking_relative_box.start.b + metrics.ascent - metrics.strikeout_offset;
+ stacking_relative_box.size.block = metrics.strikeout_size;
+ self.build_display_list_for_text_decoration(
+ state,
+ &text_color,
+ &stacking_relative_box,
+ clip,
+ );
+ }
+
+ // Pop all the PushTextShadows
+ if !text_shadows.is_empty() {
+ state.add_display_item(DisplayItem::PopAllTextShadows(Box::new(
+ PopAllTextShadowsDisplayItem { base },
+ )));
+ }
+ }
+
+ /// Creates the display item for a text decoration: underline, overline, or line-through.
+ fn build_display_list_for_text_decoration(
+ &self,
+ state: &mut DisplayListBuildState,
+ color: &RGBA,
+ stacking_relative_box: &LogicalRect<Au>,
+ clip: Rect<Au>,
+ ) {
+ // FIXME(pcwalton, #2795): Get the real container size.
+ let container_size = Size2D::zero();
+ let stacking_relative_box =
+ stacking_relative_box.to_physical(self.style.writing_mode, container_size);
+ let base = state.create_base_display_item(
+ clip,
+ self.node,
+ get_cursor(&self.style, Cursor::Default),
+ DisplayListSection::Content,
+ );
+
+ // TODO(gw): Use a better estimate for wavy line thickness.
+ let area = stacking_relative_box.to_layout();
+ let wavy_line_thickness = (0.33 * area.size.height).ceil();
+ state.add_display_item(DisplayItem::Line(CommonDisplayItem::new(
+ base,
+ webrender_api::LineDisplayItem {
+ common: items::empty_common_item_properties(),
+ area,
+ orientation: webrender_api::LineOrientation::Horizontal,
+ wavy_line_thickness,
+ color: color.to_layout(),
+ style: LineStyle::Solid,
+ },
+ )));
+ }
+
+ fn unique_id(&self) -> u64 {
+ let fragment_type = self.fragment_type();
+ let id = self.node.id() as usize;
+ combine_id_with_fragment_type(id, fragment_type) as u64
+ }
+
+ fn fragment_type(&self) -> FragmentType {
+ self.pseudo.fragment_type()
+ }
+}
+
+bitflags! {
+ pub struct StackingContextCollectionFlags: u8 {
+ /// This flow never establishes a containing block.
+ const POSITION_NEVER_CREATES_CONTAINING_BLOCK = 0b001;
+ /// This flow never creates a ClipScrollNode.
+ const NEVER_CREATES_CLIP_SCROLL_NODE = 0b010;
+ /// This flow never creates a stacking context.
+ const NEVER_CREATES_STACKING_CONTEXT = 0b100;
+ }
+}
+
+/// This structure manages ensuring that modification to StackingContextCollectionState is
+/// only temporary. It's useful for moving recursively down the flow tree and ensuring
+/// that the state is restored for siblings. To use this structure, we must call
+/// SavedStackingContextCollectionState::restore in order to restore the state.
+/// TODO(mrobinson): It would be nice to use RAII here to avoid having to call restore.
+pub struct SavedStackingContextCollectionState {
+ stacking_context_id: StackingContextId,
+ real_stacking_context_id: StackingContextId,
+ parent_reference_frame_id: ClipScrollNodeIndex,
+ clipping_and_scrolling: ClippingAndScrolling,
+ containing_block_clipping_and_scrolling: ClippingAndScrolling,
+ clips_pushed: usize,
+ containing_block_clips_pushed: usize,
+ stacking_relative_content_box: Rect<Au>,
+}
+
+impl SavedStackingContextCollectionState {
+ fn new(state: &mut StackingContextCollectionState) -> SavedStackingContextCollectionState {
+ SavedStackingContextCollectionState {
+ stacking_context_id: state.current_stacking_context_id,
+ real_stacking_context_id: state.current_real_stacking_context_id,
+ parent_reference_frame_id: state.current_parent_reference_frame_id,
+ clipping_and_scrolling: state.current_clipping_and_scrolling,
+ containing_block_clipping_and_scrolling: state.containing_block_clipping_and_scrolling,
+ clips_pushed: 0,
+ containing_block_clips_pushed: 0,
+ stacking_relative_content_box: state.parent_stacking_relative_content_box,
+ }
+ }
+
+ fn switch_to_containing_block_clip(&mut self, state: &mut StackingContextCollectionState) {
+ let clip = state
+ .containing_block_clip_stack
+ .last()
+ .cloned()
+ .unwrap_or_else(MaxRect::max_rect);
+ state.clip_stack.push(clip);
+ self.clips_pushed += 1;
+ }
+
+ fn restore(self, state: &mut StackingContextCollectionState) {
+ state.current_stacking_context_id = self.stacking_context_id;
+ state.current_real_stacking_context_id = self.real_stacking_context_id;
+ state.current_parent_reference_frame_id = self.parent_reference_frame_id;
+ state.current_clipping_and_scrolling = self.clipping_and_scrolling;
+ state.containing_block_clipping_and_scrolling =
+ self.containing_block_clipping_and_scrolling;
+ state.parent_stacking_relative_content_box = self.stacking_relative_content_box;
+
+ let truncate_length = state.clip_stack.len() - self.clips_pushed;
+ state.clip_stack.truncate(truncate_length);
+
+ let truncate_length =
+ state.containing_block_clip_stack.len() - self.containing_block_clips_pushed;
+ state.containing_block_clip_stack.truncate(truncate_length);
+ }
+
+ fn push_clip(
+ &mut self,
+ state: &mut StackingContextCollectionState,
+ mut clip: Rect<Au>,
+ positioning: StylePosition,
+ ) {
+ if positioning != StylePosition::Fixed {
+ if let Some(old_clip) = state.clip_stack.last() {
+ clip = old_clip.intersection(&clip).unwrap_or_else(Rect::zero);
+ }
+ }
+
+ state.clip_stack.push(clip);
+ self.clips_pushed += 1;
+
+ if StylePosition::Absolute == positioning {
+ state.containing_block_clip_stack.push(clip);
+ self.containing_block_clips_pushed += 1;
+ }
+ }
+}
+
+impl BlockFlow {
+ fn transform_clip_to_coordinate_space(
+ &mut self,
+ state: &mut StackingContextCollectionState,
+ preserved_state: &mut SavedStackingContextCollectionState,
+ ) {
+ if state.clip_stack.is_empty() {
+ return;
+ }
+ let border_box = self.stacking_relative_border_box(CoordinateSystem::Parent);
+ let transform = match self.fragment.transform_matrix(&border_box) {
+ Some(transform) => transform,
+ None => return,
+ };
+
+ let perspective = self
+ .fragment
+ .perspective_matrix(&border_box)
+ .unwrap_or(LayoutTransform::identity());
+ let transform = transform.pre_mul(&perspective).inverse();
+
+ let origin = border_box.origin;
+ let transform_clip = |clip: Rect<Au>| {
+ if clip == Rect::max_rect() {
+ return clip;
+ }
+
+ match transform {
+ Some(transform) if transform.m13 != 0.0 || transform.m23 != 0.0 => {
+ // We cannot properly handle perspective transforms, because there may be a
+ // situation where an element is transformed from outside the clip into the
+ // clip region. Here we don't have enough information to detect when that is
+ // happening. For the moment we just punt on trying to optimize the display
+ // list for those cases.
+ Rect::max_rect()
+ },
+ Some(transform) => {
+ let clip = rect(
+ (clip.origin.x - origin.x).to_f32_px(),
+ (clip.origin.y - origin.y).to_f32_px(),
+ clip.size.width.to_f32_px(),
+ clip.size.height.to_f32_px(),
+ );
+
+ let clip = transform.transform_rect(&clip).unwrap();
+
+ rect(
+ Au::from_f32_px(clip.origin.x),
+ Au::from_f32_px(clip.origin.y),
+ Au::from_f32_px(clip.size.width),
+ Au::from_f32_px(clip.size.height),
+ )
+ },
+ None => Rect::zero(),
+ }
+ };
+
+ if let Some(clip) = state.clip_stack.last().cloned() {
+ state.clip_stack.push(transform_clip(clip));
+ preserved_state.clips_pushed += 1;
+ }
+
+ if let Some(clip) = state.containing_block_clip_stack.last().cloned() {
+ state.containing_block_clip_stack.push(transform_clip(clip));
+ preserved_state.containing_block_clips_pushed += 1;
+ }
+ }
+
+ /// Returns true if this fragment may establish a reference frame and this block
+ /// creates a stacking context. Both are necessary in order to establish a reference
+ /// frame.
+ fn is_reference_frame(&self, context_type: Option<StackingContextType>) -> bool {
+ match context_type {
+ Some(StackingContextType::Real) => self.fragment.can_establish_reference_frame(),
+ _ => false,
+ }
+ }
+
+ pub fn collect_stacking_contexts_for_block(
+ &mut self,
+ state: &mut StackingContextCollectionState,
+ flags: StackingContextCollectionFlags,
+ ) {
+ let mut preserved_state = SavedStackingContextCollectionState::new(state);
+
+ let stacking_context_type = self.stacking_context_type(flags);
+ self.base.stacking_context_id = match stacking_context_type {
+ None => state.current_stacking_context_id,
+ Some(sc_type) => state.allocate_stacking_context_info(sc_type),
+ };
+ state.current_stacking_context_id = self.base.stacking_context_id;
+
+ if stacking_context_type == Some(StackingContextType::Real) {
+ state.current_real_stacking_context_id = self.base.stacking_context_id;
+ }
+
+ let established_reference_frame = if self.is_reference_frame(stacking_context_type) {
+ // WebRender currently creates reference frames automatically, so just add
+ // a placeholder node to allocate a ClipScrollNodeIndex for this reference frame.
+ Some(state.add_clip_scroll_node(ClipScrollNode::placeholder()))
+ } else {
+ None
+ };
+
+ // We are getting the id of the scroll root that contains us here, not the id of
+ // any scroll root that we create. If we create a scroll root, its index will be
+ // stored in state.current_clipping_and_scrolling. If we create a stacking context,
+ // we don't want it to be contained by its own scroll root.
+ let containing_clipping_and_scrolling = self.setup_clipping_for_block(
+ state,
+ &mut preserved_state,
+ stacking_context_type,
+ established_reference_frame,
+ flags,
+ );
+
+ let creates_containing_block = !flags
+ .contains(StackingContextCollectionFlags::POSITION_NEVER_CREATES_CONTAINING_BLOCK);
+ let abspos_containing_block = established_reference_frame.is_some() ||
+ (creates_containing_block && self.positioning() != StylePosition::Static);
+ if abspos_containing_block {
+ state.containing_block_clipping_and_scrolling = state.current_clipping_and_scrolling;
+ }
+
+ match stacking_context_type {
+ None => self.base.collect_stacking_contexts_for_children(state),
+ Some(StackingContextType::Real) => {
+ self.create_real_stacking_context_for_block(
+ preserved_state.stacking_context_id,
+ containing_clipping_and_scrolling,
+ established_reference_frame,
+ state,
+ );
+ },
+ Some(stacking_context_type) => {
+ self.create_pseudo_stacking_context_for_block(
+ stacking_context_type,
+ preserved_state.stacking_context_id,
+ containing_clipping_and_scrolling,
+ state,
+ );
+ },
+ }
+
+ preserved_state.restore(state);
+ }
+
+ fn setup_clipping_for_block(
+ &mut self,
+ state: &mut StackingContextCollectionState,
+ preserved_state: &mut SavedStackingContextCollectionState,
+ stacking_context_type: Option<StackingContextType>,
+ established_reference_frame: Option<ClipScrollNodeIndex>,
+ flags: StackingContextCollectionFlags,
+ ) -> ClippingAndScrolling {
+ // If this block is absolutely positioned, we should be clipped and positioned by
+ // the scroll root of our nearest ancestor that establishes a containing block.
+ let containing_clipping_and_scrolling = match self.positioning() {
+ StylePosition::Absolute => {
+ preserved_state.switch_to_containing_block_clip(state);
+ state.current_clipping_and_scrolling =
+ state.containing_block_clipping_and_scrolling;
+ state.containing_block_clipping_and_scrolling
+ },
+ StylePosition::Fixed => {
+ // If we are a fixed positioned stacking context, we want to be scrolled by
+ // our reference frame instead of the clip scroll node that we are inside.
+ preserved_state.push_clip(state, Rect::max_rect(), StylePosition::Fixed);
+ state.current_clipping_and_scrolling.scrolling =
+ state.current_parent_reference_frame_id;
+ state.current_clipping_and_scrolling
+ },
+ _ => state.current_clipping_and_scrolling,
+ };
+ self.base.clipping_and_scrolling = Some(containing_clipping_and_scrolling);
+
+ if let Some(reference_frame_index) = established_reference_frame {
+ let clipping_and_scrolling = ClippingAndScrolling::simple(reference_frame_index);
+ state.current_clipping_and_scrolling = clipping_and_scrolling;
+ self.base.clipping_and_scrolling = Some(clipping_and_scrolling);
+ }
+
+ let stacking_relative_border_box = if self.fragment.establishes_stacking_context() {
+ self.stacking_relative_border_box(CoordinateSystem::Own)
+ } else {
+ self.stacking_relative_border_box(CoordinateSystem::Parent)
+ };
+
+ if stacking_context_type == Some(StackingContextType::Real) {
+ self.transform_clip_to_coordinate_space(state, preserved_state);
+ }
+
+ if !flags.contains(StackingContextCollectionFlags::NEVER_CREATES_CLIP_SCROLL_NODE) {
+ self.setup_clip_scroll_node_for_position(state, stacking_relative_border_box);
+ self.setup_clip_scroll_node_for_overflow(state, stacking_relative_border_box);
+ self.setup_clip_scroll_node_for_css_clip(
+ state,
+ preserved_state,
+ stacking_relative_border_box,
+ );
+ }
+ self.base.clip = state
+ .clip_stack
+ .last()
+ .cloned()
+ .unwrap_or_else(Rect::max_rect);
+
+ // We keep track of our position so that any stickily positioned elements can
+ // properly determine the extent of their movement relative to scrolling containers.
+ if !flags.contains(StackingContextCollectionFlags::POSITION_NEVER_CREATES_CONTAINING_BLOCK)
+ {
+ let border_box = if self.fragment.establishes_stacking_context() {
+ stacking_relative_border_box
+ } else {
+ self.stacking_relative_border_box(CoordinateSystem::Own)
+ };
+ state.parent_stacking_relative_content_box =
+ self.fragment.stacking_relative_content_box(border_box)
+ }
+
+ match self.positioning() {
+ StylePosition::Absolute | StylePosition::Relative | StylePosition::Fixed => {
+ state.containing_block_clipping_and_scrolling = state.current_clipping_and_scrolling
+ },
+ _ => {},
+ }
+
+ containing_clipping_and_scrolling
+ }
+
+ fn setup_clip_scroll_node_for_position(
+ &mut self,
+ state: &mut StackingContextCollectionState,
+ border_box: Rect<Au>,
+ ) {
+ if self.positioning() != StylePosition::Sticky {
+ return;
+ }
+
+ let sticky_position = self.sticky_position();
+ if sticky_position.left == MaybeAuto::Auto &&
+ sticky_position.right == MaybeAuto::Auto &&
+ sticky_position.top == MaybeAuto::Auto &&
+ sticky_position.bottom == MaybeAuto::Auto
+ {
+ return;
+ }
+
+ // Since position: sticky elements always establish a stacking context, we will
+ // have previously calculated our border box in our own coordinate system. In
+ // order to properly calculate max offsets we need to compare our size and
+ // position in our parent's coordinate system.
+ let border_box_in_parent = self.stacking_relative_border_box(CoordinateSystem::Parent);
+ let margins = self.fragment.margin.to_physical(
+ self.base
+ .early_absolute_position_info
+ .relative_containing_block_mode,
+ );
+
+ // Position:sticky elements are always restricted based on the size and position of
+ // their containing block, which for sticky items is like relative and statically
+ // positioned items: just the parent block.
+ let constraint_rect = state.parent_stacking_relative_content_box;
+
+ let to_offset_bound = |constraint_edge: Au, moving_edge: Au| -> f32 {
+ (constraint_edge - moving_edge).to_f32_px()
+ };
+
+ // This is the minimum negative offset and then the maximum positive offset. We just
+ // specify every edge, but if the corresponding margin is None, that offset has no effect.
+ let vertical_offset_bounds = StickyOffsetBounds::new(
+ to_offset_bound(
+ constraint_rect.min_y(),
+ border_box_in_parent.min_y() - margins.top,
+ ),
+ to_offset_bound(constraint_rect.max_y(), border_box_in_parent.max_y()),
+ );
+ let horizontal_offset_bounds = StickyOffsetBounds::new(
+ to_offset_bound(
+ constraint_rect.min_x(),
+ border_box_in_parent.min_x() - margins.left,
+ ),
+ to_offset_bound(constraint_rect.max_x(), border_box_in_parent.max_x()),
+ );
+
+ // The margins control which edges have sticky behavior.
+ let sticky_frame_data = StickyFrameData {
+ margins: SideOffsets2D::new(
+ sticky_position.top.to_option().map(|v| v.to_f32_px()),
+ sticky_position.right.to_option().map(|v| v.to_f32_px()),
+ sticky_position.bottom.to_option().map(|v| v.to_f32_px()),
+ sticky_position.left.to_option().map(|v| v.to_f32_px()),
+ ),
+ vertical_offset_bounds,
+ horizontal_offset_bounds,
+ };
+
+ let new_clip_scroll_index = state.add_clip_scroll_node(ClipScrollNode {
+ parent_index: self.clipping_and_scrolling().scrolling,
+ clip: ClippingRegion::from_rect(border_box.to_layout()),
+ content_rect: LayoutRect::zero(),
+ node_type: ClipScrollNodeType::StickyFrame(sticky_frame_data),
+ });
+
+ let new_clipping_and_scrolling = ClippingAndScrolling::simple(new_clip_scroll_index);
+ self.base.clipping_and_scrolling = Some(new_clipping_and_scrolling);
+ state.current_clipping_and_scrolling = new_clipping_and_scrolling;
+ }
+
+ fn setup_clip_scroll_node_for_overflow(
+ &mut self,
+ state: &mut StackingContextCollectionState,
+ border_box: Rect<Au>,
+ ) {
+ if !self.overflow_style_may_require_clip_scroll_node() {
+ return;
+ }
+
+ let content_box = self.fragment.stacking_relative_content_box(border_box);
+ let has_scrolling_overflow = self.base.overflow.scroll.origin != Point2D::zero() ||
+ self.base.overflow.scroll.size.width > content_box.size.width ||
+ self.base.overflow.scroll.size.height > content_box.size.height ||
+ StyleOverflow::Hidden == self.fragment.style.get_box().overflow_x ||
+ StyleOverflow::Hidden == self.fragment.style.get_box().overflow_y;
+
+ self.mark_scrolling_overflow(has_scrolling_overflow);
+ if !has_scrolling_overflow {
+ return;
+ }
+
+ let sensitivity = if StyleOverflow::Hidden == self.fragment.style.get_box().overflow_x &&
+ StyleOverflow::Hidden == self.fragment.style.get_box().overflow_y
+ {
+ ScrollSensitivity::Script
+ } else {
+ ScrollSensitivity::ScriptAndInputEvents
+ };
+
+ let border_widths = self
+ .fragment
+ .style
+ .logical_border_width()
+ .to_physical(self.fragment.style.writing_mode);
+ let clip_rect = border_box.inner_rect(border_widths);
+
+ let mut clip = ClippingRegion::from_rect(clip_rect.to_layout());
+ let radii = build_border_radius_for_inner_rect(border_box, &self.fragment.style);
+ if !radii.is_zero() {
+ clip.intersect_with_rounded_rect(clip_rect.to_layout(), radii)
+ }
+
+ let content_size = self.base.overflow.scroll.origin + self.base.overflow.scroll.size;
+ let content_size = Size2D::new(content_size.x, content_size.y);
+
+ let external_id =
+ ExternalScrollId(self.fragment.unique_id(), state.pipeline_id.to_webrender());
+ let new_clip_scroll_index = state.add_clip_scroll_node(ClipScrollNode {
+ parent_index: self.clipping_and_scrolling().scrolling,
+ clip: clip,
+ content_rect: Rect::new(content_box.origin, content_size).to_layout(),
+ node_type: ClipScrollNodeType::ScrollFrame(sensitivity, external_id),
+ });
+
+ let new_clipping_and_scrolling = ClippingAndScrolling::simple(new_clip_scroll_index);
+ self.base.clipping_and_scrolling = Some(new_clipping_and_scrolling);
+ state.current_clipping_and_scrolling = new_clipping_and_scrolling;
+ }
+
+ /// Adds a scroll root for a block to take the `clip` property into account
+ /// per CSS 2.1 § 11.1.2.
+ fn setup_clip_scroll_node_for_css_clip(
+ &mut self,
+ state: &mut StackingContextCollectionState,
+ preserved_state: &mut SavedStackingContextCollectionState,
+ stacking_relative_border_box: Rect<Au>,
+ ) {
+ // Account for `clip` per CSS 2.1 § 11.1.2.
+ let style_clip_rect = match self.fragment.style().get_effects().clip {
+ Either::First(style_clip_rect) => style_clip_rect,
+ _ => return,
+ };
+
+ // CSS `clip` should only apply to position:absolute or positione:fixed elements.
+ // CSS Masking Appendix A: "Applies to: Absolutely positioned elements."
+ match self.positioning() {
+ StylePosition::Absolute | StylePosition::Fixed => {},
+ _ => return,
+ }
+
+ fn extract_clip_component(p: &LengthOrAuto) -> Option<Au> {
+ match *p {
+ LengthOrAuto::Auto => None,
+ LengthOrAuto::LengthPercentage(ref length) => Some(Au::from(*length)),
+ }
+ }
+
+ let clip_origin = Point2D::new(
+ stacking_relative_border_box.origin.x +
+ extract_clip_component(&style_clip_rect.left).unwrap_or_default(),
+ stacking_relative_border_box.origin.y +
+ extract_clip_component(&style_clip_rect.top).unwrap_or_default(),
+ );
+ let right = extract_clip_component(&style_clip_rect.right)
+ .unwrap_or(stacking_relative_border_box.size.width);
+ let bottom = extract_clip_component(&style_clip_rect.bottom)
+ .unwrap_or(stacking_relative_border_box.size.height);
+ let clip_size = Size2D::new(right - clip_origin.x, bottom - clip_origin.y);
+
+ let clip_rect = Rect::new(clip_origin, clip_size);
+ preserved_state.push_clip(state, clip_rect, self.positioning());
+
+ let new_index = state.add_clip_scroll_node(ClipScrollNode {
+ parent_index: self.clipping_and_scrolling().scrolling,
+ clip: ClippingRegion::from_rect(clip_rect.to_layout()),
+ content_rect: LayoutRect::zero(), // content_rect isn't important for clips.
+ node_type: ClipScrollNodeType::Clip,
+ });
+
+ let new_indices = ClippingAndScrolling::new(new_index, new_index);
+ self.base.clipping_and_scrolling = Some(new_indices);
+ state.current_clipping_and_scrolling = new_indices;
+ }
+
+ fn create_pseudo_stacking_context_for_block(
+ &mut self,
+ stacking_context_type: StackingContextType,
+ parent_stacking_context_id: StackingContextId,
+ parent_clipping_and_scrolling: ClippingAndScrolling,
+ state: &mut StackingContextCollectionState,
+ ) {
+ let new_context = self.fragment.create_stacking_context(
+ self.base.stacking_context_id,
+ &self.base,
+ stacking_context_type,
+ None,
+ parent_clipping_and_scrolling,
+ );
+ state.add_stacking_context(parent_stacking_context_id, new_context);
+
+ self.base.collect_stacking_contexts_for_children(state);
+
+ let children = state
+ .stacking_context_info
+ .get_mut(&self.base.stacking_context_id)
+ .map(|info| info.take_children());
+ if let Some(children) = children {
+ for child in children {
+ if child.context_type == StackingContextType::PseudoFloat {
+ state.add_stacking_context(self.base.stacking_context_id, child);
+ } else {
+ state.add_stacking_context(parent_stacking_context_id, child);
+ }
+ }
+ }
+ }
+
+ fn create_real_stacking_context_for_block(
+ &mut self,
+ parent_stacking_context_id: StackingContextId,
+ parent_clipping_and_scrolling: ClippingAndScrolling,
+ established_reference_frame: Option<ClipScrollNodeIndex>,
+ state: &mut StackingContextCollectionState,
+ ) {
+ let stacking_context = self.fragment.create_stacking_context(
+ self.base.stacking_context_id,
+ &self.base,
+ StackingContextType::Real,
+ established_reference_frame,
+ parent_clipping_and_scrolling,
+ );
+
+ state.add_stacking_context(parent_stacking_context_id, stacking_context);
+ self.base.collect_stacking_contexts_for_children(state);
+ }
+
+ pub fn build_display_list_for_block_no_damage(
+ &self,
+ state: &mut DisplayListBuildState,
+ border_painting_mode: BorderPaintingMode,
+ ) {
+ let background_border_section = self.background_border_section();
+
+ state.processing_scrolling_overflow_element = self.has_scrolling_overflow();
+
+ let content_size = if state.processing_scrolling_overflow_element {
+ let content_size = self.base.overflow.scroll.origin + self.base.overflow.scroll.size;
+ Some(Size2D::new(content_size.x, content_size.y))
+ } else {
+ None
+ };
+
+ let stacking_relative_border_box = self
+ .base
+ .stacking_relative_border_box_for_display_list(&self.fragment);
+ // Add the box that starts the block context.
+ self.fragment.build_display_list_no_damage(
+ state,
+ stacking_relative_border_box,
+ border_painting_mode,
+ background_border_section,
+ self.base.clip,
+ content_size,
+ );
+
+ self.base
+ .build_display_items_for_debugging_tint(state, self.fragment.node);
+
+ state.processing_scrolling_overflow_element = false;
+ }
+
+ pub fn build_display_list_for_block(
+ &mut self,
+ state: &mut DisplayListBuildState,
+ border_painting_mode: BorderPaintingMode,
+ ) {
+ self.fragment
+ .restyle_damage
+ .remove(ServoRestyleDamage::REPAINT);
+ self.build_display_list_for_block_no_damage(state, border_painting_mode);
+ }
+
+ pub fn build_display_list_for_background_if_applicable_with_background(
+ &self,
+ state: &mut DisplayListBuildState,
+ background: &style_structs::Background,
+ background_color: RGBA,
+ ) {
+ let stacking_relative_border_box = self
+ .base
+ .stacking_relative_border_box_for_display_list(&self.fragment);
+ let background_border_section = self.background_border_section();
+
+ self.fragment
+ .build_display_list_for_background_if_applicable_with_background(
+ state,
+ self.fragment.style(),
+ background,
+ background_color,
+ background_border_section,
+ stacking_relative_border_box,
+ )
+ }
+
+ #[inline]
+ fn stacking_context_type(
+ &self,
+ flags: StackingContextCollectionFlags,
+ ) -> Option<StackingContextType> {
+ if flags.contains(StackingContextCollectionFlags::NEVER_CREATES_STACKING_CONTEXT) {
+ return None;
+ }
+
+ if self.fragment.establishes_stacking_context() {
+ return Some(StackingContextType::Real);
+ }
+
+ if self
+ .base
+ .flags
+ .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED)
+ {
+ return Some(StackingContextType::PseudoPositioned);
+ }
+
+ if self.fragment.style.get_box().position != StylePosition::Static {
+ return Some(StackingContextType::PseudoPositioned);
+ }
+
+ if self.base.flags.is_float() {
+ return Some(StackingContextType::PseudoFloat);
+ }
+
+ None
+ }
+}
+
+impl BaseFlow {
+ pub fn build_display_items_for_debugging_tint(
+ &self,
+ state: &mut DisplayListBuildState,
+ node: OpaqueNode,
+ ) {
+ if !opts::get().show_debug_parallel_layout {
+ return;
+ }
+
+ let thread_id = self.thread_id;
+ let stacking_context_relative_bounds = Rect::new(
+ self.stacking_relative_position.to_point(),
+ self.position.size.to_physical(self.writing_mode),
+ );
+
+ let mut color = THREAD_TINT_COLORS[thread_id as usize % THREAD_TINT_COLORS.len()];
+ color.a = 1.0;
+ let base =
+ state.create_base_display_item(self.clip, node, None, DisplayListSection::Content);
+ let bounds = stacking_context_relative_bounds.inflate(Au::from_px(2), Au::from_px(2));
+ state.add_display_item(DisplayItem::Border(CommonDisplayItem::with_data(
+ base,
+ webrender_api::BorderDisplayItem {
+ bounds: bounds.to_layout(),
+ common: items::empty_common_item_properties(),
+ widths: SideOffsets2D::new_all_same(Au::from_px(2)).to_layout(),
+ details: BorderDetails::Normal(border::simple(
+ color,
+ webrender_api::BorderStyle::Solid,
+ )),
+ },
+ Vec::new(),
+ )));
+ }
+}
+
+/// Gets the cursor to use given the specific ComputedValues. `default_cursor` specifies
+/// the cursor to use if `cursor` is `auto`. Typically, this will be `PointerCursor`, but for
+/// text display items it may be `TextCursor` or `VerticalTextCursor`.
+#[inline]
+fn get_cursor(values: &ComputedValues, default_cursor: Cursor) -> Option<Cursor> {
+ let inherited_ui = values.get_inherited_ui();
+ if inherited_ui.pointer_events == PointerEvents::None {
+ return None;
+ }
+
+ Some(match inherited_ui.cursor.keyword {
+ CursorKind::Auto => default_cursor,
+ CursorKind::None => Cursor::None,
+ CursorKind::Default => Cursor::Default,
+ CursorKind::Pointer => Cursor::Pointer,
+ CursorKind::ContextMenu => Cursor::ContextMenu,
+ CursorKind::Help => Cursor::Help,
+ CursorKind::Progress => Cursor::Progress,
+ CursorKind::Wait => Cursor::Wait,
+ CursorKind::Cell => Cursor::Cell,
+ CursorKind::Crosshair => Cursor::Crosshair,
+ CursorKind::Text => Cursor::Text,
+ CursorKind::VerticalText => Cursor::VerticalText,
+ CursorKind::Alias => Cursor::Alias,
+ CursorKind::Copy => Cursor::Copy,
+ CursorKind::Move => Cursor::Move,
+ CursorKind::NoDrop => Cursor::NoDrop,
+ CursorKind::NotAllowed => Cursor::NotAllowed,
+ CursorKind::Grab => Cursor::Grab,
+ CursorKind::Grabbing => Cursor::Grabbing,
+ CursorKind::EResize => Cursor::EResize,
+ CursorKind::NResize => Cursor::NResize,
+ CursorKind::NeResize => Cursor::NeResize,
+ CursorKind::NwResize => Cursor::NwResize,
+ CursorKind::SResize => Cursor::SResize,
+ CursorKind::SeResize => Cursor::SeResize,
+ CursorKind::SwResize => Cursor::SwResize,
+ CursorKind::WResize => Cursor::WResize,
+ CursorKind::EwResize => Cursor::EwResize,
+ CursorKind::NsResize => Cursor::NsResize,
+ CursorKind::NeswResize => Cursor::NeswResize,
+ CursorKind::NwseResize => Cursor::NwseResize,
+ CursorKind::ColResize => Cursor::ColResize,
+ CursorKind::RowResize => Cursor::RowResize,
+ CursorKind::AllScroll => Cursor::AllScroll,
+ CursorKind::ZoomIn => Cursor::ZoomIn,
+ CursorKind::ZoomOut => Cursor::ZoomOut,
+ })
+}
+
+/// Adjusts borders as appropriate to account for a fragment's status as the
+/// first or last fragment within the range of an element.
+///
+/// Specifically, this function sets border widths to zero on the sides for
+/// which the fragment is not outermost.
+fn modify_border_width_for_inline_sides(
+ border_width: &mut LogicalMargin<Au>,
+ inline_border_info: InlineNodeBorderInfo,
+) {
+ if !inline_border_info.is_first_fragment_of_element {
+ border_width.inline_start = Au(0);
+ }
+
+ if !inline_border_info.is_last_fragment_of_element {
+ border_width.inline_end = Au(0);
+ }
+}
+
+/// Describes how to paint the borders.
+#[derive(Clone, Copy)]
+pub enum BorderPaintingMode<'a> {
+ /// Paint borders separately (`border-collapse: separate`).
+ Separate,
+ /// Paint collapsed borders.
+ Collapse(&'a CollapsedBordersForCell),
+ /// Paint no borders.
+ Hidden,
+}
+
+fn convert_text_run_to_glyphs(
+ text_run: Arc<TextRun>,
+ range: Range<ByteIndex>,
+ mut origin: Point2D<Au>,
+) -> Vec<GlyphInstance> {
+ let mut glyphs = vec![];
+
+ for slice in text_run.natural_word_slices_in_visual_order(&range) {
+ for glyph in slice.glyphs.iter_glyphs_for_byte_range(&slice.range) {
+ let glyph_advance = if glyph.char_is_space() {
+ glyph.advance() + text_run.extra_word_spacing
+ } else {
+ glyph.advance()
+ };
+ if !slice.glyphs.is_whitespace() {
+ let glyph_offset = glyph.offset().unwrap_or(Point2D::zero());
+ let point = origin + glyph_offset.to_vector();
+ let glyph = GlyphInstance {
+ index: glyph.id(),
+ point: point.to_layout(),
+ };
+ glyphs.push(glyph);
+ }
+ origin.x += glyph_advance;
+ }
+ }
+ return glyphs;
+}
+
+pub struct IndexableTextItem {
+ /// The placement of the text item on the plane.
+ pub origin: Point2D<Au>,
+ /// The text run.
+ pub text_run: Arc<TextRun>,
+ /// The range of text within the text run.
+ pub range: Range<ByteIndex>,
+ /// The position of the start of the baseline of this text.
+ pub baseline_origin: Point2D<Au>,
+}
+
+#[derive(Default)]
+pub struct IndexableText {
+ inner: FnvHashMap<OpaqueNode, Vec<IndexableTextItem>>,
+}
+
+impl IndexableText {
+ fn insert(&mut self, node: OpaqueNode, item: IndexableTextItem) {
+ let entries = self.inner.entry(node).or_insert(Vec::new());
+ entries.push(item);
+ }
+
+ pub fn get(&self, node: OpaqueNode) -> Option<&[IndexableTextItem]> {
+ self.inner.get(&node).map(|x| x.as_slice())
+ }
+
+ // Returns the text index within a node for the point of interest.
+ pub fn text_index(&self, node: OpaqueNode, point_in_item: Point2D<Au>) -> Option<usize> {
+ let item = self.inner.get(&node)?;
+ // TODO(#20020): access all elements
+ let point = point_in_item + item[0].origin.to_vector();
+ let offset = point - item[0].baseline_origin;
+ Some(
+ item[0]
+ .text_run
+ .range_index_of_advance(&item[0].range, offset.x),
+ )
+ }
+}
+
+trait ToF32Px {
+ type Output;
+ fn to_f32_px(&self) -> Self::Output;
+}
+
+impl ToF32Px for TypedRect<Au> {
+ type Output = LayoutRect;
+ fn to_f32_px(&self) -> LayoutRect {
+ LayoutRect::from_untyped(&servo_geometry::au_rect_to_f32_rect(*self))
+ }
+}
diff --git a/components/layout_2020/display_list/conversions.rs b/components/layout_2020/display_list/conversions.rs
new file mode 100644
index 00000000000..4624b5f8dd0
--- /dev/null
+++ b/components/layout_2020/display_list/conversions.rs
@@ -0,0 +1,167 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+use app_units::Au;
+use euclid::{Point2D, Rect, SideOffsets2D, Size2D, Vector2D};
+use style::computed_values::image_rendering::T as ImageRendering;
+use style::computed_values::mix_blend_mode::T as MixBlendMode;
+use style::computed_values::transform_style::T as TransformStyle;
+use style::values::computed::{BorderStyle, Filter};
+use style::values::specified::border::BorderImageRepeatKeyword;
+use style::values::RGBA;
+use webrender_api as wr;
+
+pub trait ToLayout {
+ type Type;
+ fn to_layout(&self) -> Self::Type;
+}
+
+impl ToLayout for BorderStyle {
+ type Type = wr::BorderStyle;
+ fn to_layout(&self) -> Self::Type {
+ match *self {
+ BorderStyle::None => wr::BorderStyle::None,
+ BorderStyle::Solid => wr::BorderStyle::Solid,
+ BorderStyle::Double => wr::BorderStyle::Double,
+ BorderStyle::Dotted => wr::BorderStyle::Dotted,
+ BorderStyle::Dashed => wr::BorderStyle::Dashed,
+ BorderStyle::Hidden => wr::BorderStyle::Hidden,
+ BorderStyle::Groove => wr::BorderStyle::Groove,
+ BorderStyle::Ridge => wr::BorderStyle::Ridge,
+ BorderStyle::Inset => wr::BorderStyle::Inset,
+ BorderStyle::Outset => wr::BorderStyle::Outset,
+ }
+ }
+}
+
+impl ToLayout for Filter {
+ type Type = wr::FilterOp;
+ fn to_layout(&self) -> Self::Type {
+ match *self {
+ Filter::Blur(radius) => wr::FilterOp::Blur(radius.px()),
+ Filter::Brightness(amount) => wr::FilterOp::Brightness(amount.0),
+ Filter::Contrast(amount) => wr::FilterOp::Contrast(amount.0),
+ Filter::Grayscale(amount) => wr::FilterOp::Grayscale(amount.0),
+ Filter::HueRotate(angle) => wr::FilterOp::HueRotate(angle.radians()),
+ Filter::Invert(amount) => wr::FilterOp::Invert(amount.0),
+ Filter::Opacity(amount) => wr::FilterOp::Opacity(amount.0.into(), amount.0),
+ Filter::Saturate(amount) => wr::FilterOp::Saturate(amount.0),
+ Filter::Sepia(amount) => wr::FilterOp::Sepia(amount.0),
+ // Statically check that DropShadow is impossible.
+ Filter::DropShadow(ref shadow) => match *shadow {},
+ // Statically check that Url is impossible.
+ Filter::Url(ref url) => match *url {},
+ }
+ }
+}
+
+impl ToLayout for ImageRendering {
+ type Type = wr::ImageRendering;
+ fn to_layout(&self) -> Self::Type {
+ match *self {
+ ImageRendering::Auto => wr::ImageRendering::Auto,
+ ImageRendering::CrispEdges => wr::ImageRendering::CrispEdges,
+ ImageRendering::Pixelated => wr::ImageRendering::Pixelated,
+ }
+ }
+}
+
+impl ToLayout for MixBlendMode {
+ type Type = wr::MixBlendMode;
+ fn to_layout(&self) -> Self::Type {
+ match *self {
+ MixBlendMode::Normal => wr::MixBlendMode::Normal,
+ MixBlendMode::Multiply => wr::MixBlendMode::Multiply,
+ MixBlendMode::Screen => wr::MixBlendMode::Screen,
+ MixBlendMode::Overlay => wr::MixBlendMode::Overlay,
+ MixBlendMode::Darken => wr::MixBlendMode::Darken,
+ MixBlendMode::Lighten => wr::MixBlendMode::Lighten,
+ MixBlendMode::ColorDodge => wr::MixBlendMode::ColorDodge,
+ MixBlendMode::ColorBurn => wr::MixBlendMode::ColorBurn,
+ MixBlendMode::HardLight => wr::MixBlendMode::HardLight,
+ MixBlendMode::SoftLight => wr::MixBlendMode::SoftLight,
+ MixBlendMode::Difference => wr::MixBlendMode::Difference,
+ MixBlendMode::Exclusion => wr::MixBlendMode::Exclusion,
+ MixBlendMode::Hue => wr::MixBlendMode::Hue,
+ MixBlendMode::Saturation => wr::MixBlendMode::Saturation,
+ MixBlendMode::Color => wr::MixBlendMode::Color,
+ MixBlendMode::Luminosity => wr::MixBlendMode::Luminosity,
+ }
+ }
+}
+
+impl ToLayout for TransformStyle {
+ type Type = wr::TransformStyle;
+ fn to_layout(&self) -> Self::Type {
+ match *self {
+ TransformStyle::Auto | TransformStyle::Flat => wr::TransformStyle::Flat,
+ TransformStyle::Preserve3d => wr::TransformStyle::Preserve3D,
+ }
+ }
+}
+
+impl ToLayout for RGBA {
+ type Type = wr::ColorF;
+ fn to_layout(&self) -> Self::Type {
+ wr::ColorF::new(
+ self.red_f32(),
+ self.green_f32(),
+ self.blue_f32(),
+ self.alpha_f32(),
+ )
+ }
+}
+
+impl ToLayout for Point2D<Au> {
+ type Type = wr::units::LayoutPoint;
+ fn to_layout(&self) -> Self::Type {
+ wr::units::LayoutPoint::new(self.x.to_f32_px(), self.y.to_f32_px())
+ }
+}
+
+impl ToLayout for Rect<Au> {
+ type Type = wr::units::LayoutRect;
+ fn to_layout(&self) -> Self::Type {
+ wr::units::LayoutRect::new(self.origin.to_layout(), self.size.to_layout())
+ }
+}
+
+impl ToLayout for SideOffsets2D<Au> {
+ type Type = wr::units::LayoutSideOffsets;
+ fn to_layout(&self) -> Self::Type {
+ wr::units::LayoutSideOffsets::new(
+ self.top.to_f32_px(),
+ self.right.to_f32_px(),
+ self.bottom.to_f32_px(),
+ self.left.to_f32_px(),
+ )
+ }
+}
+
+impl ToLayout for Size2D<Au> {
+ type Type = wr::units::LayoutSize;
+ fn to_layout(&self) -> Self::Type {
+ wr::units::LayoutSize::new(self.width.to_f32_px(), self.height.to_f32_px())
+ }
+}
+
+impl ToLayout for Vector2D<Au> {
+ type Type = wr::units::LayoutVector2D;
+ fn to_layout(&self) -> Self::Type {
+ wr::units::LayoutVector2D::new(self.x.to_f32_px(), self.y.to_f32_px())
+ }
+}
+
+impl ToLayout for BorderImageRepeatKeyword {
+ type Type = wr::RepeatMode;
+
+ fn to_layout(&self) -> Self::Type {
+ match *self {
+ BorderImageRepeatKeyword::Stretch => wr::RepeatMode::Stretch,
+ BorderImageRepeatKeyword::Repeat => wr::RepeatMode::Repeat,
+ BorderImageRepeatKeyword::Round => wr::RepeatMode::Round,
+ BorderImageRepeatKeyword::Space => wr::RepeatMode::Space,
+ }
+ }
+}
diff --git a/components/layout_2020/display_list/gradient.rs b/components/layout_2020/display_list/gradient.rs
new file mode 100644
index 00000000000..ab56b1d6c9c
--- /dev/null
+++ b/components/layout_2020/display_list/gradient.rs
@@ -0,0 +1,323 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+use crate::display_list::ToLayout;
+use app_units::Au;
+use euclid::{Point2D, Size2D, Vector2D};
+use style::properties::ComputedValues;
+use style::values::computed::image::{EndingShape, LineDirection};
+use style::values::computed::{Angle, GradientItem, LengthPercentage, Percentage, Position};
+use style::values::generics::image::{Circle, ColorStop, Ellipse, ShapeExtent};
+use webrender_api::{ExtendMode, Gradient, GradientBuilder, GradientStop, RadialGradient};
+
+/// A helper data structure for gradients.
+#[derive(Clone, Copy)]
+struct StopRun {
+ start_offset: f32,
+ end_offset: f32,
+ start_index: usize,
+ stop_count: usize,
+}
+
+/// Determines the radius of a circle if it was not explictly provided.
+/// <https://drafts.csswg.org/css-images-3/#typedef-size>
+fn circle_size_keyword(
+ keyword: ShapeExtent,
+ size: &Size2D<Au>,
+ center: &Point2D<Au>,
+) -> Size2D<Au> {
+ let radius = match keyword {
+ ShapeExtent::ClosestSide | ShapeExtent::Contain => {
+ let dist = distance_to_sides(size, center, ::std::cmp::min);
+ ::std::cmp::min(dist.width, dist.height)
+ },
+ ShapeExtent::FarthestSide => {
+ let dist = distance_to_sides(size, center, ::std::cmp::max);
+ ::std::cmp::max(dist.width, dist.height)
+ },
+ ShapeExtent::ClosestCorner => distance_to_corner(size, center, ::std::cmp::min),
+ ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
+ distance_to_corner(size, center, ::std::cmp::max)
+ },
+ };
+ Size2D::new(radius, radius)
+}
+
+/// Returns the radius for an ellipse with the same ratio as if it was matched to the sides.
+fn ellipse_radius<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Size2D<Au>
+where
+ F: Fn(Au, Au) -> Au,
+{
+ let dist = distance_to_sides(size, center, cmp);
+ Size2D::new(
+ dist.width.scale_by(::std::f32::consts::FRAC_1_SQRT_2 * 2.0),
+ dist.height
+ .scale_by(::std::f32::consts::FRAC_1_SQRT_2 * 2.0),
+ )
+}
+
+/// Determines the radius of an ellipse if it was not explictly provided.
+/// <https://drafts.csswg.org/css-images-3/#typedef-size>
+fn ellipse_size_keyword(
+ keyword: ShapeExtent,
+ size: &Size2D<Au>,
+ center: &Point2D<Au>,
+) -> Size2D<Au> {
+ match keyword {
+ ShapeExtent::ClosestSide | ShapeExtent::Contain => {
+ distance_to_sides(size, center, ::std::cmp::min)
+ },
+ ShapeExtent::FarthestSide => distance_to_sides(size, center, ::std::cmp::max),
+ ShapeExtent::ClosestCorner => ellipse_radius(size, center, ::std::cmp::min),
+ ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
+ ellipse_radius(size, center, ::std::cmp::max)
+ },
+ }
+}
+
+fn convert_gradient_stops(
+ style: &ComputedValues,
+ gradient_items: &[GradientItem],
+ total_length: Au,
+) -> GradientBuilder {
+ // Determine the position of each stop per CSS-IMAGES § 3.4.
+
+ // Only keep the color stops, discard the color interpolation hints.
+ let mut stop_items = gradient_items
+ .iter()
+ .filter_map(|item| match *item {
+ GradientItem::SimpleColorStop(color) => Some(ColorStop {
+ color,
+ position: None,
+ }),
+ GradientItem::ComplexColorStop { color, position } => Some(ColorStop {
+ color,
+ position: Some(position),
+ }),
+ _ => None,
+ })
+ .collect::<Vec<_>>();
+
+ assert!(stop_items.len() >= 2);
+
+ // Run the algorithm from
+ // https://drafts.csswg.org/css-images-3/#color-stop-syntax
+
+ // Step 1:
+ // If the first color stop does not have a position, set its position to 0%.
+ {
+ let first = stop_items.first_mut().unwrap();
+ if first.position.is_none() {
+ first.position = Some(LengthPercentage::new_percent(Percentage(0.)));
+ }
+ }
+ // If the last color stop does not have a position, set its position to 100%.
+ {
+ let last = stop_items.last_mut().unwrap();
+ if last.position.is_none() {
+ last.position = Some(LengthPercentage::new_percent(Percentage(1.0)));
+ }
+ }
+
+ // Step 2: Move any stops placed before earlier stops to the
+ // same position as the preceding stop.
+ let mut last_stop_position = stop_items.first().unwrap().position.unwrap();
+ for stop in stop_items.iter_mut().skip(1) {
+ if let Some(pos) = stop.position {
+ if position_to_offset(last_stop_position, total_length) >
+ position_to_offset(pos, total_length)
+ {
+ stop.position = Some(last_stop_position);
+ }
+ last_stop_position = stop.position.unwrap();
+ }
+ }
+
+ // Step 3: Evenly space stops without position.
+ let mut stops = GradientBuilder::new();
+ let mut stop_run = None;
+ for (i, stop) in stop_items.iter().enumerate() {
+ let offset = match stop.position {
+ None => {
+ if stop_run.is_none() {
+ // Initialize a new stop run.
+ // `unwrap()` here should never fail because this is the beginning of
+ // a stop run, which is always bounded by a length or percentage.
+ let start_offset =
+ position_to_offset(stop_items[i - 1].position.unwrap(), total_length);
+ // `unwrap()` here should never fail because this is the end of
+ // a stop run, which is always bounded by a length or percentage.
+ let (end_index, end_stop) = stop_items[(i + 1)..]
+ .iter()
+ .enumerate()
+ .find(|&(_, ref stop)| stop.position.is_some())
+ .unwrap();
+ let end_offset = position_to_offset(end_stop.position.unwrap(), total_length);
+ stop_run = Some(StopRun {
+ start_offset,
+ end_offset,
+ start_index: i - 1,
+ stop_count: end_index,
+ })
+ }
+
+ let stop_run = stop_run.unwrap();
+ let stop_run_length = stop_run.end_offset - stop_run.start_offset;
+ stop_run.start_offset +
+ stop_run_length * (i - stop_run.start_index) as f32 /
+ ((2 + stop_run.stop_count) as f32)
+ },
+ Some(position) => {
+ stop_run = None;
+ position_to_offset(position, total_length)
+ },
+ };
+ assert!(offset.is_finite());
+ stops.push(GradientStop {
+ offset: offset,
+ color: style.resolve_color(stop.color).to_layout(),
+ })
+ }
+ stops
+}
+
+fn extend_mode(repeating: bool) -> ExtendMode {
+ if repeating {
+ ExtendMode::Repeat
+ } else {
+ ExtendMode::Clamp
+ }
+}
+/// Returns the the distance to the nearest or farthest corner depending on the comperator.
+fn distance_to_corner<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Au
+where
+ F: Fn(Au, Au) -> Au,
+{
+ let dist = distance_to_sides(size, center, cmp);
+ Au::from_f32_px(dist.width.to_f32_px().hypot(dist.height.to_f32_px()))
+}
+
+/// Returns the distance to the nearest or farthest sides depending on the comparator.
+///
+/// The first return value is horizontal distance the second vertical distance.
+fn distance_to_sides<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Size2D<Au>
+where
+ F: Fn(Au, Au) -> Au,
+{
+ let top_side = center.y;
+ let right_side = size.width - center.x;
+ let bottom_side = size.height - center.y;
+ let left_side = center.x;
+ Size2D::new(cmp(left_side, right_side), cmp(top_side, bottom_side))
+}
+
+fn position_to_offset(position: LengthPercentage, total_length: Au) -> f32 {
+ if total_length == Au(0) {
+ return 0.0;
+ }
+ position.to_used_value(total_length).0 as f32 / total_length.0 as f32
+}
+
+pub fn linear(
+ style: &ComputedValues,
+ size: Size2D<Au>,
+ stops: &[GradientItem],
+ direction: LineDirection,
+ repeating: bool,
+) -> (Gradient, Vec<GradientStop>) {
+ use style::values::specified::position::HorizontalPositionKeyword::*;
+ use style::values::specified::position::VerticalPositionKeyword::*;
+ let angle = match direction {
+ LineDirection::Angle(angle) => angle.radians(),
+ LineDirection::Horizontal(x) => match x {
+ Left => Angle::from_degrees(270.).radians(),
+ Right => Angle::from_degrees(90.).radians(),
+ },
+ LineDirection::Vertical(y) => match y {
+ Top => Angle::from_degrees(0.).radians(),
+ Bottom => Angle::from_degrees(180.).radians(),
+ },
+ LineDirection::Corner(horizontal, vertical) => {
+ // This the angle for one of the diagonals of the box. Our angle
+ // will either be this one, this one + PI, or one of the other
+ // two perpendicular angles.
+ let atan = (size.height.to_f32_px() / size.width.to_f32_px()).atan();
+ match (horizontal, vertical) {
+ (Right, Bottom) => ::std::f32::consts::PI - atan,
+ (Left, Bottom) => ::std::f32::consts::PI + atan,
+ (Right, Top) => atan,
+ (Left, Top) => -atan,
+ }
+ },
+ };
+
+ // Get correct gradient line length, based on:
+ // https://drafts.csswg.org/css-images-3/#linear-gradients
+ let dir = Point2D::new(angle.sin(), -angle.cos());
+
+ let line_length =
+ (dir.x * size.width.to_f32_px()).abs() + (dir.y * size.height.to_f32_px()).abs();
+
+ let inv_dir_length = 1.0 / (dir.x * dir.x + dir.y * dir.y).sqrt();
+
+ // This is the vector between the center and the ending point; i.e. half
+ // of the distance between the starting point and the ending point.
+ let delta = Vector2D::new(
+ Au::from_f32_px(dir.x * inv_dir_length * line_length / 2.0),
+ Au::from_f32_px(dir.y * inv_dir_length * line_length / 2.0),
+ );
+
+ // This is the length of the gradient line.
+ let length = Au::from_f32_px((delta.x.to_f32_px() * 2.0).hypot(delta.y.to_f32_px() * 2.0));
+
+ let mut builder = convert_gradient_stops(style, stops, length);
+
+ let center = Point2D::new(size.width / 2, size.height / 2);
+
+ (
+ builder.gradient(
+ (center - delta).to_layout(),
+ (center + delta).to_layout(),
+ extend_mode(repeating),
+ ),
+ builder.into_stops(),
+ )
+}
+
+pub fn radial(
+ style: &ComputedValues,
+ size: Size2D<Au>,
+ stops: &[GradientItem],
+ shape: EndingShape,
+ center: Position,
+ repeating: bool,
+) -> (RadialGradient, Vec<GradientStop>) {
+ let center = Point2D::new(
+ center.horizontal.to_used_value(size.width),
+ center.vertical.to_used_value(size.height),
+ );
+ let radius = match shape {
+ EndingShape::Circle(Circle::Radius(length)) => {
+ let length = Au::from(length);
+ Size2D::new(length, length)
+ },
+ EndingShape::Circle(Circle::Extent(extent)) => circle_size_keyword(extent, &size, &center),
+ EndingShape::Ellipse(Ellipse::Radii(x, y)) => {
+ Size2D::new(x.to_used_value(size.width), y.to_used_value(size.height))
+ },
+ EndingShape::Ellipse(Ellipse::Extent(extent)) => {
+ ellipse_size_keyword(extent, &size, &center)
+ },
+ };
+
+ let mut builder = convert_gradient_stops(style, stops, radius.width);
+ (
+ builder.radial_gradient(
+ center.to_layout(),
+ radius.to_layout(),
+ extend_mode(repeating),
+ ),
+ builder.into_stops(),
+ )
+}
diff --git a/components/layout_2020/display_list/items.rs b/components/layout_2020/display_list/items.rs
new file mode 100644
index 00000000000..b6674d972fe
--- /dev/null
+++ b/components/layout_2020/display_list/items.rs
@@ -0,0 +1,795 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+//! Servo heavily uses display lists, which are retained-mode lists of painting commands to
+//! perform. Using a list instead of painting elements in immediate mode allows transforms, hit
+//! testing, and invalidation to be performed using the same primitives as painting. It also allows
+//! Servo to aggressively cull invisible and out-of-bounds painting elements, to reduce overdraw.
+//!
+//! Display items describe relatively high-level drawing operations (for example, entire borders
+//! and shadows instead of lines and blur operations), to reduce the amount of allocation required.
+//! They are therefore not exactly analogous to constructs like Skia pictures, which consist of
+//! low-level drawing primitives.
+
+use euclid::{SideOffsets2D, Vector2D};
+use gfx_traits::print_tree::PrintTree;
+use gfx_traits::{self, StackingContextId};
+use msg::constellation_msg::PipelineId;
+use net_traits::image::base::Image;
+use servo_geometry::MaxRect;
+use std::cmp::Ordering;
+use std::collections::HashMap;
+use std::f32;
+use std::fmt;
+use style::computed_values::_servo_top_layer::T as InTopLayer;
+use webrender_api as wr;
+use webrender_api::units::{LayoutPoint, LayoutRect, LayoutSize, LayoutTransform};
+use webrender_api::{BorderRadius, ClipId, ClipMode, CommonItemProperties, ComplexClipRegion};
+use webrender_api::{ExternalScrollId, FilterOp, GlyphInstance, GradientStop, ImageKey};
+use webrender_api::{MixBlendMode, ScrollSensitivity, Shadow, SpatialId};
+use webrender_api::{StickyOffsetBounds, TransformStyle};
+
+pub use style::dom::OpaqueNode;
+
+/// The factor that we multiply the blur radius by in order to inflate the boundaries of display
+/// items that involve a blur. This ensures that the display item boundaries include all the ink.
+pub static BLUR_INFLATION_FACTOR: i32 = 3;
+
+/// An index into the vector of ClipScrollNodes. During WebRender conversion these nodes
+/// are given ClipIds.
+#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
+pub struct ClipScrollNodeIndex(usize);
+
+impl ClipScrollNodeIndex {
+ pub fn root_scroll_node() -> ClipScrollNodeIndex {
+ ClipScrollNodeIndex(1)
+ }
+
+ pub fn root_reference_frame() -> ClipScrollNodeIndex {
+ ClipScrollNodeIndex(0)
+ }
+
+ pub fn new(index: usize) -> ClipScrollNodeIndex {
+ assert_ne!(index, 0, "Use the root_reference_frame constructor");
+ assert_ne!(index, 1, "Use the root_scroll_node constructor");
+ ClipScrollNodeIndex(index)
+ }
+
+ pub fn is_root_scroll_node(&self) -> bool {
+ *self == Self::root_scroll_node()
+ }
+
+ pub fn to_define_item(&self) -> DisplayItem {
+ DisplayItem::DefineClipScrollNode(Box::new(DefineClipScrollNodeItem {
+ base: BaseDisplayItem::empty(),
+ node_index: *self,
+ }))
+ }
+
+ pub fn to_index(self) -> usize {
+ self.0
+ }
+}
+
+/// A set of indices into the clip scroll node vector for a given item.
+#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
+pub struct ClippingAndScrolling {
+ pub scrolling: ClipScrollNodeIndex,
+ pub clipping: Option<ClipScrollNodeIndex>,
+}
+
+impl ClippingAndScrolling {
+ pub fn simple(scrolling: ClipScrollNodeIndex) -> ClippingAndScrolling {
+ ClippingAndScrolling {
+ scrolling,
+ clipping: None,
+ }
+ }
+
+ pub fn new(scrolling: ClipScrollNodeIndex, clipping: ClipScrollNodeIndex) -> Self {
+ ClippingAndScrolling {
+ scrolling,
+ clipping: Some(clipping),
+ }
+ }
+}
+
+#[derive(Serialize)]
+pub struct DisplayList {
+ pub list: Vec<DisplayItem>,
+ pub clip_scroll_nodes: Vec<ClipScrollNode>,
+}
+
+impl DisplayList {
+ /// Return the bounds of this display list based on the dimensions of the root
+ /// stacking context.
+ pub fn bounds(&self) -> LayoutRect {
+ match self.list.get(0) {
+ Some(&DisplayItem::PushStackingContext(ref item)) => item.stacking_context.bounds,
+ Some(_) => unreachable!("Root element of display list not stacking context."),
+ None => LayoutRect::zero(),
+ }
+ }
+
+ pub fn print(&self) {
+ let mut print_tree = PrintTree::new("Display List".to_owned());
+ self.print_with_tree(&mut print_tree);
+ }
+
+ pub fn print_with_tree(&self, print_tree: &mut PrintTree) {
+ print_tree.new_level("ClipScrollNodes".to_owned());
+ for node in &self.clip_scroll_nodes {
+ print_tree.add_item(format!("{:?}", node));
+ }
+ print_tree.end_level();
+
+ print_tree.new_level("Items".to_owned());
+ for item in &self.list {
+ print_tree.add_item(format!(
+ "{:?} StackingContext: {:?} {:?}",
+ item,
+ item.base().stacking_context_id,
+ item.clipping_and_scrolling()
+ ));
+ }
+ print_tree.end_level();
+ }
+}
+
+impl gfx_traits::DisplayList for DisplayList {
+ /// Analyze the display list to figure out if this may be the first
+ /// contentful paint (i.e. the display list contains items of type text,
+ /// image, non-white canvas or SVG). Used by metrics.
+ fn is_contentful(&self) -> bool {
+ for item in &self.list {
+ match item {
+ &DisplayItem::Text(_) | &DisplayItem::Image(_) => return true,
+ _ => (),
+ }
+ }
+
+ false
+ }
+}
+
+/// Display list sections that make up a stacking context. Each section here refers
+/// to the steps in CSS 2.1 Appendix E.
+///
+#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
+pub enum DisplayListSection {
+ BackgroundAndBorders,
+ BlockBackgroundsAndBorders,
+ Content,
+ Outlines,
+}
+
+#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
+pub enum StackingContextType {
+ Real,
+ PseudoPositioned,
+ PseudoFloat,
+}
+
+#[derive(Clone, Serialize)]
+/// Represents one CSS stacking context, which may or may not have a hardware layer.
+pub struct StackingContext {
+ /// The ID of this StackingContext for uniquely identifying it.
+ pub id: StackingContextId,
+
+ /// The type of this StackingContext. Used for collecting and sorting.
+ pub context_type: StackingContextType,
+
+ /// The position and size of this stacking context.
+ pub bounds: LayoutRect,
+
+ /// The overflow rect for this stacking context in its coordinate system.
+ pub overflow: LayoutRect,
+
+ /// The `z-index` for this stacking context.
+ pub z_index: i32,
+
+ /// Whether this is the top layer.
+ pub in_top_layer: InTopLayer,
+
+ /// CSS filters to be applied to this stacking context (including opacity).
+ pub filters: Vec<FilterOp>,
+
+ /// The blend mode with which this stacking context blends with its backdrop.
+ pub mix_blend_mode: MixBlendMode,
+
+ /// A transform to be applied to this stacking context.
+ pub transform: Option<LayoutTransform>,
+
+ /// The transform style of this stacking context.
+ pub transform_style: TransformStyle,
+
+ /// The perspective matrix to be applied to children.
+ pub perspective: Option<LayoutTransform>,
+
+ /// The clip and scroll info for this StackingContext.
+ pub parent_clipping_and_scrolling: ClippingAndScrolling,
+
+ /// The index of the reference frame that this stacking context estalishes.
+ pub established_reference_frame: Option<ClipScrollNodeIndex>,
+}
+
+impl StackingContext {
+ /// Creates a new stacking context.
+ #[inline]
+ pub fn new(
+ id: StackingContextId,
+ context_type: StackingContextType,
+ bounds: LayoutRect,
+ overflow: LayoutRect,
+ z_index: i32,
+ in_top_layer: InTopLayer,
+ filters: Vec<FilterOp>,
+ mix_blend_mode: MixBlendMode,
+ transform: Option<LayoutTransform>,
+ transform_style: TransformStyle,
+ perspective: Option<LayoutTransform>,
+ parent_clipping_and_scrolling: ClippingAndScrolling,
+ established_reference_frame: Option<ClipScrollNodeIndex>,
+ ) -> StackingContext {
+ StackingContext {
+ id,
+ context_type,
+ bounds,
+ overflow,
+ z_index,
+ in_top_layer,
+ filters,
+ mix_blend_mode,
+ transform,
+ transform_style,
+ perspective,
+ parent_clipping_and_scrolling,
+ established_reference_frame,
+ }
+ }
+
+ #[inline]
+ pub fn root() -> StackingContext {
+ StackingContext::new(
+ StackingContextId::root(),
+ StackingContextType::Real,
+ LayoutRect::zero(),
+ LayoutRect::zero(),
+ 0,
+ InTopLayer::None,
+ vec![],
+ MixBlendMode::Normal,
+ None,
+ TransformStyle::Flat,
+ None,
+ ClippingAndScrolling::simple(ClipScrollNodeIndex::root_scroll_node()),
+ None,
+ )
+ }
+
+ pub fn to_display_list_items(self) -> (DisplayItem, DisplayItem) {
+ let mut base_item = BaseDisplayItem::empty();
+ base_item.stacking_context_id = self.id;
+ base_item.clipping_and_scrolling = self.parent_clipping_and_scrolling;
+
+ let pop_item = DisplayItem::PopStackingContext(Box::new(PopStackingContextItem {
+ base: base_item.clone(),
+ stacking_context_id: self.id,
+ }));
+
+ let push_item = DisplayItem::PushStackingContext(Box::new(PushStackingContextItem {
+ base: base_item,
+ stacking_context: self,
+ }));
+
+ (push_item, pop_item)
+ }
+}
+
+impl Ord for StackingContext {
+ fn cmp(&self, other: &Self) -> Ordering {
+ if self.in_top_layer == InTopLayer::Top {
+ if other.in_top_layer == InTopLayer::Top {
+ return Ordering::Equal;
+ } else {
+ return Ordering::Greater;
+ }
+ } else if other.in_top_layer == InTopLayer::Top {
+ return Ordering::Less;
+ }
+
+ if self.z_index != 0 || other.z_index != 0 {
+ return self.z_index.cmp(&other.z_index);
+ }
+
+ match (self.context_type, other.context_type) {
+ (StackingContextType::PseudoFloat, StackingContextType::PseudoFloat) => Ordering::Equal,
+ (StackingContextType::PseudoFloat, _) => Ordering::Less,
+ (_, StackingContextType::PseudoFloat) => Ordering::Greater,
+ (_, _) => Ordering::Equal,
+ }
+ }
+}
+
+impl PartialOrd for StackingContext {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+impl Eq for StackingContext {}
+impl PartialEq for StackingContext {
+ fn eq(&self, other: &Self) -> bool {
+ self.id == other.id
+ }
+}
+
+impl fmt::Debug for StackingContext {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let type_string = if self.context_type == StackingContextType::Real {
+ "StackingContext"
+ } else {
+ "Pseudo-StackingContext"
+ };
+
+ write!(
+ f,
+ "{} at {:?} with overflow {:?}: {:?}",
+ type_string, self.bounds, self.overflow, self.id
+ )
+ }
+}
+
+#[derive(Clone, Debug, PartialEq, Serialize)]
+pub struct StickyFrameData {
+ pub margins: SideOffsets2D<Option<f32>>,
+ pub vertical_offset_bounds: StickyOffsetBounds,
+ pub horizontal_offset_bounds: StickyOffsetBounds,
+}
+
+#[derive(Clone, Debug, PartialEq, Serialize)]
+pub enum ClipScrollNodeType {
+ Placeholder,
+ ScrollFrame(ScrollSensitivity, ExternalScrollId),
+ StickyFrame(StickyFrameData),
+ Clip,
+}
+
+/// Defines a clip scroll node.
+#[derive(Clone, Debug, Serialize)]
+pub struct ClipScrollNode {
+ /// The index of the parent of this ClipScrollNode.
+ pub parent_index: ClipScrollNodeIndex,
+
+ /// The position of this scroll root's frame in the parent stacking context.
+ pub clip: ClippingRegion,
+
+ /// The rect of the contents that can be scrolled inside of the scroll root.
+ pub content_rect: LayoutRect,
+
+ /// The type of this ClipScrollNode.
+ pub node_type: ClipScrollNodeType,
+}
+
+impl ClipScrollNode {
+ pub fn placeholder() -> ClipScrollNode {
+ ClipScrollNode {
+ parent_index: ClipScrollNodeIndex(0),
+ clip: ClippingRegion::from_rect(LayoutRect::zero()),
+ content_rect: LayoutRect::zero(),
+ node_type: ClipScrollNodeType::Placeholder,
+ }
+ }
+
+ pub fn is_placeholder(&self) -> bool {
+ self.node_type == ClipScrollNodeType::Placeholder
+ }
+}
+
+/// One drawing command in the list.
+#[derive(Clone, Serialize)]
+pub enum DisplayItem {
+ Rectangle(Box<CommonDisplayItem<wr::RectangleDisplayItem>>),
+ Text(Box<CommonDisplayItem<wr::TextDisplayItem, Vec<GlyphInstance>>>),
+ Image(Box<CommonDisplayItem<wr::ImageDisplayItem>>),
+ Border(Box<CommonDisplayItem<wr::BorderDisplayItem, Vec<GradientStop>>>),
+ Gradient(Box<CommonDisplayItem<wr::GradientDisplayItem, Vec<GradientStop>>>),
+ RadialGradient(Box<CommonDisplayItem<wr::RadialGradientDisplayItem, Vec<GradientStop>>>),
+ Line(Box<CommonDisplayItem<wr::LineDisplayItem>>),
+ BoxShadow(Box<CommonDisplayItem<wr::BoxShadowDisplayItem>>),
+ PushTextShadow(Box<PushTextShadowDisplayItem>),
+ PopAllTextShadows(Box<PopAllTextShadowsDisplayItem>),
+ Iframe(Box<IframeDisplayItem>),
+ PushStackingContext(Box<PushStackingContextItem>),
+ PopStackingContext(Box<PopStackingContextItem>),
+ DefineClipScrollNode(Box<DefineClipScrollNodeItem>),
+}
+
+/// Information common to all display items.
+#[derive(Clone, Serialize)]
+pub struct BaseDisplayItem {
+ /// Metadata attached to this display item.
+ pub metadata: DisplayItemMetadata,
+
+ /// The clip rectangle to use for this item.
+ pub clip_rect: LayoutRect,
+
+ /// The section of the display list that this item belongs to.
+ pub section: DisplayListSection,
+
+ /// The id of the stacking context this item belongs to.
+ pub stacking_context_id: StackingContextId,
+
+ /// The clip and scroll info for this item.
+ pub clipping_and_scrolling: ClippingAndScrolling,
+}
+
+impl BaseDisplayItem {
+ #[inline(always)]
+ pub fn new(
+ metadata: DisplayItemMetadata,
+ clip_rect: LayoutRect,
+ section: DisplayListSection,
+ stacking_context_id: StackingContextId,
+ clipping_and_scrolling: ClippingAndScrolling,
+ ) -> BaseDisplayItem {
+ BaseDisplayItem {
+ metadata,
+ clip_rect,
+ section,
+ stacking_context_id,
+ clipping_and_scrolling,
+ }
+ }
+
+ #[inline(always)]
+ pub fn empty() -> BaseDisplayItem {
+ BaseDisplayItem {
+ metadata: DisplayItemMetadata {
+ node: OpaqueNode(0),
+ pointing: None,
+ },
+ // Create a rectangle of maximal size.
+ clip_rect: LayoutRect::max_rect(),
+ section: DisplayListSection::Content,
+ stacking_context_id: StackingContextId::root(),
+ clipping_and_scrolling: ClippingAndScrolling::simple(
+ ClipScrollNodeIndex::root_scroll_node(),
+ ),
+ }
+ }
+}
+
+pub fn empty_common_item_properties() -> CommonItemProperties {
+ CommonItemProperties {
+ clip_rect: LayoutRect::max_rect(),
+ clip_id: ClipId::root(wr::PipelineId::dummy()),
+ spatial_id: SpatialId::root_scroll_node(wr::PipelineId::dummy()),
+ hit_info: None,
+ is_backface_visible: false,
+ }
+}
+
+/// A clipping region for a display item. Currently, this can describe rectangles, rounded
+/// rectangles (for `border-radius`), or arbitrary intersections of the two. Arbitrary transforms
+/// are not supported because those are handled by the higher-level `StackingContext` abstraction.
+#[derive(Clone, PartialEq, Serialize)]
+pub struct ClippingRegion {
+ /// The main rectangular region. This does not include any corners.
+ pub main: LayoutRect,
+ /// Any complex regions.
+ ///
+ /// TODO(pcwalton): Atomically reference count these? Not sure if it's worth the trouble.
+ /// Measure and follow up.
+ pub complex: Vec<ComplexClipRegion>,
+}
+
+impl ClippingRegion {
+ /// Returns an empty clipping region that, if set, will result in no pixels being visible.
+ #[inline]
+ pub fn empty() -> ClippingRegion {
+ ClippingRegion {
+ main: LayoutRect::zero(),
+ complex: Vec::new(),
+ }
+ }
+
+ /// Returns an all-encompassing clipping region that clips no pixels out.
+ #[inline]
+ pub fn max() -> ClippingRegion {
+ ClippingRegion {
+ main: LayoutRect::max_rect(),
+ complex: Vec::new(),
+ }
+ }
+
+ /// Returns a clipping region that represents the given rectangle.
+ #[inline]
+ pub fn from_rect(rect: LayoutRect) -> ClippingRegion {
+ ClippingRegion {
+ main: rect,
+ complex: Vec::new(),
+ }
+ }
+
+ /// Intersects this clipping region with the given rounded rectangle.
+ #[inline]
+ pub fn intersect_with_rounded_rect(&mut self, rect: LayoutRect, radii: BorderRadius) {
+ let new_complex_region = ComplexClipRegion {
+ rect,
+ radii,
+ mode: ClipMode::Clip,
+ };
+
+ // FIXME(pcwalton): This is O(n²) worst case for disjoint clipping regions. Is that OK?
+ // They're slow anyway…
+ //
+ // Possibly relevant if we want to do better:
+ //
+ // http://www.inrg.csie.ntu.edu.tw/algorithm2014/presentation/D&C%20Lee-84.pdf
+ for existing_complex_region in &mut self.complex {
+ if completely_encloses(&existing_complex_region, &new_complex_region) {
+ *existing_complex_region = new_complex_region;
+ return;
+ }
+ if completely_encloses(&new_complex_region, &existing_complex_region) {
+ return;
+ }
+ }
+
+ self.complex.push(new_complex_region);
+ }
+}
+
+impl fmt::Debug for ClippingRegion {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ if *self == ClippingRegion::max() {
+ write!(f, "ClippingRegion::Max")
+ } else if *self == ClippingRegion::empty() {
+ write!(f, "ClippingRegion::Empty")
+ } else if self.main == LayoutRect::max_rect() {
+ write!(f, "ClippingRegion(Complex={:?})", self.complex)
+ } else {
+ write!(
+ f,
+ "ClippingRegion(Rect={:?}, Complex={:?})",
+ self.main, self.complex
+ )
+ }
+ }
+}
+
+// TODO(pcwalton): This could be more aggressive by considering points that touch the inside of
+// the border radius ellipse.
+fn completely_encloses(this: &ComplexClipRegion, other: &ComplexClipRegion) -> bool {
+ let left = this.radii.top_left.width.max(this.radii.bottom_left.width);
+ let top = this.radii.top_left.height.max(this.radii.top_right.height);
+ let right = this
+ .radii
+ .top_right
+ .width
+ .max(this.radii.bottom_right.width);
+ let bottom = this
+ .radii
+ .bottom_left
+ .height
+ .max(this.radii.bottom_right.height);
+ let interior = LayoutRect::new(
+ LayoutPoint::new(this.rect.origin.x + left, this.rect.origin.y + top),
+ LayoutSize::new(
+ this.rect.size.width - left - right,
+ this.rect.size.height - top - bottom,
+ ),
+ );
+ interior.origin.x <= other.rect.origin.x &&
+ interior.origin.y <= other.rect.origin.y &&
+ interior.max_x() >= other.rect.max_x() &&
+ interior.max_y() >= other.rect.max_y()
+}
+
+/// Metadata attached to each display item. This is useful for performing auxiliary threads with
+/// the display list involving hit testing: finding the originating DOM node and determining the
+/// cursor to use when the element is hovered over.
+#[derive(Clone, Copy, Serialize)]
+pub struct DisplayItemMetadata {
+ /// The DOM node from which this display item originated.
+ pub node: OpaqueNode,
+ /// The value of the `cursor` property when the mouse hovers over this display item. If `None`,
+ /// this display item is ineligible for pointer events (`pointer-events: none`).
+ pub pointing: Option<u16>,
+}
+
+#[derive(Clone, Eq, PartialEq, Serialize)]
+pub enum TextOrientation {
+ Upright,
+ SidewaysLeft,
+ SidewaysRight,
+}
+
+/// Paints an iframe.
+#[derive(Clone, Serialize)]
+pub struct IframeDisplayItem {
+ pub base: BaseDisplayItem,
+ pub iframe: PipelineId,
+ pub bounds: LayoutRect,
+}
+
+#[derive(Clone, Serialize)]
+pub struct CommonDisplayItem<T, U = ()> {
+ pub base: BaseDisplayItem,
+ pub item: T,
+ pub data: U,
+}
+
+impl<T> CommonDisplayItem<T> {
+ pub fn new(base: BaseDisplayItem, item: T) -> Box<CommonDisplayItem<T>> {
+ Box::new(CommonDisplayItem {
+ base,
+ item,
+ data: (),
+ })
+ }
+}
+
+impl<T, U> CommonDisplayItem<T, U> {
+ pub fn with_data(base: BaseDisplayItem, item: T, data: U) -> Box<CommonDisplayItem<T, U>> {
+ Box::new(CommonDisplayItem { base, item, data })
+ }
+}
+
+/// Defines a text shadow that affects all items until the paired PopTextShadow.
+#[derive(Clone, Serialize)]
+pub struct PushTextShadowDisplayItem {
+ /// Fields common to all display items.
+ pub base: BaseDisplayItem,
+
+ pub shadow: Shadow,
+}
+
+/// Defines a text shadow that affects all items until the next PopTextShadow.
+#[derive(Clone, Serialize)]
+pub struct PopAllTextShadowsDisplayItem {
+ /// Fields common to all display items.
+ pub base: BaseDisplayItem,
+}
+
+/// Defines a stacking context.
+#[derive(Clone, Serialize)]
+pub struct PushStackingContextItem {
+ /// Fields common to all display items.
+ pub base: BaseDisplayItem,
+
+ pub stacking_context: StackingContext,
+}
+
+/// Defines a stacking context.
+#[derive(Clone, Serialize)]
+pub struct PopStackingContextItem {
+ /// Fields common to all display items.
+ pub base: BaseDisplayItem,
+
+ pub stacking_context_id: StackingContextId,
+}
+
+/// Starts a group of items inside a particular scroll root.
+#[derive(Clone, Serialize)]
+pub struct DefineClipScrollNodeItem {
+ /// Fields common to all display items.
+ pub base: BaseDisplayItem,
+
+ /// The scroll root that this item starts.
+ pub node_index: ClipScrollNodeIndex,
+}
+
+impl DisplayItem {
+ pub fn base(&self) -> &BaseDisplayItem {
+ match *self {
+ DisplayItem::Rectangle(ref rect) => &rect.base,
+ DisplayItem::Text(ref text) => &text.base,
+ DisplayItem::Image(ref image_item) => &image_item.base,
+ DisplayItem::Border(ref border) => &border.base,
+ DisplayItem::Gradient(ref gradient) => &gradient.base,
+ DisplayItem::RadialGradient(ref gradient) => &gradient.base,
+ DisplayItem::Line(ref line) => &line.base,
+ DisplayItem::BoxShadow(ref box_shadow) => &box_shadow.base,
+ DisplayItem::PushTextShadow(ref push_text_shadow) => &push_text_shadow.base,
+ DisplayItem::PopAllTextShadows(ref pop_text_shadow) => &pop_text_shadow.base,
+ DisplayItem::Iframe(ref iframe) => &iframe.base,
+ DisplayItem::PushStackingContext(ref stacking_context) => &stacking_context.base,
+ DisplayItem::PopStackingContext(ref item) => &item.base,
+ DisplayItem::DefineClipScrollNode(ref item) => &item.base,
+ }
+ }
+
+ pub fn clipping_and_scrolling(&self) -> ClippingAndScrolling {
+ self.base().clipping_and_scrolling
+ }
+
+ pub fn stacking_context_id(&self) -> StackingContextId {
+ self.base().stacking_context_id
+ }
+
+ pub fn section(&self) -> DisplayListSection {
+ self.base().section
+ }
+
+ pub fn bounds(&self) -> LayoutRect {
+ match *self {
+ DisplayItem::Rectangle(ref item) => item.item.common.clip_rect,
+ DisplayItem::Text(ref item) => item.item.bounds,
+ DisplayItem::Image(ref item) => item.item.bounds,
+ DisplayItem::Border(ref item) => item.item.bounds,
+ DisplayItem::Gradient(ref item) => item.item.bounds,
+ DisplayItem::RadialGradient(ref item) => item.item.bounds,
+ DisplayItem::Line(ref item) => item.item.area,
+ DisplayItem::BoxShadow(ref item) => item.item.box_bounds,
+ DisplayItem::PushTextShadow(_) => LayoutRect::zero(),
+ DisplayItem::PopAllTextShadows(_) => LayoutRect::zero(),
+ DisplayItem::Iframe(ref item) => item.bounds,
+ DisplayItem::PushStackingContext(ref item) => item.stacking_context.bounds,
+ DisplayItem::PopStackingContext(_) => LayoutRect::zero(),
+ DisplayItem::DefineClipScrollNode(_) => LayoutRect::zero(),
+ }
+ }
+}
+
+impl fmt::Debug for DisplayItem {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ if let DisplayItem::PushStackingContext(ref item) = *self {
+ return write!(f, "PushStackingContext({:?})", item.stacking_context);
+ }
+
+ if let DisplayItem::PopStackingContext(ref item) = *self {
+ return write!(f, "PopStackingContext({:?}", item.stacking_context_id);
+ }
+
+ if let DisplayItem::DefineClipScrollNode(ref item) = *self {
+ return write!(f, "DefineClipScrollNode({:?}", item.node_index);
+ }
+
+ write!(
+ f,
+ "{} @ {:?} {:?}",
+ match *self {
+ DisplayItem::Rectangle(_) => "Rectangle".to_owned(),
+ DisplayItem::Text(_) => "Text".to_owned(),
+ DisplayItem::Image(_) => "Image".to_owned(),
+ DisplayItem::Border(_) => "Border".to_owned(),
+ DisplayItem::Gradient(_) => "Gradient".to_owned(),
+ DisplayItem::RadialGradient(_) => "RadialGradient".to_owned(),
+ DisplayItem::Line(_) => "Line".to_owned(),
+ DisplayItem::BoxShadow(_) => "BoxShadow".to_owned(),
+ DisplayItem::PushTextShadow(_) => "PushTextShadow".to_owned(),
+ DisplayItem::PopAllTextShadows(_) => "PopTextShadow".to_owned(),
+ DisplayItem::Iframe(_) => "Iframe".to_owned(),
+ DisplayItem::PushStackingContext(_) |
+ DisplayItem::PopStackingContext(_) |
+ DisplayItem::DefineClipScrollNode(_) => "".to_owned(),
+ },
+ self.bounds(),
+ self.base().clip_rect
+ )
+ }
+}
+
+#[derive(Clone, Copy, Serialize)]
+pub struct WebRenderImageInfo {
+ pub width: u32,
+ pub height: u32,
+ pub key: Option<ImageKey>,
+}
+
+impl WebRenderImageInfo {
+ #[inline]
+ pub fn from_image(image: &Image) -> WebRenderImageInfo {
+ WebRenderImageInfo {
+ width: image.width,
+ height: image.height,
+ key: image.id,
+ }
+ }
+}
+
+/// The type of the scroll offset list. This is only populated if WebRender is in use.
+pub type ScrollOffsetMap = HashMap<ExternalScrollId, Vector2D<f32>>;
diff --git a/components/layout_2020/display_list/mod.rs b/components/layout_2020/display_list/mod.rs
new file mode 100644
index 00000000000..302728fbf22
--- /dev/null
+++ b/components/layout_2020/display_list/mod.rs
@@ -0,0 +1,19 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+pub use self::builder::BorderPaintingMode;
+pub use self::builder::DisplayListBuildState;
+pub use self::builder::IndexableText;
+pub use self::builder::StackingContextCollectionFlags;
+pub use self::builder::StackingContextCollectionState;
+pub use self::conversions::ToLayout;
+pub use self::webrender_helpers::WebRenderDisplayListConverter;
+
+mod background;
+mod border;
+mod builder;
+mod conversions;
+mod gradient;
+pub mod items;
+mod webrender_helpers;
diff --git a/components/layout_2020/display_list/webrender_helpers.rs b/components/layout_2020/display_list/webrender_helpers.rs
new file mode 100644
index 00000000000..6bdb1bfcfdc
--- /dev/null
+++ b/components/layout_2020/display_list/webrender_helpers.rs
@@ -0,0 +1,321 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+// TODO(gw): This contains helper traits and implementations for converting Servo display lists
+// into WebRender display lists. In the future, this step should be completely removed.
+// This might be achieved by sharing types between WR and Servo display lists, or
+// completely converting layout to directly generate WebRender display lists, for example.
+
+use crate::display_list::items::{BaseDisplayItem, ClipScrollNode, ClipScrollNodeType};
+use crate::display_list::items::{DisplayItem, DisplayList, StackingContextType};
+use msg::constellation_msg::PipelineId;
+use webrender_api::units::LayoutPoint;
+use webrender_api::{self, ClipId, CommonItemProperties, DisplayItem as WrDisplayItem};
+use webrender_api::{DisplayListBuilder, PropertyBinding, PushStackingContextDisplayItem};
+use webrender_api::{
+ RasterSpace, ReferenceFrameKind, SpaceAndClipInfo, SpatialId, StackingContext,
+};
+
+pub trait WebRenderDisplayListConverter {
+ fn convert_to_webrender(&mut self, pipeline_id: PipelineId) -> DisplayListBuilder;
+}
+
+struct ClipScrollState {
+ clip_ids: Vec<Option<ClipId>>,
+ spatial_ids: Vec<Option<SpatialId>>,
+ active_clip_id: ClipId,
+ active_spatial_id: SpatialId,
+}
+
+trait WebRenderDisplayItemConverter {
+ fn convert_to_webrender(
+ &mut self,
+ clip_scroll_nodes: &[ClipScrollNode],
+ state: &mut ClipScrollState,
+ builder: &mut DisplayListBuilder,
+ );
+}
+
+impl WebRenderDisplayListConverter for DisplayList {
+ fn convert_to_webrender(&mut self, pipeline_id: PipelineId) -> DisplayListBuilder {
+ let mut clip_ids = vec![None; self.clip_scroll_nodes.len()];
+ let mut spatial_ids = vec![None; self.clip_scroll_nodes.len()];
+
+ // We need to add the WebRender root reference frame and root scroll node ids
+ // here manually, because WebRender creates these automatically.
+ // We also follow the "old" WebRender API for clip/scroll for now,
+ // hence both arrays are initialized based on FIRST_SPATIAL_NODE_INDEX,
+ // while FIRST_CLIP_NODE_INDEX is not taken into account.
+
+ let webrender_pipeline = pipeline_id.to_webrender();
+ clip_ids[0] = Some(ClipId::root(webrender_pipeline));
+ clip_ids[1] = Some(ClipId::root(webrender_pipeline));
+ spatial_ids[0] = Some(SpatialId::root_reference_frame(webrender_pipeline));
+ spatial_ids[1] = Some(SpatialId::root_scroll_node(webrender_pipeline));
+
+ let mut state = ClipScrollState {
+ clip_ids,
+ spatial_ids,
+ active_clip_id: ClipId::root(webrender_pipeline),
+ active_spatial_id: SpatialId::root_scroll_node(webrender_pipeline),
+ };
+
+ let mut builder = DisplayListBuilder::with_capacity(
+ webrender_pipeline,
+ self.bounds().size,
+ 1024 * 1024, // 1 MB of space
+ );
+
+ for item in &mut self.list {
+ item.convert_to_webrender(&self.clip_scroll_nodes, &mut state, &mut builder);
+ }
+
+ builder
+ }
+}
+
+impl WebRenderDisplayItemConverter for DisplayItem {
+ fn convert_to_webrender(
+ &mut self,
+ clip_scroll_nodes: &[ClipScrollNode],
+ state: &mut ClipScrollState,
+ builder: &mut DisplayListBuilder,
+ ) {
+ // Note: for each time of a display item, if we register one of `clip_ids` or `spatial_ids`,
+ // we also register the other one as inherited from the current state or the stack.
+ // This is not an ideal behavior, but it is compatible with the old WebRender model
+ // of the clip-scroll tree.
+
+ let clip_and_scroll_indices = self.base().clipping_and_scrolling;
+ trace!("converting {:?}", clip_and_scroll_indices);
+
+ let cur_spatial_id = state.spatial_ids[clip_and_scroll_indices.scrolling.to_index()]
+ .expect("Tried to use WebRender SpatialId before it was defined.");
+ if cur_spatial_id != state.active_spatial_id {
+ state.active_spatial_id = cur_spatial_id;
+ }
+
+ let internal_clip_id = clip_and_scroll_indices
+ .clipping
+ .unwrap_or(clip_and_scroll_indices.scrolling);
+ let cur_clip_id = state.clip_ids[internal_clip_id.to_index()]
+ .expect("Tried to use WebRender ClipId before it was defined.");
+ if cur_clip_id != state.active_clip_id {
+ state.active_clip_id = cur_clip_id;
+ }
+
+ match *self {
+ DisplayItem::Rectangle(ref mut item) => {
+ item.item.common = build_common_item_properties(&item.base, state);
+ builder.push_item(&WrDisplayItem::Rectangle(item.item));
+ },
+ DisplayItem::Text(ref mut item) => {
+ item.item.common = build_common_item_properties(&item.base, state);
+ builder.push_item(&WrDisplayItem::Text(item.item));
+ builder.push_iter(item.data.iter());
+ },
+ DisplayItem::Image(ref mut item) => {
+ item.item.common = build_common_item_properties(&item.base, state);
+ builder.push_item(&WrDisplayItem::Image(item.item));
+ },
+ DisplayItem::Border(ref mut item) => {
+ item.item.common = build_common_item_properties(&item.base, state);
+ if !item.data.is_empty() {
+ builder.push_stops(item.data.as_ref());
+ }
+ builder.push_item(&WrDisplayItem::Border(item.item));
+ },
+ DisplayItem::Gradient(ref mut item) => {
+ item.item.common = build_common_item_properties(&item.base, state);
+ builder.push_stops(item.data.as_ref());
+ builder.push_item(&WrDisplayItem::Gradient(item.item));
+ },
+ DisplayItem::RadialGradient(ref mut item) => {
+ item.item.common = build_common_item_properties(&item.base, state);
+ builder.push_stops(item.data.as_ref());
+ builder.push_item(&WrDisplayItem::RadialGradient(item.item));
+ },
+ DisplayItem::Line(ref mut item) => {
+ item.item.common = build_common_item_properties(&item.base, state);
+ builder.push_item(&WrDisplayItem::Line(item.item));
+ },
+ DisplayItem::BoxShadow(ref mut item) => {
+ item.item.common = build_common_item_properties(&item.base, state);
+ builder.push_item(&WrDisplayItem::BoxShadow(item.item));
+ },
+ DisplayItem::PushTextShadow(ref mut item) => {
+ let common = build_common_item_properties(&item.base, state);
+ builder.push_shadow(
+ &SpaceAndClipInfo {
+ spatial_id: common.spatial_id,
+ clip_id: common.clip_id,
+ },
+ item.shadow,
+ true,
+ );
+ },
+ DisplayItem::PopAllTextShadows(_) => {
+ builder.push_item(&WrDisplayItem::PopAllShadows);
+ },
+ DisplayItem::Iframe(ref mut item) => {
+ let common = build_common_item_properties(&item.base, state);
+ builder.push_iframe(
+ item.bounds,
+ common.clip_rect,
+ &SpaceAndClipInfo {
+ spatial_id: common.spatial_id,
+ clip_id: common.clip_id,
+ },
+ item.iframe.to_webrender(),
+ true,
+ );
+ },
+ DisplayItem::PushStackingContext(ref mut item) => {
+ let stacking_context = &item.stacking_context;
+ debug_assert_eq!(stacking_context.context_type, StackingContextType::Real);
+
+ //let mut info = webrender_api::LayoutPrimitiveInfo::new(stacking_context.bounds);
+ let mut bounds = stacking_context.bounds;
+ let spatial_id =
+ if let Some(frame_index) = stacking_context.established_reference_frame {
+ let (transform, ref_frame) =
+ match (stacking_context.transform, stacking_context.perspective) {
+ (None, Some(p)) => (
+ p,
+ ReferenceFrameKind::Perspective {
+ scrolling_relative_to: None,
+ },
+ ),
+ (Some(t), None) => (t, ReferenceFrameKind::Transform),
+ (Some(t), Some(p)) => (
+ t.pre_mul(&p),
+ ReferenceFrameKind::Perspective {
+ scrolling_relative_to: None,
+ },
+ ),
+ (None, None) => unreachable!(),
+ };
+
+ let spatial_id = builder.push_reference_frame(
+ stacking_context.bounds.origin,
+ state.active_spatial_id,
+ stacking_context.transform_style,
+ PropertyBinding::Value(transform),
+ ref_frame,
+ );
+
+ state.spatial_ids[frame_index.to_index()] = Some(spatial_id);
+ state.clip_ids[frame_index.to_index()] = Some(cur_clip_id);
+
+ bounds.origin = LayoutPoint::zero();
+ spatial_id
+ } else {
+ state.active_spatial_id
+ };
+
+ if !stacking_context.filters.is_empty() {
+ builder.push_item(&WrDisplayItem::SetFilterOps);
+ builder.push_iter(&stacking_context.filters);
+ }
+
+ let wr_item = PushStackingContextDisplayItem {
+ origin: bounds.origin,
+ spatial_id,
+ is_backface_visible: true,
+ stacking_context: StackingContext {
+ transform_style: stacking_context.transform_style,
+ mix_blend_mode: stacking_context.mix_blend_mode,
+ clip_id: None,
+ raster_space: RasterSpace::Screen,
+ // TODO(pcwalton): Enable picture caching?
+ cache_tiles: false,
+ },
+ };
+
+ builder.push_item(&WrDisplayItem::PushStackingContext(wr_item));
+ },
+ DisplayItem::PopStackingContext(_) => builder.pop_stacking_context(),
+ DisplayItem::DefineClipScrollNode(ref mut item) => {
+ let node = &clip_scroll_nodes[item.node_index.to_index()];
+ let item_rect = node.clip.main;
+
+ let parent_spatial_id = state.spatial_ids[node.parent_index.to_index()]
+ .expect("Tried to use WebRender parent SpatialId before it was defined.");
+ let parent_clip_id = state.clip_ids[node.parent_index.to_index()]
+ .expect("Tried to use WebRender parent ClipId before it was defined.");
+
+ match node.node_type {
+ ClipScrollNodeType::Clip => {
+ let id = builder.define_clip(
+ &SpaceAndClipInfo {
+ clip_id: parent_clip_id,
+ spatial_id: parent_spatial_id,
+ },
+ item_rect,
+ node.clip.complex.clone(),
+ None,
+ );
+
+ state.spatial_ids[item.node_index.to_index()] = Some(parent_spatial_id);
+ state.clip_ids[item.node_index.to_index()] = Some(id);
+ },
+ ClipScrollNodeType::ScrollFrame(scroll_sensitivity, external_id) => {
+ let space_clip_info = builder.define_scroll_frame(
+ &SpaceAndClipInfo {
+ clip_id: parent_clip_id,
+ spatial_id: parent_spatial_id,
+ },
+ Some(external_id),
+ node.content_rect,
+ node.clip.main,
+ node.clip.complex.clone(),
+ None,
+ scroll_sensitivity,
+ webrender_api::units::LayoutVector2D::zero(),
+ );
+
+ state.clip_ids[item.node_index.to_index()] = Some(space_clip_info.clip_id);
+ state.spatial_ids[item.node_index.to_index()] =
+ Some(space_clip_info.spatial_id);
+ },
+ ClipScrollNodeType::StickyFrame(ref sticky_data) => {
+ // TODO: Add define_sticky_frame_with_parent to WebRender.
+ let id = builder.define_sticky_frame(
+ parent_spatial_id,
+ item_rect,
+ sticky_data.margins,
+ sticky_data.vertical_offset_bounds,
+ sticky_data.horizontal_offset_bounds,
+ webrender_api::units::LayoutVector2D::zero(),
+ );
+
+ state.spatial_ids[item.node_index.to_index()] = Some(id);
+ state.clip_ids[item.node_index.to_index()] = Some(parent_clip_id);
+ },
+ ClipScrollNodeType::Placeholder => {
+ unreachable!("Found DefineClipScrollNode for Placeholder type node.");
+ },
+ };
+ },
+ }
+ }
+}
+
+fn build_common_item_properties(
+ base: &BaseDisplayItem,
+ state: &ClipScrollState,
+) -> CommonItemProperties {
+ let tag = match base.metadata.pointing {
+ Some(cursor) => Some((base.metadata.node.0 as u64, cursor)),
+ None => None,
+ };
+ CommonItemProperties {
+ clip_rect: base.clip_rect,
+ spatial_id: state.active_spatial_id,
+ clip_id: state.active_clip_id,
+ // TODO(gw): Make use of the WR backface visibility functionality.
+ is_backface_visible: true,
+ hit_info: tag,
+ }
+}
diff --git a/components/layout_2020/flex.rs b/components/layout_2020/flex.rs
new file mode 100644
index 00000000000..e6aa2f7b2c8
--- /dev/null
+++ b/components/layout_2020/flex.rs
@@ -0,0 +1,1128 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+//! Layout for elements with a CSS `display` property of `flex`.
+
+use crate::block::{AbsoluteAssignBSizesTraversal, BlockFlow, MarginsMayCollapseFlag};
+use crate::context::LayoutContext;
+use crate::display_list::{
+ BorderPaintingMode, DisplayListBuildState, StackingContextCollectionState,
+};
+use crate::floats::FloatKind;
+use crate::flow::{Flow, FlowClass, FlowFlags, GetBaseFlow, ImmutableFlowUtils, OpaqueFlow};
+use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
+use crate::layout_debug;
+use crate::model::{self, AdjoiningMargins, CollapsibleMargins};
+use crate::model::{IntrinsicISizes, MaybeAuto, SizeConstraint};
+use crate::traversal::PreorderFlowTraversal;
+use app_units::{Au, MAX_AU};
+use euclid::Point2D;
+use std::cmp::{max, min};
+use std::ops::Range;
+use style::computed_values::align_content::T as AlignContent;
+use style::computed_values::align_self::T as AlignSelf;
+use style::computed_values::flex_direction::T as FlexDirection;
+use style::computed_values::flex_wrap::T as FlexWrap;
+use style::computed_values::justify_content::T as JustifyContent;
+use style::logical_geometry::{Direction, LogicalSize};
+use style::properties::ComputedValues;
+use style::servo::restyle_damage::ServoRestyleDamage;
+use style::values::computed::flex::FlexBasis;
+use style::values::computed::{MaxSize, Size};
+
+/// The size of an axis. May be a specified size, a min/max
+/// constraint, or an unlimited size
+#[derive(Debug, Serialize)]
+enum AxisSize {
+ Definite(Au),
+ MinMax(SizeConstraint),
+ Infinite,
+}
+
+impl AxisSize {
+ /// Generate a new available cross or main axis size from the specified size of the container,
+ /// containing block size, min constraint, and max constraint
+ pub fn new(size: Size, content_size: Option<Au>, min: Size, max: MaxSize) -> AxisSize {
+ match size {
+ Size::Auto => AxisSize::MinMax(SizeConstraint::new(content_size, min, max, None)),
+ Size::LengthPercentage(ref lp) => match lp.maybe_to_used_value(content_size) {
+ Some(length) => AxisSize::Definite(length),
+ None => AxisSize::Infinite,
+ },
+ }
+ }
+}
+
+/// This function accepts the flex-basis and the size property in main direction from style,
+/// and the container size, then return the used value of flex basis. it can be used to help
+/// determining the flex base size and to indicate whether the main size of the item
+/// is definite after flex size resolving.
+fn from_flex_basis(flex_basis: FlexBasis, main_length: Size, containing_length: Au) -> MaybeAuto {
+ let width = match flex_basis {
+ FlexBasis::Content => return MaybeAuto::Auto,
+ FlexBasis::Size(width) => width,
+ };
+
+ let width = match width {
+ Size::Auto => main_length,
+ _ => width,
+ };
+
+ match width {
+ Size::Auto => MaybeAuto::Auto,
+ Size::LengthPercentage(ref lp) => MaybeAuto::Specified(lp.to_used_value(containing_length)),
+ }
+}
+
+/// Represents a child in a flex container. Most fields here are used in
+/// flex size resolving, and items are sorted by the 'order' property.
+#[derive(Debug, Serialize)]
+struct FlexItem {
+ /// Main size of a flex item, used to store results of flexible length calcuation.
+ pub main_size: Au,
+ /// Used flex base size.
+ pub base_size: Au,
+ /// The minimal size in main direction.
+ pub min_size: Au,
+ /// The maximal main size. If this property is not actually set by style
+ /// It will be the largest size available for code reuse.
+ pub max_size: Au,
+ /// The index of the actual flow in our child list.
+ pub index: usize,
+ /// The 'flex-grow' property of this item.
+ pub flex_grow: f32,
+ /// The 'flex-shrink' property of this item.
+ pub flex_shrink: f32,
+ /// The 'order' property of this item.
+ pub order: i32,
+ /// Whether the main size has met its constraint.
+ pub is_frozen: bool,
+ /// True if this flow has property 'visibility::collapse'.
+ pub is_strut: bool,
+}
+
+impl FlexItem {
+ pub fn new(index: usize, flow: &dyn Flow) -> FlexItem {
+ let style = &flow.as_block().fragment.style;
+ let flex_grow = style.get_position().flex_grow;
+ let flex_shrink = style.get_position().flex_shrink;
+ let order = style.get_position().order;
+ // TODO(stshine): for item with 'visibility:collapse', set is_strut to true.
+
+ FlexItem {
+ main_size: Au(0),
+ base_size: Au(0),
+ min_size: Au(0),
+ max_size: MAX_AU,
+ index: index,
+ flex_grow: flex_grow.into(),
+ flex_shrink: flex_shrink.into(),
+ order: order,
+ is_frozen: false,
+ is_strut: false,
+ }
+ }
+
+ /// Initialize the used flex base size, minimal main size and maximal main size.
+ /// For block mode container this method should be called in assign_block_size()
+ /// pass so that the item has already been layouted.
+ pub fn init_sizes(&mut self, flow: &mut dyn Flow, containing_length: Au, direction: Direction) {
+ let block = flow.as_mut_block();
+ match direction {
+ // TODO(stshine): the definition of min-{width, height} in style component
+ // should change to LengthPercentageOrAuto for automatic implied minimal size.
+ // https://drafts.csswg.org/css-flexbox-1/#min-size-auto
+ Direction::Inline => {
+ let basis = from_flex_basis(
+ block.fragment.style.get_position().flex_basis,
+ block.fragment.style.content_inline_size(),
+ containing_length,
+ );
+
+ // These methods compute auto margins to zero length, which is exactly what we want.
+ block.fragment.compute_border_and_padding(containing_length);
+ block
+ .fragment
+ .compute_inline_direction_margins(containing_length);
+ block
+ .fragment
+ .compute_block_direction_margins(containing_length);
+
+ let (border_padding, margin) = block.fragment.surrounding_intrinsic_inline_size();
+ let content_size = block.base.intrinsic_inline_sizes.preferred_inline_size -
+ border_padding -
+ margin +
+ block.fragment.box_sizing_boundary(direction);
+ self.base_size = basis.specified_or_default(content_size);
+ self.max_size = block
+ .fragment
+ .style
+ .max_inline_size()
+ .to_used_value(containing_length)
+ .unwrap_or(MAX_AU);
+ self.min_size = block
+ .fragment
+ .style
+ .min_inline_size()
+ .to_used_value(containing_length)
+ .unwrap_or(Au(0));
+ },
+ Direction::Block => {
+ let basis = from_flex_basis(
+ block.fragment.style.get_position().flex_basis,
+ block.fragment.style.content_block_size(),
+ containing_length,
+ );
+ let content_size = block.fragment.border_box.size.block -
+ block.fragment.border_padding.block_start_end() +
+ block.fragment.box_sizing_boundary(direction);
+ self.base_size = basis.specified_or_default(content_size);
+ self.max_size = block
+ .fragment
+ .style
+ .max_block_size()
+ .to_used_value(containing_length)
+ .unwrap_or(MAX_AU);
+ self.min_size = block
+ .fragment
+ .style
+ .min_block_size()
+ .to_used_value(containing_length)
+ .unwrap_or(Au(0));
+ },
+ }
+ }
+
+ /// Returns the outer main size of the item, including paddings and margins,
+ /// clamped by max and min size.
+ pub fn outer_main_size(&self, flow: &dyn Flow, direction: Direction) -> Au {
+ let ref fragment = flow.as_block().fragment;
+ let outer_width = match direction {
+ Direction::Inline => {
+ fragment.border_padding.inline_start_end() + fragment.margin.inline_start_end()
+ },
+ Direction::Block => {
+ fragment.border_padding.block_start_end() + fragment.margin.block_start_end()
+ },
+ };
+ max(self.min_size, min(self.base_size, self.max_size)) -
+ fragment.box_sizing_boundary(direction) +
+ outer_width
+ }
+
+ /// Returns the number of auto margins in given direction.
+ pub fn auto_margin_count(&self, flow: &dyn Flow, direction: Direction) -> i32 {
+ let margin = flow.as_block().fragment.style.logical_margin();
+ let mut margin_count = 0;
+ match direction {
+ Direction::Inline => {
+ if margin.inline_start.is_auto() {
+ margin_count += 1;
+ }
+ if margin.inline_end.is_auto() {
+ margin_count += 1;
+ }
+ },
+ Direction::Block => {
+ if margin.block_start.is_auto() {
+ margin_count += 1;
+ }
+ if margin.block_end.is_auto() {
+ margin_count += 1;
+ }
+ },
+ }
+ margin_count
+ }
+}
+
+/// A line in a flex container.
+// TODO(stshine): More fields are required to handle collapsed items and baseline alignment.
+#[derive(Debug, Serialize)]
+struct FlexLine {
+ /// Range of items belong to this line in 'self.items'.
+ pub range: Range<usize>,
+ /// Remaining free space of this line, items will grow or shrink based on it being positive or negative.
+ pub free_space: Au,
+ /// The number of auto margins of items.
+ pub auto_margin_count: i32,
+ /// Line size in the block direction.
+ pub cross_size: Au,
+}
+
+impl FlexLine {
+ pub fn new(range: Range<usize>, free_space: Au, auto_margin_count: i32) -> FlexLine {
+ FlexLine {
+ range: range,
+ auto_margin_count: auto_margin_count,
+ free_space: free_space,
+ cross_size: Au(0),
+ }
+ }
+
+ /// This method implements the flexible lengths resolving algorithm.
+ /// The 'collapse' parameter is used to indicate whether items with 'visibility: collapse'
+ /// is included in length resolving. The result main size is stored in 'item.main_size'.
+ /// <https://drafts.csswg.org/css-flexbox/#resolve-flexible-lengths>
+ pub fn flex_resolve(&mut self, items: &mut [FlexItem], collapse: bool) {
+ let mut total_grow = 0.0;
+ let mut total_shrink = 0.0;
+ let mut total_scaled = 0.0;
+ let mut active_count = 0;
+ // Iterate through items, collect total factors and freeze those that have already met
+ // their constraints or won't grow/shrink in corresponding scenario.
+ // https://drafts.csswg.org/css-flexbox/#resolve-flexible-lengths
+ for item in items.iter_mut().filter(|i| !(i.is_strut && collapse)) {
+ item.main_size = max(item.min_size, min(item.base_size, item.max_size));
+ if (self.free_space > Au(0) &&
+ (item.flex_grow == 0.0 || item.base_size >= item.max_size)) ||
+ (self.free_space < Au(0) &&
+ (item.flex_shrink == 0.0 || item.base_size <= item.min_size))
+ {
+ item.is_frozen = true;
+ } else {
+ item.is_frozen = false;
+ total_grow += item.flex_grow;
+ total_shrink += item.flex_shrink;
+ // The scaled factor is used to calculate flex shrink
+ total_scaled += item.flex_shrink * item.base_size.0 as f32;
+ active_count += 1;
+ }
+ }
+
+ let initial_free_space = self.free_space;
+ let mut total_variation = Au(1);
+ // If there is no remaining free space or all items are frozen, stop loop.
+ while total_variation != Au(0) && self.free_space != Au(0) && active_count > 0 {
+ self.free_space =
+ // https://drafts.csswg.org/css-flexbox/#remaining-free-space
+ if self.free_space > Au(0) {
+ min(initial_free_space.scale_by(total_grow), self.free_space)
+ } else {
+ max(initial_free_space.scale_by(total_shrink), self.free_space)
+ };
+
+ total_variation = Au(0);
+ for item in items
+ .iter_mut()
+ .filter(|i| !i.is_frozen)
+ .filter(|i| !(i.is_strut && collapse))
+ {
+ // Use this and the 'abs()' below to make the code work in both grow and shrink scenarios.
+ let (factor, end_size) = if self.free_space > Au(0) {
+ (item.flex_grow / total_grow, item.max_size)
+ } else {
+ (
+ item.flex_shrink * item.base_size.0 as f32 / total_scaled,
+ item.min_size,
+ )
+ };
+ let variation = self.free_space.scale_by(factor);
+ if variation.0.abs() >= (end_size - item.main_size).0.abs() {
+ // Use constraint as the target main size, and freeze item.
+ total_variation += end_size - item.main_size;
+ item.main_size = end_size;
+ item.is_frozen = true;
+ active_count -= 1;
+ total_shrink -= item.flex_shrink;
+ total_grow -= item.flex_grow;
+ total_scaled -= item.flex_shrink * item.base_size.0 as f32;
+ } else {
+ total_variation += variation;
+ item.main_size += variation;
+ }
+ }
+ self.free_space -= total_variation;
+ }
+ }
+}
+
+#[allow(unsafe_code)]
+unsafe impl crate::flow::HasBaseFlow for FlexFlow {}
+
+/// A block with the CSS `display` property equal to `flex`.
+#[derive(Debug, Serialize)]
+#[repr(C)]
+pub struct FlexFlow {
+ /// Data common to all block flows.
+ block_flow: BlockFlow,
+ /// The logical axis which the main axis will be parallel with.
+ /// The cross axis will be parallel with the opposite logical axis.
+ main_mode: Direction,
+ /// The available main axis size
+ available_main_size: AxisSize,
+ /// The available cross axis size
+ available_cross_size: AxisSize,
+ /// List of flex lines in the container.
+ lines: Vec<FlexLine>,
+ /// List of flex-items that belong to this flex-container
+ items: Vec<FlexItem>,
+ /// True if the flex-direction is *-reversed
+ main_reverse: bool,
+ /// True if this flex container can be multiline.
+ is_wrappable: bool,
+ /// True if the cross direction is reversed.
+ cross_reverse: bool,
+}
+
+impl FlexFlow {
+ pub fn from_fragment(fragment: Fragment, flotation: Option<FloatKind>) -> FlexFlow {
+ let main_mode;
+ let main_reverse;
+ let is_wrappable;
+ let cross_reverse;
+ {
+ let style = fragment.style();
+ let (mode, reverse) = match style.get_position().flex_direction {
+ FlexDirection::Row => (Direction::Inline, false),
+ FlexDirection::RowReverse => (Direction::Inline, true),
+ FlexDirection::Column => (Direction::Block, false),
+ FlexDirection::ColumnReverse => (Direction::Block, true),
+ };
+ main_mode = mode;
+ main_reverse = reverse == style.writing_mode.is_bidi_ltr();
+ let (wrappable, reverse) = match fragment.style.get_position().flex_wrap {
+ FlexWrap::Nowrap => (false, false),
+ FlexWrap::Wrap => (true, false),
+ FlexWrap::WrapReverse => (true, true),
+ };
+ is_wrappable = wrappable;
+ // TODO(stshine): Handle vertical writing mode.
+ cross_reverse = reverse;
+ }
+
+ FlexFlow {
+ block_flow: BlockFlow::from_fragment_and_float_kind(fragment, flotation),
+ main_mode: main_mode,
+ available_main_size: AxisSize::Infinite,
+ available_cross_size: AxisSize::Infinite,
+ lines: Vec::new(),
+ items: Vec::new(),
+ main_reverse: main_reverse,
+ is_wrappable: is_wrappable,
+ cross_reverse: cross_reverse,
+ }
+ }
+
+ pub fn main_mode(&self) -> Direction {
+ self.main_mode
+ }
+
+ /// Returns a line start after the last item that is already in a line.
+ /// Note that when the container main size is infinite(i.e. A column flexbox with auto height),
+ /// we do not need to do flex resolving and this can be considered as a fast-path, so the
+ /// 'container_size' param does not need to be 'None'. A line has to contain at least one item;
+ /// (except this) if the container can be multi-line the sum of outer main size of items should
+ /// be less than the container size; a line should be filled by items as much as possible.
+ /// After been collected in a line a item should have its main sizes initialized.
+ fn get_flex_line(&mut self, container_size: Au) -> Option<FlexLine> {
+ let start = self.lines.last().map(|line| line.range.end).unwrap_or(0);
+ if start == self.items.len() {
+ return None;
+ }
+ let mut end = start;
+ let mut total_line_size = Au(0);
+ let mut margin_count = 0;
+
+ let items = &mut self.items[start..];
+ let mut children = self.block_flow.base.children.random_access_mut();
+ for item in items {
+ let kid = children.get(item.index);
+ item.init_sizes(kid, container_size, self.main_mode);
+ let outer_main_size = item.outer_main_size(kid, self.main_mode);
+ if total_line_size + outer_main_size > container_size &&
+ end != start &&
+ self.is_wrappable
+ {
+ break;
+ }
+ margin_count += item.auto_margin_count(kid, self.main_mode);
+ total_line_size += outer_main_size;
+ end += 1;
+ }
+
+ let line = FlexLine::new(start..end, container_size - total_line_size, margin_count);
+ Some(line)
+ }
+
+ // TODO(zentner): This function should use flex-basis.
+ // Currently, this is the core of BlockFlow::bubble_inline_sizes() with all float logic
+ // stripped out, and max replaced with union_nonbreaking_inline.
+ fn inline_mode_bubble_inline_sizes(&mut self) {
+ // FIXME(emilio): This doesn't handle at all writing-modes.
+ let fixed_width =
+ !model::style_length(self.block_flow.fragment.style().get_position().width, None)
+ .is_auto();
+
+ let mut computation = self.block_flow.fragment.compute_intrinsic_inline_sizes();
+ if !fixed_width {
+ for kid in self.block_flow.base.children.iter_mut() {
+ let base = kid.mut_base();
+ let is_absolutely_positioned =
+ base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED);
+ if !is_absolutely_positioned {
+ let flex_item_inline_sizes = IntrinsicISizes {
+ minimum_inline_size: base.intrinsic_inline_sizes.minimum_inline_size,
+ preferred_inline_size: base.intrinsic_inline_sizes.preferred_inline_size,
+ };
+ computation.union_nonbreaking_inline(&flex_item_inline_sizes);
+ }
+ }
+ }
+ self.block_flow.base.intrinsic_inline_sizes = computation.finish();
+ }
+
+ // TODO(zentner): This function should use flex-basis.
+ // Currently, this is the core of BlockFlow::bubble_inline_sizes() with all float logic
+ // stripped out.
+ fn block_mode_bubble_inline_sizes(&mut self) {
+ let fixed_width =
+ !model::style_length(self.block_flow.fragment.style().get_position().width, None)
+ .is_auto();
+
+ let mut computation = self.block_flow.fragment.compute_intrinsic_inline_sizes();
+ if !fixed_width {
+ for kid in self.block_flow.base.children.iter_mut() {
+ let base = kid.mut_base();
+ let is_absolutely_positioned =
+ base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED);
+ if !is_absolutely_positioned {
+ computation.content_intrinsic_sizes.minimum_inline_size = max(
+ computation.content_intrinsic_sizes.minimum_inline_size,
+ base.intrinsic_inline_sizes.minimum_inline_size,
+ );
+
+ computation.content_intrinsic_sizes.preferred_inline_size = max(
+ computation.content_intrinsic_sizes.preferred_inline_size,
+ base.intrinsic_inline_sizes.preferred_inline_size,
+ );
+ }
+ }
+ }
+ self.block_flow.base.intrinsic_inline_sizes = computation.finish();
+ }
+
+ // TODO(zentner): This function needs to be radically different for multi-line flexbox.
+ // Currently, this is the core of BlockFlow::propagate_assigned_inline_size_to_children() with
+ // all float and table logic stripped out.
+ fn block_mode_assign_inline_sizes(
+ &mut self,
+ _layout_context: &LayoutContext,
+ inline_start_content_edge: Au,
+ inline_end_content_edge: Au,
+ content_inline_size: Au,
+ ) {
+ let _scope = layout_debug_scope!("flex::block_mode_assign_inline_sizes");
+ debug!("flex::block_mode_assign_inline_sizes");
+
+ // FIXME (mbrubeck): Get correct mode for absolute containing block
+ let containing_block_mode = self.block_flow.base.writing_mode;
+
+ let container_block_size = match self.available_main_size {
+ AxisSize::Definite(length) => Some(length),
+ _ => None,
+ };
+ let container_inline_size = match self.available_cross_size {
+ AxisSize::Definite(length) => length,
+ AxisSize::MinMax(ref constraint) => constraint.clamp(content_inline_size),
+ AxisSize::Infinite => content_inline_size,
+ };
+
+ let mut children = self.block_flow.base.children.random_access_mut();
+ for kid in &mut self.items {
+ let kid_base = children.get(kid.index).mut_base();
+ kid_base.block_container_explicit_block_size = container_block_size;
+ if kid_base
+ .flags
+ .contains(FlowFlags::INLINE_POSITION_IS_STATIC)
+ {
+ // 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.
+ kid_base.position.start.i =
+ if kid_base.writing_mode.is_bidi_ltr() == containing_block_mode.is_bidi_ltr() {
+ inline_start_content_edge
+ } else {
+ // The kid's inline 'start' is at the parent's 'end'
+ inline_end_content_edge
+ };
+ }
+ kid_base.block_container_inline_size = container_inline_size;
+ kid_base.block_container_writing_mode = containing_block_mode;
+ kid_base.position.start.i = inline_start_content_edge;
+ }
+ }
+
+ fn inline_mode_assign_inline_sizes(
+ &mut self,
+ layout_context: &LayoutContext,
+ inline_start_content_edge: Au,
+ _inline_end_content_edge: Au,
+ content_inline_size: Au,
+ ) {
+ let _scope = layout_debug_scope!("flex::inline_mode_assign_inline_sizes");
+ debug!("inline_mode_assign_inline_sizes");
+
+ debug!("content_inline_size = {:?}", content_inline_size);
+
+ let child_count = ImmutableFlowUtils::child_count(self as &dyn Flow) as i32;
+ debug!("child_count = {:?}", child_count);
+ if child_count == 0 {
+ return;
+ }
+
+ let inline_size = match self.available_main_size {
+ AxisSize::Definite(length) => length,
+ AxisSize::MinMax(ref constraint) => constraint.clamp(content_inline_size),
+ AxisSize::Infinite => content_inline_size,
+ };
+
+ let container_mode = self.block_flow.base.block_container_writing_mode;
+ self.block_flow.base.position.size.inline = inline_size;
+
+ // Calculate non-auto block size to pass to children.
+ let box_border = self
+ .block_flow
+ .fragment
+ .box_sizing_boundary(Direction::Block);
+
+ let parent_container_size = self
+ .block_flow
+ .explicit_block_containing_size(layout_context.shared_context());
+ // https://drafts.csswg.org/css-ui-3/#box-sizing
+ let explicit_content_size = self
+ .block_flow
+ .explicit_block_size(parent_container_size)
+ .map(|x| max(x - box_border, Au(0)));
+ let containing_block_text_align = self
+ .block_flow
+ .fragment
+ .style()
+ .get_inherited_text()
+ .text_align;
+
+ while let Some(mut line) = self.get_flex_line(inline_size) {
+ let items = &mut self.items[line.range.clone()];
+ line.flex_resolve(items, false);
+ // TODO(stshine): if this flex line contain children that have
+ // property visibility:collapse, exclude them and resolve again.
+
+ let item_count = items.len() as i32;
+ let mut cur_i = inline_start_content_edge;
+ let item_interval = if line.free_space >= Au(0) && line.auto_margin_count == 0 {
+ match self
+ .block_flow
+ .fragment
+ .style()
+ .get_position()
+ .justify_content
+ {
+ JustifyContent::SpaceBetween => {
+ if item_count == 1 {
+ Au(0)
+ } else {
+ line.free_space / (item_count - 1)
+ }
+ },
+ JustifyContent::SpaceAround => line.free_space / item_count,
+ _ => Au(0),
+ }
+ } else {
+ Au(0)
+ };
+
+ match self
+ .block_flow
+ .fragment
+ .style()
+ .get_position()
+ .justify_content
+ {
+ // Overflow equally in both ends of line.
+ JustifyContent::Center | JustifyContent::SpaceAround => {
+ cur_i += (line.free_space - item_interval * (item_count - 1)) / 2;
+ },
+ JustifyContent::FlexEnd => {
+ cur_i += line.free_space;
+ },
+ _ => {},
+ }
+
+ let mut children = self.block_flow.base.children.random_access_mut();
+ for item in items.iter_mut() {
+ let block = children.get(item.index).as_mut_block();
+
+ block.base.block_container_writing_mode = container_mode;
+ block.base.block_container_inline_size = inline_size;
+ block.base.block_container_explicit_block_size = explicit_content_size;
+ // Per CSS 2.1 § 16.3.1, text alignment propagates to all children in flow.
+ //
+ // TODO(#2265, pcwalton): Do this in the cascade instead.
+ block.base.flags.set_text_align(containing_block_text_align);
+
+ let margin = block.fragment.style().logical_margin();
+ let auto_len = if line.auto_margin_count == 0 || line.free_space <= Au(0) {
+ Au(0)
+ } else {
+ line.free_space / line.auto_margin_count
+ };
+ let margin_inline_start = MaybeAuto::from_style(margin.inline_start, inline_size)
+ .specified_or_default(auto_len);
+ let margin_inline_end = MaybeAuto::from_style(margin.inline_end, inline_size)
+ .specified_or_default(auto_len);
+ let item_inline_size = item.main_size -
+ block.fragment.box_sizing_boundary(self.main_mode) +
+ block.fragment.border_padding.inline_start_end();
+ let item_outer_size = item_inline_size + block.fragment.margin.inline_start_end();
+
+ block.fragment.margin.inline_start = margin_inline_start;
+ block.fragment.margin.inline_end = margin_inline_end;
+ block.fragment.border_box.start.i = margin_inline_start;
+ block.fragment.border_box.size.inline = item_inline_size;
+ block.base.position.start.i = if !self.main_reverse {
+ cur_i
+ } else {
+ inline_start_content_edge * 2 + content_inline_size - cur_i - item_outer_size
+ };
+ block.base.position.size.inline = item_outer_size;
+ cur_i += item_outer_size + item_interval;
+ }
+ self.lines.push(line);
+ }
+ }
+
+ // TODO(zentner): This function should actually flex elements!
+ fn block_mode_assign_block_size(&mut self) {
+ let mut cur_b = if !self.main_reverse {
+ self.block_flow.fragment.border_padding.block_start
+ } else {
+ self.block_flow.fragment.border_box.size.block
+ };
+
+ let mut children = self.block_flow.base.children.random_access_mut();
+ for item in &mut self.items {
+ let base = children.get(item.index).mut_base();
+ if !self.main_reverse {
+ base.position.start.b = cur_b;
+ cur_b = cur_b + base.position.size.block;
+ } else {
+ cur_b = cur_b - base.position.size.block;
+ base.position.start.b = cur_b;
+ }
+ }
+ }
+
+ fn inline_mode_assign_block_size(&mut self, layout_context: &LayoutContext) {
+ let _scope = layout_debug_scope!("flex::inline_mode_assign_block_size");
+
+ let line_count = self.lines.len() as i32;
+ let line_align = self
+ .block_flow
+ .fragment
+ .style()
+ .get_position()
+ .align_content;
+ let mut cur_b = self.block_flow.fragment.border_padding.block_start;
+ let mut total_cross_size = Au(0);
+ let mut line_interval = Au(0);
+
+ {
+ let mut children = self.block_flow.base.children.random_access_mut();
+ for line in self.lines.iter_mut() {
+ for item in &self.items[line.range.clone()] {
+ let fragment = &children.get(item.index).as_block().fragment;
+ line.cross_size = max(
+ line.cross_size,
+ fragment.border_box.size.block + fragment.margin.block_start_end(),
+ );
+ }
+ total_cross_size += line.cross_size;
+ }
+ }
+
+ let box_border = self
+ .block_flow
+ .fragment
+ .box_sizing_boundary(Direction::Block);
+ let parent_container_size = self
+ .block_flow
+ .explicit_block_containing_size(layout_context.shared_context());
+ // https://drafts.csswg.org/css-ui-3/#box-sizing
+ let explicit_content_size = self
+ .block_flow
+ .explicit_block_size(parent_container_size)
+ .map(|x| max(x - box_border, Au(0)));
+
+ if let Some(container_block_size) = explicit_content_size {
+ let free_space = container_block_size - total_cross_size;
+ total_cross_size = container_block_size;
+
+ if line_align == AlignContent::Stretch && free_space > Au(0) {
+ for line in self.lines.iter_mut() {
+ line.cross_size += free_space / line_count;
+ }
+ }
+
+ line_interval = match line_align {
+ AlignContent::SpaceBetween => {
+ if line_count <= 1 {
+ Au(0)
+ } else {
+ free_space / (line_count - 1)
+ }
+ },
+ AlignContent::SpaceAround => {
+ if line_count == 0 {
+ Au(0)
+ } else {
+ free_space / line_count
+ }
+ },
+ _ => Au(0),
+ };
+
+ match line_align {
+ AlignContent::Center | AlignContent::SpaceAround => {
+ cur_b += (free_space - line_interval * (line_count - 1)) / 2;
+ },
+ AlignContent::FlexEnd => {
+ cur_b += free_space;
+ },
+ _ => {},
+ }
+ }
+
+ let mut children = self.block_flow.base.children.random_access_mut();
+ for line in &self.lines {
+ for item in self.items[line.range.clone()].iter_mut() {
+ let block = children.get(item.index).as_mut_block();
+ let auto_margin_count = item.auto_margin_count(block, Direction::Block);
+ let margin = block.fragment.style().logical_margin();
+
+ let mut margin_block_start = block.fragment.margin.block_start;
+ let mut margin_block_end = block.fragment.margin.block_end;
+ let mut free_space = line.cross_size -
+ block.base.position.size.block -
+ block.fragment.margin.block_start_end();
+
+ // The spec is a little vague here, but if I understand it correctly, the outer
+ // cross size of item should equal to the line size if any auto margin exists.
+ // https://drafts.csswg.org/css-flexbox/#algo-cross-margins
+ if auto_margin_count > 0 {
+ if margin.block_start.is_auto() {
+ margin_block_start = if free_space < Au(0) {
+ Au(0)
+ } else {
+ free_space / auto_margin_count
+ };
+ }
+ margin_block_end =
+ line.cross_size - margin_block_start - block.base.position.size.block;
+ free_space = Au(0);
+ }
+
+ let self_align = block.fragment.style().get_position().align_self;
+ if self_align == AlignSelf::Stretch &&
+ block.fragment.style().content_block_size().is_auto()
+ {
+ free_space = Au(0);
+ block.base.block_container_explicit_block_size = Some(line.cross_size);
+ block.base.position.size.block =
+ line.cross_size - margin_block_start - margin_block_end;
+ block.fragment.border_box.size.block = block.base.position.size.block;
+ // FIXME(stshine): item with 'align-self: stretch' and auto cross size should act
+ // as if it has a fixed cross size, all child blocks should resolve against it.
+ // block.assign_block_size(layout_context);
+ }
+ block.base.position.start.b = margin_block_start +
+ if !self.cross_reverse {
+ cur_b
+ } else {
+ self.block_flow.fragment.border_padding.block_start * 2 + total_cross_size -
+ cur_b -
+ line.cross_size
+ };
+ // TODO(stshine): support baseline alignment.
+ if free_space != Au(0) {
+ let flex_cross = match self_align {
+ AlignSelf::FlexEnd => free_space,
+ AlignSelf::Center => free_space / 2,
+ _ => Au(0),
+ };
+ block.base.position.start.b += if !self.cross_reverse {
+ flex_cross
+ } else {
+ free_space - flex_cross
+ };
+ }
+ }
+ cur_b += line_interval + line.cross_size;
+ }
+ let total_block_size =
+ total_cross_size + self.block_flow.fragment.border_padding.block_start_end();
+ self.block_flow.fragment.border_box.size.block = total_block_size;
+ self.block_flow.base.position.size.block = total_block_size;
+ }
+}
+
+impl Flow for FlexFlow {
+ fn class(&self) -> FlowClass {
+ FlowClass::Flex
+ }
+
+ fn as_flex(&self) -> &FlexFlow {
+ self
+ }
+
+ fn as_block(&self) -> &BlockFlow {
+ &self.block_flow
+ }
+
+ fn as_mut_block(&mut self) -> &mut BlockFlow {
+ &mut self.block_flow
+ }
+
+ fn mark_as_root(&mut self) {
+ self.block_flow.mark_as_root();
+ }
+
+ fn bubble_inline_sizes(&mut self) {
+ let _scope = layout_debug_scope!(
+ "flex::bubble_inline_sizes {:x}",
+ self.block_flow.base.debug_id()
+ );
+
+ // Flexbox Section 9.0: Generate anonymous flex items:
+ // This part was handled in the flow constructor.
+
+ // Flexbox Section 9.1: Re-order flex items according to their order.
+ // FIXME(stshine): This should be done during flow construction.
+ let mut items: Vec<FlexItem> = self
+ .block_flow
+ .base
+ .children
+ .iter()
+ .enumerate()
+ .filter(|&(_, flow)| {
+ !flow
+ .as_block()
+ .base
+ .flags
+ .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED)
+ })
+ .map(|(index, flow)| FlexItem::new(index, flow))
+ .collect();
+
+ items.sort_by_key(|item| item.order);
+ self.items = items;
+
+ match self.main_mode {
+ Direction::Inline => self.inline_mode_bubble_inline_sizes(),
+ Direction::Block => self.block_mode_bubble_inline_sizes(),
+ }
+ }
+
+ fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
+ let _scope = layout_debug_scope!(
+ "flex::assign_inline_sizes {:x}",
+ self.block_flow.base.debug_id()
+ );
+ debug!("assign_inline_sizes");
+
+ if !self
+ .block_flow
+ .base
+ .restyle_damage
+ .intersects(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW)
+ {
+ return;
+ }
+
+ self.block_flow
+ .initialize_container_size_for_root(layout_context.shared_context());
+
+ // 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.block_flow.base.block_container_inline_size;
+ self.block_flow.compute_used_inline_size(
+ layout_context.shared_context(),
+ containing_block_inline_size,
+ );
+ if self.block_flow.base.flags.is_float() {
+ self.block_flow
+ .float
+ .as_mut()
+ .unwrap()
+ .containing_inline_size = containing_block_inline_size
+ }
+
+ let (available_block_size, available_inline_size) = {
+ let style = &self.block_flow.fragment.style;
+ let (specified_block_size, specified_inline_size) = if style.writing_mode.is_vertical()
+ {
+ (style.get_position().width, style.get_position().height)
+ } else {
+ (style.get_position().height, style.get_position().width)
+ };
+
+ let available_inline_size = AxisSize::new(
+ specified_inline_size,
+ Some(self.block_flow.base.block_container_inline_size),
+ style.min_inline_size(),
+ style.max_inline_size(),
+ );
+
+ let available_block_size = AxisSize::new(
+ specified_block_size,
+ self.block_flow.base.block_container_explicit_block_size,
+ style.min_block_size(),
+ style.max_block_size(),
+ );
+ (available_block_size, available_inline_size)
+ };
+
+ // Move in from the inline-start border edge.
+ let inline_start_content_edge = self.block_flow.fragment.border_box.start.i +
+ self.block_flow.fragment.border_padding.inline_start;
+
+ debug!(
+ "inline_start_content_edge = {:?}",
+ inline_start_content_edge
+ );
+
+ let padding_and_borders = self.block_flow.fragment.border_padding.inline_start_end();
+
+ // Distance from the inline-end margin edge to the inline-end content edge.
+ let inline_end_content_edge = self.block_flow.fragment.margin.inline_end +
+ self.block_flow.fragment.border_padding.inline_end;
+
+ debug!("padding_and_borders = {:?}", padding_and_borders);
+ debug!(
+ "self.block_flow.fragment.border_box.size.inline = {:?}",
+ self.block_flow.fragment.border_box.size.inline
+ );
+ let content_inline_size =
+ self.block_flow.fragment.border_box.size.inline - padding_and_borders;
+
+ match self.main_mode {
+ Direction::Inline => {
+ self.available_main_size = available_inline_size;
+ self.available_cross_size = available_block_size;
+ self.inline_mode_assign_inline_sizes(
+ layout_context,
+ inline_start_content_edge,
+ inline_end_content_edge,
+ content_inline_size,
+ )
+ },
+ Direction::Block => {
+ self.available_main_size = available_block_size;
+ self.available_cross_size = available_inline_size;
+ self.block_mode_assign_inline_sizes(
+ layout_context,
+ inline_start_content_edge,
+ inline_end_content_edge,
+ content_inline_size,
+ )
+ },
+ }
+ }
+
+ fn assign_block_size(&mut self, layout_context: &LayoutContext) {
+ match self.main_mode {
+ Direction::Inline => {
+ self.inline_mode_assign_block_size(layout_context);
+ let block_start =
+ AdjoiningMargins::from_margin(self.block_flow.fragment.margin.block_start);
+ let block_end =
+ AdjoiningMargins::from_margin(self.block_flow.fragment.margin.block_end);
+ self.block_flow.base.collapsible_margins =
+ CollapsibleMargins::Collapse(block_start, block_end);
+
+ // TODO(stshine): assign proper static position for absolute descendants.
+ if (&*self as &dyn Flow).contains_roots_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 containing block, which may also be an absolute flow.
+ let assign_abs_b_sizes =
+ AbsoluteAssignBSizesTraversal(layout_context.shared_context());
+ assign_abs_b_sizes.traverse_absolute_flows(&mut *self);
+ }
+ },
+ Direction::Block => {
+ self.block_flow.assign_block_size_block_base(
+ layout_context,
+ None,
+ MarginsMayCollapseFlag::MarginsMayNotCollapse,
+ );
+ self.block_mode_assign_block_size();
+ },
+ }
+ }
+
+ fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
+ self.block_flow
+ .compute_stacking_relative_position(layout_context)
+ }
+
+ fn place_float_if_applicable<'a>(&mut self) {
+ self.block_flow.place_float_if_applicable()
+ }
+
+ fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
+ self.block_flow
+ .update_late_computed_inline_position_if_necessary(inline_position)
+ }
+
+ fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
+ self.block_flow
+ .update_late_computed_block_position_if_necessary(block_position)
+ }
+
+ fn build_display_list(&mut self, state: &mut DisplayListBuildState) {
+ // Draw the rest of the block.
+ self.as_mut_block()
+ .build_display_list_for_block(state, BorderPaintingMode::Separate)
+ }
+
+ fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
+ self.block_flow.collect_stacking_contexts(state);
+ }
+
+ fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>) {
+ self.block_flow.repair_style(new_style)
+ }
+
+ fn compute_overflow(&self) -> Overflow {
+ self.block_flow.compute_overflow()
+ }
+
+ fn contains_roots_of_absolute_flow_tree(&self) -> bool {
+ self.block_flow.contains_roots_of_absolute_flow_tree()
+ }
+
+ fn is_absolute_containing_block(&self) -> bool {
+ self.block_flow.is_absolute_containing_block()
+ }
+
+ fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
+ self.block_flow.generated_containing_block_size(flow)
+ }
+
+ fn iterate_through_fragment_border_boxes(
+ &self,
+ iterator: &mut dyn FragmentBorderBoxIterator,
+ level: i32,
+ stacking_context_position: &Point2D<Au>,
+ ) {
+ self.block_flow.iterate_through_fragment_border_boxes(
+ iterator,
+ level,
+ stacking_context_position,
+ );
+ }
+
+ fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) {
+ self.block_flow.mutate_fragments(mutator);
+ }
+}
diff --git a/components/layout_2020/floats.rs b/components/layout_2020/floats.rs
new file mode 100644
index 00000000000..42df4e07dfd
--- /dev/null
+++ b/components/layout_2020/floats.rs
@@ -0,0 +1,605 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+use crate::block::FormattingContextType;
+use crate::flow::{Flow, FlowFlags, GetBaseFlow, ImmutableFlowUtils};
+use crate::persistent_list::PersistentList;
+use app_units::{Au, MAX_AU};
+use std::cmp::{max, min};
+use std::fmt;
+use style::computed_values::float::T as StyleFloat;
+use style::logical_geometry::{LogicalRect, LogicalSize, WritingMode};
+use style::values::computed::Size;
+
+/// The kind of float: left or right.
+#[derive(Clone, Copy, Debug, Serialize)]
+pub enum FloatKind {
+ Left,
+ Right,
+}
+
+impl FloatKind {
+ pub fn from_property(property: StyleFloat) -> Option<FloatKind> {
+ match property {
+ StyleFloat::None => None,
+ StyleFloat::Left => Some(FloatKind::Left),
+ StyleFloat::Right => Some(FloatKind::Right),
+ }
+ }
+}
+
+/// The kind of clearance: left, right, or both.
+#[derive(Clone, Copy)]
+pub enum ClearType {
+ Left,
+ Right,
+ Both,
+}
+
+/// Information about a single float.
+#[derive(Clone, Copy)]
+struct Float {
+ /// The boundaries of this float.
+ bounds: LogicalRect<Au>,
+ /// The kind of float: left or right.
+ kind: FloatKind,
+}
+
+impl fmt::Debug 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.
+#[derive(Clone)]
+struct FloatList {
+ /// Information about each of the floats here.
+ floats: PersistentList<Float>,
+ /// Cached copy of the maximum block-start offset of the float.
+ max_block_start: Option<Au>,
+}
+
+impl FloatList {
+ fn new() -> FloatList {
+ FloatList {
+ floats: PersistentList::new(),
+ max_block_start: 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.floats.len() > 0
+ }
+}
+
+impl fmt::Debug for FloatList {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "max_block_start={:?} floats={}",
+ self.max_block_start,
+ self.floats.len()
+ )?;
+ for float in self.floats.iter() {
+ write!(f, " {:?}", float)?;
+ }
+ Ok(())
+ }
+}
+
+/// 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::Debug 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.
+#[derive(Clone)]
+pub struct Floats {
+ /// The list of floats.
+ list: FloatList,
+ /// The offset of the flow relative to the first float.
+ offset: LogicalSize<Au>,
+ /// The writing mode of these floats.
+ pub writing_mode: WritingMode,
+}
+
+impl fmt::Debug for Floats {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ if !self.list.is_present() {
+ write!(f, "[empty]")
+ } else {
+ write!(f, "offset={:?} floats={:?}", self.offset, self.list)
+ }
+ }
+}
+
+impl Floats {
+ /// Creates a new `Floats` object.
+ pub fn new(writing_mode: WritingMode) -> Floats {
+ Floats {
+ list: FloatList::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<LogicalRect<Au>> {
+ match self.list.floats.front() {
+ None => None,
+ Some(float) => Some(float.bounds.translate_by_size(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
+ /// inline-size 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 = &self.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 range in the block direction.
+ 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 {
+ FloatKind::Left
+ 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
+ );
+ }
+ FloatKind::Right
+ 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
+ );
+ }
+ FloatKind::Left | FloatKind::Right => {},
+ }
+ }
+
+ // 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,
+ _ => panic!("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 understand 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 = PlacementInfo {
+ size: info.size,
+ ceiling: match self.list.max_block_start {
+ None => info.ceiling,
+ Some(max_block_start) => max(info.ceiling, 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,
+ };
+
+ self.list.floats = self.list.floats.prepend_elem(new_float);
+ self.list.max_block_start = match self.list.max_block_start {
+ None => Some(new_float.bounds.start.b),
+ Some(max_block_start) => Some(max(max_block_start, new_float.bounds.start.b)),
+ }
+ }
+
+ /// Given the three sides of the bounding rectangle in the block-start direction, 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 = &self.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 {
+ FloatKind::Left => {
+ return LogicalRect::new(
+ self.writing_mode,
+ Au(0),
+ info.ceiling,
+ info.max_inline_size,
+ MAX_AU,
+ );
+ },
+ FloatKind::Right => {
+ return LogicalRect::new(
+ self.writing_mode,
+ info.max_inline_size - info.size.inline,
+ info.ceiling,
+ info.max_inline_size,
+ MAX_AU,
+ );
+ },
+ }
+ }
+
+ // 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 block-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 {
+ FloatKind::Left => LogicalRect::new(
+ self.writing_mode,
+ Au(0),
+ float_b,
+ info.max_inline_size,
+ MAX_AU,
+ ),
+ FloatKind::Right => LogicalRect::new(
+ self.writing_mode,
+ info.max_inline_size - info.size.inline,
+ float_b,
+ info.max_inline_size,
+ MAX_AU,
+ ),
+ };
+ },
+ Some(rect) => {
+ assert_ne!(
+ 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(MAX_AU);
+ return match info.kind {
+ FloatKind::Left => LogicalRect::new(
+ self.writing_mode,
+ rect.start.i,
+ float_b,
+ rect.size.inline,
+ block_size,
+ ),
+ FloatKind::Right => 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 = &self.list;
+ let mut clearance = Au(0);
+ for float in list.floats.iter() {
+ match (clear, float.kind) {
+ (ClearType::Left, FloatKind::Left) |
+ (ClearType::Right, FloatKind::Right) |
+ (ClearType::Both, _) => {
+ let b = self.offset.block + float.bounds.start.b + float.bounds.size.block;
+ clearance = max(clearance, b);
+ },
+ _ => {},
+ }
+ }
+ clearance
+ }
+
+ pub fn is_present(&self) -> bool {
+ self.list.is_present()
+ }
+}
+
+/// The speculated inline sizes of floats flowing through or around a flow (depending on whether
+/// the flow is a block formatting context). These speculations are always *upper bounds*; the
+/// actual inline sizes might be less. Note that this implies that a speculated value of zero is a
+/// guarantee that there will be no floats on that side.
+///
+/// This is used for two purposes: (a) determining whether we can lay out blocks in parallel; (b)
+/// guessing the inline-sizes of block formatting contexts in an effort to lay them out in
+/// parallel.
+#[derive(Clone, Copy)]
+pub struct SpeculatedFloatPlacement {
+ /// The estimated inline size (an upper bound) of the left floats flowing through this flow.
+ pub left: Au,
+ /// The estimated inline size (an upper bound) of the right floats flowing through this flow.
+ pub right: Au,
+}
+
+impl fmt::Debug for SpeculatedFloatPlacement {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "L {:?} R {:?}", self.left, self.right)
+ }
+}
+
+impl SpeculatedFloatPlacement {
+ /// Returns a `SpeculatedFloatPlacement` objects with both left and right speculated inline
+ /// sizes initialized to zero.
+ pub fn zero() -> SpeculatedFloatPlacement {
+ SpeculatedFloatPlacement {
+ left: Au(0),
+ right: Au(0),
+ }
+ }
+
+ /// Given the speculated inline size of the floats out for the inorder predecessor of this
+ /// flow, computes the speculated inline size of the floats flowing in.
+ pub fn compute_floats_in(&mut self, flow: &mut dyn Flow) {
+ let base_flow = flow.base();
+ if base_flow.flags.contains(FlowFlags::CLEARS_LEFT) {
+ self.left = Au(0)
+ }
+ if base_flow.flags.contains(FlowFlags::CLEARS_RIGHT) {
+ self.right = Au(0)
+ }
+ }
+
+ /// Given the speculated inline size of the floats out for this flow's last child, computes the
+ /// speculated inline size of the floats out for this flow.
+ pub fn compute_floats_out(&mut self, flow: &mut dyn Flow) {
+ if flow.is_block_like() {
+ let block_flow = flow.as_block();
+ if block_flow.formatting_context_type() != FormattingContextType::None {
+ *self = block_flow.base.speculated_float_placement_in;
+ } else {
+ if self.left > Au(0) || self.right > Au(0) {
+ let speculated_inline_content_edge_offsets =
+ block_flow.fragment.guess_inline_content_edge_offsets();
+ if self.left > Au(0) && speculated_inline_content_edge_offsets.start > Au(0) {
+ self.left = self.left + speculated_inline_content_edge_offsets.start
+ }
+ if self.right > Au(0) && speculated_inline_content_edge_offsets.end > Au(0) {
+ self.right = self.right + speculated_inline_content_edge_offsets.end
+ }
+ }
+
+ self.left = max(
+ self.left,
+ block_flow.base.speculated_float_placement_in.left,
+ );
+ self.right = max(
+ self.right,
+ block_flow.base.speculated_float_placement_in.right,
+ );
+ }
+ }
+
+ let base_flow = flow.base();
+ if !base_flow.flags.is_float() {
+ return;
+ }
+
+ let mut float_inline_size = base_flow.intrinsic_inline_sizes.preferred_inline_size;
+ if float_inline_size == Au(0) {
+ if flow.is_block_like() {
+ // Hack: If the size of the float is not fixed, then there's no
+ // way we can guess at its size now. So just pick an arbitrary
+ // nonzero value (in this case, 1px) so that the layout
+ // traversal logic will know that objects later in the document
+ // might flow around this float.
+ let inline_size = flow.as_block().fragment.style.content_inline_size();
+ let fixed = match inline_size {
+ Size::Auto => false,
+ Size::LengthPercentage(ref lp) => {
+ lp.0.is_definitely_zero() || lp.0.maybe_to_used_value(None).is_some()
+ },
+ };
+ if !fixed {
+ float_inline_size = Au::from_px(1)
+ }
+ }
+ }
+
+ match base_flow.flags.float_kind() {
+ StyleFloat::None => {},
+ StyleFloat::Left => self.left = self.left + float_inline_size,
+ StyleFloat::Right => self.right = self.right + float_inline_size,
+ }
+ }
+
+ /// Given a flow, computes the speculated inline size of the floats in of its first child.
+ pub fn compute_floats_in_for_first_child(
+ parent_flow: &mut dyn Flow,
+ ) -> SpeculatedFloatPlacement {
+ if !parent_flow.is_block_like() {
+ return parent_flow.base().speculated_float_placement_in;
+ }
+
+ let parent_block_flow = parent_flow.as_block();
+ if parent_block_flow.formatting_context_type() != FormattingContextType::None {
+ return SpeculatedFloatPlacement::zero();
+ }
+
+ let mut placement = parent_block_flow.base.speculated_float_placement_in;
+ let speculated_inline_content_edge_offsets = parent_block_flow
+ .fragment
+ .guess_inline_content_edge_offsets();
+
+ if speculated_inline_content_edge_offsets.start > Au(0) {
+ placement.left = if placement.left > speculated_inline_content_edge_offsets.start {
+ placement.left - speculated_inline_content_edge_offsets.start
+ } else {
+ Au(0)
+ }
+ }
+ if speculated_inline_content_edge_offsets.end > Au(0) {
+ placement.right = if placement.right > speculated_inline_content_edge_offsets.end {
+ placement.right - speculated_inline_content_edge_offsets.end
+ } else {
+ Au(0)
+ }
+ }
+
+ placement
+ }
+}
diff --git a/components/layout_2020/flow.rs b/components/layout_2020/flow.rs
new file mode 100644
index 00000000000..d7ab73cd8d3
--- /dev/null
+++ b/components/layout_2020/flow.rs
@@ -0,0 +1,1458 @@
+/* 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 https://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 crate::block::{BlockFlow, FormattingContextType};
+use crate::context::LayoutContext;
+use crate::display_list::items::ClippingAndScrolling;
+use crate::display_list::{DisplayListBuildState, StackingContextCollectionState};
+use crate::flex::FlexFlow;
+use crate::floats::{Floats, SpeculatedFloatPlacement};
+use crate::flow_list::{FlowList, FlowListIterator, MutFlowListIterator};
+use crate::flow_ref::{FlowRef, WeakFlowRef};
+use crate::fragment::{CoordinateSystem, Fragment, FragmentBorderBoxIterator, Overflow};
+use crate::inline::InlineFlow;
+use crate::model::{CollapsibleMargins, IntrinsicISizes};
+use crate::parallel::FlowParallelInfo;
+use crate::table::TableFlow;
+use crate::table_cell::TableCellFlow;
+use crate::table_colgroup::TableColGroupFlow;
+use crate::table_row::TableRowFlow;
+use crate::table_rowgroup::TableRowGroupFlow;
+use crate::table_wrapper::TableWrapperFlow;
+use app_units::Au;
+use euclid::{Point2D, Rect, Size2D, Vector2D};
+use gfx_traits::print_tree::PrintTree;
+use gfx_traits::StackingContextId;
+use num_traits::cast::FromPrimitive;
+use serde::ser::{Serialize, SerializeStruct, Serializer};
+use servo_geometry::{au_rect_to_f32_rect, f32_rect_to_au_rect, MaxRect};
+use std::fmt;
+use std::slice::IterMut;
+use std::sync::atomic::Ordering;
+use std::sync::Arc;
+use style::computed_values::clear::T as Clear;
+use style::computed_values::float::T as Float;
+use style::computed_values::overflow_x::T as StyleOverflow;
+use style::computed_values::position::T as Position;
+use style::computed_values::text_align::T as TextAlign;
+use style::context::SharedStyleContext;
+use style::logical_geometry::{LogicalRect, LogicalSize, WritingMode};
+use style::properties::ComputedValues;
+use style::selector_parser::RestyleDamage;
+use style::servo::restyle_damage::ServoRestyleDamage;
+use style::values::computed::LengthPercentageOrAuto;
+use webrender_api::units::LayoutTransform;
+
+/// This marker trait indicates that a type is a struct with `#[repr(C)]` whose first field
+/// is of type `BaseFlow` or some type that also implements this trait.
+///
+/// In other words, the memory representation of `BaseFlow` must be a prefix
+/// of the memory representation of types implementing `HasBaseFlow`.
+#[allow(unsafe_code)]
+pub unsafe trait HasBaseFlow {}
+
+/// Methods to get the `BaseFlow` from any `HasBaseFlow` type.
+pub trait GetBaseFlow {
+ fn base(&self) -> &BaseFlow;
+ fn mut_base(&mut self) -> &mut BaseFlow;
+}
+
+impl<T: HasBaseFlow + ?Sized> GetBaseFlow for T {
+ #[inline(always)]
+ #[allow(unsafe_code)]
+ fn base(&self) -> &BaseFlow {
+ let ptr: *const Self = self;
+ let ptr = ptr as *const BaseFlow;
+ unsafe { &*ptr }
+ }
+
+ #[inline(always)]
+ #[allow(unsafe_code)]
+ fn mut_base(&mut self) -> &mut BaseFlow {
+ let ptr: *mut Self = self;
+ let ptr = ptr as *mut BaseFlow;
+ unsafe { &mut *ptr }
+ }
+}
+
+/// 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: HasBaseFlow + fmt::Debug + Sync + Send + 'static {
+ // 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. Fails otherwise.
+ fn as_block(&self) -> &BlockFlow {
+ panic!("called as_block() on a non-block flow")
+ }
+
+ /// If this is a block flow, returns the underlying object, borrowed mutably. Fails otherwise.
+ fn as_mut_block(&mut self) -> &mut BlockFlow {
+ debug!("called as_mut_block() on a flow of type {:?}", self.class());
+ panic!("called as_mut_block() on a non-block flow")
+ }
+
+ /// If this is a flex flow, returns the underlying object. Fails otherwise.
+ fn as_flex(&self) -> &FlexFlow {
+ panic!("called as_flex() on a non-flex flow")
+ }
+
+ /// If this is an inline flow, returns the underlying object. Fails otherwise.
+ fn as_inline(&self) -> &InlineFlow {
+ panic!("called as_inline() on a non-inline flow")
+ }
+
+ /// If this is an inline flow, returns the underlying object, borrowed mutably. Fails
+ /// otherwise.
+ fn as_mut_inline(&mut self) -> &mut InlineFlow {
+ panic!("called as_mut_inline() on a non-inline flow")
+ }
+
+ /// If this is a table wrapper flow, returns the underlying object. Fails otherwise.
+ fn as_table_wrapper(&self) -> &TableWrapperFlow {
+ panic!("called as_table_wrapper() on a non-tablewrapper flow")
+ }
+
+ /// If this is a table flow, returns the underlying object, borrowed mutably. Fails otherwise.
+ fn as_mut_table(&mut self) -> &mut TableFlow {
+ panic!("called as_mut_table() on a non-table flow")
+ }
+
+ /// If this is a table flow, returns the underlying object. Fails otherwise.
+ fn as_table(&self) -> &TableFlow {
+ panic!("called as_table() on a non-table flow")
+ }
+
+ /// If this is a table colgroup flow, returns the underlying object, borrowed mutably. Fails
+ /// otherwise.
+ fn as_mut_table_colgroup(&mut self) -> &mut TableColGroupFlow {
+ panic!("called as_mut_table_colgroup() on a non-tablecolgroup flow")
+ }
+
+ /// If this is a table colgroup flow, returns the underlying object. Fails
+ /// otherwise.
+ fn as_table_colgroup(&self) -> &TableColGroupFlow {
+ panic!("called as_table_colgroup() on a non-tablecolgroup flow")
+ }
+
+ /// If this is a table rowgroup flow, returns the underlying object, borrowed mutably. Fails
+ /// otherwise.
+ fn as_mut_table_rowgroup(&mut self) -> &mut TableRowGroupFlow {
+ panic!("called as_mut_table_rowgroup() on a non-tablerowgroup flow")
+ }
+
+ /// If this is a table rowgroup flow, returns the underlying object. Fails otherwise.
+ fn as_table_rowgroup(&self) -> &TableRowGroupFlow {
+ panic!("called as_table_rowgroup() on a non-tablerowgroup flow")
+ }
+
+ /// If this is a table row flow, returns the underlying object, borrowed mutably. Fails
+ /// otherwise.
+ fn as_mut_table_row(&mut self) -> &mut TableRowFlow {
+ panic!("called as_mut_table_row() on a non-tablerow flow")
+ }
+
+ /// If this is a table row flow, returns the underlying object. Fails otherwise.
+ fn as_table_row(&self) -> &TableRowFlow {
+ panic!("called as_table_row() on a non-tablerow flow")
+ }
+
+ /// If this is a table cell flow, returns the underlying object, borrowed mutably. Fails
+ /// otherwise.
+ fn as_mut_table_cell(&mut self) -> &mut TableCellFlow {
+ panic!("called as_mut_table_cell() on a non-tablecell flow")
+ }
+
+ /// If this is a table cell flow, returns the underlying object. Fails otherwise.
+ fn as_table_cell(&self) -> &TableCellFlow {
+ panic!("called as_table_cell() on a non-tablecell flow")
+ }
+
+ // 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) {
+ panic!("bubble_inline_sizes not yet implemented")
+ }
+
+ /// Pass 2 of reflow: computes inline-size.
+ fn assign_inline_sizes(&mut self, _ctx: &LayoutContext) {
+ panic!("assign_inline_sizes not yet implemented")
+ }
+
+ /// Pass 3a of reflow: computes block-size.
+ fn assign_block_size(&mut self, _ctx: &LayoutContext) {
+ panic!("assign_block_size not yet implemented")
+ }
+
+ /// Like `assign_block_size`, but is recurses explicitly into descendants.
+ /// Fit as much content as possible within `available_block_size`.
+ /// If that’s not all of it, truncate the contents of `self`
+ /// and return a new flow similar to `self` with the rest of the content.
+ ///
+ /// The default is to make a flow "atomic": it can not be fragmented.
+ fn fragment(
+ &mut self,
+ layout_context: &LayoutContext,
+ _fragmentation_context: Option<FragmentationContext>,
+ ) -> Option<Arc<dyn Flow>> {
+ fn recursive_assign_block_size<F: ?Sized + Flow + GetBaseFlow>(
+ flow: &mut F,
+ ctx: &LayoutContext,
+ ) {
+ for child in flow.mut_base().child_iter_mut() {
+ recursive_assign_block_size(child, ctx)
+ }
+ flow.assign_block_size(ctx);
+ }
+ recursive_assign_block_size(self, layout_context);
+ None
+ }
+
+ fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState);
+
+ /// If this is a float, places it. The default implementation does nothing.
+ fn place_float_if_applicable<'a>(&mut self) {}
+
+ /// Assigns block-sizes in-order; or, if this is a float, places the float. The default
+ /// implementation simply assigns block-sizes if this flow might have floats in. Returns true
+ /// if it was determined that this child might have had floats in or false otherwise.
+ ///
+ /// `parent_thread_id` is the thread ID of the parent. This is used for the layout tinting
+ /// debug mode; if the block size of this flow was determined by its parent, we should treat
+ /// it as laid out by its parent.
+ fn assign_block_size_for_inorder_child_if_necessary(
+ &mut self,
+ layout_context: &LayoutContext,
+ parent_thread_id: u8,
+ _content_box: LogicalRect<Au>,
+ ) -> bool {
+ let might_have_floats_in_or_out =
+ self.base().might_have_floats_in() || self.base().might_have_floats_out();
+ if might_have_floats_in_or_out {
+ self.mut_base().thread_id = parent_thread_id;
+ self.assign_block_size(layout_context);
+ self.mut_base()
+ .restyle_damage
+ .remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW);
+ }
+ might_have_floats_in_or_out
+ }
+
+ fn get_overflow_in_parent_coordinates(&self) -> Overflow {
+ // FIXME(#2795): Get the real container size.
+ let container_size = Size2D::zero();
+ let position = self
+ .base()
+ .position
+ .to_physical(self.base().writing_mode, container_size);
+
+ let mut overflow = self.base().overflow;
+
+ match self.class() {
+ FlowClass::Block | FlowClass::TableCaption | FlowClass::TableCell => {},
+ _ => {
+ overflow.translate(&position.origin.to_vector());
+ return overflow;
+ },
+ }
+
+ let border_box = self.as_block().fragment.stacking_relative_border_box(
+ &self.base().stacking_relative_position,
+ &self
+ .base()
+ .early_absolute_position_info
+ .relative_containing_block_size,
+ self.base()
+ .early_absolute_position_info
+ .relative_containing_block_mode,
+ CoordinateSystem::Own,
+ );
+ if StyleOverflow::Visible != self.as_block().fragment.style.get_box().overflow_x {
+ overflow.paint.origin.x = Au(0);
+ overflow.paint.size.width = border_box.size.width;
+ overflow.scroll.origin.x = Au(0);
+ overflow.scroll.size.width = border_box.size.width;
+ }
+ if StyleOverflow::Visible != self.as_block().fragment.style.get_box().overflow_y {
+ overflow.paint.origin.y = Au(0);
+ overflow.paint.size.height = border_box.size.height;
+ overflow.scroll.origin.y = Au(0);
+ overflow.scroll.size.height = border_box.size.height;
+ }
+
+ if !self.as_block().fragment.establishes_stacking_context() ||
+ self.as_block()
+ .fragment
+ .style
+ .get_box()
+ .transform
+ .0
+ .is_empty()
+ {
+ overflow.translate(&position.origin.to_vector());
+ return overflow;
+ }
+
+ // TODO: Take into account 3d transforms, even though it's a fairly
+ // uncommon case.
+ let transform_2d = self
+ .as_block()
+ .fragment
+ .transform_matrix(&position)
+ .unwrap_or(LayoutTransform::identity())
+ .to_2d()
+ .to_untyped();
+ let transformed_overflow = Overflow {
+ paint: f32_rect_to_au_rect(
+ transform_2d.transform_rect(&au_rect_to_f32_rect(overflow.paint)),
+ ),
+ scroll: f32_rect_to_au_rect(
+ transform_2d.transform_rect(&au_rect_to_f32_rect(overflow.scroll)),
+ ),
+ };
+
+ // TODO: We are taking the union of the overflow and transformed overflow here, which
+ // happened implicitly in the previous version of this code. This will probably be
+ // unnecessary once we are taking into account 3D transformations above.
+ overflow.union(&transformed_overflow);
+
+ overflow.translate(&position.origin.to_vector());
+ overflow
+ }
+
+ ///
+ /// CSS Section 11.1
+ /// This is the union of rectangles of the flows for which we define the
+ /// Containing Block.
+ ///
+ /// FIXME(pcwalton): This should not be a virtual method, but currently is due to a compiler
+ /// bug ("the trait `Sized` is not implemented for `self`").
+ ///
+ /// 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(&mut self, _: &LayoutContext) {
+ // Calculate overflow on a per-fragment basis.
+ let mut overflow = self.compute_overflow();
+ match self.class() {
+ FlowClass::Block | FlowClass::TableCaption | FlowClass::TableCell => {
+ for kid in self.mut_base().children.iter_mut() {
+ overflow.union(&kid.get_overflow_in_parent_coordinates());
+ }
+ },
+ _ => {},
+ }
+ self.mut_base().overflow = overflow
+ }
+
+ /// Phase 4 of reflow: Compute the stacking-relative position (origin of the content box,
+ /// in coordinates relative to the nearest ancestor stacking context).
+ fn compute_stacking_relative_position(&mut self, _: &LayoutContext) {
+ // The default implementation is a no-op.
+ }
+
+ /// Phase 5 of reflow: builds display lists.
+ fn build_display_list(&mut self, state: &mut DisplayListBuildState);
+
+ /// Returns the union of all overflow rects of all of this flow's fragments.
+ fn compute_overflow(&self) -> Overflow;
+
+ /// Iterates through border boxes of all of this flow's fragments.
+ /// Level provides a zero based index indicating the current
+ /// depth of the flow tree during fragment iteration.
+ fn iterate_through_fragment_border_boxes(
+ &self,
+ iterator: &mut dyn FragmentBorderBoxIterator,
+ level: i32,
+ stacking_context_position: &Point2D<Au>,
+ );
+
+ /// Mutably iterates through fragments in this flow.
+ fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment));
+
+ /// Marks this flow as the root flow. The default implementation is a no-op.
+ fn mark_as_root(&mut self) {
+ debug!("called mark_as_root() on a flow of type {:?}", self.class());
+ panic!("called mark_as_root() on an unhandled flow");
+ }
+
+ // 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.
+
+ fn is_root(&self) -> bool {
+ false
+ }
+
+ /// The 'position' property of this flow.
+ fn positioning(&self) -> Position {
+ Position::Static
+ }
+
+ /// Return true if this flow has position 'fixed'.
+ fn is_fixed(&self) -> bool {
+ self.positioning() == Position::Fixed
+ }
+
+ fn contains_positioned_fragments(&self) -> bool {
+ self.contains_relatively_positioned_fragments() ||
+ self.base()
+ .flags
+ .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED)
+ }
+
+ fn contains_relatively_positioned_fragments(&self) -> bool {
+ self.positioning() == Position::Relative
+ }
+
+ /// Returns true if this is an absolute containing block.
+ fn is_absolute_containing_block(&self) -> bool {
+ self.contains_positioned_fragments()
+ }
+
+ /// Returns true if this flow contains fragments that are roots of an absolute flow tree.
+ fn contains_roots_of_absolute_flow_tree(&self) -> bool {
+ self.contains_relatively_positioned_fragments() || self.is_root()
+ }
+
+ /// Updates the inline position of a child flow during the assign-height traversal. At present,
+ /// this is only used for absolutely-positioned inline-blocks.
+ fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au);
+
+ /// Updates the block position of a child flow during the assign-height traversal. At present,
+ /// this is only used for absolutely-positioned inline-blocks.
+ fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au);
+
+ /// Return the size of the containing block generated by this flow for the absolutely-
+ /// positioned descendant referenced by `for_flow`. For block flows, this is the padding box.
+ ///
+ /// NB: Do not change this `&self` to `&mut self` under any circumstances! It has security
+ /// implications because this can be called on parents concurrently from descendants!
+ fn generated_containing_block_size(&self, _: OpaqueFlow) -> LogicalSize<Au>;
+
+ /// Attempts to perform incremental fixup of this flow by replacing its fragment's style with
+ /// the new style. This can only succeed if the flow has exactly one fragment.
+ fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>);
+
+ /// Print any extra children (such as fragments) contained in this Flow
+ /// for debugging purposes. Any items inserted into the tree will become
+ /// children of this flow.
+ fn print_extra_flow_children(&self, _: &mut PrintTree) {}
+
+ fn clipping_and_scrolling(&self) -> ClippingAndScrolling {
+ match self.base().clipping_and_scrolling {
+ Some(info) => info,
+ None => unreachable!("Tried to access scroll root id on Flow before assignment"),
+ }
+ }
+}
+
+pub trait ImmutableFlowUtils {
+ // Convenience functions
+
+ /// Returns true if this flow is a block flow or subclass thereof.
+ 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 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 the number of children that this flow possesses.
+ fn child_count(self) -> usize;
+
+ /// 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 print(self, title: String);
+
+ /// Dumps the flow tree for debugging into the given PrintTree.
+ fn print_with_tree(self, print_tree: &mut PrintTree);
+
+ /// Returns true if floats might flow through this flow, as determined by the float placement
+ /// speculation pass.
+ fn floats_might_flow_through(self) -> bool;
+
+ fn baseline_offset_of_last_line_box_in_flow(self) -> Option<Au>;
+}
+
+pub trait MutableFlowUtils {
+ /// Calls `repair_style` and `bubble_inline_sizes`. You should use this method instead of
+ /// calling them individually, since there is no reason not to perform both operations.
+ fn repair_style_and_bubble_inline_sizes(self, style: &crate::ServoArc<ComputedValues>);
+}
+
+pub trait MutableOwnedFlowUtils {
+ /// Set absolute descendants for this flow.
+ ///
+ /// Set this flow as the Containing Block for all the absolute descendants.
+ fn set_absolute_descendants(&mut self, abs_descendants: AbsoluteDescendants);
+
+ /// Sets the flow as the containing block for all absolute descendants that have been marked
+ /// as having reached their containing block. This is needed in order to handle cases like:
+ ///
+ /// ```html
+ /// <div>
+ /// <span style="position: relative">
+ /// <span style="position: absolute; ..."></span>
+ /// </span>
+ /// </div>
+ /// ```
+ fn take_applicable_absolute_descendants(
+ &mut self,
+ absolute_descendants: &mut AbsoluteDescendants,
+ );
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
+pub enum FlowClass {
+ Block,
+ Inline,
+ ListItem,
+ TableWrapper,
+ Table,
+ TableColGroup,
+ TableRowGroup,
+ TableRow,
+ TableCaption,
+ TableCell,
+ Multicol,
+ MulticolColumn,
+ Flex,
+}
+
+impl FlowClass {
+ fn is_block_like(self) -> bool {
+ match self {
+ FlowClass::Block |
+ FlowClass::ListItem |
+ FlowClass::Table |
+ FlowClass::TableRowGroup |
+ FlowClass::TableRow |
+ FlowClass::TableCaption |
+ FlowClass::TableCell |
+ FlowClass::TableWrapper |
+ FlowClass::Flex => true,
+ _ => false,
+ }
+ }
+}
+
+bitflags! {
+ #[doc = "Flags used in flows."]
+ pub struct FlowFlags: u32 {
+ // text align flags
+ #[doc = "Whether this flow is absolutely positioned. This is checked all over layout, so a"]
+ #[doc = "virtual call is too expensive."]
+ const IS_ABSOLUTELY_POSITIONED = 0b0000_0000_0000_0000_0100_0000;
+ #[doc = "Whether this flow clears to the left. This is checked all over layout, so a"]
+ #[doc = "virtual call is too expensive."]
+ const CLEARS_LEFT = 0b0000_0000_0000_0000_1000_0000;
+ #[doc = "Whether this flow clears to the right. This is checked all over layout, so a"]
+ #[doc = "virtual call is too expensive."]
+ const CLEARS_RIGHT = 0b0000_0000_0000_0001_0000_0000;
+ #[doc = "Whether this flow is left-floated. This is checked all over layout, so a"]
+ #[doc = "virtual call is too expensive."]
+ const FLOATS_LEFT = 0b0000_0000_0000_0010_0000_0000;
+ #[doc = "Whether this flow is right-floated. This is checked all over layout, so a"]
+ #[doc = "virtual call is too expensive."]
+ const FLOATS_RIGHT = 0b0000_0000_0000_0100_0000_0000;
+ #[doc = "Text alignment. \
+
+ NB: If you update this, update `TEXT_ALIGN_SHIFT` below."]
+ const TEXT_ALIGN = 0b0000_0000_0111_1000_0000_0000;
+ #[doc = "Whether this flow has a fragment with `counter-reset` or `counter-increment` \
+ styles."]
+ const AFFECTS_COUNTERS = 0b0000_0000_1000_0000_0000_0000;
+ #[doc = "Whether this flow's descendants have fragments that affect `counter-reset` or \
+ `counter-increment` styles."]
+ const HAS_COUNTER_AFFECTING_CHILDREN = 0b0000_0001_0000_0000_0000_0000;
+ #[doc = "Whether this flow behaves as though it had `position: static` for the purposes \
+ of positioning in the inline direction. This is set for flows with `position: \
+ static` and `position: relative` as well as absolutely-positioned flows with \
+ unconstrained positions in the inline direction."]
+ const INLINE_POSITION_IS_STATIC = 0b0000_0010_0000_0000_0000_0000;
+ #[doc = "Whether this flow behaves as though it had `position: static` for the purposes \
+ of positioning in the block direction. This is set for flows with `position: \
+ static` and `position: relative` as well as absolutely-positioned flows with \
+ unconstrained positions in the block direction."]
+ const BLOCK_POSITION_IS_STATIC = 0b0000_0100_0000_0000_0000_0000;
+
+ /// Whether any ancestor is a fragmentation container
+ const CAN_BE_FRAGMENTED = 0b0000_1000_0000_0000_0000_0000;
+
+ /// Whether this flow contains any text and/or replaced fragments.
+ const CONTAINS_TEXT_OR_REPLACED_FRAGMENTS = 0b0001_0000_0000_0000_0000_0000;
+
+ /// Whether margins are prohibited from collapsing with this flow.
+ const MARGINS_CANNOT_COLLAPSE = 0b0010_0000_0000_0000_0000_0000;
+ }
+}
+
+/// The number of bits we must shift off to handle the text alignment field.
+///
+/// NB: If you update this, update `TEXT_ALIGN` above.
+static TEXT_ALIGN_SHIFT: usize = 11;
+
+impl FlowFlags {
+ #[inline]
+ pub fn text_align(self) -> TextAlign {
+ TextAlign::from_u32((self & FlowFlags::TEXT_ALIGN).bits() >> TEXT_ALIGN_SHIFT).unwrap()
+ }
+
+ #[inline]
+ pub fn set_text_align(&mut self, value: TextAlign) {
+ *self = (*self & !FlowFlags::TEXT_ALIGN) |
+ FlowFlags::from_bits((value as u32) << TEXT_ALIGN_SHIFT).unwrap();
+ }
+
+ #[inline]
+ pub fn float_kind(&self) -> Float {
+ if self.contains(FlowFlags::FLOATS_LEFT) {
+ Float::Left
+ } else if self.contains(FlowFlags::FLOATS_RIGHT) {
+ Float::Right
+ } else {
+ Float::None
+ }
+ }
+
+ #[inline]
+ pub fn is_float(&self) -> bool {
+ self.contains(FlowFlags::FLOATS_LEFT) || self.contains(FlowFlags::FLOATS_RIGHT)
+ }
+
+ #[inline]
+ pub fn clears_floats(&self) -> bool {
+ self.contains(FlowFlags::CLEARS_LEFT) || self.contains(FlowFlags::CLEARS_RIGHT)
+ }
+}
+
+/// Absolutely-positioned descendants of this flow.
+#[derive(Clone)]
+pub struct AbsoluteDescendants {
+ /// Links to every descendant. This must be private because it is unsafe to leak `FlowRef`s to
+ /// layout.
+ descendant_links: Vec<AbsoluteDescendantInfo>,
+}
+
+impl AbsoluteDescendants {
+ pub fn new() -> AbsoluteDescendants {
+ AbsoluteDescendants {
+ descendant_links: Vec::new(),
+ }
+ }
+
+ pub fn len(&self) -> usize {
+ self.descendant_links.len()
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self.descendant_links.is_empty()
+ }
+
+ pub fn push(&mut self, given_descendant: FlowRef) {
+ self.descendant_links.push(AbsoluteDescendantInfo {
+ flow: given_descendant,
+ has_reached_containing_block: false,
+ });
+ }
+
+ /// 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: AbsoluteDescendants) {
+ for elem in given_descendants.descendant_links {
+ self.descendant_links.push(elem);
+ }
+ }
+
+ /// Return an iterator over the descendant flows.
+ pub fn iter(&mut self) -> AbsoluteDescendantIter {
+ AbsoluteDescendantIter {
+ iter: self.descendant_links.iter_mut(),
+ }
+ }
+
+ /// Mark these descendants as having reached their containing block.
+ pub fn mark_as_having_reached_containing_block(&mut self) {
+ for descendant_info in self.descendant_links.iter_mut() {
+ descendant_info.has_reached_containing_block = true
+ }
+ }
+}
+
+/// Information about each absolutely-positioned descendant of the given flow.
+#[derive(Clone)]
+pub struct AbsoluteDescendantInfo {
+ /// The absolute descendant flow in question.
+ flow: FlowRef,
+
+ /// Whether the absolute descendant has reached its containing block. This exists so that we
+ /// can handle cases like the following:
+ ///
+ /// ```html
+ /// <div>
+ /// <span id=a style="position: absolute; ...">foo</span>
+ /// <span style="position: relative">
+ /// <span id=b style="position: absolute; ...">bar</span>
+ /// </span>
+ /// </div>
+ /// ```
+ ///
+ /// When we go to create the `InlineFlow` for the outer `div`, our absolute descendants will
+ /// be `a` and `b`. At this point, we need a way to distinguish between the two, because the
+ /// containing block for `a` will be different from the containing block for `b`. Specifically,
+ /// the latter's containing block is the inline flow itself, while the former's containing
+ /// block is going to be some parent of the outer `div`. Hence we need this flag as a way to
+ /// distinguish the two; it will be false for `a` and true for `b`.
+ has_reached_containing_block: bool,
+}
+
+pub struct AbsoluteDescendantIter<'a> {
+ iter: IterMut<'a, AbsoluteDescendantInfo>,
+}
+
+impl<'a> Iterator for AbsoluteDescendantIter<'a> {
+ type Item = &'a mut dyn Flow;
+ fn next(&mut self) -> Option<&'a mut dyn Flow> {
+ self.iter
+ .next()
+ .map(|info| FlowRef::deref_mut(&mut info.flow))
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ self.iter.size_hint()
+ }
+}
+
+/// Information needed to compute absolute (i.e. viewport-relative) flow positions (not to be
+/// confused with absolutely-positioned flows) that is computed during block-size assignment.
+#[derive(Clone, Copy)]
+pub struct EarlyAbsolutePositionInfo {
+ /// The size of the containing block for relatively-positioned descendants.
+ pub relative_containing_block_size: LogicalSize<Au>,
+
+ /// The writing mode for `relative_containing_block_size`.
+ pub relative_containing_block_mode: WritingMode,
+}
+
+impl EarlyAbsolutePositionInfo {
+ pub fn new(writing_mode: WritingMode) -> EarlyAbsolutePositionInfo {
+ // FIXME(pcwalton): The initial relative containing block-size should be equal to the size
+ // of the root layer.
+ EarlyAbsolutePositionInfo {
+ relative_containing_block_size: LogicalSize::zero(writing_mode),
+ relative_containing_block_mode: writing_mode,
+ }
+ }
+}
+
+/// Information needed to compute absolute (i.e. viewport-relative) flow positions (not to be
+/// confused with absolutely-positioned flows) that is computed during final position assignment.
+#[derive(Clone, Copy, Serialize)]
+pub struct LateAbsolutePositionInfo {
+ /// The position of the absolute containing block relative to the nearest ancestor stacking
+ /// context. If the absolute containing block establishes the stacking context for this flow,
+ /// and this flow is not itself absolutely-positioned, then this is (0, 0).
+ pub stacking_relative_position_of_absolute_containing_block: Point2D<Au>,
+}
+
+impl LateAbsolutePositionInfo {
+ pub fn new() -> LateAbsolutePositionInfo {
+ LateAbsolutePositionInfo {
+ stacking_relative_position_of_absolute_containing_block: Point2D::zero(),
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct FragmentationContext {
+ pub available_block_size: Au,
+ pub this_fragment_is_empty: bool,
+}
+
+/// Data common to all flows.
+pub struct BaseFlow {
+ pub restyle_damage: RestyleDamage,
+
+ /// The children of this flow.
+ pub children: FlowList,
+
+ /// Intrinsic inline sizes for this flow.
+ 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: Overflow,
+
+ /// 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,
+
+ /// Metrics for floats in computed during the float metrics speculation phase.
+ pub speculated_float_placement_in: SpeculatedFloatPlacement,
+
+ /// Metrics for floats out computed during the float metrics speculation phase.
+ pub speculated_float_placement_out: SpeculatedFloatPlacement,
+
+ /// The collapsible margins for this flow, if any.
+ pub collapsible_margins: CollapsibleMargins,
+
+ /// The position of this flow relative to the start of the nearest ancestor stacking context.
+ /// This is computed during the top-down pass of display list construction.
+ pub stacking_relative_position: Vector2D<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: AbsoluteDescendants,
+
+ /// The inline-size of the block container of this flow. Used for computing percentage and
+ /// automatic values for `width`.
+ pub block_container_inline_size: Au,
+
+ /// The writing mode of the block container of this flow.
+ ///
+ /// FIXME (mbrubeck): Combine this and block_container_inline_size and maybe
+ /// block_container_explicit_block_size into a struct, to guarantee they are set at the same
+ /// time? Or just store a link to the containing block flow.
+ pub block_container_writing_mode: WritingMode,
+
+ /// The block-size of the block container of this flow, if it is an explicit size (does not
+ /// depend on content heights). Used for computing percentage values for `height`.
+ pub block_container_explicit_block_size: Option<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) that is computed during block-size assignment.
+ pub early_absolute_position_info: EarlyAbsolutePositionInfo,
+
+ /// Information needed to compute absolute (i.e. viewport-relative) flow positions (not to be
+ /// confused with absolutely-positioned flows) that is computed during final position
+ /// assignment.
+ pub late_absolute_position_info: LateAbsolutePositionInfo,
+
+ /// The clipping rectangle for this flow and its descendants, in the coordinate system of the
+ /// nearest ancestor stacking context. If this flow itself represents a stacking context, then
+ /// this is in the flow's own coordinate system.
+ pub clip: Rect<Au>,
+
+ /// The writing mode for this flow.
+ pub writing_mode: WritingMode,
+
+ /// For debugging and profiling, the identifier of the thread that laid out this fragment.
+ pub thread_id: u8,
+
+ /// Various flags for flows, tightly packed to save space.
+ pub flags: FlowFlags,
+
+ /// The ID of the StackingContext that contains this flow. This is initialized
+ /// to 0, but it assigned during the collect_stacking_contexts phase of display
+ /// list construction.
+ pub stacking_context_id: StackingContextId,
+
+ /// The indices of this Flow's ClipScrollNode. This is used to place the node's
+ /// display items into scrolling frames and clipping nodes.
+ pub clipping_and_scrolling: Option<ClippingAndScrolling>,
+}
+
+impl fmt::Debug for BaseFlow {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let child_count = self.parallel.children_count.load(Ordering::SeqCst);
+ let child_count_string = if child_count > 0 {
+ format!("\nchildren={}", child_count)
+ } else {
+ "".to_owned()
+ };
+
+ let absolute_descendants_string = if self.abs_descendants.len() > 0 {
+ format!("\nabs-descendents={}", self.abs_descendants.len())
+ } else {
+ "".to_owned()
+ };
+
+ let damage_string = if self.restyle_damage != RestyleDamage::empty() {
+ format!("\ndamage={:?}", self.restyle_damage)
+ } else {
+ "".to_owned()
+ };
+
+ write!(
+ f,
+ "\nsc={:?}\
+ \npos={:?}{}{}\
+ \nfloatspec-in={:?}\
+ \nfloatspec-out={:?}\
+ \noverflow={:?}{}{}{}",
+ self.stacking_context_id,
+ self.position,
+ if self.flags.contains(FlowFlags::FLOATS_LEFT) {
+ "FL"
+ } else {
+ ""
+ },
+ if self.flags.contains(FlowFlags::FLOATS_RIGHT) {
+ "FR"
+ } else {
+ ""
+ },
+ self.speculated_float_placement_in,
+ self.speculated_float_placement_out,
+ self.overflow,
+ child_count_string,
+ absolute_descendants_string,
+ damage_string
+ )
+ }
+}
+
+impl Serialize for BaseFlow {
+ fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+ let mut serializer = serializer.serialize_struct("base", 5)?;
+ serializer.serialize_field("id", &self.debug_id())?;
+ serializer.serialize_field(
+ "stacking_relative_position",
+ &self.stacking_relative_position,
+ )?;
+ serializer.serialize_field("intrinsic_inline_sizes", &self.intrinsic_inline_sizes)?;
+ serializer.serialize_field("position", &self.position)?;
+ serializer.serialize_field("children", &self.children)?;
+ serializer.end()
+ }
+}
+
+/// Whether a base flow should be forced to be nonfloated. This can affect e.g. `TableFlow`, which
+/// is never floated because the table wrapper flow is the floated one.
+#[derive(Clone, PartialEq)]
+pub enum ForceNonfloatedFlag {
+ /// The flow should be floated if the node has a `float` property.
+ FloatIfNecessary,
+ /// The flow should be forced to be nonfloated.
+ ForceNonfloated,
+}
+
+impl BaseFlow {
+ #[inline]
+ pub fn new(
+ style: Option<&ComputedValues>,
+ writing_mode: WritingMode,
+ force_nonfloated: ForceNonfloatedFlag,
+ ) -> BaseFlow {
+ let mut flags = FlowFlags::empty();
+ match style {
+ Some(style) => {
+ if style.can_be_fragmented() {
+ flags.insert(FlowFlags::CAN_BE_FRAGMENTED);
+ }
+
+ match style.get_box().position {
+ Position::Absolute | Position::Fixed => {
+ flags.insert(FlowFlags::IS_ABSOLUTELY_POSITIONED);
+
+ let logical_position = style.logical_position();
+ if logical_position.inline_start == LengthPercentageOrAuto::Auto &&
+ logical_position.inline_end == LengthPercentageOrAuto::Auto
+ {
+ flags.insert(FlowFlags::INLINE_POSITION_IS_STATIC);
+ }
+ if logical_position.block_start == LengthPercentageOrAuto::Auto &&
+ logical_position.block_end == LengthPercentageOrAuto::Auto
+ {
+ flags.insert(FlowFlags::BLOCK_POSITION_IS_STATIC);
+ }
+ },
+ _ => flags.insert(
+ FlowFlags::BLOCK_POSITION_IS_STATIC | FlowFlags::INLINE_POSITION_IS_STATIC,
+ ),
+ }
+
+ if force_nonfloated == ForceNonfloatedFlag::FloatIfNecessary {
+ match style.get_box().float {
+ Float::None => {},
+ Float::Left => flags.insert(FlowFlags::FLOATS_LEFT),
+ Float::Right => flags.insert(FlowFlags::FLOATS_RIGHT),
+ }
+ }
+
+ match style.get_box().clear {
+ Clear::None => {},
+ Clear::Left => flags.insert(FlowFlags::CLEARS_LEFT),
+ Clear::Right => flags.insert(FlowFlags::CLEARS_RIGHT),
+ Clear::Both => {
+ flags.insert(FlowFlags::CLEARS_LEFT);
+ flags.insert(FlowFlags::CLEARS_RIGHT);
+ },
+ }
+
+ if !style.get_counters().counter_reset.is_empty() ||
+ !style.get_counters().counter_increment.is_empty()
+ {
+ flags.insert(FlowFlags::AFFECTS_COUNTERS)
+ }
+ },
+ None => flags
+ .insert(FlowFlags::BLOCK_POSITION_IS_STATIC | FlowFlags::INLINE_POSITION_IS_STATIC),
+ }
+
+ // New flows start out as fully damaged.
+ let mut damage = RestyleDamage::rebuild_and_reflow();
+ damage.remove(ServoRestyleDamage::RECONSTRUCT_FLOW);
+
+ BaseFlow {
+ restyle_damage: damage,
+ children: FlowList::new(),
+ intrinsic_inline_sizes: IntrinsicISizes::new(),
+ position: LogicalRect::zero(writing_mode),
+ overflow: Overflow::new(),
+ parallel: FlowParallelInfo::new(),
+ floats: Floats::new(writing_mode),
+ collapsible_margins: CollapsibleMargins::new(),
+ stacking_relative_position: Vector2D::zero(),
+ abs_descendants: AbsoluteDescendants::new(),
+ speculated_float_placement_in: SpeculatedFloatPlacement::zero(),
+ speculated_float_placement_out: SpeculatedFloatPlacement::zero(),
+ block_container_inline_size: Au(0),
+ block_container_writing_mode: writing_mode,
+ block_container_explicit_block_size: None,
+ absolute_cb: ContainingBlockLink::new(),
+ early_absolute_position_info: EarlyAbsolutePositionInfo::new(writing_mode),
+ late_absolute_position_info: LateAbsolutePositionInfo::new(),
+ clip: MaxRect::max_rect(),
+ flags: flags,
+ writing_mode: writing_mode,
+ thread_id: 0,
+ stacking_context_id: StackingContextId::root(),
+ clipping_and_scrolling: None,
+ }
+ }
+
+ /// Update the 'flags' field when computed styles have changed.
+ ///
+ /// These flags are initially set during flow construction. They only need to be updated here
+ /// if they are based on properties that can change without triggering `RECONSTRUCT_FLOW`.
+ pub fn update_flags_if_needed(&mut self, style: &ComputedValues) {
+ // For absolutely-positioned flows, changes to top/bottom/left/right can cause these flags
+ // to get out of date:
+ if self
+ .restyle_damage
+ .contains(ServoRestyleDamage::REFLOW_OUT_OF_FLOW)
+ {
+ // Note: We don't need to check whether IS_ABSOLUTELY_POSITIONED has changed, because
+ // changes to the 'position' property trigger flow reconstruction.
+ if self.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) {
+ let logical_position = style.logical_position();
+ self.flags.set(
+ FlowFlags::INLINE_POSITION_IS_STATIC,
+ logical_position.inline_start == LengthPercentageOrAuto::Auto &&
+ logical_position.inline_end == LengthPercentageOrAuto::Auto,
+ );
+ self.flags.set(
+ FlowFlags::BLOCK_POSITION_IS_STATIC,
+ logical_position.block_start == LengthPercentageOrAuto::Auto &&
+ logical_position.block_end == LengthPercentageOrAuto::Auto,
+ );
+ }
+ }
+ }
+
+ /// Return a new BaseFlow like this one but with the given children list
+ pub fn clone_with_children(&self, children: FlowList) -> BaseFlow {
+ BaseFlow {
+ children: children,
+ restyle_damage: self.restyle_damage |
+ ServoRestyleDamage::REPAINT |
+ ServoRestyleDamage::REFLOW_OUT_OF_FLOW |
+ ServoRestyleDamage::REFLOW,
+ parallel: FlowParallelInfo::new(),
+ floats: self.floats.clone(),
+ abs_descendants: self.abs_descendants.clone(),
+ absolute_cb: self.absolute_cb.clone(),
+ clip: self.clip.clone(),
+
+ ..*self
+ }
+ }
+
+ /// Iterates over the children of this immutable flow.
+ pub fn child_iter(&self) -> FlowListIterator {
+ self.children.iter()
+ }
+
+ pub fn child_iter_mut(&mut self) -> MutFlowListIterator {
+ self.children.iter_mut()
+ }
+
+ pub fn debug_id(&self) -> usize {
+ let p = self as *const _;
+ p as usize
+ }
+
+ pub fn collect_stacking_contexts_for_children(
+ &mut self,
+ state: &mut StackingContextCollectionState,
+ ) {
+ for kid in self.children.iter_mut() {
+ kid.collect_stacking_contexts(state);
+ }
+ }
+
+ #[inline]
+ pub fn might_have_floats_in(&self) -> bool {
+ self.speculated_float_placement_in.left > Au(0) ||
+ self.speculated_float_placement_in.right > Au(0)
+ }
+
+ #[inline]
+ pub fn might_have_floats_out(&self) -> bool {
+ self.speculated_float_placement_out.left > Au(0) ||
+ self.speculated_float_placement_out.right > Au(0)
+ }
+
+ /// Compute the fragment position relative to the parent stacking context. If the fragment
+ /// itself establishes a stacking context, then the origin of its position will be (0, 0)
+ /// for the purposes of this computation.
+ pub fn stacking_relative_border_box_for_display_list(&self, fragment: &Fragment) -> Rect<Au> {
+ fragment.stacking_relative_border_box(
+ &self.stacking_relative_position,
+ &self
+ .early_absolute_position_info
+ .relative_containing_block_size,
+ self.early_absolute_position_info
+ .relative_containing_block_mode,
+ CoordinateSystem::Own,
+ )
+ }
+}
+
+impl<'a> ImmutableFlowUtils for &'a dyn Flow {
+ /// Returns true if this flow is a block flow or subclass thereof.
+ fn is_block_like(self) -> bool {
+ self.class().is_block_like()
+ }
+
+ /// Returns true if this flow is a table row flow.
+ fn is_table_row(self) -> bool {
+ match self.class() {
+ FlowClass::TableRow => true,
+ _ => false,
+ }
+ }
+
+ /// Returns true if this flow is a table cell flow.
+ fn is_table_cell(self) -> bool {
+ match self.class() {
+ FlowClass::TableCell => true,
+ _ => false,
+ }
+ }
+
+ /// Returns true if this flow is a table colgroup flow.
+ fn is_table_colgroup(self) -> bool {
+ match self.class() {
+ FlowClass::TableColGroup => true,
+ _ => false,
+ }
+ }
+
+ /// Returns true if this flow is a table flow.
+ fn is_table(self) -> bool {
+ match self.class() {
+ FlowClass::Table => true,
+ _ => false,
+ }
+ }
+
+ /// Returns true if this flow is a table caption flow.
+ fn is_table_caption(self) -> bool {
+ match self.class() {
+ FlowClass::TableCaption => true,
+ _ => false,
+ }
+ }
+
+ /// Returns true if this flow is a table rowgroup flow.
+ fn is_table_rowgroup(self) -> bool {
+ match self.class() {
+ FlowClass::TableRowGroup => true,
+ _ => false,
+ }
+ }
+
+ /// Returns the number of children that this flow possesses.
+ fn child_count(self) -> usize {
+ self.base().children.len()
+ }
+
+ /// Returns true if this flow is a block flow.
+ fn is_block_flow(self) -> bool {
+ match self.class() {
+ FlowClass::Block => true,
+ _ => false,
+ }
+ }
+
+ /// Returns true if this flow is an inline flow.
+ fn is_inline_flow(self) -> bool {
+ match self.class() {
+ FlowClass::Inline => true,
+ _ => false,
+ }
+ }
+
+ /// Dumps the flow tree for debugging.
+ fn print(self, title: String) {
+ let mut print_tree = PrintTree::new(title);
+ self.print_with_tree(&mut print_tree);
+ }
+
+ /// Dumps the flow tree for debugging into the given PrintTree.
+ fn print_with_tree(self, print_tree: &mut PrintTree) {
+ print_tree.new_level(format!("{:?}", self));
+ self.print_extra_flow_children(print_tree);
+ for kid in self.base().child_iter() {
+ kid.print_with_tree(print_tree);
+ }
+ print_tree.end_level();
+ }
+
+ fn floats_might_flow_through(self) -> bool {
+ if !self.base().might_have_floats_in() && !self.base().might_have_floats_out() {
+ return false;
+ }
+ if self.is_root() {
+ return false;
+ }
+ if !self.is_block_like() {
+ return true;
+ }
+ self.as_block().formatting_context_type() == FormattingContextType::None
+ }
+
+ fn baseline_offset_of_last_line_box_in_flow(self) -> Option<Au> {
+ for kid in self.base().children.iter().rev() {
+ if kid.is_inline_flow() {
+ if let Some(baseline_offset) = kid.as_inline().baseline_offset_of_last_line() {
+ return Some(kid.base().position.start.b + baseline_offset);
+ }
+ }
+ if kid.is_block_like() &&
+ !kid.base()
+ .flags
+ .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED)
+ {
+ if let Some(baseline_offset) = kid.baseline_offset_of_last_line_box_in_flow() {
+ return Some(kid.base().position.start.b + baseline_offset);
+ }
+ }
+ }
+ None
+ }
+}
+
+impl<'a> MutableFlowUtils for &'a mut dyn Flow {
+ /// Calls `repair_style` and `bubble_inline_sizes`. You should use this method instead of
+ /// calling them individually, since there is no reason not to perform both operations.
+ fn repair_style_and_bubble_inline_sizes(self, style: &crate::ServoArc<ComputedValues>) {
+ self.repair_style(style);
+ self.mut_base().update_flags_if_needed(style);
+ self.bubble_inline_sizes();
+ }
+}
+
+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_absolute_descendants(&mut self, abs_descendants: AbsoluteDescendants) {
+ let this = self.clone();
+ let base = FlowRef::deref_mut(self).mut_base();
+ base.abs_descendants = abs_descendants;
+ for descendant_link in base.abs_descendants.descendant_links.iter_mut() {
+ debug_assert!(!descendant_link.has_reached_containing_block);
+ let descendant_base = FlowRef::deref_mut(&mut descendant_link.flow).mut_base();
+ descendant_base.absolute_cb.set(this.clone());
+ }
+ }
+
+ /// Sets the flow as the containing block for all absolute descendants that have been marked
+ /// as having reached their containing block. This is needed in order to handle cases like:
+ ///
+ /// ```html
+ /// <div>
+ /// <span style="position: relative">
+ /// <span style="position: absolute; ..."></span>
+ /// </span>
+ /// </div>
+ /// ```
+ fn take_applicable_absolute_descendants(
+ &mut self,
+ absolute_descendants: &mut AbsoluteDescendants,
+ ) {
+ let mut applicable_absolute_descendants = AbsoluteDescendants::new();
+ for absolute_descendant in absolute_descendants.descendant_links.iter() {
+ if absolute_descendant.has_reached_containing_block {
+ applicable_absolute_descendants.push(absolute_descendant.flow.clone());
+ }
+ }
+ absolute_descendants
+ .descendant_links
+ .retain(|descendant| !descendant.has_reached_containing_block);
+
+ let this = self.clone();
+ let base = FlowRef::deref_mut(self).mut_base();
+ base.abs_descendants = applicable_absolute_descendants;
+ for descendant_link in base.abs_descendants.iter() {
+ let descendant_base = descendant_link.mut_base();
+ descendant_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`.
+#[derive(Clone)]
+pub struct ContainingBlockLink {
+ /// The pointer up to the containing block.
+ link: Option<WeakFlowRef>,
+}
+
+impl ContainingBlockLink {
+ fn new() -> ContainingBlockLink {
+ ContainingBlockLink { link: None }
+ }
+
+ fn set(&mut self, link: FlowRef) {
+ self.link = Some(FlowRef::downgrade(&link))
+ }
+
+ #[inline]
+ pub fn generated_containing_block_size(&self, for_flow: OpaqueFlow) -> LogicalSize<Au> {
+ match self.link {
+ None => panic!(
+ "Link to containing block not established; perhaps you forgot to call \
+ `set_absolute_descendants`?"
+ ),
+ Some(ref link) => {
+ let flow = link.upgrade().unwrap();
+ flow.generated_containing_block_size(for_flow)
+ },
+ }
+ }
+
+ #[inline]
+ pub fn explicit_block_containing_size(
+ &self,
+ shared_context: &SharedStyleContext,
+ ) -> Option<Au> {
+ match self.link {
+ None => panic!(
+ "Link to containing block not established; perhaps you forgot to call \
+ `set_absolute_descendants`?"
+ ),
+ Some(ref link) => {
+ let flow = link.upgrade().unwrap();
+ if flow.is_block_like() {
+ flow.as_block()
+ .explicit_block_containing_size(shared_context)
+ } else if flow.is_inline_flow() {
+ Some(flow.as_inline().minimum_line_metrics.space_above_baseline)
+ } else {
+ None
+ }
+ },
+ }
+ }
+}
+
+/// A wrapper for the pointer address of a flow. These pointer addresses may only be compared for
+/// equality with other such pointer addresses, never dereferenced.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub struct OpaqueFlow(pub usize);
+
+impl OpaqueFlow {
+ pub fn from_flow(flow: &dyn Flow) -> OpaqueFlow {
+ let object_ptr: *const dyn Flow = flow;
+ let data_ptr = object_ptr as *const ();
+ OpaqueFlow(data_ptr as usize)
+ }
+}
diff --git a/components/layout_2020/flow_list.rs b/components/layout_2020/flow_list.rs
new file mode 100644
index 00000000000..a27db03920a
--- /dev/null
+++ b/components/layout_2020/flow_list.rs
@@ -0,0 +1,197 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+use crate::flow::{Flow, FlowClass};
+use crate::flow_ref::FlowRef;
+use serde::ser::{Serialize, SerializeSeq, Serializer};
+use serde_json::{to_value, Map, Value};
+use std::collections::{linked_list, LinkedList};
+use std::ops::Deref;
+use std::sync::Arc;
+
+/// This needs to be reworked now that we have dynamically-sized types in Rust.
+/// Until then, it's just a wrapper around LinkedList.
+///
+/// SECURITY-NOTE(pcwalton): It is very important that `FlowRef` values not leak directly to
+/// layout. Layout code must only interact with `&Flow` or `&mut Flow` values. Otherwise, layout
+/// could stash `FlowRef` values in random places unknown to the system and thereby cause data
+/// races. Those data races can lead to memory safety problems, potentially including arbitrary
+/// remote code execution! In general, do not add new methods to this file (e.g. new ways of
+/// iterating over flows) unless you are *very* sure of what you are doing.
+pub struct FlowList {
+ flows: LinkedList<FlowRef>,
+}
+
+impl Serialize for FlowList {
+ fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+ let mut serializer = serializer.serialize_seq(Some(self.len()))?;
+ for f in self.iter() {
+ let mut flow_val = Map::new();
+ flow_val.insert("class".to_owned(), to_value(f.class()).unwrap());
+ let data = match f.class() {
+ FlowClass::Block => to_value(f.as_block()).unwrap(),
+ FlowClass::Inline => to_value(f.as_inline()).unwrap(),
+ FlowClass::Table => to_value(f.as_table()).unwrap(),
+ FlowClass::TableWrapper => to_value(f.as_table_wrapper()).unwrap(),
+ FlowClass::TableRowGroup => to_value(f.as_table_rowgroup()).unwrap(),
+ FlowClass::TableRow => to_value(f.as_table_row()).unwrap(),
+ FlowClass::TableCell => to_value(f.as_table_cell()).unwrap(),
+ FlowClass::Flex => to_value(f.as_flex()).unwrap(),
+ FlowClass::ListItem |
+ FlowClass::TableColGroup |
+ FlowClass::TableCaption |
+ FlowClass::Multicol |
+ FlowClass::MulticolColumn => {
+ Value::Null // Not implemented yet
+ },
+ };
+ flow_val.insert("data".to_owned(), data);
+ serializer.serialize_element(&flow_val)?;
+ }
+ serializer.end()
+ }
+}
+
+pub struct MutFlowListIterator<'a> {
+ it: linked_list::IterMut<'a, FlowRef>,
+}
+
+pub struct FlowListIterator<'a> {
+ it: linked_list::Iter<'a, FlowRef>,
+}
+
+impl FlowList {
+ /// Add an element last in the list
+ ///
+ /// O(1)
+ pub fn push_back(&mut self, new_tail: FlowRef) {
+ self.flows.push_back(new_tail);
+ }
+
+ pub fn push_back_arc(&mut self, new_head: Arc<dyn Flow>) {
+ self.flows.push_back(FlowRef::new(new_head));
+ }
+
+ pub fn push_front_arc(&mut self, new_head: Arc<dyn Flow>) {
+ self.flows.push_front(FlowRef::new(new_head));
+ }
+
+ pub fn pop_front_arc(&mut self) -> Option<Arc<dyn Flow>> {
+ self.flows.pop_front().map(FlowRef::into_arc)
+ }
+
+ /// Create an empty list
+ #[inline]
+ pub fn new() -> FlowList {
+ FlowList {
+ flows: LinkedList::new(),
+ }
+ }
+
+ /// Provide a forward iterator.
+ ///
+ /// SECURITY-NOTE(pcwalton): This does not hand out `FlowRef`s by design. Do not add a method
+ /// to do so! See the comment above in `FlowList`.
+ #[inline]
+ pub fn iter<'a>(&'a self) -> FlowListIterator {
+ FlowListIterator {
+ it: self.flows.iter(),
+ }
+ }
+
+ /// Provide a forward iterator with mutable references
+ ///
+ /// SECURITY-NOTE(pcwalton): This does not hand out `FlowRef`s by design. Do not add a method
+ /// to do so! See the comment above in `FlowList`.
+ #[inline]
+ pub fn iter_mut(&mut self) -> MutFlowListIterator {
+ MutFlowListIterator {
+ it: self.flows.iter_mut(),
+ }
+ }
+
+ /// Provides a caching random-access iterator that yields mutable references. This is
+ /// guaranteed to perform no more than O(n) pointer chases.
+ ///
+ /// SECURITY-NOTE(pcwalton): This does not hand out `FlowRef`s by design. Do not add a method
+ /// to do so! See the comment above in `FlowList`.
+ #[inline]
+ pub fn random_access_mut(&mut self) -> FlowListRandomAccessMut {
+ let length = self.flows.len();
+ FlowListRandomAccessMut {
+ iterator: self.flows.iter_mut(),
+ cache: Vec::with_capacity(length),
+ }
+ }
+
+ /// O(1)
+ #[inline]
+ pub fn len(&self) -> usize {
+ self.flows.len()
+ }
+
+ #[inline]
+ pub fn split_off(&mut self, i: usize) -> Self {
+ FlowList {
+ flows: self.flows.split_off(i),
+ }
+ }
+}
+
+impl<'a> DoubleEndedIterator for FlowListIterator<'a> {
+ fn next_back(&mut self) -> Option<&'a dyn Flow> {
+ self.it.next_back().map(Deref::deref)
+ }
+}
+
+impl<'a> DoubleEndedIterator for MutFlowListIterator<'a> {
+ fn next_back(&mut self) -> Option<&'a mut dyn Flow> {
+ self.it.next_back().map(FlowRef::deref_mut)
+ }
+}
+
+impl<'a> Iterator for FlowListIterator<'a> {
+ type Item = &'a dyn Flow;
+ #[inline]
+ fn next(&mut self) -> Option<&'a dyn Flow> {
+ self.it.next().map(Deref::deref)
+ }
+
+ #[inline]
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ self.it.size_hint()
+ }
+}
+
+impl<'a> Iterator for MutFlowListIterator<'a> {
+ type Item = &'a mut dyn Flow;
+ #[inline]
+ fn next(&mut self) -> Option<&'a mut dyn Flow> {
+ self.it.next().map(FlowRef::deref_mut)
+ }
+
+ #[inline]
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ self.it.size_hint()
+ }
+}
+
+/// A caching random-access iterator that yields mutable references. This is guaranteed to perform
+/// no more than O(n) pointer chases.
+pub struct FlowListRandomAccessMut<'a> {
+ iterator: linked_list::IterMut<'a, FlowRef>,
+ cache: Vec<FlowRef>,
+}
+
+impl<'a> FlowListRandomAccessMut<'a> {
+ pub fn get<'b>(&'b mut self, index: usize) -> &'b mut dyn Flow {
+ while index >= self.cache.len() {
+ match self.iterator.next() {
+ None => panic!("Flow index out of range!"),
+ Some(next_flow) => self.cache.push((*next_flow).clone()),
+ }
+ }
+ FlowRef::deref_mut(&mut self.cache[index])
+ }
+}
diff --git a/components/layout_2020/flow_ref.rs b/components/layout_2020/flow_ref.rs
new file mode 100644
index 00000000000..f00740aa4ed
--- /dev/null
+++ b/components/layout_2020/flow_ref.rs
@@ -0,0 +1,64 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+//! Reference-counted pointers to flows.
+//!
+//! Eventually, with dynamically sized types in Rust, much of this code will
+//! be superfluous. This design is largely duplicating logic of Arc<T> and
+//! Weak<T>; please see comments there for details.
+
+use crate::flow::Flow;
+use std::ops::Deref;
+use std::sync::{Arc, Weak};
+
+#[derive(Clone, Debug)]
+pub struct FlowRef(Arc<dyn Flow>);
+
+impl Deref for FlowRef {
+ type Target = dyn Flow;
+ fn deref(&self) -> &dyn Flow {
+ self.0.deref()
+ }
+}
+
+impl FlowRef {
+ /// `FlowRef`s can only be made available to the traversal code.
+ /// See https://github.com/servo/servo/issues/14014 for more details.
+ pub fn new(mut r: Arc<dyn Flow>) -> Self {
+ // This assertion checks that this `FlowRef` does not alias normal `Arc`s.
+ // If that happens, we're in trouble.
+ assert!(Arc::get_mut(&mut r).is_some());
+ FlowRef(r)
+ }
+ pub fn get_mut(this: &mut FlowRef) -> Option<&mut dyn Flow> {
+ Arc::get_mut(&mut this.0)
+ }
+ pub fn downgrade(this: &FlowRef) -> WeakFlowRef {
+ WeakFlowRef(Arc::downgrade(&this.0))
+ }
+ pub fn into_arc(mut this: FlowRef) -> Arc<dyn Flow> {
+ // This assertion checks that this `FlowRef` does not alias normal `Arc`s.
+ // If that happens, we're in trouble.
+ assert!(FlowRef::get_mut(&mut this).is_some());
+ this.0
+ }
+ /// WARNING: This should only be used when there is no aliasing:
+ /// when the traversal ensures that no other threads accesses the same flow at the same time.
+ /// See https://github.com/servo/servo/issues/6503
+ /// Use Arc::get_mut instead when possible (e.g. on an Arc that was just created).
+ #[allow(unsafe_code)]
+ pub fn deref_mut(this: &mut FlowRef) -> &mut dyn Flow {
+ let ptr: *const dyn Flow = &*this.0;
+ unsafe { &mut *(ptr as *mut dyn Flow) }
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct WeakFlowRef(Weak<dyn Flow>);
+
+impl WeakFlowRef {
+ pub fn upgrade(&self) -> Option<FlowRef> {
+ self.0.upgrade().map(FlowRef)
+ }
+}
diff --git a/components/layout_2020/fragment.rs b/components/layout_2020/fragment.rs
new file mode 100644
index 00000000000..879e2af2f64
--- /dev/null
+++ b/components/layout_2020/fragment.rs
@@ -0,0 +1,3455 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+//! The `Fragment` type, which represents the leaves of the layout tree.
+
+use crate::context::{with_thread_local_font_context, LayoutContext};
+use crate::display_list::items::{ClipScrollNodeIndex, OpaqueNode, BLUR_INFLATION_FACTOR};
+use crate::display_list::ToLayout;
+use crate::floats::ClearType;
+use crate::flow::{GetBaseFlow, ImmutableFlowUtils};
+use crate::flow_ref::FlowRef;
+use crate::inline::{InlineFragmentContext, InlineFragmentNodeFlags, InlineFragmentNodeInfo};
+use crate::inline::{InlineMetrics, LineMetrics};
+#[cfg(debug_assertions)]
+use crate::layout_debug;
+use crate::model::style_length;
+use crate::model::{self, IntrinsicISizes, IntrinsicISizesContribution, MaybeAuto, SizeConstraint};
+use crate::text;
+use crate::text::TextRunScanner;
+use crate::wrapper::ThreadSafeLayoutNodeHelpers;
+use crate::ServoArc;
+use app_units::Au;
+use canvas_traits::canvas::{CanvasId, CanvasMsg};
+use euclid::{Point2D, Rect, Size2D, Vector2D};
+use gfx::text::glyph::ByteIndex;
+use gfx::text::text_run::{TextRun, TextRunSlice};
+use gfx_traits::StackingContextId;
+use ipc_channel::ipc::IpcSender;
+use msg::constellation_msg::{BrowsingContextId, PipelineId};
+use net_traits::image::base::{Image, ImageMetadata};
+use net_traits::image_cache::{ImageOrMetadataAvailable, UsePlaceholder};
+use range::*;
+use script_layout_interface::wrapper_traits::{
+ PseudoElementType, ThreadSafeLayoutElement, ThreadSafeLayoutNode,
+};
+use script_layout_interface::{HTMLCanvasData, HTMLCanvasDataSource, HTMLMediaData, SVGSVGData};
+use serde::ser::{Serialize, SerializeStruct, Serializer};
+use servo_url::ServoUrl;
+use std::borrow::ToOwned;
+use std::cmp::{max, min, Ordering};
+use std::collections::LinkedList;
+use std::sync::{Arc, Mutex};
+use std::{f32, fmt};
+use style::computed_values::border_collapse::T as BorderCollapse;
+use style::computed_values::box_sizing::T as BoxSizing;
+use style::computed_values::clear::T as Clear;
+use style::computed_values::color::T as Color;
+use style::computed_values::display::T as Display;
+use style::computed_values::mix_blend_mode::T as MixBlendMode;
+use style::computed_values::overflow_wrap::T as OverflowWrap;
+use style::computed_values::overflow_x::T as StyleOverflow;
+use style::computed_values::position::T as Position;
+use style::computed_values::text_decoration_line::T as TextDecorationLine;
+use style::computed_values::transform_style::T as TransformStyle;
+use style::computed_values::white_space::T as WhiteSpace;
+use style::computed_values::word_break::T as WordBreak;
+use style::logical_geometry::{Direction, LogicalMargin, LogicalRect, LogicalSize, WritingMode};
+use style::properties::ComputedValues;
+use style::selector_parser::RestyleDamage;
+use style::servo::restyle_damage::ServoRestyleDamage;
+use style::str::char_is_whitespace;
+use style::values::computed::counters::ContentItem;
+use style::values::computed::{LengthPercentage, LengthPercentageOrAuto, Size, VerticalAlign};
+use style::values::generics::box_::{Perspective, VerticalAlignKeyword};
+use style::values::generics::transform;
+use style::Zero;
+use webrender_api;
+use webrender_api::units::LayoutTransform;
+
+// From gfxFontConstants.h in Firefox.
+static FONT_SUBSCRIPT_OFFSET_RATIO: f32 = 0.20;
+static FONT_SUPERSCRIPT_OFFSET_RATIO: f32 = 0.34;
+
+// https://drafts.csswg.org/css-images/#default-object-size
+static DEFAULT_REPLACED_WIDTH: i32 = 300;
+static DEFAULT_REPLACED_HEIGHT: i32 = 150;
+
+/// 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 `SpecificFragmentInfo::Generic` 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.
+///
+/// Do not add fields to this structure unless they're really really mega necessary! Fragments get
+/// moved around a lot and thus their size impacts performance of layout quite a bit.
+///
+/// FIXME(#2260, pcwalton): This can be slimmed down some by (at least) moving `inline_context`
+/// to be on `InlineFlow` only.
+#[derive(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: ServoArc<ComputedValues>,
+
+ /// The CSS style of this fragment when it's selected
+ pub selected_style: ServoArc<ComputedValues>,
+
+ /// The position of this fragment relative to its owning flow. The size includes padding and
+ /// border, but not margin.
+ ///
+ /// NB: This does not account for relative positioning.
+ /// NB: Collapsed borders are not included in this.
+ 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,
+
+ /// Holds the style context information for fragments that are part of an inline formatting
+ /// context.
+ pub inline_context: Option<InlineFragmentContext>,
+
+ /// How damaged this fragment is since last reflow.
+ pub restyle_damage: RestyleDamage,
+
+ /// The pseudo-element that this fragment represents.
+ pub pseudo: PseudoElementType,
+
+ /// Various flags for this fragment.
+ pub flags: FragmentFlags,
+
+ /// A debug ID that is consistent for the life of this fragment (via transform etc).
+ /// This ID should not be considered stable across multiple layouts or fragment
+ /// manipulations.
+ debug_id: DebugId,
+
+ /// The ID of the StackingContext that contains this fragment. This is initialized
+ /// to 0, but it assigned during the collect_stacking_contexts phase of display
+ /// list construction.
+ pub stacking_context_id: StackingContextId,
+
+ /// The indices of this Fragment's ClipScrollNode. If this fragment doesn't have a
+ /// `established_reference_frame` assigned, it will use the `clipping_and_scrolling` of the
+ /// parent block.
+ pub established_reference_frame: Option<ClipScrollNodeIndex>,
+}
+
+impl Serialize for Fragment {
+ fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+ let mut serializer = serializer.serialize_struct("fragment", 3)?;
+ serializer.serialize_field("id", &self.debug_id)?;
+ serializer.serialize_field("border_box", &self.border_box)?;
+ serializer.serialize_field("margin", &self.margin)?;
+ serializer.end()
+ }
+}
+
+/// Info specific to the kind of fragment.
+///
+/// Keep this enum small. As in, no more than one word. Or pcwalton will yell at you.
+#[derive(Clone)]
+pub enum SpecificFragmentInfo {
+ Generic,
+
+ /// A piece of generated content that cannot be resolved into `ScannedText` until the generated
+ /// content resolution phase (e.g. an ordered list item marker).
+ GeneratedContent(Box<GeneratedContentInfo>),
+
+ Iframe(IframeFragmentInfo),
+ Image(Box<ImageFragmentInfo>),
+ Media(Box<MediaFragmentInfo>),
+ Canvas(Box<CanvasFragmentInfo>),
+ Svg(Box<SvgFragmentInfo>),
+
+ /// A hypothetical box (see CSS 2.1 § 10.3.7) for an absolutely-positioned block that was
+ /// declared with `display: inline;`.
+ InlineAbsoluteHypothetical(InlineAbsoluteHypotheticalFragmentInfo),
+
+ InlineBlock(InlineBlockFragmentInfo),
+
+ /// An inline fragment that establishes an absolute containing block for its descendants (i.e.
+ /// a positioned inline fragment).
+ InlineAbsolute(InlineAbsoluteFragmentInfo),
+
+ ScannedText(Box<ScannedTextFragmentInfo>),
+ Table,
+ TableCell,
+ TableColumn(TableColumnFragmentInfo),
+ TableRow,
+ TableWrapper,
+ Multicol,
+ MulticolColumn,
+ UnscannedText(Box<UnscannedTextFragmentInfo>),
+
+ /// A container for a fragment that got truncated by text-overflow.
+ /// "Totally truncated fragments" are not rendered at all.
+ /// Text fragments may be partially truncated (in which case this renders like a text fragment).
+ /// Other fragments can only be totally truncated or not truncated at all.
+ TruncatedFragment(Box<TruncatedFragmentInfo>),
+}
+
+impl SpecificFragmentInfo {
+ fn restyle_damage(&self) -> RestyleDamage {
+ let flow = match *self {
+ SpecificFragmentInfo::Canvas(_) |
+ SpecificFragmentInfo::GeneratedContent(_) |
+ SpecificFragmentInfo::Iframe(_) |
+ SpecificFragmentInfo::Image(_) |
+ SpecificFragmentInfo::Media(_) |
+ SpecificFragmentInfo::ScannedText(_) |
+ SpecificFragmentInfo::Svg(_) |
+ SpecificFragmentInfo::Table |
+ SpecificFragmentInfo::TableCell |
+ SpecificFragmentInfo::TableColumn(_) |
+ SpecificFragmentInfo::TableRow |
+ SpecificFragmentInfo::TableWrapper |
+ SpecificFragmentInfo::Multicol |
+ SpecificFragmentInfo::MulticolColumn |
+ SpecificFragmentInfo::UnscannedText(_) |
+ SpecificFragmentInfo::TruncatedFragment(_) |
+ SpecificFragmentInfo::Generic => return RestyleDamage::empty(),
+ SpecificFragmentInfo::InlineAbsoluteHypothetical(ref info) => &info.flow_ref,
+ SpecificFragmentInfo::InlineAbsolute(ref info) => &info.flow_ref,
+ SpecificFragmentInfo::InlineBlock(ref info) => &info.flow_ref,
+ };
+
+ flow.base().restyle_damage
+ }
+
+ pub fn get_type(&self) -> &'static str {
+ match *self {
+ SpecificFragmentInfo::Canvas(_) => "SpecificFragmentInfo::Canvas",
+ SpecificFragmentInfo::Media(_) => "SpecificFragmentInfo::Media",
+ SpecificFragmentInfo::Generic => "SpecificFragmentInfo::Generic",
+ SpecificFragmentInfo::GeneratedContent(_) => "SpecificFragmentInfo::GeneratedContent",
+ SpecificFragmentInfo::Iframe(_) => "SpecificFragmentInfo::Iframe",
+ SpecificFragmentInfo::Image(_) => "SpecificFragmentInfo::Image",
+ SpecificFragmentInfo::InlineAbsolute(_) => "SpecificFragmentInfo::InlineAbsolute",
+ SpecificFragmentInfo::InlineAbsoluteHypothetical(_) => {
+ "SpecificFragmentInfo::InlineAbsoluteHypothetical"
+ },
+ SpecificFragmentInfo::InlineBlock(_) => "SpecificFragmentInfo::InlineBlock",
+ SpecificFragmentInfo::ScannedText(_) => "SpecificFragmentInfo::ScannedText",
+ SpecificFragmentInfo::Svg(_) => "SpecificFragmentInfo::Svg",
+ SpecificFragmentInfo::Table => "SpecificFragmentInfo::Table",
+ SpecificFragmentInfo::TableCell => "SpecificFragmentInfo::TableCell",
+ SpecificFragmentInfo::TableColumn(_) => "SpecificFragmentInfo::TableColumn",
+ SpecificFragmentInfo::TableRow => "SpecificFragmentInfo::TableRow",
+ SpecificFragmentInfo::TableWrapper => "SpecificFragmentInfo::TableWrapper",
+ SpecificFragmentInfo::Multicol => "SpecificFragmentInfo::Multicol",
+ SpecificFragmentInfo::MulticolColumn => "SpecificFragmentInfo::MulticolColumn",
+ SpecificFragmentInfo::UnscannedText(_) => "SpecificFragmentInfo::UnscannedText",
+ SpecificFragmentInfo::TruncatedFragment(_) => "SpecificFragmentInfo::TruncatedFragment",
+ }
+ }
+}
+
+impl fmt::Debug for SpecificFragmentInfo {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ SpecificFragmentInfo::ScannedText(ref info) => write!(f, "{:?}", info.text()),
+ SpecificFragmentInfo::UnscannedText(ref info) => write!(f, "{:?}", info.text),
+ _ => Ok(()),
+ }
+ }
+}
+
+/// Information for generated content.
+#[derive(Clone)]
+pub enum GeneratedContentInfo {
+ ListItem,
+ ContentItem(ContentItem),
+ /// Placeholder for elements with generated content that did not generate any fragments.
+ Empty,
+}
+
+/// A hypothetical box (see CSS 2.1 § 10.3.7) for an absolutely-positioned block that was declared
+/// with `display: inline;`.
+///
+/// FIXME(pcwalton): Stop leaking this `FlowRef` to layout; that is not memory safe because layout
+/// can clone it.
+#[derive(Clone)]
+pub struct InlineAbsoluteHypotheticalFragmentInfo {
+ pub flow_ref: FlowRef,
+}
+
+impl InlineAbsoluteHypotheticalFragmentInfo {
+ pub fn new(flow_ref: FlowRef) -> InlineAbsoluteHypotheticalFragmentInfo {
+ InlineAbsoluteHypotheticalFragmentInfo { flow_ref: flow_ref }
+ }
+}
+
+/// A fragment that represents an inline-block element.
+///
+/// FIXME(pcwalton): Stop leaking this `FlowRef` to layout; that is not memory safe because layout
+/// can clone it.
+#[derive(Clone)]
+pub struct InlineBlockFragmentInfo {
+ pub flow_ref: FlowRef,
+}
+
+impl InlineBlockFragmentInfo {
+ pub fn new(flow_ref: FlowRef) -> InlineBlockFragmentInfo {
+ InlineBlockFragmentInfo { flow_ref: flow_ref }
+ }
+}
+
+/// An inline fragment that establishes an absolute containing block for its descendants (i.e.
+/// a positioned inline fragment).
+///
+/// FIXME(pcwalton): Stop leaking this `FlowRef` to layout; that is not memory safe because layout
+/// can clone it.
+#[derive(Clone)]
+pub struct InlineAbsoluteFragmentInfo {
+ pub flow_ref: FlowRef,
+}
+
+impl InlineAbsoluteFragmentInfo {
+ pub fn new(flow_ref: FlowRef) -> InlineAbsoluteFragmentInfo {
+ InlineAbsoluteFragmentInfo { flow_ref: flow_ref }
+ }
+}
+
+#[derive(Clone)]
+pub enum CanvasFragmentSource {
+ WebGL(webrender_api::ImageKey),
+ Image(Option<Arc<Mutex<IpcSender<CanvasMsg>>>>),
+}
+
+#[derive(Clone)]
+pub struct CanvasFragmentInfo {
+ pub source: CanvasFragmentSource,
+ pub dom_width: Au,
+ pub dom_height: Au,
+ pub canvas_id: CanvasId,
+}
+
+impl CanvasFragmentInfo {
+ pub fn new(data: HTMLCanvasData) -> CanvasFragmentInfo {
+ let source = match data.source {
+ HTMLCanvasDataSource::WebGL(texture_id) => CanvasFragmentSource::WebGL(texture_id),
+ HTMLCanvasDataSource::Image(ipc_sender) => CanvasFragmentSource::Image(
+ ipc_sender.map(|renderer| Arc::new(Mutex::new(renderer))),
+ ),
+ };
+
+ CanvasFragmentInfo {
+ source: source,
+ dom_width: Au::from_px(data.width as i32),
+ dom_height: Au::from_px(data.height as i32),
+ canvas_id: data.canvas_id,
+ }
+ }
+}
+
+#[derive(Clone)]
+pub struct MediaFragmentInfo {
+ pub current_frame: Option<(webrender_api::ImageKey, i32, i32)>,
+}
+
+impl MediaFragmentInfo {
+ pub fn new(data: HTMLMediaData) -> MediaFragmentInfo {
+ MediaFragmentInfo {
+ current_frame: data.current_frame,
+ }
+ }
+}
+
+#[derive(Clone)]
+pub struct SvgFragmentInfo {
+ pub dom_width: Au,
+ pub dom_height: Au,
+}
+
+impl SvgFragmentInfo {
+ pub fn new(data: SVGSVGData) -> SvgFragmentInfo {
+ SvgFragmentInfo {
+ dom_width: Au::from_px(data.width as i32),
+ dom_height: Au::from_px(data.height as i32),
+ }
+ }
+}
+
+/// A fragment that represents a replaced content image and its accompanying borders, shadows, etc.
+#[derive(Clone)]
+pub struct ImageFragmentInfo {
+ pub image: Option<Arc<Image>>,
+ pub metadata: Option<ImageMetadata>,
+}
+
+enum ImageOrMetadata {
+ Image(Arc<Image>),
+ Metadata(ImageMetadata),
+}
+
+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<N: ThreadSafeLayoutNode>(
+ url: Option<ServoUrl>,
+ density: Option<f64>,
+ node: &N,
+ layout_context: &LayoutContext,
+ ) -> ImageFragmentInfo {
+ // First use any image data present in the element...
+ let image_or_metadata = node
+ .image_data()
+ .and_then(|(image, metadata)| match (image, metadata) {
+ (Some(image), _) => Some(ImageOrMetadata::Image(image)),
+ (None, Some(metadata)) => Some(ImageOrMetadata::Metadata(metadata)),
+ _ => None,
+ })
+ .or_else(|| {
+ url.and_then(|url| {
+ // Otherwise query the image cache for anything known about the associated source URL.
+ layout_context
+ .get_or_request_image_or_meta(node.opaque(), url, UsePlaceholder::Yes)
+ .map(|result| match result {
+ ImageOrMetadataAvailable::ImageAvailable(i, _) => {
+ ImageOrMetadata::Image(i)
+ },
+ ImageOrMetadataAvailable::MetadataAvailable(m) => {
+ ImageOrMetadata::Metadata(m)
+ },
+ })
+ })
+ });
+
+ let current_pixel_density = density.unwrap_or(1f64);
+
+ let (image, metadata) = match image_or_metadata {
+ Some(ImageOrMetadata::Image(i)) => {
+ let height = (i.height as f64 / current_pixel_density) as u32;
+ let width = (i.width as f64 / current_pixel_density) as u32;
+ (
+ Some(Arc::new(Image {
+ height: height,
+ width: width,
+ ..(*i).clone()
+ })),
+ Some(ImageMetadata {
+ height: height,
+ width: width,
+ }),
+ )
+ },
+ Some(ImageOrMetadata::Metadata(m)) => (
+ None,
+ Some(ImageMetadata {
+ height: (m.height as f64 / current_pixel_density) as u32,
+ width: (m.width as f64 / current_pixel_density) as u32,
+ }),
+ ),
+ None => (None, None),
+ };
+
+ ImageFragmentInfo {
+ image: image,
+ metadata: metadata,
+ }
+ }
+}
+
+/// A fragment that represents an inline frame (iframe). This stores the frame ID so that the
+/// size of this iframe can be communicated via the constellation to the iframe's own layout thread.
+#[derive(Clone)]
+pub struct IframeFragmentInfo {
+ /// The frame ID of this iframe. None if there is no nested browsing context.
+ pub browsing_context_id: Option<BrowsingContextId>,
+ /// The pipelineID of this iframe. None if there is no nested browsing context.
+ pub pipeline_id: Option<PipelineId>,
+}
+
+impl IframeFragmentInfo {
+ /// Creates the information specific to an iframe fragment.
+ pub fn new<N: ThreadSafeLayoutNode>(node: &N) -> IframeFragmentInfo {
+ let browsing_context_id = node.iframe_browsing_context_id();
+ let pipeline_id = node.iframe_pipeline_id();
+ IframeFragmentInfo {
+ browsing_context_id: browsing_context_id,
+ pipeline_id: pipeline_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.
+#[derive(Clone)]
+pub struct ScannedTextFragmentInfo {
+ /// The text run that this represents.
+ pub run: Arc<TextRun>,
+
+ /// The intrinsic size of the text fragment.
+ pub content_size: LogicalSize<Au>,
+
+ /// The byte offset of the insertion point, if any.
+ pub insertion_point: Option<ByteIndex>,
+
+ /// The range within the above text run that this represents.
+ pub range: Range<ByteIndex>,
+
+ /// The endpoint of the above range, including whitespace that was stripped out. This exists
+ /// so that we can restore the range to its original value (before line breaking occurred) when
+ /// performing incremental reflow.
+ pub range_end_including_stripped_whitespace: ByteIndex,
+
+ pub flags: ScannedTextFlags,
+}
+
+bitflags! {
+ pub struct ScannedTextFlags: u8 {
+ /// Whether a line break is required after this fragment if wrapping on newlines (e.g. if
+ /// `white-space: pre` is in effect).
+ const REQUIRES_LINE_BREAK_AFTERWARD_IF_WRAPPING_ON_NEWLINES = 0x01;
+
+ /// Is this fragment selected?
+ const SELECTED = 0x02;
+
+ /// Suppress line breaking between this and the previous fragment
+ ///
+ /// This handles cases like Foo<span>bar</span>
+ const SUPPRESS_LINE_BREAK_BEFORE = 0x04;
+ }
+}
+
+impl ScannedTextFragmentInfo {
+ /// Creates the information specific to a scanned text fragment from a range and a text run.
+ pub fn new(
+ run: Arc<TextRun>,
+ range: Range<ByteIndex>,
+ content_size: LogicalSize<Au>,
+ insertion_point: Option<ByteIndex>,
+ flags: ScannedTextFlags,
+ ) -> ScannedTextFragmentInfo {
+ ScannedTextFragmentInfo {
+ run: run,
+ range: range,
+ insertion_point: insertion_point,
+ content_size: content_size,
+ range_end_including_stripped_whitespace: range.end(),
+ flags: flags,
+ }
+ }
+
+ pub fn text(&self) -> &str {
+ &self.run.text[self.range.begin().to_usize()..self.range.end().to_usize()]
+ }
+
+ pub fn requires_line_break_afterward_if_wrapping_on_newlines(&self) -> bool {
+ self.flags
+ .contains(ScannedTextFlags::REQUIRES_LINE_BREAK_AFTERWARD_IF_WRAPPING_ON_NEWLINES)
+ }
+
+ pub fn selected(&self) -> bool {
+ self.flags.contains(ScannedTextFlags::SELECTED)
+ }
+}
+
+/// Describes how to split a fragment. This is used during line breaking as part of the return
+/// value of `find_split_info_for_inline_size()`.
+#[derive(Clone, Debug)]
+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<ByteIndex>,
+ pub inline_size: Au,
+}
+
+impl SplitInfo {
+ fn new(range: Range<ByteIndex>, info: &ScannedTextFragmentInfo) -> SplitInfo {
+ let inline_size = info.run.advance_for_range(&range);
+ SplitInfo {
+ range: range,
+ inline_size: inline_size,
+ }
+ }
+}
+
+/// Describes how to split a fragment into two. This contains up to two `SplitInfo`s.
+pub struct SplitResult {
+ /// The part of the fragment that goes on the first line.
+ pub inline_start: Option<SplitInfo>,
+ /// The part of the fragment that goes on the second line.
+ pub inline_end: Option<SplitInfo>,
+ /// The text run which is being split.
+ pub text_run: Arc<TextRun>,
+}
+
+/// Describes how a fragment should be truncated.
+struct TruncationResult {
+ /// The part of the fragment remaining after truncation.
+ split: SplitInfo,
+ /// The text run which is being truncated.
+ text_run: Arc<TextRun>,
+}
+
+/// Data for an unscanned text fragment. Unscanned text fragments are the results of flow
+/// construction that have not yet had their inline-size determined.
+#[derive(Clone)]
+pub struct UnscannedTextFragmentInfo {
+ /// The text inside the fragment.
+ pub text: Box<str>,
+
+ /// The selected text range. An empty range represents the insertion point.
+ pub selection: Option<Range<ByteIndex>>,
+}
+
+impl UnscannedTextFragmentInfo {
+ /// Creates a new instance of `UnscannedTextFragmentInfo` from the given text.
+ #[inline]
+ pub fn new(text: Box<str>, selection: Option<Range<ByteIndex>>) -> UnscannedTextFragmentInfo {
+ UnscannedTextFragmentInfo {
+ text: text,
+ selection: selection,
+ }
+ }
+}
+
+/// A fragment that represents a table column.
+#[derive(Clone, Copy)]
+pub struct TableColumnFragmentInfo {
+ /// the number of columns a <col> element should span
+ pub span: u32,
+}
+
+impl TableColumnFragmentInfo {
+ /// Create the information specific to an table column fragment.
+ pub fn new<N: ThreadSafeLayoutNode>(node: &N) -> TableColumnFragmentInfo {
+ let element = node.as_element().unwrap();
+ let span = element
+ .get_attr(&ns!(), &local_name!("span"))
+ .and_then(|string| string.parse().ok())
+ .unwrap_or(0);
+ TableColumnFragmentInfo { span: span }
+ }
+}
+
+/// A wrapper for fragments that have been truncated by the `text-overflow` property.
+/// This may have an associated text node, or, if the fragment was completely truncated,
+/// it may act as an invisible marker for incremental reflow.
+#[derive(Clone)]
+pub struct TruncatedFragmentInfo {
+ pub text_info: Option<ScannedTextFragmentInfo>,
+ pub full: Fragment,
+}
+
+impl Fragment {
+ /// Constructs a new `Fragment` instance.
+ pub fn new<N: ThreadSafeLayoutNode>(
+ node: &N,
+ specific: SpecificFragmentInfo,
+ ctx: &LayoutContext,
+ ) -> Fragment {
+ let shared_context = ctx.shared_context();
+ let style = node.style(shared_context);
+ let writing_mode = style.writing_mode;
+
+ let mut restyle_damage = node.restyle_damage();
+ restyle_damage.remove(ServoRestyleDamage::RECONSTRUCT_FLOW);
+
+ Fragment {
+ node: node.opaque(),
+ style: style,
+ selected_style: node.selected_style(),
+ restyle_damage: restyle_damage,
+ border_box: LogicalRect::zero(writing_mode),
+ border_padding: LogicalMargin::zero(writing_mode),
+ margin: LogicalMargin::zero(writing_mode),
+ specific: specific,
+ inline_context: None,
+ pseudo: node.get_pseudo_element_type(),
+ flags: FragmentFlags::empty(),
+ debug_id: DebugId::new(),
+ stacking_context_id: StackingContextId::root(),
+ established_reference_frame: None,
+ }
+ }
+
+ /// Constructs a new `Fragment` instance from an opaque node.
+ pub fn from_opaque_node_and_style(
+ node: OpaqueNode,
+ pseudo: PseudoElementType,
+ style: ServoArc<ComputedValues>,
+ selected_style: ServoArc<ComputedValues>,
+ mut restyle_damage: RestyleDamage,
+ specific: SpecificFragmentInfo,
+ ) -> Fragment {
+ let writing_mode = style.writing_mode;
+
+ restyle_damage.remove(ServoRestyleDamage::RECONSTRUCT_FLOW);
+
+ Fragment {
+ node: node,
+ style: style,
+ selected_style: selected_style,
+ restyle_damage: restyle_damage,
+ border_box: LogicalRect::zero(writing_mode),
+ border_padding: LogicalMargin::zero(writing_mode),
+ margin: LogicalMargin::zero(writing_mode),
+ specific: specific,
+ inline_context: None,
+ pseudo: pseudo,
+ flags: FragmentFlags::empty(),
+ debug_id: DebugId::new(),
+ stacking_context_id: StackingContextId::root(),
+ established_reference_frame: None,
+ }
+ }
+
+ /// Creates an anonymous fragment just like this one but with the given style and fragment
+ /// type. For the new anonymous fragment, layout-related values (border box, etc.) are reset to
+ /// initial values.
+ pub fn create_similar_anonymous_fragment(
+ &self,
+ style: ServoArc<ComputedValues>,
+ specific: SpecificFragmentInfo,
+ ) -> Fragment {
+ let writing_mode = style.writing_mode;
+ Fragment {
+ node: self.node,
+ style: style,
+ selected_style: self.selected_style.clone(),
+ restyle_damage: self.restyle_damage,
+ border_box: LogicalRect::zero(writing_mode),
+ border_padding: LogicalMargin::zero(writing_mode),
+ margin: LogicalMargin::zero(writing_mode),
+ specific: specific,
+ inline_context: None,
+ pseudo: self.pseudo,
+ flags: FragmentFlags::empty(),
+ debug_id: DebugId::new(),
+ stacking_context_id: StackingContextId::root(),
+ established_reference_frame: None,
+ }
+ }
+
+ /// 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>, info: SpecificFragmentInfo) -> Fragment {
+ let new_border_box =
+ LogicalRect::from_point_size(self.style.writing_mode, self.border_box.start, size);
+
+ let mut restyle_damage = RestyleDamage::rebuild_and_reflow();
+ restyle_damage.remove(ServoRestyleDamage::RECONSTRUCT_FLOW);
+
+ Fragment {
+ node: self.node,
+ style: self.style.clone(),
+ selected_style: self.selected_style.clone(),
+ restyle_damage: restyle_damage,
+ border_box: new_border_box,
+ border_padding: self.border_padding,
+ margin: self.margin,
+ specific: info,
+ inline_context: self.inline_context.clone(),
+ pseudo: self.pseudo.clone(),
+ flags: FragmentFlags::empty(),
+ debug_id: self.debug_id.clone(),
+ stacking_context_id: StackingContextId::root(),
+ established_reference_frame: None,
+ }
+ }
+
+ /// Transforms this fragment using the given `SplitInfo`, preserving all the other data.
+ ///
+ /// If this is the first half of a split, `first` is true
+ pub fn transform_with_split_info(
+ &self,
+ split: &SplitInfo,
+ text_run: Arc<TextRun>,
+ first: bool,
+ ) -> Fragment {
+ let size = LogicalSize::new(
+ self.style.writing_mode,
+ split.inline_size,
+ self.border_box.size.block,
+ );
+ // Preserve the insertion point if it is in this fragment's range or it is at line end.
+ let (mut flags, insertion_point) = match self.specific {
+ SpecificFragmentInfo::ScannedText(ref info) => match info.insertion_point {
+ Some(index) if split.range.contains(index) => (info.flags, info.insertion_point),
+ Some(index)
+ if index == ByteIndex(text_run.text.chars().count() as isize - 1) &&
+ index == split.range.end() =>
+ {
+ (info.flags, info.insertion_point)
+ },
+ _ => (info.flags, None),
+ },
+ _ => (ScannedTextFlags::empty(), None),
+ };
+
+ if !first {
+ flags.set(ScannedTextFlags::SUPPRESS_LINE_BREAK_BEFORE, false);
+ }
+
+ let info = Box::new(ScannedTextFragmentInfo::new(
+ text_run,
+ split.range,
+ size,
+ insertion_point,
+ flags,
+ ));
+ self.transform(size, SpecificFragmentInfo::ScannedText(info))
+ }
+
+ /// Transforms this fragment into an ellipsis fragment, preserving all the other data.
+ pub fn transform_into_ellipsis(
+ &self,
+ layout_context: &LayoutContext,
+ text_overflow_string: String,
+ ) -> Fragment {
+ let mut unscanned_ellipsis_fragments = LinkedList::new();
+ let mut ellipsis_fragment = self.transform(
+ self.border_box.size,
+ SpecificFragmentInfo::UnscannedText(Box::new(UnscannedTextFragmentInfo::new(
+ text_overflow_string.into_boxed_str(),
+ None,
+ ))),
+ );
+ unscanned_ellipsis_fragments.push_back(ellipsis_fragment);
+ let ellipsis_fragments = with_thread_local_font_context(layout_context, |font_context| {
+ TextRunScanner::new().scan_for_runs(font_context, unscanned_ellipsis_fragments)
+ });
+ debug_assert_eq!(ellipsis_fragments.len(), 1);
+ ellipsis_fragment = ellipsis_fragments.fragments.into_iter().next().unwrap();
+ ellipsis_fragment.flags |= FragmentFlags::IS_ELLIPSIS;
+ ellipsis_fragment
+ }
+
+ pub fn restyle_damage(&self) -> RestyleDamage {
+ self.restyle_damage | self.specific.restyle_damage()
+ }
+
+ pub fn contains_node(&self, node_address: OpaqueNode) -> bool {
+ node_address == self.node ||
+ self.inline_context
+ .as_ref()
+ .map_or(false, |ctx| ctx.contains_node(node_address))
+ }
+
+ /// 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, node_info: InlineFragmentNodeInfo) {
+ if self.inline_context.is_none() {
+ self.inline_context = Some(InlineFragmentContext::new());
+ }
+ self.inline_context.as_mut().unwrap().nodes.push(node_info);
+ }
+
+ /// Determines which quantities (border/padding/margin/specified) should be included in the
+ /// intrinsic inline size of this fragment.
+ fn quantities_included_in_intrinsic_inline_size(
+ &self,
+ ) -> QuantitiesIncludedInIntrinsicInlineSizes {
+ match self.specific {
+ SpecificFragmentInfo::Canvas(_) |
+ SpecificFragmentInfo::Media(_) |
+ SpecificFragmentInfo::Generic |
+ SpecificFragmentInfo::GeneratedContent(_) |
+ SpecificFragmentInfo::Iframe(_) |
+ SpecificFragmentInfo::Image(_) |
+ SpecificFragmentInfo::InlineAbsolute(_) |
+ SpecificFragmentInfo::Multicol |
+ SpecificFragmentInfo::Svg(_) => {
+ QuantitiesIncludedInIntrinsicInlineSizes::all()
+ }
+ SpecificFragmentInfo::Table => {
+ QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_SPECIFIED |
+ QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_PADDING |
+ QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_BORDER
+ }
+ SpecificFragmentInfo::TableCell => {
+ let base_quantities = QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_PADDING |
+ QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_SPECIFIED;
+ if self.style.get_inherited_table().border_collapse ==
+ BorderCollapse::Separate {
+ base_quantities | QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_BORDER
+ } else {
+ base_quantities
+ }
+ }
+ SpecificFragmentInfo::TableWrapper => {
+ let base_quantities = QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_MARGINS |
+ QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_SPECIFIED;
+ if self.style.get_inherited_table().border_collapse ==
+ BorderCollapse::Separate {
+ base_quantities | QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_BORDER
+ } else {
+ base_quantities
+ }
+ }
+ SpecificFragmentInfo::TableRow => {
+ let base_quantities =
+ QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_SPECIFIED;
+ if self.style.get_inherited_table().border_collapse ==
+ BorderCollapse::Separate {
+ base_quantities | QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_BORDER
+ } else {
+ base_quantities
+ }
+ }
+ SpecificFragmentInfo::TruncatedFragment(_) |
+ SpecificFragmentInfo::ScannedText(_) |
+ SpecificFragmentInfo::TableColumn(_) |
+ SpecificFragmentInfo::UnscannedText(_) |
+ SpecificFragmentInfo::InlineAbsoluteHypothetical(_) |
+ SpecificFragmentInfo::InlineBlock(_) |
+ SpecificFragmentInfo::MulticolColumn => {
+ QuantitiesIncludedInIntrinsicInlineSizes::empty()
+ }
+ }
+ }
+
+ /// Returns the portion of the intrinsic inline-size that consists of borders/padding and
+ /// margins, respectively.
+ ///
+ /// FIXME(#2261, pcwalton): This won't work well for inlines: is this OK?
+ pub fn surrounding_intrinsic_inline_size(&self) -> (Au, Au) {
+ let flags = self.quantities_included_in_intrinsic_inline_size();
+ let style = self.style();
+
+ // FIXME(pcwalton): Percentages should be relative to any definite size per CSS-SIZING.
+ // This will likely need to be done by pushing down definite sizes during selector
+ // cascading.
+ let margin = if flags.contains(
+ QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_MARGINS,
+ ) {
+ let margin = style.logical_margin();
+ (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)
+ };
+
+ // FIXME(pcwalton): Percentages should be relative to any definite size per CSS-SIZING.
+ // This will likely need to be done by pushing down definite sizes during selector
+ // cascading.
+ let padding = if flags.contains(
+ QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_PADDING,
+ ) {
+ let padding = style.logical_padding();
+ (padding.inline_start.to_used_value(Au(0)) + padding.inline_end.to_used_value(Au(0)))
+ } else {
+ Au(0)
+ };
+
+ let border = if flags.contains(
+ QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_BORDER,
+ ) {
+ self.border_width().inline_start_end()
+ } else {
+ Au(0)
+ };
+
+ (border + padding, margin)
+ }
+
+ /// Uses the style only to estimate the intrinsic inline-sizes. These may be modified for text
+ /// or replaced elements.
+ pub fn style_specified_intrinsic_inline_size(&self) -> IntrinsicISizesContribution {
+ let flags = self.quantities_included_in_intrinsic_inline_size();
+ let style = self.style();
+
+ // FIXME(#2261, pcwalton): This won't work well for inlines: is this OK?
+ let (border_padding, margin) = self.surrounding_intrinsic_inline_size();
+
+ let mut specified = Au(0);
+ if flags.contains(
+ QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_SPECIFIED,
+ ) {
+ specified = style
+ .content_inline_size()
+ .to_used_value(Au(0))
+ .unwrap_or(Au(0));
+ specified = max(
+ style
+ .min_inline_size()
+ .to_used_value(Au(0))
+ .unwrap_or(Au(0)),
+ specified,
+ );
+ if let Some(max) = style.max_inline_size().to_used_value(Au(0)) {
+ specified = min(specified, max)
+ }
+
+ if self.style.get_position().box_sizing == BoxSizing::BorderBox {
+ specified = max(Au(0), specified - border_padding);
+ }
+ }
+
+ IntrinsicISizesContribution {
+ content_intrinsic_sizes: IntrinsicISizes {
+ minimum_inline_size: specified,
+ preferred_inline_size: specified,
+ },
+ surrounding_size: border_padding + margin,
+ }
+ }
+
+ /// intrinsic width of this replaced element.
+ #[inline]
+ pub fn intrinsic_width(&self) -> Au {
+ match self.specific {
+ SpecificFragmentInfo::Image(ref info) => {
+ if let Some(ref data) = info.metadata {
+ Au::from_px(data.width as i32)
+ } else {
+ Au(0)
+ }
+ },
+ SpecificFragmentInfo::Media(ref info) => {
+ if let Some((_, width, _)) = info.current_frame {
+ Au::from_px(width as i32)
+ } else {
+ Au(0)
+ }
+ },
+ SpecificFragmentInfo::Canvas(ref info) => info.dom_width,
+ SpecificFragmentInfo::Svg(ref info) => info.dom_width,
+ // Note: Currently for replaced element with no intrinsic size,
+ // this function simply returns the default object size. As long as
+ // these elements do not have intrinsic aspect ratio this should be
+ // sufficient, but we may need to investigate if this is enough for
+ // use cases like SVG.
+ SpecificFragmentInfo::Iframe(_) => Au::from_px(DEFAULT_REPLACED_WIDTH),
+ _ => panic!("Trying to get intrinsic width on non-replaced element!"),
+ }
+ }
+
+ /// intrinsic width of this replaced element.
+ #[inline]
+ pub fn intrinsic_height(&self) -> Au {
+ match self.specific {
+ SpecificFragmentInfo::Image(ref info) => {
+ if let Some(ref data) = info.metadata {
+ Au::from_px(data.height as i32)
+ } else {
+ Au(0)
+ }
+ },
+ SpecificFragmentInfo::Media(ref info) => {
+ if let Some((_, _, height)) = info.current_frame {
+ Au::from_px(height as i32)
+ } else {
+ Au(0)
+ }
+ },
+ SpecificFragmentInfo::Canvas(ref info) => info.dom_height,
+ SpecificFragmentInfo::Svg(ref info) => info.dom_height,
+ SpecificFragmentInfo::Iframe(_) => Au::from_px(DEFAULT_REPLACED_HEIGHT),
+ _ => panic!("Trying to get intrinsic height on non-replaced element!"),
+ }
+ }
+
+ /// Whether this replace element has intrinsic aspect ratio.
+ pub fn has_intrinsic_ratio(&self) -> bool {
+ match self.specific {
+ SpecificFragmentInfo::Image(_) |
+ SpecificFragmentInfo::Canvas(_) |
+ SpecificFragmentInfo::Media(_) |
+ // TODO(stshine): According to the SVG spec, whether a SVG element has intrinsic
+ // aspect ratio is determined by the `preserveAspectRatio` attribute. Since for
+ // now SVG is far from implemented, we simply choose the default behavior that
+ // the intrinsic aspect ratio is preserved.
+ // https://svgwg.org/svg2-draft/coords.html#PreserveAspectRatioAttribute
+ SpecificFragmentInfo::Svg(_) =>
+ self.intrinsic_width() != Au(0) && self.intrinsic_height() != Au(0),
+ _ => false
+ }
+ }
+
+ /// CSS 2.1 § 10.3.2 & 10.6.2 Calculate the used width and height of a replaced element.
+ /// When a parameter is `None` it means the specified size in certain direction
+ /// is unconstrained. The inline containing size can also be `None` since this
+ /// method is also used for calculating intrinsic inline size contribution.
+ pub fn calculate_replaced_sizes(
+ &self,
+ containing_inline_size: Option<Au>,
+ containing_block_size: Option<Au>,
+ ) -> (Au, Au) {
+ let (intrinsic_inline_size, intrinsic_block_size) = if self.style.writing_mode.is_vertical()
+ {
+ (self.intrinsic_height(), self.intrinsic_width())
+ } else {
+ (self.intrinsic_width(), self.intrinsic_height())
+ };
+
+ // Make sure the size we used here is for content box since they may be
+ // transferred by the intrinsic aspect ratio.
+ let inline_size = style_length(self.style.content_inline_size(), containing_inline_size)
+ .map(|x| x - self.box_sizing_boundary(Direction::Inline));
+ let block_size = style_length(self.style.content_block_size(), containing_block_size)
+ .map(|x| x - self.box_sizing_boundary(Direction::Block));
+ let inline_constraint = self.size_constraint(containing_inline_size, Direction::Inline);
+ let block_constraint = self.size_constraint(containing_block_size, Direction::Block);
+
+ // https://drafts.csswg.org/css-images-3/#default-sizing
+ match (inline_size, block_size) {
+ // If the specified size is a definite width and height, the concrete
+ // object size is given that width and height.
+ (MaybeAuto::Specified(inline_size), MaybeAuto::Specified(block_size)) => (
+ inline_constraint.clamp(inline_size),
+ block_constraint.clamp(block_size),
+ ),
+
+ // If the specified size is only a width or height (but not both)
+ // then the concrete object size is given that specified width or
+ // height. The other dimension is calculated as follows:
+ //
+ // If the object has an intrinsic aspect ratio, the missing dimension
+ // of the concrete object size is calculated using the intrinsic
+ // aspect ratio and the present dimension.
+ //
+ // Otherwise, if the missing dimension is present in the object’s intrinsic
+ // dimensions, the missing dimension is taken from the object’s intrinsic
+ // dimensions. Otherwise it is taken from the default object size.
+ (MaybeAuto::Specified(inline_size), MaybeAuto::Auto) => {
+ let inline_size = inline_constraint.clamp(inline_size);
+ let block_size = if self.has_intrinsic_ratio() {
+ // Note: We can not precompute the ratio and store it as a float, because
+ // doing so may result one pixel difference in calculation for certain
+ // images, thus make some tests fail.
+ Au::new(
+ (inline_size.0 as i64 * intrinsic_block_size.0 as i64 /
+ intrinsic_inline_size.0 as i64) as i32,
+ )
+ } else {
+ intrinsic_block_size
+ };
+ (inline_size, block_constraint.clamp(block_size))
+ },
+ (MaybeAuto::Auto, MaybeAuto::Specified(block_size)) => {
+ let block_size = block_constraint.clamp(block_size);
+ let inline_size = if self.has_intrinsic_ratio() {
+ Au::new(
+ (block_size.0 as i64 * intrinsic_inline_size.0 as i64 /
+ intrinsic_block_size.0 as i64) as i32,
+ )
+ } else {
+ intrinsic_inline_size
+ };
+ (inline_constraint.clamp(inline_size), block_size)
+ },
+ // https://drafts.csswg.org/css2/visudet.html#min-max-widths
+ (MaybeAuto::Auto, MaybeAuto::Auto) => {
+ if self.has_intrinsic_ratio() {
+ // This approch follows the spirit of cover and contain constraint.
+ // https://drafts.csswg.org/css-images-3/#cover-contain
+
+ // First, create two rectangles that keep aspect ratio while may be clamped
+ // by the contraints;
+ let first_isize = inline_constraint.clamp(intrinsic_inline_size);
+ let first_bsize = Au::new(
+ (first_isize.0 as i64 * intrinsic_block_size.0 as i64 /
+ intrinsic_inline_size.0 as i64) as i32,
+ );
+ let second_bsize = block_constraint.clamp(intrinsic_block_size);
+ let second_isize = Au::new(
+ (second_bsize.0 as i64 * intrinsic_inline_size.0 as i64 /
+ intrinsic_block_size.0 as i64) as i32,
+ );
+ let (inline_size, block_size) = match (
+ first_isize.cmp(&intrinsic_inline_size),
+ second_isize.cmp(&intrinsic_inline_size),
+ ) {
+ (Ordering::Equal, Ordering::Equal) => (first_isize, first_bsize),
+ // When only one rectangle is clamped, use it;
+ (Ordering::Equal, _) => (second_isize, second_bsize),
+ (_, Ordering::Equal) => (first_isize, first_bsize),
+ // When both rectangles grow (smaller than min sizes),
+ // Choose the larger one;
+ (Ordering::Greater, Ordering::Greater) => {
+ if first_isize > second_isize {
+ (first_isize, first_bsize)
+ } else {
+ (second_isize, second_bsize)
+ }
+ },
+ // When both rectangles shrink (larger than max sizes),
+ // Choose the smaller one;
+ (Ordering::Less, Ordering::Less) => {
+ if first_isize > second_isize {
+ (second_isize, second_bsize)
+ } else {
+ (first_isize, first_bsize)
+ }
+ },
+ // It does not matter which we choose here, because both sizes
+ // will be clamped to constraint;
+ (Ordering::Less, Ordering::Greater) |
+ (Ordering::Greater, Ordering::Less) => (first_isize, first_bsize),
+ };
+ // Clamp the result and we are done :-)
+ (
+ inline_constraint.clamp(inline_size),
+ block_constraint.clamp(block_size),
+ )
+ } else {
+ (
+ inline_constraint.clamp(intrinsic_inline_size),
+ block_constraint.clamp(intrinsic_block_size),
+ )
+ }
+ },
+ }
+ }
+
+ /// Return a size constraint that can be used the clamp size in given direction.
+ /// To take `box-sizing: border-box` into account, the `border_padding` field
+ /// must be initialized first.
+ ///
+ /// TODO(stshine): Maybe there is a more convenient way.
+ pub fn size_constraint(
+ &self,
+ containing_size: Option<Au>,
+ direction: Direction,
+ ) -> SizeConstraint {
+ let (style_min_size, style_max_size) = match direction {
+ Direction::Inline => (self.style.min_inline_size(), self.style.max_inline_size()),
+ Direction::Block => (self.style.min_block_size(), self.style.max_block_size()),
+ };
+
+ let border = if self.style().get_position().box_sizing == BoxSizing::BorderBox {
+ Some(self.border_padding.start_end(direction))
+ } else {
+ None
+ };
+
+ SizeConstraint::new(containing_size, style_min_size, style_max_size, border)
+ }
+
+ /// Returns a guess as to the distances from the margin edge of this fragment to its content
+ /// in the inline direction. This will generally be correct unless percentages are involved.
+ ///
+ /// This is used for the float placement speculation logic.
+ pub fn guess_inline_content_edge_offsets(&self) -> SpeculatedInlineContentEdgeOffsets {
+ let logical_margin = self.style.logical_margin();
+ let logical_padding = self.style.logical_padding();
+ let border_width = self.border_width();
+ SpeculatedInlineContentEdgeOffsets {
+ start: MaybeAuto::from_style(logical_margin.inline_start, Au(0)).specified_or_zero() +
+ logical_padding.inline_start.to_used_value(Au(0)) +
+ border_width.inline_start,
+ end: MaybeAuto::from_style(logical_margin.inline_end, Au(0)).specified_or_zero() +
+ logical_padding.inline_end.to_used_value(Au(0)) +
+ border_width.inline_end,
+ }
+ }
+
+ /// Returns the sum of the inline-sizes of all the borders of this fragment. Note that this
+ /// can be expensive to compute, so if possible use the `border_padding` field instead.
+ #[inline]
+ pub fn border_width(&self) -> LogicalMargin<Au> {
+ let style_border_width = self.style().logical_border_width();
+
+ // NOTE: We can have nodes with different writing mode inside
+ // the inline fragment context, so we need to overwrite the
+ // writing mode to compute the child logical sizes.
+ let writing_mode = self.style.writing_mode;
+ let context_border = match self.inline_context {
+ None => LogicalMargin::zero(writing_mode),
+ Some(ref inline_fragment_context) => inline_fragment_context.nodes.iter().fold(
+ style_border_width,
+ |accumulator, node| {
+ let mut this_border_width =
+ node.style.border_width_for_writing_mode(writing_mode);
+ if !node
+ .flags
+ .contains(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT)
+ {
+ this_border_width.inline_start = Au(0)
+ }
+ if !node
+ .flags
+ .contains(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT)
+ {
+ this_border_width.inline_end = Au(0)
+ }
+ accumulator + this_border_width
+ },
+ ),
+ };
+ style_border_width + context_border
+ }
+
+ /// Returns the border width in given direction if this fragment has property
+ /// 'box-sizing: border-box'. The `border_padding` field must have been initialized.
+ pub fn box_sizing_boundary(&self, direction: Direction) -> Au {
+ match (self.style().get_position().box_sizing, direction) {
+ (BoxSizing::BorderBox, Direction::Inline) => self.border_padding.inline_start_end(),
+ (BoxSizing::BorderBox, Direction::Block) => self.border_padding.block_start_end(),
+ _ => Au(0),
+ }
+ }
+
+ /// Computes the margins in the inline direction from the containing block inline-size and the
+ /// style. After this call, the inline direction of the `margin` field will be correct.
+ ///
+ /// Do not use this method if the inline direction margins are to be computed some other way
+ /// (for example, via constraint solving for blocks).
+ pub fn compute_inline_direction_margins(&mut self, containing_block_inline_size: Au) {
+ match self.specific {
+ SpecificFragmentInfo::Table |
+ SpecificFragmentInfo::TableCell |
+ SpecificFragmentInfo::TableRow |
+ SpecificFragmentInfo::TableColumn(_) |
+ SpecificFragmentInfo::InlineAbsoluteHypothetical(_) => {
+ self.margin.inline_start = Au(0);
+ self.margin.inline_end = Au(0);
+ return;
+ },
+ _ => {
+ let margin = self.style().logical_margin();
+ self.margin.inline_start =
+ MaybeAuto::from_style(margin.inline_start, containing_block_inline_size)
+ .specified_or_zero();
+ self.margin.inline_end =
+ MaybeAuto::from_style(margin.inline_end, containing_block_inline_size)
+ .specified_or_zero();
+ },
+ }
+
+ if let Some(ref inline_context) = self.inline_context {
+ for node in &inline_context.nodes {
+ let margin = node.style.logical_margin();
+ let this_inline_start_margin = if !node
+ .flags
+ .contains(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT)
+ {
+ Au(0)
+ } else {
+ MaybeAuto::from_style(margin.inline_start, containing_block_inline_size)
+ .specified_or_zero()
+ };
+ let this_inline_end_margin = if !node
+ .flags
+ .contains(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT)
+ {
+ Au(0)
+ } else {
+ MaybeAuto::from_style(margin.inline_end, containing_block_inline_size)
+ .specified_or_zero()
+ };
+
+ self.margin.inline_start += this_inline_start_margin;
+ self.margin.inline_end += this_inline_end_margin;
+ }
+ }
+ }
+
+ /// Computes the margins in the block direction from the containing block inline-size and the
+ /// style. After this call, the block direction of the `margin` field will be correct.
+ ///
+ /// Do not use this method if the block direction margins are to be computed some other way
+ /// (for example, via constraint solving for absolutely-positioned flows).
+ pub fn compute_block_direction_margins(&mut self, containing_block_inline_size: Au) {
+ match self.specific {
+ SpecificFragmentInfo::Table |
+ SpecificFragmentInfo::TableCell |
+ SpecificFragmentInfo::TableRow |
+ SpecificFragmentInfo::TableColumn(_) => {
+ 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();
+ },
+ }
+ }
+
+ /// Computes the border and padding in both inline and block directions from the containing
+ /// block inline-size and the style. After this call, the `border_padding` field will be
+ /// correct.
+ pub fn compute_border_and_padding(&mut self, containing_block_inline_size: Au) {
+ // Compute border.
+ let border = match self.style.get_inherited_table().border_collapse {
+ BorderCollapse::Separate => self.border_width(),
+ BorderCollapse::Collapse => LogicalMargin::zero(self.style.writing_mode),
+ };
+
+ // Compute padding from the fragment's style.
+ let padding_from_style = match self.specific {
+ SpecificFragmentInfo::TableColumn(_) |
+ SpecificFragmentInfo::TableRow |
+ SpecificFragmentInfo::TableWrapper => LogicalMargin::zero(self.style.writing_mode),
+ _ => model::padding_from_style(
+ self.style(),
+ containing_block_inline_size,
+ self.style().writing_mode,
+ ),
+ };
+
+ // Compute padding from the inline fragment context.
+ let padding_from_inline_fragment_context = match (&self.specific, &self.inline_context) {
+ (_, &None) |
+ (&SpecificFragmentInfo::TableColumn(_), _) |
+ (&SpecificFragmentInfo::TableRow, _) |
+ (&SpecificFragmentInfo::TableWrapper, _) => {
+ LogicalMargin::zero(self.style.writing_mode)
+ },
+ (_, &Some(ref inline_fragment_context)) => {
+ let writing_mode = self.style.writing_mode;
+ let zero_padding = LogicalMargin::zero(writing_mode);
+ inline_fragment_context
+ .nodes
+ .iter()
+ .fold(zero_padding, |accumulator, node| {
+ let mut padding =
+ model::padding_from_style(&*node.style, Au(0), writing_mode);
+ if !node
+ .flags
+ .contains(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT)
+ {
+ padding.inline_start = Au(0)
+ }
+ if !node
+ .flags
+ .contains(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT)
+ {
+ padding.inline_end = Au(0)
+ }
+ accumulator + padding
+ })
+ },
+ };
+
+ self.border_padding = border + padding_from_style + padding_from_inline_fragment_context
+ }
+
+ // 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 != LengthPercentageOrAuto::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 != LengthPercentageOrAuto::Auto {
+ MaybeAuto::from_style(offsets.block_start, container_size.block).specified_or_zero()
+ } else {
+ -MaybeAuto::from_style(offsets.block_end, container_size.block).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 = if self.style().get_box().position == Position::Relative {
+ from_style(self.style(), containing_block_size)
+ } else {
+ LogicalSize::zero(self.style.writing_mode)
+ };
+
+ if let Some(ref inline_fragment_context) = self.inline_context {
+ for node in &inline_fragment_context.nodes {
+ if node.style.get_box().position == Position::Relative {
+ rel_pos = rel_pos + from_style(&*node.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(ClearType::Left),
+ Clear::Right => Some(ClearType::Right),
+ Clear::Both => Some(ClearType::Both),
+ }
+ }
+
+ #[inline(always)]
+ pub fn style(&self) -> &ComputedValues {
+ &*self.style
+ }
+
+ #[inline(always)]
+ pub fn selected_style(&self) -> &ComputedValues {
+ &*self.selected_style
+ }
+
+ pub fn white_space(&self) -> WhiteSpace {
+ self.style().get_inherited_text().white_space
+ }
+
+ pub fn color(&self) -> Color {
+ self.style().get_inherited_text().color
+ }
+
+ /// Returns the text decoration line of this fragment, according to the style of the nearest ancestor
+ /// element.
+ ///
+ /// NB: This may not be the actual text decoration line, 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_line(&self) -> TextDecorationLine {
+ self.style().get_text().text_decoration_line
+ }
+
+ /// 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 {
+ SpecificFragmentInfo::TableWrapper => self.margin.inline_start,
+ SpecificFragmentInfo::Table |
+ SpecificFragmentInfo::TableCell |
+ SpecificFragmentInfo::TableRow => self.border_padding.inline_start,
+ SpecificFragmentInfo::TableColumn(_) => Au(0),
+ _ => self.margin.inline_start + self.border_padding.inline_start,
+ }
+ }
+
+ /// If this is a Column fragment, get the col span
+ ///
+ /// Panics for non-column fragments
+ pub fn column_span(&self) -> u32 {
+ match self.specific {
+ SpecificFragmentInfo::TableColumn(col_fragment) => max(col_fragment.span, 1),
+ _ => panic!("non-table-column fragment inside table column?!"),
+ }
+ }
+
+ /// Returns true if this element can be split. This is true for text fragments, unless
+ /// `white-space: pre` or `white-space: nowrap` is set.
+ pub fn can_split(&self) -> bool {
+ self.is_scanned_text_fragment() && self.white_space().allow_wrap()
+ }
+
+ /// Returns true if and only if this fragment is a generated content fragment.
+ pub fn is_unscanned_generated_content(&self) -> bool {
+ match self.specific {
+ SpecificFragmentInfo::GeneratedContent(ref content) => match **content {
+ GeneratedContentInfo::Empty => false,
+ _ => true,
+ },
+ _ => false,
+ }
+ }
+
+ /// Returns true if and only if this is a scanned text fragment.
+ pub fn is_scanned_text_fragment(&self) -> bool {
+ match self.specific {
+ SpecificFragmentInfo::ScannedText(..) => true,
+ _ => false,
+ }
+ }
+
+ pub fn suppress_line_break_before(&self) -> bool {
+ match self.specific {
+ SpecificFragmentInfo::ScannedText(ref st) => st
+ .flags
+ .contains(ScannedTextFlags::SUPPRESS_LINE_BREAK_BEFORE),
+ _ => false,
+ }
+ }
+
+ /// Computes the intrinsic inline-sizes of this fragment.
+ pub fn compute_intrinsic_inline_sizes(&mut self) -> IntrinsicISizesContribution {
+ let mut result = self.style_specified_intrinsic_inline_size();
+ match self.specific {
+ SpecificFragmentInfo::Generic |
+ SpecificFragmentInfo::GeneratedContent(_) |
+ SpecificFragmentInfo::Table |
+ SpecificFragmentInfo::TableCell |
+ SpecificFragmentInfo::TableColumn(_) |
+ SpecificFragmentInfo::TableRow |
+ SpecificFragmentInfo::TableWrapper |
+ SpecificFragmentInfo::Multicol |
+ SpecificFragmentInfo::MulticolColumn |
+ SpecificFragmentInfo::InlineAbsoluteHypothetical(_) => {},
+ SpecificFragmentInfo::InlineBlock(ref info) => {
+ let block_flow = info.flow_ref.as_block();
+ result.union_block(&block_flow.base.intrinsic_inline_sizes)
+ },
+ SpecificFragmentInfo::InlineAbsolute(ref info) => {
+ let block_flow = info.flow_ref.as_block();
+ result.union_block(&block_flow.base.intrinsic_inline_sizes)
+ },
+ SpecificFragmentInfo::Image(_) |
+ SpecificFragmentInfo::Media(_) |
+ SpecificFragmentInfo::Canvas(_) |
+ SpecificFragmentInfo::Iframe(_) |
+ SpecificFragmentInfo::Svg(_) => {
+ let inline_size = match self.style.content_inline_size() {
+ Size::Auto => None,
+ Size::LengthPercentage(ref lp) => lp.maybe_to_used_value(None),
+ };
+
+ let mut inline_size = inline_size.unwrap_or_else(|| {
+ // We have to initialize the `border_padding` field first to make
+ // the size constraints work properly.
+ // TODO(stshine): Find a cleaner way to do this.
+ let padding = self.style.logical_padding();
+ self.border_padding.inline_start = padding.inline_start.to_used_value(Au(0));
+ self.border_padding.inline_end = padding.inline_end.to_used_value(Au(0));
+ self.border_padding.block_start = padding.block_start.to_used_value(Au(0));
+ self.border_padding.block_end = padding.block_end.to_used_value(Au(0));
+ let border = self.border_width();
+ self.border_padding.inline_start += border.inline_start;
+ self.border_padding.inline_end += border.inline_end;
+ self.border_padding.block_start += border.block_start;
+ self.border_padding.block_end += border.block_end;
+ let (result_inline, _) = self.calculate_replaced_sizes(None, None);
+ result_inline
+ });
+
+ let size_constraint = self.size_constraint(None, Direction::Inline);
+ inline_size = size_constraint.clamp(inline_size);
+
+ result.union_block(&IntrinsicISizes {
+ minimum_inline_size: inline_size,
+ preferred_inline_size: inline_size,
+ });
+ },
+
+ SpecificFragmentInfo::TruncatedFragment(ref t) if t.text_info.is_some() => {
+ let text_fragment_info = t.text_info.as_ref().unwrap();
+ handle_text(text_fragment_info, self, &mut result)
+ },
+ SpecificFragmentInfo::ScannedText(ref text_fragment_info) => {
+ handle_text(text_fragment_info, self, &mut result)
+ },
+
+ SpecificFragmentInfo::TruncatedFragment(_) => {
+ return IntrinsicISizesContribution::new()
+ },
+
+ SpecificFragmentInfo::UnscannedText(..) => {
+ panic!("Unscanned text fragments should have been scanned by now!")
+ },
+ };
+
+ fn handle_text(
+ text_fragment_info: &ScannedTextFragmentInfo,
+ self_: &Fragment,
+ result: &mut IntrinsicISizesContribution,
+ ) {
+ let range = &text_fragment_info.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;
+
+ let min_line_inline_size = if self_.white_space().allow_wrap() {
+ text_fragment_info.run.min_width_for_range(range)
+ } else {
+ max_line_inline_size
+ };
+
+ result.union_block(&IntrinsicISizes {
+ minimum_inline_size: min_line_inline_size,
+ preferred_inline_size: max_line_inline_size,
+ })
+ }
+
+ // Take borders and padding for parent inline fragments into account.
+ let writing_mode = self.style.writing_mode;
+ if let Some(ref context) = self.inline_context {
+ for node in &context.nodes {
+ let mut border_width = node.style.logical_border_width();
+ let mut padding = model::padding_from_style(&*node.style, Au(0), writing_mode);
+ let mut margin = model::specified_margin_from_style(&*node.style, writing_mode);
+ if !node
+ .flags
+ .contains(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT)
+ {
+ border_width.inline_start = Au(0);
+ padding.inline_start = Au(0);
+ margin.inline_start = Au(0);
+ }
+ if !node
+ .flags
+ .contains(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT)
+ {
+ border_width.inline_end = Au(0);
+ padding.inline_end = Au(0);
+ margin.inline_end = Au(0);
+ }
+
+ result.surrounding_size = result.surrounding_size +
+ border_width.inline_start_end() +
+ padding.inline_start_end() +
+ margin.inline_start_end();
+ }
+ }
+
+ result
+ }
+
+ /// Returns the narrowest inline-size that the first splittable part of this fragment could
+ /// possibly be split to. (In most cases, this returns the inline-size of the first word in
+ /// this fragment.)
+ pub fn minimum_splittable_inline_size(&self) -> Au {
+ match self.specific {
+ SpecificFragmentInfo::TruncatedFragment(ref t) if t.text_info.is_some() => {
+ let text = t.text_info.as_ref().unwrap();
+ text.run.minimum_splittable_inline_size(&text.range)
+ },
+ SpecificFragmentInfo::ScannedText(ref text) => {
+ text.run.minimum_splittable_inline_size(&text.range)
+ },
+ _ => Au(0),
+ }
+ }
+
+ /// 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
+ }
+
+ /// 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.
+ pub fn calculate_split_position(
+ &self,
+ max_inline_size: Au,
+ starts_line: bool,
+ ) -> Option<SplitResult> {
+ let text_fragment_info = match self.specific {
+ SpecificFragmentInfo::ScannedText(ref text_fragment_info) => text_fragment_info,
+ _ => return None,
+ };
+
+ let mut flags = SplitOptions::empty();
+ if starts_line {
+ flags.insert(SplitOptions::STARTS_LINE);
+ if self.style().get_inherited_text().overflow_wrap == OverflowWrap::BreakWord {
+ flags.insert(SplitOptions::RETRY_AT_CHARACTER_BOUNDARIES)
+ }
+ }
+
+ match self.style().get_inherited_text().word_break {
+ WordBreak::Normal | WordBreak::KeepAll => {
+ // Break at normal word boundaries. keep-all forbids soft wrap opportunities.
+ let natural_word_breaking_strategy = text_fragment_info
+ .run
+ .natural_word_slices_in_range(&text_fragment_info.range);
+ self.calculate_split_position_using_breaking_strategy(
+ natural_word_breaking_strategy,
+ max_inline_size,
+ flags,
+ )
+ },
+ WordBreak::BreakAll => {
+ // Break at character boundaries.
+ let character_breaking_strategy = text_fragment_info
+ .run
+ .character_slices_in_range(&text_fragment_info.range);
+ flags.remove(SplitOptions::RETRY_AT_CHARACTER_BOUNDARIES);
+ self.calculate_split_position_using_breaking_strategy(
+ character_breaking_strategy,
+ max_inline_size,
+ flags,
+ )
+ },
+ }
+ }
+
+ /// Does this fragment start on a glyph run boundary?
+ pub fn is_on_glyph_run_boundary(&self) -> bool {
+ let text_fragment_info = match self.specific {
+ SpecificFragmentInfo::ScannedText(ref text_fragment_info) => text_fragment_info,
+ _ => return true,
+ };
+ text_fragment_info
+ .run
+ .on_glyph_run_boundary(text_fragment_info.range.begin())
+ }
+
+ /// Truncates this fragment to the given `max_inline_size`, using a character-based breaking
+ /// strategy. The resulting fragment will have `SpecificFragmentInfo::TruncatedFragment`,
+ /// preserving the original fragment for use in incremental reflow.
+ ///
+ /// This function will panic if self is already truncated.
+ pub fn truncate_to_inline_size(self, max_inline_size: Au) -> Fragment {
+ if let SpecificFragmentInfo::TruncatedFragment(_) = self.specific {
+ panic!("Cannot truncate an already truncated fragment");
+ }
+ let info = self.calculate_truncate_to_inline_size(max_inline_size);
+ let (size, text_info) = match info {
+ Some(TruncationResult {
+ split: SplitInfo { inline_size, range },
+ text_run,
+ }) => {
+ let size = LogicalSize::new(
+ self.style.writing_mode,
+ inline_size,
+ self.border_box.size.block,
+ );
+ // Preserve the insertion point if it is in this fragment's range or it is at line end.
+ let (flags, insertion_point) = match self.specific {
+ SpecificFragmentInfo::ScannedText(ref info) => match info.insertion_point {
+ Some(index) if range.contains(index) => (info.flags, info.insertion_point),
+ Some(index)
+ if index == ByteIndex(text_run.text.chars().count() as isize - 1) &&
+ index == range.end() =>
+ {
+ (info.flags, info.insertion_point)
+ },
+ _ => (info.flags, None),
+ },
+ _ => (ScannedTextFlags::empty(), None),
+ };
+ let text_info =
+ ScannedTextFragmentInfo::new(text_run, range, size, insertion_point, flags);
+ (size, Some(text_info))
+ },
+ None => (LogicalSize::zero(self.style.writing_mode), None),
+ };
+ let mut result = self.transform(size, SpecificFragmentInfo::Generic);
+ result.specific =
+ SpecificFragmentInfo::TruncatedFragment(Box::new(TruncatedFragmentInfo {
+ text_info: text_info,
+ full: self,
+ }));
+ result
+ }
+
+ /// Truncates this fragment to the given `max_inline_size`, using a character-based breaking
+ /// strategy. If no characters could fit, returns `None`.
+ fn calculate_truncate_to_inline_size(&self, max_inline_size: Au) -> Option<TruncationResult> {
+ let text_fragment_info =
+ if let SpecificFragmentInfo::ScannedText(ref text_fragment_info) = self.specific {
+ text_fragment_info
+ } else {
+ return None;
+ };
+
+ let character_breaking_strategy = text_fragment_info
+ .run
+ .character_slices_in_range(&text_fragment_info.range);
+
+ let split_info = self.calculate_split_position_using_breaking_strategy(
+ character_breaking_strategy,
+ max_inline_size,
+ SplitOptions::empty(),
+ )?;
+
+ let split = split_info.inline_start?;
+ Some(TruncationResult {
+ split: split,
+ text_run: split_info.text_run.clone(),
+ })
+ }
+
+ /// A helper method that uses the breaking strategy described by `slice_iterator` (at present,
+ /// either natural word breaking or character breaking) to split this fragment.
+ fn calculate_split_position_using_breaking_strategy<'a, I>(
+ &self,
+ slice_iterator: I,
+ max_inline_size: Au,
+ flags: SplitOptions,
+ ) -> Option<SplitResult>
+ where
+ I: Iterator<Item = TextRunSlice<'a>>,
+ {
+ let text_fragment_info = match self.specific {
+ SpecificFragmentInfo::ScannedText(ref text_fragment_info) => text_fragment_info,
+ _ => return None,
+ };
+
+ let mut remaining_inline_size = max_inline_size - self.border_padding.inline_start_end();
+ let mut inline_start_range = Range::new(text_fragment_info.range.begin(), ByteIndex(0));
+ let mut inline_end_range = None;
+ let mut overflowing = false;
+
+ debug!(
+ "calculate_split_position_using_breaking_strategy: splitting text fragment \
+ (strlen={}, range={:?}, max_inline_size={:?})",
+ text_fragment_info.run.text.len(),
+ text_fragment_info.range,
+ max_inline_size
+ );
+
+ for slice in slice_iterator {
+ debug!(
+ "calculate_split_position_using_breaking_strategy: considering slice \
+ (offset={:?}, slice range={:?}, remaining_inline_size={:?})",
+ slice.offset, slice.range, remaining_inline_size
+ );
+
+ // Use the `remaining_inline_size` to find a split point if possible. If not, go around
+ // the loop again with the next slice.
+ let metrics = text_fragment_info
+ .run
+ .metrics_for_slice(slice.glyphs, &slice.range);
+ let advance = metrics.advance_width;
+
+ // Have we found the split point?
+ if advance <= remaining_inline_size || slice.glyphs.is_whitespace() {
+ // Keep going; we haven't found the split point yet.
+ debug!("calculate_split_position_using_breaking_strategy: enlarging span");
+ remaining_inline_size = remaining_inline_size - advance;
+ inline_start_range.extend_by(slice.range.length());
+ continue;
+ }
+
+ // The advance is more than the remaining inline-size, so split here. First, check to
+ // see if we're going to overflow the line. If so, perform a best-effort split.
+ let mut remaining_range = slice.text_run_range();
+ let split_is_empty = inline_start_range.is_empty() &&
+ !(self.requires_line_break_afterward_if_wrapping_on_newlines() &&
+ !self.white_space().allow_wrap());
+ if split_is_empty {
+ // We're going to overflow the line.
+ overflowing = true;
+ inline_start_range = slice.text_run_range();
+ remaining_range = Range::new(slice.text_run_range().end(), ByteIndex(0));
+ remaining_range.extend_to(text_fragment_info.range.end());
+ }
+
+ // Check to see if we need to create an inline-end chunk.
+ let slice_begin = remaining_range.begin();
+ if slice_begin < text_fragment_info.range.end() {
+ // There still some things left over at the end of the line, so create the
+ // inline-end chunk.
+ let mut inline_end = remaining_range;
+ inline_end.extend_to(text_fragment_info.range.end());
+ inline_end_range = Some(inline_end);
+ debug!(
+ "calculate_split_position: splitting remainder with inline-end range={:?}",
+ inline_end
+ );
+ }
+
+ // If we failed to find a suitable split point, we're on the verge of overflowing the
+ // line.
+ if split_is_empty || overflowing {
+ // If we've been instructed to retry at character boundaries (probably via
+ // `overflow-wrap: break-word`), do so.
+ if flags.contains(SplitOptions::RETRY_AT_CHARACTER_BOUNDARIES) {
+ let character_breaking_strategy = text_fragment_info
+ .run
+ .character_slices_in_range(&text_fragment_info.range);
+ let mut flags = flags;
+ flags.remove(SplitOptions::RETRY_AT_CHARACTER_BOUNDARIES);
+ return self.calculate_split_position_using_breaking_strategy(
+ character_breaking_strategy,
+ max_inline_size,
+ flags,
+ );
+ }
+
+ // We aren't at the start of the line, so don't overflow. Let inline layout wrap to
+ // the next line instead.
+ if !flags.contains(SplitOptions::STARTS_LINE) {
+ return None;
+ }
+ }
+
+ break;
+ }
+
+ let split_is_empty = inline_start_range.is_empty() &&
+ !self.requires_line_break_afterward_if_wrapping_on_newlines();
+ let inline_start = if !split_is_empty {
+ 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(SplitResult {
+ inline_start: inline_start,
+ inline_end: inline_end,
+ text_run: text_fragment_info.run.clone(),
+ })
+ }
+
+ /// The opposite of `calculate_split_position_using_breaking_strategy`: merges this fragment
+ /// with the next one.
+ pub fn merge_with(&mut self, next_fragment: Fragment) {
+ match (&mut self.specific, &next_fragment.specific) {
+ (
+ &mut SpecificFragmentInfo::ScannedText(ref mut this_info),
+ &SpecificFragmentInfo::ScannedText(ref other_info),
+ ) => {
+ debug_assert!(Arc::ptr_eq(&this_info.run, &other_info.run));
+ this_info.range_end_including_stripped_whitespace =
+ other_info.range_end_including_stripped_whitespace;
+ if other_info.requires_line_break_afterward_if_wrapping_on_newlines() {
+ this_info.flags.insert(
+ ScannedTextFlags::REQUIRES_LINE_BREAK_AFTERWARD_IF_WRAPPING_ON_NEWLINES,
+ );
+ }
+ if other_info.insertion_point.is_some() {
+ this_info.insertion_point = other_info.insertion_point;
+ }
+ self.border_padding.inline_end = next_fragment.border_padding.inline_end;
+ self.margin.inline_end = next_fragment.margin.inline_end;
+ },
+ _ => panic!("Can only merge two scanned-text fragments!"),
+ }
+ self.reset_text_range_and_inline_size();
+ self.meld_with_next_inline_fragment(&next_fragment);
+ }
+
+ /// Restore any whitespace that was stripped from a text fragment, and recompute inline metrics
+ /// if necessary.
+ pub fn reset_text_range_and_inline_size(&mut self) {
+ if let SpecificFragmentInfo::ScannedText(ref mut info) = self.specific {
+ if info.run.extra_word_spacing != Au(0) {
+ Arc::make_mut(&mut info.run).extra_word_spacing = Au(0);
+ }
+
+ // FIXME (mbrubeck): Do we need to restore leading too?
+ let range_end = info.range_end_including_stripped_whitespace;
+ if info.range.end() == range_end {
+ return;
+ }
+ info.range.extend_to(range_end);
+ info.content_size.inline = info.run.metrics_for_range(&info.range).advance_width;
+ self.border_box.size.inline =
+ info.content_size.inline + self.border_padding.inline_start_end();
+ }
+ }
+
+ /// 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,
+ container_block_size: Option<Au>,
+ ) {
+ match self.specific {
+ SpecificFragmentInfo::TruncatedFragment(ref t) if t.text_info.is_none() => return,
+ SpecificFragmentInfo::Generic |
+ SpecificFragmentInfo::GeneratedContent(_) |
+ SpecificFragmentInfo::Table |
+ SpecificFragmentInfo::TableCell |
+ SpecificFragmentInfo::TableRow |
+ SpecificFragmentInfo::TableWrapper |
+ SpecificFragmentInfo::Multicol |
+ SpecificFragmentInfo::MulticolColumn => return,
+ SpecificFragmentInfo::TableColumn(_) => {
+ panic!("Table column fragments do not have inline size")
+ },
+ SpecificFragmentInfo::UnscannedText(_) => {
+ panic!("Unscanned text fragments should have been scanned by now!")
+ },
+ SpecificFragmentInfo::Canvas(_) |
+ SpecificFragmentInfo::Image(_) |
+ SpecificFragmentInfo::Media(_) |
+ SpecificFragmentInfo::Iframe(_) |
+ SpecificFragmentInfo::InlineBlock(_) |
+ SpecificFragmentInfo::InlineAbsoluteHypothetical(_) |
+ SpecificFragmentInfo::InlineAbsolute(_) |
+ SpecificFragmentInfo::ScannedText(_) |
+ SpecificFragmentInfo::TruncatedFragment(_) |
+ SpecificFragmentInfo::Svg(_) => {},
+ };
+
+ match self.specific {
+ // Inline blocks
+ SpecificFragmentInfo::InlineAbsoluteHypothetical(ref mut info) => {
+ let block_flow = FlowRef::deref_mut(&mut info.flow_ref).as_mut_block();
+ block_flow.base.position.size.inline =
+ block_flow.base.intrinsic_inline_sizes.preferred_inline_size;
+
+ // This is a hypothetical box, so it takes up no space.
+ self.border_box.size.inline = Au(0);
+ },
+ SpecificFragmentInfo::InlineBlock(ref mut info) => {
+ let block_flow = FlowRef::deref_mut(&mut info.flow_ref).as_mut_block();
+ self.border_box.size.inline = max(
+ block_flow.base.intrinsic_inline_sizes.minimum_inline_size,
+ block_flow.base.intrinsic_inline_sizes.preferred_inline_size,
+ );
+ block_flow.base.block_container_inline_size = self.border_box.size.inline;
+ block_flow.base.block_container_writing_mode = self.style.writing_mode;
+ },
+ SpecificFragmentInfo::InlineAbsolute(ref mut info) => {
+ let block_flow = FlowRef::deref_mut(&mut info.flow_ref).as_mut_block();
+ self.border_box.size.inline = max(
+ block_flow.base.intrinsic_inline_sizes.minimum_inline_size,
+ block_flow.base.intrinsic_inline_sizes.preferred_inline_size,
+ );
+ block_flow.base.block_container_inline_size = self.border_box.size.inline;
+ block_flow.base.block_container_writing_mode = self.style.writing_mode;
+ },
+
+ // Text
+ SpecificFragmentInfo::TruncatedFragment(ref t) if t.text_info.is_some() => {
+ let info = t.text_info.as_ref().unwrap();
+ // Scanned text fragments will have already had their content inline-sizes assigned
+ // by this point.
+ self.border_box.size.inline =
+ info.content_size.inline + self.border_padding.inline_start_end();
+ },
+ SpecificFragmentInfo::ScannedText(ref info) => {
+ // Scanned text fragments will have already had their content inline-sizes assigned
+ // by this point.
+ self.border_box.size.inline =
+ info.content_size.inline + self.border_padding.inline_start_end();
+ },
+
+ // Replaced elements
+ _ if self.is_replaced() => {
+ let (inline_size, block_size) = self
+ .calculate_replaced_sizes(Some(container_inline_size), container_block_size);
+ self.border_box.size.inline = inline_size + self.border_padding.inline_start_end();
+ self.border_box.size.block = block_size + self.border_padding.block_start_end();
+ },
+
+ ref unhandled @ _ => {
+ panic!("this case should have been handled above: {:?}", unhandled)
+ },
+ }
+ }
+
+ /// 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 {
+ SpecificFragmentInfo::TruncatedFragment(ref t) if t.text_info.is_none() => return,
+ SpecificFragmentInfo::Generic |
+ SpecificFragmentInfo::GeneratedContent(_) |
+ SpecificFragmentInfo::Table |
+ SpecificFragmentInfo::TableCell |
+ SpecificFragmentInfo::TableRow |
+ SpecificFragmentInfo::TableWrapper |
+ SpecificFragmentInfo::Multicol |
+ SpecificFragmentInfo::MulticolColumn => return,
+ SpecificFragmentInfo::TableColumn(_) => {
+ panic!("Table column fragments do not have block size")
+ },
+ SpecificFragmentInfo::UnscannedText(_) => {
+ panic!("Unscanned text fragments should have been scanned by now!")
+ },
+ SpecificFragmentInfo::Canvas(_) |
+ SpecificFragmentInfo::Iframe(_) |
+ SpecificFragmentInfo::Image(_) |
+ SpecificFragmentInfo::Media(_) |
+ SpecificFragmentInfo::InlineBlock(_) |
+ SpecificFragmentInfo::InlineAbsoluteHypothetical(_) |
+ SpecificFragmentInfo::InlineAbsolute(_) |
+ SpecificFragmentInfo::ScannedText(_) |
+ SpecificFragmentInfo::TruncatedFragment(_) |
+ SpecificFragmentInfo::Svg(_) => {},
+ }
+
+ match self.specific {
+ // Text
+ SpecificFragmentInfo::TruncatedFragment(ref t) if t.text_info.is_some() => {
+ let info = t.text_info.as_ref().unwrap();
+ // Scanned text fragments' content block-sizes are calculated by the text run
+ // scanner during flow construction.
+ self.border_box.size.block =
+ info.content_size.block + self.border_padding.block_start_end();
+ },
+ SpecificFragmentInfo::ScannedText(ref info) => {
+ // Scanned text fragments' content block-sizes are calculated by the text run
+ // scanner during flow construction.
+ self.border_box.size.block =
+ info.content_size.block + self.border_padding.block_start_end();
+ },
+
+ // Inline blocks
+ SpecificFragmentInfo::InlineBlock(ref mut info) => {
+ // Not the primary fragment, so we do not take the noncontent size into account.
+ let block_flow = FlowRef::deref_mut(&mut info.flow_ref).as_block();
+ self.border_box.size.block = block_flow.base.position.size.block +
+ block_flow.fragment.margin.block_start_end()
+ },
+ SpecificFragmentInfo::InlineAbsoluteHypothetical(ref mut info) => {
+ // Not the primary fragment, so we do not take the noncontent size into account.
+ let block_flow = FlowRef::deref_mut(&mut info.flow_ref).as_block();
+ self.border_box.size.block = block_flow.base.position.size.block;
+ },
+ SpecificFragmentInfo::InlineAbsolute(ref mut info) => {
+ // Not the primary fragment, so we do not take the noncontent size into account.
+ let block_flow = FlowRef::deref_mut(&mut info.flow_ref).as_block();
+ self.border_box.size.block = block_flow.base.position.size.block +
+ block_flow.fragment.margin.block_start_end()
+ },
+
+ // Replaced elements
+ _ if self.is_replaced() => {},
+
+ ref unhandled @ _ => panic!("should have been handled above: {:?}", unhandled),
+ }
+ }
+
+ /// Returns true if this fragment is replaced content.
+ pub fn is_replaced(&self) -> bool {
+ match self.specific {
+ SpecificFragmentInfo::Iframe(_) |
+ SpecificFragmentInfo::Canvas(_) |
+ SpecificFragmentInfo::Image(_) |
+ SpecificFragmentInfo::Media(_) |
+ SpecificFragmentInfo::Svg(_) => true,
+ _ => false,
+ }
+ }
+
+ /// Returns true if this fragment is replaced content or an inline-block or false otherwise.
+ pub fn is_replaced_or_inline_block(&self) -> bool {
+ match self.specific {
+ SpecificFragmentInfo::InlineAbsoluteHypothetical(_) |
+ SpecificFragmentInfo::InlineBlock(_) => true,
+ _ => self.is_replaced(),
+ }
+ }
+
+ /// 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.
+ ///
+ /// This does not take `vertical-align` into account. For that, use `aligned_inline_metrics()`.
+ fn content_inline_metrics(&self, layout_context: &LayoutContext) -> InlineMetrics {
+ // CSS 2.1 § 10.8: "The height of each inline-level box in the line box is
+ // calculated. For replaced elements, inline-block elements, and inline-table
+ // elements, this is the height of their margin box."
+ //
+ // FIXME(pcwalton): We have to handle `Generic` and `GeneratedContent` here to avoid
+ // crashing in a couple of `css21_dev/html4/content-` WPTs, but I don't see how those two
+ // fragment types should end up inside inlines. (In the case of `GeneratedContent`, those
+ // fragment types should have been resolved by now…)
+ let inline_metrics = match self.specific {
+ SpecificFragmentInfo::Canvas(_) |
+ SpecificFragmentInfo::Iframe(_) |
+ SpecificFragmentInfo::Image(_) |
+ SpecificFragmentInfo::Media(_) |
+ SpecificFragmentInfo::Svg(_) |
+ SpecificFragmentInfo::Generic |
+ SpecificFragmentInfo::GeneratedContent(_) => {
+ let ascent = self.border_box.size.block + self.margin.block_end;
+ InlineMetrics {
+ space_above_baseline: ascent + self.margin.block_start,
+ space_below_baseline: Au(0),
+ ascent: ascent,
+ }
+ },
+ SpecificFragmentInfo::TruncatedFragment(ref t) if t.text_info.is_some() => {
+ let info = t.text_info.as_ref().unwrap();
+ inline_metrics_of_text(info, self, layout_context)
+ },
+ SpecificFragmentInfo::ScannedText(ref info) => {
+ inline_metrics_of_text(info, self, layout_context)
+ },
+ SpecificFragmentInfo::InlineBlock(ref info) => {
+ inline_metrics_of_block(&info.flow_ref, &*self.style)
+ },
+ SpecificFragmentInfo::InlineAbsoluteHypothetical(ref info) => {
+ inline_metrics_of_block(&info.flow_ref, &*self.style)
+ },
+ SpecificFragmentInfo::TruncatedFragment(..) |
+ SpecificFragmentInfo::InlineAbsolute(_) => InlineMetrics::new(Au(0), Au(0), Au(0)),
+ SpecificFragmentInfo::Table |
+ SpecificFragmentInfo::TableCell |
+ SpecificFragmentInfo::TableColumn(_) |
+ SpecificFragmentInfo::TableRow |
+ SpecificFragmentInfo::TableWrapper |
+ SpecificFragmentInfo::Multicol |
+ SpecificFragmentInfo::MulticolColumn |
+ SpecificFragmentInfo::UnscannedText(_) => {
+ unreachable!("Shouldn't see fragments of this type here!")
+ },
+ };
+ return inline_metrics;
+
+ fn inline_metrics_of_text(
+ info: &ScannedTextFragmentInfo,
+ self_: &Fragment,
+ layout_context: &LayoutContext,
+ ) -> InlineMetrics {
+ // Fragments with no glyphs don't contribute any inline metrics.
+ // TODO: Filter out these fragments during flow construction?
+ if info.insertion_point.is_none() && info.content_size.inline == Au(0) {
+ return InlineMetrics::new(Au(0), Au(0), Au(0));
+ }
+ // See CSS 2.1 § 10.8.1.
+ let font_metrics = with_thread_local_font_context(layout_context, |font_context| {
+ text::font_metrics_for_style(font_context, self_.style.clone_font())
+ });
+ let line_height = text::line_height_from_style(&*self_.style, &font_metrics);
+ InlineMetrics::from_font_metrics(&info.run.font_metrics, line_height)
+ }
+
+ fn inline_metrics_of_block(flow: &FlowRef, style: &ComputedValues) -> InlineMetrics {
+ // CSS 2.1 § 10.8: "The height of each inline-level box in the line box is calculated.
+ // For replaced elements, inline-block elements, and inline-table elements, this is the
+ // height of their margin box."
+ //
+ // CSS 2.1 § 10.8.1: "The baseline of an 'inline-block' is the baseline of its last
+ // line box in the normal flow, unless it has either no in-flow line boxes or if its
+ // 'overflow' property has a computed value other than 'visible', in which case the
+ // baseline is the bottom margin edge."
+ //
+ // NB: We must use `block_flow.fragment.border_box.size.block` here instead of
+ // `block_flow.base.position.size.block` because sometimes the latter is late-computed
+ // and isn't up to date at this point.
+ let block_flow = flow.as_block();
+ let start_margin = block_flow.fragment.margin.block_start;
+ let end_margin = block_flow.fragment.margin.block_end;
+ let border_box_block_size = block_flow.fragment.border_box.size.block;
+
+ // --------
+ // margin
+ // top -------- + +
+ // | |
+ // | |
+ // A ..pogo.. | + baseline_offset_of_last_line_box_in_flow()
+ // |
+ // -------- + border_box_block_size
+ // margin
+ // B --------
+ //
+ // § 10.8.1 says that the baseline (and thus ascent, which is the
+ // distance from the baseline to the top) should be A if it has an
+ // in-flow line box and if overflow: visible, and B otherwise.
+ let ascent = match (
+ flow.baseline_offset_of_last_line_box_in_flow(),
+ style.get_box().overflow_y,
+ ) {
+ // Case A
+ (Some(baseline_offset), StyleOverflow::Visible) => baseline_offset,
+ // Case B
+ _ => border_box_block_size + end_margin,
+ };
+
+ let space_below_baseline = border_box_block_size + end_margin - ascent;
+ let space_above_baseline = ascent + start_margin;
+
+ InlineMetrics::new(space_above_baseline, space_below_baseline, ascent)
+ }
+ }
+
+ /// Calculates the offset from the baseline that applies to this fragment due to
+ /// `vertical-align`. Positive values represent downward displacement.
+ ///
+ /// If `actual_line_metrics` is supplied, then these metrics are used to determine the
+ /// displacement of the fragment when `top` or `bottom` `vertical-align` values are
+ /// encountered. If this is not supplied, then `top` and `bottom` values are ignored.
+ fn vertical_alignment_offset(
+ &self,
+ layout_context: &LayoutContext,
+ content_inline_metrics: &InlineMetrics,
+ minimum_line_metrics: &LineMetrics,
+ actual_line_metrics: Option<&LineMetrics>,
+ ) -> Au {
+ let mut offset = Au(0);
+ for style in self.inline_styles() {
+ // If any of the inline styles say `top` or `bottom`, adjust the vertical align
+ // appropriately.
+ //
+ // FIXME(#5624, pcwalton): This passes our current reftests but isn't the right thing
+ // to do.
+ match style.get_box().vertical_align {
+ VerticalAlign::Keyword(kw) => match kw {
+ VerticalAlignKeyword::Baseline => {},
+ VerticalAlignKeyword::Middle => {
+ let font_metrics =
+ with_thread_local_font_context(layout_context, |font_context| {
+ text::font_metrics_for_style(font_context, self.style.clone_font())
+ });
+ offset += (content_inline_metrics.ascent -
+ content_inline_metrics.space_below_baseline -
+ font_metrics.x_height)
+ .scale_by(0.5)
+ },
+ VerticalAlignKeyword::Sub => {
+ offset += minimum_line_metrics
+ .space_needed()
+ .scale_by(FONT_SUBSCRIPT_OFFSET_RATIO)
+ },
+ VerticalAlignKeyword::Super => {
+ offset -= minimum_line_metrics
+ .space_needed()
+ .scale_by(FONT_SUPERSCRIPT_OFFSET_RATIO)
+ },
+ VerticalAlignKeyword::TextTop => {
+ offset = self.content_inline_metrics(layout_context).ascent -
+ minimum_line_metrics.space_above_baseline
+ },
+ VerticalAlignKeyword::TextBottom => {
+ offset = minimum_line_metrics.space_below_baseline -
+ self.content_inline_metrics(layout_context)
+ .space_below_baseline
+ },
+ VerticalAlignKeyword::Top => {
+ if let Some(actual_line_metrics) = actual_line_metrics {
+ offset = content_inline_metrics.ascent -
+ actual_line_metrics.space_above_baseline
+ }
+ },
+ VerticalAlignKeyword::Bottom => {
+ if let Some(actual_line_metrics) = actual_line_metrics {
+ offset = actual_line_metrics.space_below_baseline -
+ content_inline_metrics.space_below_baseline
+ }
+ },
+ },
+ VerticalAlign::Length(ref lp) => {
+ offset -= lp.to_used_value(minimum_line_metrics.space_needed());
+ },
+ }
+ }
+ offset
+ }
+
+ /// Calculates block-size above baseline, depth below baseline, and ascent for this fragment
+ /// when used in an inline formatting context, taking `vertical-align` (other than `top` or
+ /// `bottom`) into account. See CSS 2.1 § 10.8.1.
+ ///
+ /// If `actual_line_metrics` is supplied, then these metrics are used to determine the
+ /// displacement of the fragment when `top` or `bottom` `vertical-align` values are
+ /// encountered. If this is not supplied, then `top` and `bottom` values are ignored.
+ pub fn aligned_inline_metrics(
+ &self,
+ layout_context: &LayoutContext,
+ minimum_line_metrics: &LineMetrics,
+ actual_line_metrics: Option<&LineMetrics>,
+ ) -> InlineMetrics {
+ let content_inline_metrics = self.content_inline_metrics(layout_context);
+ let vertical_alignment_offset = self.vertical_alignment_offset(
+ layout_context,
+ &content_inline_metrics,
+ minimum_line_metrics,
+ actual_line_metrics,
+ );
+ let mut space_above_baseline = match actual_line_metrics {
+ None => content_inline_metrics.space_above_baseline,
+ Some(actual_line_metrics) => actual_line_metrics.space_above_baseline,
+ };
+ space_above_baseline = space_above_baseline - vertical_alignment_offset;
+ let space_below_baseline =
+ content_inline_metrics.space_below_baseline + vertical_alignment_offset;
+ let ascent = content_inline_metrics.ascent - vertical_alignment_offset;
+ InlineMetrics::new(space_above_baseline, space_below_baseline, ascent)
+ }
+
+ /// Returns true if this fragment is a hypothetical box. See CSS 2.1 § 10.3.7.
+ pub fn is_hypothetical(&self) -> bool {
+ match self.specific {
+ SpecificFragmentInfo::InlineAbsoluteHypothetical(_) => true,
+ _ => false,
+ }
+ }
+
+ /// Returns true if this fragment can merge with another immediately-following fragment or
+ /// false otherwise.
+ pub fn can_merge_with_fragment(&self, other: &Fragment) -> bool {
+ match (&self.specific, &other.specific) {
+ (
+ &SpecificFragmentInfo::UnscannedText(ref first_unscanned_text),
+ &SpecificFragmentInfo::UnscannedText(_),
+ ) => {
+ // FIXME: Should probably use a whitelist of styles that can safely differ (#3165)
+ if self.style().get_font() != other.style().get_font() ||
+ self.text_decoration_line() != other.text_decoration_line() ||
+ self.white_space() != other.white_space() ||
+ self.color() != other.color()
+ {
+ return false;
+ }
+
+ if first_unscanned_text.text.ends_with('\n') {
+ return false;
+ }
+
+ // If this node has any styles that have border/padding/margins on the following
+ // side, then we can't merge with the next fragment.
+ if let Some(ref inline_context) = self.inline_context {
+ for inline_context_node in inline_context.nodes.iter() {
+ if !inline_context_node
+ .flags
+ .contains(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT)
+ {
+ continue;
+ }
+ if inline_context_node.style.logical_margin().inline_end !=
+ LengthPercentageOrAuto::zero()
+ {
+ return false;
+ }
+ if inline_context_node.style.logical_padding().inline_end !=
+ LengthPercentage::zero()
+ {
+ return false;
+ }
+ if inline_context_node.style.logical_border_width().inline_end != Au(0) {
+ return false;
+ }
+ }
+ }
+
+ // If the next fragment has any styles that have border/padding/margins on the
+ // preceding side, then it can't merge with us.
+ if let Some(ref inline_context) = other.inline_context {
+ for inline_context_node in inline_context.nodes.iter() {
+ if !inline_context_node
+ .flags
+ .contains(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT)
+ {
+ continue;
+ }
+ if inline_context_node.style.logical_margin().inline_start !=
+ LengthPercentageOrAuto::zero()
+ {
+ return false;
+ }
+ if inline_context_node.style.logical_padding().inline_start !=
+ LengthPercentage::zero()
+ {
+ return false;
+ }
+ if inline_context_node
+ .style
+ .logical_border_width()
+ .inline_start !=
+ Au(0)
+ {
+ return false;
+ }
+ }
+ }
+
+ true
+ },
+ _ => false,
+ }
+ }
+
+ /// Returns true if and only if this is the *primary fragment* for the fragment's style object
+ /// (conceptually, though style sharing makes this not really true, of course). The primary
+ /// fragment is the one that draws backgrounds, borders, etc., and takes borders, padding and
+ /// margins into account. Every style object has at most one primary fragment.
+ ///
+ /// At present, all fragments are primary fragments except for inline-block and table wrapper
+ /// fragments. Inline-block fragments are not primary fragments because the corresponding block
+ /// flow is the primary fragment, while table wrapper fragments are not primary fragments
+ /// because the corresponding table flow is the primary fragment.
+ pub fn is_primary_fragment(&self) -> bool {
+ match self.specific {
+ SpecificFragmentInfo::InlineBlock(_) |
+ SpecificFragmentInfo::InlineAbsoluteHypothetical(_) |
+ SpecificFragmentInfo::InlineAbsolute(_) |
+ SpecificFragmentInfo::MulticolColumn |
+ SpecificFragmentInfo::TableWrapper => false,
+ SpecificFragmentInfo::Canvas(_) |
+ SpecificFragmentInfo::Generic |
+ SpecificFragmentInfo::GeneratedContent(_) |
+ SpecificFragmentInfo::Iframe(_) |
+ SpecificFragmentInfo::Image(_) |
+ SpecificFragmentInfo::Media(_) |
+ SpecificFragmentInfo::ScannedText(_) |
+ SpecificFragmentInfo::Svg(_) |
+ SpecificFragmentInfo::Table |
+ SpecificFragmentInfo::TableCell |
+ SpecificFragmentInfo::TableColumn(_) |
+ SpecificFragmentInfo::TableRow |
+ SpecificFragmentInfo::TruncatedFragment(_) |
+ SpecificFragmentInfo::Multicol |
+ SpecificFragmentInfo::UnscannedText(_) => true,
+ }
+ }
+
+ /// Determines the inline sizes of inline-block fragments. These cannot be fully computed until
+ /// inline size assignment has run for the child flow: thus it is computed "late", during
+ /// block size assignment.
+ pub fn update_late_computed_replaced_inline_size_if_necessary(&mut self) {
+ if let SpecificFragmentInfo::InlineBlock(ref mut inline_block_info) = self.specific {
+ let block_flow = FlowRef::deref_mut(&mut inline_block_info.flow_ref).as_block();
+ self.border_box.size.inline = block_flow.fragment.margin_box_inline_size();
+ }
+ }
+
+ pub fn update_late_computed_inline_position_if_necessary(&mut self) {
+ if let SpecificFragmentInfo::InlineAbsoluteHypothetical(ref mut info) = self.specific {
+ let position = self.border_box.start.i;
+ FlowRef::deref_mut(&mut info.flow_ref)
+ .update_late_computed_inline_position_if_necessary(position)
+ }
+ }
+
+ pub fn update_late_computed_block_position_if_necessary(&mut self) {
+ if let SpecificFragmentInfo::InlineAbsoluteHypothetical(ref mut info) = self.specific {
+ let position = self.border_box.start.b;
+ FlowRef::deref_mut(&mut info.flow_ref)
+ .update_late_computed_block_position_if_necessary(position)
+ }
+ }
+
+ pub fn repair_style(&mut self, new_style: &ServoArc<ComputedValues>) {
+ self.style = (*new_style).clone()
+ }
+
+ /// Given the stacking-context-relative position of the containing flow, returns the border box
+ /// of this fragment relative to the parent stacking context. This takes `position: relative`
+ /// into account.
+ ///
+ /// If `coordinate_system` is `Parent`, this returns the border box in the parent stacking
+ /// context's coordinate system. Otherwise, if `coordinate_system` is `Own` and this fragment
+ /// establishes a stacking context itself, this returns a border box anchored at (0, 0). (If
+ /// this fragment does not establish a stacking context, then it always belongs to its parent
+ /// stacking context and thus `coordinate_system` is ignored.)
+ ///
+ /// This is the method you should use for display list construction as well as
+ /// `getBoundingClientRect()` and so forth.
+ pub fn stacking_relative_border_box(
+ &self,
+ stacking_relative_flow_origin: &Vector2D<Au>,
+ relative_containing_block_size: &LogicalSize<Au>,
+ relative_containing_block_mode: WritingMode,
+ coordinate_system: CoordinateSystem,
+ ) -> Rect<Au> {
+ let container_size =
+ relative_containing_block_size.to_physical(relative_containing_block_mode);
+ let border_box = self
+ .border_box
+ .to_physical(self.style.writing_mode, container_size);
+ if coordinate_system == CoordinateSystem::Own && self.establishes_stacking_context() {
+ return Rect::new(Point2D::zero(), border_box.size);
+ }
+
+ // FIXME(pcwalton): This can double-count relative position sometimes for inlines (e.g.
+ // `<div style="position:relative">x</div>`, because the `position:relative` trickles down
+ // to the inline flow. Possibly we should extend the notion of "primary fragment" to fix
+ // this.
+ let relative_position = self.relative_position(relative_containing_block_size);
+ border_box
+ .translate_by_size(&relative_position.to_physical(self.style.writing_mode))
+ .translate(&stacking_relative_flow_origin)
+ }
+
+ /// Given the stacking-context-relative border box, returns the stacking-context-relative
+ /// content box.
+ pub fn stacking_relative_content_box(
+ &self,
+ stacking_relative_border_box: Rect<Au>,
+ ) -> Rect<Au> {
+ let border_padding = self.border_padding.to_physical(self.style.writing_mode);
+ Rect::new(
+ Point2D::new(
+ stacking_relative_border_box.origin.x + border_padding.left,
+ stacking_relative_border_box.origin.y + border_padding.top,
+ ),
+ Size2D::new(
+ stacking_relative_border_box.size.width - border_padding.horizontal(),
+ stacking_relative_border_box.size.height - border_padding.vertical(),
+ ),
+ )
+ }
+
+ /// Returns true if this fragment may establish a reference frame.
+ pub fn can_establish_reference_frame(&self) -> bool {
+ !self.style().get_box().transform.0.is_empty() ||
+ self.style().get_box().perspective != Perspective::None
+ }
+
+ /// Returns true if this fragment has a filter, transform, or perspective property set.
+ pub fn has_filter_transform_or_perspective(&self) -> bool {
+ !self.style().get_box().transform.0.is_empty() ||
+ !self.style().get_effects().filter.0.is_empty() ||
+ self.style().get_box().perspective != Perspective::None
+ }
+
+ /// Returns true if this fragment establishes a new stacking context and false otherwise.
+ pub fn establishes_stacking_context(&self) -> bool {
+ // Text fragments shouldn't create stacking contexts.
+ match self.specific {
+ SpecificFragmentInfo::TruncatedFragment(_) |
+ SpecificFragmentInfo::ScannedText(_) |
+ SpecificFragmentInfo::UnscannedText(_) => return false,
+ _ => {},
+ }
+
+ if self.style().get_effects().opacity != 1.0 {
+ return true;
+ }
+
+ if self.style().get_effects().mix_blend_mode != MixBlendMode::Normal {
+ return true;
+ }
+
+ if self.has_filter_transform_or_perspective() {
+ return true;
+ }
+
+ if self.style().get_box().transform_style == TransformStyle::Preserve3d ||
+ self.style().overrides_transform_style()
+ {
+ return true;
+ }
+
+ // Fixed position and sticky position always create stacking contexts.
+ if self.style().get_box().position == Position::Fixed ||
+ self.style().get_box().position == Position::Sticky
+ {
+ return true;
+ }
+
+ // Statically positioned fragments don't establish stacking contexts if the previous
+ // conditions are not fulfilled. Furthermore, z-index doesn't apply to statically
+ // positioned fragments.
+ if self.style().get_box().position == Position::Static {
+ return false;
+ }
+
+ // For absolutely and relatively positioned fragments we only establish a stacking
+ // context if there is a z-index set.
+ // See https://www.w3.org/TR/CSS2/visuren.html#z-index
+ !self.style().get_position().z_index.is_auto()
+ }
+
+ // Get the effective z-index of this fragment. Z-indices only apply to positioned element
+ // per CSS 2 9.9.1 (http://www.w3.org/TR/CSS2/visuren.html#z-index), so this value may differ
+ // from the value specified in the style.
+ pub fn effective_z_index(&self) -> i32 {
+ match self.style().get_box().position {
+ Position::Static => {},
+ _ => return self.style().get_position().z_index.integer_or(0),
+ }
+
+ if !self.style().get_box().transform.0.is_empty() {
+ return self.style().get_position().z_index.integer_or(0);
+ }
+
+ match self.style().get_box().display {
+ Display::Flex => self.style().get_position().z_index.integer_or(0),
+ _ => 0,
+ }
+ }
+
+ /// Computes the overflow rect of this fragment relative to the start of the flow.
+ pub fn compute_overflow(
+ &self,
+ flow_size: &Size2D<Au>,
+ relative_containing_block_size: &LogicalSize<Au>,
+ ) -> Overflow {
+ let mut border_box = self
+ .border_box
+ .to_physical(self.style.writing_mode, *flow_size);
+
+ // Relative position can cause us to draw outside our border box.
+ //
+ // FIXME(pcwalton): I'm not a fan of the way this makes us crawl though so many styles all
+ // the time. Can't we handle relative positioning by just adjusting `border_box`?
+ let relative_position = self.relative_position(relative_containing_block_size);
+ border_box =
+ border_box.translate_by_size(&relative_position.to_physical(self.style.writing_mode));
+ let mut overflow = Overflow::from_rect(&border_box);
+
+ // Box shadows cause us to draw outside our border box.
+ for box_shadow in &*self.style().get_effects().box_shadow.0 {
+ let offset = Vector2D::new(
+ Au::from(box_shadow.base.horizontal),
+ Au::from(box_shadow.base.vertical),
+ );
+ let inflation = Au::from(box_shadow.spread) +
+ Au::from(box_shadow.base.blur) * BLUR_INFLATION_FACTOR;
+ overflow.paint = overflow
+ .paint
+ .union(&border_box.translate(&offset).inflate(inflation, inflation))
+ }
+
+ // Outlines cause us to draw outside our border box.
+ let outline_width = Au::from(self.style.get_outline().outline_width);
+ if outline_width != Au(0) {
+ overflow.paint = overflow
+ .paint
+ .union(&border_box.inflate(outline_width, outline_width))
+ }
+
+ // Include the overflow of the block flow, if any.
+ match self.specific {
+ SpecificFragmentInfo::InlineBlock(ref info) => {
+ let block_flow = info.flow_ref.as_block();
+ overflow.union(&block_flow.base().overflow);
+ },
+ SpecificFragmentInfo::InlineAbsolute(ref info) => {
+ let block_flow = info.flow_ref.as_block();
+ overflow.union(&block_flow.base().overflow);
+ },
+ _ => (),
+ }
+
+ // FIXME(pcwalton): Sometimes excessively fancy glyphs can make us draw outside our border
+ // box too.
+ overflow
+ }
+
+ pub fn requires_line_break_afterward_if_wrapping_on_newlines(&self) -> bool {
+ match self.specific {
+ SpecificFragmentInfo::TruncatedFragment(ref t) if t.text_info.is_some() => {
+ let text = t.text_info.as_ref().unwrap();
+ text.requires_line_break_afterward_if_wrapping_on_newlines()
+ },
+ SpecificFragmentInfo::ScannedText(ref text) => {
+ text.requires_line_break_afterward_if_wrapping_on_newlines()
+ },
+ _ => false,
+ }
+ }
+
+ pub fn strip_leading_whitespace_if_necessary(&mut self) -> WhitespaceStrippingResult {
+ if self.white_space().preserve_spaces() {
+ return WhitespaceStrippingResult::RetainFragment;
+ }
+
+ return match self.specific {
+ SpecificFragmentInfo::TruncatedFragment(ref mut t) if t.text_info.is_some() => {
+ let scanned_text_fragment_info = t.text_info.as_mut().unwrap();
+ scanned_text(scanned_text_fragment_info, &mut self.border_box)
+ },
+ SpecificFragmentInfo::ScannedText(ref mut scanned_text_fragment_info) => {
+ scanned_text(scanned_text_fragment_info, &mut self.border_box)
+ },
+ SpecificFragmentInfo::UnscannedText(ref mut unscanned_text_fragment_info) => {
+ let mut new_text_string = String::new();
+ let mut modified = false;
+ for (i, character) in unscanned_text_fragment_info.text.char_indices() {
+ if gfx::text::util::is_bidi_control(character) {
+ new_text_string.push(character);
+ continue;
+ }
+ if char_is_whitespace(character) {
+ modified = true;
+ continue;
+ }
+ // Finished processing leading control chars and whitespace.
+ if modified {
+ new_text_string.push_str(&unscanned_text_fragment_info.text[i..]);
+ }
+ break;
+ }
+ if modified {
+ unscanned_text_fragment_info.text = new_text_string.into_boxed_str();
+ }
+
+ WhitespaceStrippingResult::from_unscanned_text_fragment_info(
+ &unscanned_text_fragment_info,
+ )
+ },
+ _ => WhitespaceStrippingResult::RetainFragment,
+ };
+
+ fn scanned_text(
+ scanned_text_fragment_info: &mut ScannedTextFragmentInfo,
+ border_box: &mut LogicalRect<Au>,
+ ) -> WhitespaceStrippingResult {
+ let leading_whitespace_byte_count = scanned_text_fragment_info
+ .text()
+ .find(|c| !char_is_whitespace(c))
+ .unwrap_or(scanned_text_fragment_info.text().len());
+
+ let whitespace_len = ByteIndex(leading_whitespace_byte_count as isize);
+ let whitespace_range =
+ Range::new(scanned_text_fragment_info.range.begin(), whitespace_len);
+ let text_bounds = scanned_text_fragment_info
+ .run
+ .metrics_for_range(&whitespace_range)
+ .bounding_box;
+ border_box.size.inline = border_box.size.inline - text_bounds.size.width;
+ scanned_text_fragment_info.content_size.inline =
+ scanned_text_fragment_info.content_size.inline - text_bounds.size.width;
+
+ scanned_text_fragment_info
+ .range
+ .adjust_by(whitespace_len, -whitespace_len);
+
+ WhitespaceStrippingResult::RetainFragment
+ }
+ }
+
+ /// Returns true if the entire fragment was stripped.
+ pub fn strip_trailing_whitespace_if_necessary(&mut self) -> WhitespaceStrippingResult {
+ if self.white_space().preserve_spaces() {
+ return WhitespaceStrippingResult::RetainFragment;
+ }
+
+ return match self.specific {
+ SpecificFragmentInfo::TruncatedFragment(ref mut t) if t.text_info.is_some() => {
+ let scanned_text_fragment_info = t.text_info.as_mut().unwrap();
+ scanned_text(scanned_text_fragment_info, &mut self.border_box)
+ },
+ SpecificFragmentInfo::ScannedText(ref mut scanned_text_fragment_info) => {
+ scanned_text(scanned_text_fragment_info, &mut self.border_box)
+ },
+ SpecificFragmentInfo::UnscannedText(ref mut unscanned_text_fragment_info) => {
+ let mut trailing_bidi_control_characters_to_retain = Vec::new();
+ let (mut modified, mut last_character_index) = (true, 0);
+ for (i, character) in unscanned_text_fragment_info.text.char_indices().rev() {
+ if gfx::text::util::is_bidi_control(character) {
+ trailing_bidi_control_characters_to_retain.push(character);
+ continue;
+ }
+ if char_is_whitespace(character) {
+ modified = true;
+ continue;
+ }
+ last_character_index = i + character.len_utf8();
+ break;
+ }
+ if modified {
+ let mut text = unscanned_text_fragment_info.text.to_string();
+ text.truncate(last_character_index);
+ for character in trailing_bidi_control_characters_to_retain.iter().rev() {
+ text.push(*character);
+ }
+ unscanned_text_fragment_info.text = text.into_boxed_str();
+ }
+
+ WhitespaceStrippingResult::from_unscanned_text_fragment_info(
+ &unscanned_text_fragment_info,
+ )
+ },
+ _ => WhitespaceStrippingResult::RetainFragment,
+ };
+
+ fn scanned_text(
+ scanned_text_fragment_info: &mut ScannedTextFragmentInfo,
+ border_box: &mut LogicalRect<Au>,
+ ) -> WhitespaceStrippingResult {
+ let mut trailing_whitespace_start_byte = 0;
+ for (i, c) in scanned_text_fragment_info.text().char_indices().rev() {
+ if !char_is_whitespace(c) {
+ trailing_whitespace_start_byte = i + c.len_utf8();
+ break;
+ }
+ }
+ let whitespace_start = ByteIndex(trailing_whitespace_start_byte as isize);
+ let whitespace_len = scanned_text_fragment_info.range.length() - whitespace_start;
+ let mut whitespace_range = Range::new(whitespace_start, whitespace_len);
+ whitespace_range.shift_by(scanned_text_fragment_info.range.begin());
+
+ let text_bounds = scanned_text_fragment_info
+ .run
+ .metrics_for_range(&whitespace_range)
+ .bounding_box;
+ border_box.size.inline -= text_bounds.size.width;
+ scanned_text_fragment_info.content_size.inline -= text_bounds.size.width;
+
+ scanned_text_fragment_info.range.extend_by(-whitespace_len);
+ WhitespaceStrippingResult::RetainFragment
+ }
+ }
+
+ pub fn inline_styles(&self) -> InlineStyleIterator {
+ InlineStyleIterator::new(self)
+ }
+
+ /// Returns the inline-size of this fragment's margin box.
+ pub fn margin_box_inline_size(&self) -> Au {
+ self.border_box.size.inline + self.margin.inline_start_end()
+ }
+
+ /// Returns true if this node *or any of the nodes within its inline fragment context* have
+ /// non-`static` `position`.
+ pub fn is_positioned(&self) -> bool {
+ if self.style.get_box().position != Position::Static {
+ return true;
+ }
+ if let Some(ref inline_context) = self.inline_context {
+ for node in inline_context.nodes.iter() {
+ if node.style.get_box().position != Position::Static {
+ return true;
+ }
+ }
+ }
+ false
+ }
+
+ /// Returns true if this node is absolutely positioned.
+ pub fn is_absolutely_positioned(&self) -> bool {
+ self.style.get_box().position == Position::Absolute
+ }
+
+ pub fn is_inline_absolute(&self) -> bool {
+ match self.specific {
+ SpecificFragmentInfo::InlineAbsolute(..) => true,
+ _ => false,
+ }
+ }
+
+ pub fn meld_with_next_inline_fragment(&mut self, next_fragment: &Fragment) {
+ if let Some(ref mut inline_context_of_this_fragment) = self.inline_context {
+ if let Some(ref inline_context_of_next_fragment) = next_fragment.inline_context {
+ for (
+ inline_context_node_from_this_fragment,
+ inline_context_node_from_next_fragment,
+ ) in inline_context_of_this_fragment
+ .nodes
+ .iter_mut()
+ .rev()
+ .zip(inline_context_of_next_fragment.nodes.iter().rev())
+ {
+ if !inline_context_node_from_next_fragment
+ .flags
+ .contains(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT)
+ {
+ continue;
+ }
+ if inline_context_node_from_next_fragment.address !=
+ inline_context_node_from_this_fragment.address
+ {
+ continue;
+ }
+ inline_context_node_from_this_fragment
+ .flags
+ .insert(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT);
+ }
+ }
+ }
+ }
+
+ pub fn meld_with_prev_inline_fragment(&mut self, prev_fragment: &Fragment) {
+ if let Some(ref mut inline_context_of_this_fragment) = self.inline_context {
+ if let Some(ref inline_context_of_prev_fragment) = prev_fragment.inline_context {
+ for (
+ inline_context_node_from_prev_fragment,
+ inline_context_node_from_this_fragment,
+ ) in inline_context_of_prev_fragment
+ .nodes
+ .iter()
+ .rev()
+ .zip(inline_context_of_this_fragment.nodes.iter_mut().rev())
+ {
+ if !inline_context_node_from_prev_fragment
+ .flags
+ .contains(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT)
+ {
+ continue;
+ }
+ if inline_context_node_from_prev_fragment.address !=
+ inline_context_node_from_this_fragment.address
+ {
+ continue;
+ }
+ inline_context_node_from_this_fragment
+ .flags
+ .insert(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT);
+ }
+ }
+ }
+ }
+
+ /// Returns true if any of the inline styles associated with this fragment have
+ /// `vertical-align` set to `top` or `bottom`.
+ pub fn is_vertically_aligned_to_top_or_bottom(&self) -> bool {
+ fn is_top_or_bottom(v: &VerticalAlign) -> bool {
+ match *v {
+ VerticalAlign::Keyword(VerticalAlignKeyword::Top) |
+ VerticalAlign::Keyword(VerticalAlignKeyword::Bottom) => true,
+ _ => false,
+ }
+ }
+
+ if is_top_or_bottom(&self.style.get_box().vertical_align) {
+ return true;
+ }
+
+ if let Some(ref inline_context) = self.inline_context {
+ for node in &inline_context.nodes {
+ if is_top_or_bottom(&node.style.get_box().vertical_align) {
+ return true;
+ }
+ }
+ }
+ false
+ }
+
+ pub fn is_text_or_replaced(&self) -> bool {
+ match self.specific {
+ SpecificFragmentInfo::Generic |
+ SpecificFragmentInfo::InlineAbsolute(_) |
+ SpecificFragmentInfo::InlineAbsoluteHypothetical(_) |
+ SpecificFragmentInfo::InlineBlock(_) |
+ SpecificFragmentInfo::Multicol |
+ SpecificFragmentInfo::MulticolColumn |
+ SpecificFragmentInfo::Table |
+ SpecificFragmentInfo::TableCell |
+ SpecificFragmentInfo::TableColumn(_) |
+ SpecificFragmentInfo::TableRow |
+ SpecificFragmentInfo::TableWrapper => false,
+ SpecificFragmentInfo::Canvas(_) |
+ SpecificFragmentInfo::GeneratedContent(_) |
+ SpecificFragmentInfo::Iframe(_) |
+ SpecificFragmentInfo::Image(_) |
+ SpecificFragmentInfo::Media(_) |
+ SpecificFragmentInfo::ScannedText(_) |
+ SpecificFragmentInfo::TruncatedFragment(_) |
+ SpecificFragmentInfo::Svg(_) |
+ SpecificFragmentInfo::UnscannedText(_) => true,
+ }
+ }
+
+ /// Returns the 4D matrix representing this fragment's transform.
+ pub fn transform_matrix(
+ &self,
+ stacking_relative_border_box: &Rect<Au>,
+ ) -> Option<LayoutTransform> {
+ let list = &self.style.get_box().transform;
+ let transform = LayoutTransform::from_untyped(
+ &list
+ .to_transform_3d_matrix(Some(stacking_relative_border_box))
+ .ok()?
+ .0,
+ );
+
+ let transform_origin = &self.style.get_box().transform_origin;
+ let transform_origin_x = transform_origin
+ .horizontal
+ .to_used_value(stacking_relative_border_box.size.width)
+ .to_f32_px();
+ let transform_origin_y = transform_origin
+ .vertical
+ .to_used_value(stacking_relative_border_box.size.height)
+ .to_f32_px();
+ let transform_origin_z = transform_origin.depth.px();
+
+ let pre_transform = LayoutTransform::create_translation(
+ transform_origin_x,
+ transform_origin_y,
+ transform_origin_z,
+ );
+ let post_transform = LayoutTransform::create_translation(
+ -transform_origin_x,
+ -transform_origin_y,
+ -transform_origin_z,
+ );
+
+ Some(pre_transform.pre_mul(&transform).pre_mul(&post_transform))
+ }
+
+ /// Returns the 4D matrix representing this fragment's perspective.
+ pub fn perspective_matrix(
+ &self,
+ stacking_relative_border_box: &Rect<Au>,
+ ) -> Option<LayoutTransform> {
+ match self.style().get_box().perspective {
+ Perspective::Length(length) => {
+ let perspective_origin = self.style().get_box().perspective_origin;
+ let perspective_origin = Point2D::new(
+ perspective_origin
+ .horizontal
+ .to_used_value(stacking_relative_border_box.size.width),
+ perspective_origin
+ .vertical
+ .to_used_value(stacking_relative_border_box.size.height),
+ )
+ .to_layout();
+
+ let pre_transform = LayoutTransform::create_translation(
+ perspective_origin.x,
+ perspective_origin.y,
+ 0.0,
+ );
+ let post_transform = LayoutTransform::create_translation(
+ -perspective_origin.x,
+ -perspective_origin.y,
+ 0.0,
+ );
+
+ let perspective_matrix = LayoutTransform::from_untyped(
+ &transform::create_perspective_matrix(length.px()),
+ );
+
+ Some(
+ pre_transform
+ .pre_mul(&perspective_matrix)
+ .pre_mul(&post_transform),
+ )
+ },
+ Perspective::None => None,
+ }
+ }
+}
+
+impl fmt::Debug for Fragment {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let border_padding_string = if !self.border_padding.is_zero() {
+ format!("\nborder_padding={:?}", self.border_padding)
+ } else {
+ "".to_owned()
+ };
+
+ let margin_string = if !self.margin.is_zero() {
+ format!("\nmargin={:?}", self.margin)
+ } else {
+ "".to_owned()
+ };
+
+ let damage_string = if self.restyle_damage != RestyleDamage::empty() {
+ format!("\ndamage={:?}", self.restyle_damage)
+ } else {
+ "".to_owned()
+ };
+
+ write!(
+ f,
+ "\n{}({}) [{:?}]\nborder_box={:?}{}{}{}",
+ self.specific.get_type(),
+ self.debug_id,
+ self.specific,
+ self.border_box,
+ border_padding_string,
+ margin_string,
+ damage_string
+ )
+ }
+}
+
+bitflags! {
+ struct QuantitiesIncludedInIntrinsicInlineSizes: u8 {
+ const INTRINSIC_INLINE_SIZE_INCLUDES_MARGINS = 0x01;
+ const INTRINSIC_INLINE_SIZE_INCLUDES_PADDING = 0x02;
+ const INTRINSIC_INLINE_SIZE_INCLUDES_BORDER = 0x04;
+ const INTRINSIC_INLINE_SIZE_INCLUDES_SPECIFIED = 0x08;
+ }
+}
+
+bitflags! {
+ // Various flags we can use when splitting fragments. See
+ // `calculate_split_position_using_breaking_strategy()`.
+ struct SplitOptions: u8 {
+ #[doc = "True if this is the first fragment on the line."]
+ const STARTS_LINE = 0x01;
+ #[doc = "True if we should attempt to split at character boundaries if this split fails. \
+ This is used to implement `overflow-wrap: break-word`."]
+ const RETRY_AT_CHARACTER_BOUNDARIES = 0x02;
+ }
+}
+
+/// A top-down fragment border box iteration handler.
+pub trait FragmentBorderBoxIterator {
+ /// The operation to perform.
+ fn process(&mut self, fragment: &Fragment, level: i32, overflow: &Rect<Au>);
+
+ /// Returns true if this fragment must be processed in-order. If this returns false,
+ /// we skip the operation for this fragment, but continue processing siblings.
+ fn should_process(&mut self, fragment: &Fragment) -> bool;
+}
+
+/// The coordinate system used in `stacking_relative_border_box()`. See the documentation of that
+/// method for details.
+#[derive(Clone, Debug, PartialEq)]
+pub enum CoordinateSystem {
+ /// The border box returned is relative to the fragment's parent stacking context.
+ Parent,
+ /// The border box returned is relative to the fragment's own stacking context, if applicable.
+ Own,
+}
+
+pub struct InlineStyleIterator<'a> {
+ fragment: &'a Fragment,
+ inline_style_index: usize,
+ primary_style_yielded: bool,
+}
+
+impl<'a> Iterator for InlineStyleIterator<'a> {
+ type Item = &'a ComputedValues;
+
+ fn next(&mut self) -> Option<&'a ComputedValues> {
+ if !self.primary_style_yielded {
+ self.primary_style_yielded = true;
+ return Some(&*self.fragment.style);
+ }
+ let inline_context = self.fragment.inline_context.as_ref()?;
+ let inline_style_index = self.inline_style_index;
+ if inline_style_index == inline_context.nodes.len() {
+ return None;
+ }
+ self.inline_style_index += 1;
+ Some(&*inline_context.nodes[inline_style_index].style)
+ }
+}
+
+impl<'a> InlineStyleIterator<'a> {
+ fn new(fragment: &Fragment) -> InlineStyleIterator {
+ InlineStyleIterator {
+ fragment: fragment,
+ inline_style_index: 0,
+ primary_style_yielded: false,
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum WhitespaceStrippingResult {
+ RetainFragment,
+ FragmentContainedOnlyBidiControlCharacters,
+ FragmentContainedOnlyWhitespace,
+}
+
+impl WhitespaceStrippingResult {
+ fn from_unscanned_text_fragment_info(
+ info: &UnscannedTextFragmentInfo,
+ ) -> WhitespaceStrippingResult {
+ if info.text.is_empty() {
+ WhitespaceStrippingResult::FragmentContainedOnlyWhitespace
+ } else if info.text.chars().all(gfx::text::util::is_bidi_control) {
+ WhitespaceStrippingResult::FragmentContainedOnlyBidiControlCharacters
+ } else {
+ WhitespaceStrippingResult::RetainFragment
+ }
+ }
+}
+
+/// The overflow area. We need two different notions of overflow: paint overflow and scrollable
+/// overflow.
+#[derive(Clone, Copy, Debug)]
+pub struct Overflow {
+ pub scroll: Rect<Au>,
+ pub paint: Rect<Au>,
+}
+
+impl Overflow {
+ pub fn new() -> Overflow {
+ Overflow {
+ scroll: Rect::zero(),
+ paint: Rect::zero(),
+ }
+ }
+
+ pub fn from_rect(border_box: &Rect<Au>) -> Overflow {
+ Overflow {
+ scroll: *border_box,
+ paint: *border_box,
+ }
+ }
+
+ pub fn union(&mut self, other: &Overflow) {
+ self.scroll = self.scroll.union(&other.scroll);
+ self.paint = self.paint.union(&other.paint);
+ }
+
+ pub fn translate(&mut self, by: &Vector2D<Au>) {
+ self.scroll = self.scroll.translate(by);
+ self.paint = self.paint.translate(by);
+ }
+}
+
+bitflags! {
+ pub struct FragmentFlags: u8 {
+ // TODO(stshine): find a better name since these flags can also be used for grid item.
+ /// Whether this fragment represents a child in a row flex container.
+ const IS_INLINE_FLEX_ITEM = 0b0000_0001;
+ /// Whether this fragment represents a child in a column flex container.
+ const IS_BLOCK_FLEX_ITEM = 0b0000_0010;
+ /// Whether this fragment represents the generated text from a text-overflow clip.
+ const IS_ELLIPSIS = 0b0000_0100;
+ }
+}
+
+/// Specified distances from the margin edge of a block to its content in the inline direction.
+/// These are returned by `guess_inline_content_edge_offsets()` and are used in the float placement
+/// speculation logic.
+#[derive(Clone, Copy, Debug)]
+pub struct SpeculatedInlineContentEdgeOffsets {
+ pub start: Au,
+ pub end: Au,
+}
+
+#[cfg(not(debug_assertions))]
+#[derive(Clone)]
+struct DebugId;
+
+#[cfg(debug_assertions)]
+#[derive(Clone)]
+struct DebugId(u16);
+
+#[cfg(not(debug_assertions))]
+impl DebugId {
+ pub fn new() -> DebugId {
+ DebugId
+ }
+}
+
+#[cfg(debug_assertions)]
+impl DebugId {
+ pub fn new() -> DebugId {
+ DebugId(layout_debug::generate_unique_debug_id())
+ }
+}
+
+#[cfg(not(debug_assertions))]
+impl fmt::Display for DebugId {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{:p}", &self)
+ }
+}
+
+#[cfg(debug_assertions)]
+impl fmt::Display for DebugId {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+
+#[cfg(not(debug_assertions))]
+impl Serialize for DebugId {
+ fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+ serializer.serialize_str(&format!("{:p}", &self))
+ }
+}
+
+#[cfg(debug_assertions)]
+impl Serialize for DebugId {
+ fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+ serializer.serialize_u16(self.0)
+ }
+}
diff --git a/components/layout_2020/generated_content.rs b/components/layout_2020/generated_content.rs
new file mode 100644
index 00000000000..e1ebc570a62
--- /dev/null
+++ b/components/layout_2020/generated_content.rs
@@ -0,0 +1,651 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+//! The generated content assignment phase.
+//!
+//! This phase handles CSS counters, quotes, and ordered lists per CSS § 12.3-12.5. It cannot be
+//! done in parallel and is therefore a sequential pass that runs on as little of the flow tree
+//! as possible.
+
+use crate::context::{with_thread_local_font_context, LayoutContext};
+use crate::display_list::items::OpaqueNode;
+use crate::flow::{Flow, FlowFlags, GetBaseFlow, ImmutableFlowUtils};
+use crate::fragment::{
+ Fragment, GeneratedContentInfo, SpecificFragmentInfo, UnscannedTextFragmentInfo,
+};
+use crate::text::TextRunScanner;
+use crate::traversal::InorderFlowTraversal;
+use script_layout_interface::wrapper_traits::PseudoElementType;
+use smallvec::SmallVec;
+use std::collections::{HashMap, LinkedList};
+use style::computed_values::display::T as Display;
+use style::computed_values::list_style_type::T as ListStyleType;
+use style::properties::ComputedValues;
+use style::selector_parser::RestyleDamage;
+use style::servo::restyle_damage::ServoRestyleDamage;
+use style::values::generics::counters::ContentItem;
+
+// Decimal styles per CSS-COUNTER-STYLES § 6.1:
+static DECIMAL: [char; 10] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
+// TODO(pcwalton): `decimal-leading-zero`
+static ARABIC_INDIC: [char; 10] = ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩'];
+// TODO(pcwalton): `armenian`, `upper-armenian`, `lower-armenian`
+static BENGALI: [char; 10] = [
+ '০', '১', '২', '৩', '৪', '৫', '৬', '৭', '৮', '৯',
+];
+static CAMBODIAN: [char; 10] = [
+ '០', '១', '២', '៣', '៤', '៥', '៦', '៧', '៨', '៩',
+];
+// TODO(pcwalton): Suffix for CJK decimal.
+static CJK_DECIMAL: [char; 10] = [
+ '〇', '一', '二', '三', '四', '五', '六', '七', '八', '九',
+];
+static DEVANAGARI: [char; 10] = [
+ '०', '१', '२', '३', '४', '५', '६', '७', '८', '९',
+];
+// TODO(pcwalton): `georgian`
+static GUJARATI: [char; 10] = [
+ '૦', '૧', '૨', '૩', '૪', '૫', '૬', '૭', '૮', '૯',
+];
+static GURMUKHI: [char; 10] = [
+ '੦', '੧', '੨', '੩', '੪', '੫', '੬', '੭', '੮', '੯',
+];
+// TODO(pcwalton): `hebrew`
+static KANNADA: [char; 10] = [
+ '೦', '೧', '೨', '೩', '೪', '೫', '೬', '೭', '೮', '೯',
+];
+static LAO: [char; 10] = [
+ '໐', '໑', '໒', '໓', '໔', '໕', '໖', '໗', '໘', '໙',
+];
+static MALAYALAM: [char; 10] = [
+ '൦', '൧', '൨', '൩', '൪', '൫', '൬', '൭', '൮', '൯',
+];
+static MONGOLIAN: [char; 10] = [
+ '᠐', '᠑', '᠒', '᠓', '᠔', '᠕', '᠖', '᠗', '᠘', '᠙',
+];
+static MYANMAR: [char; 10] = [
+ '၀', '၁', '၂', '၃', '၄', '၅', '၆', '၇', '၈', '၉',
+];
+static ORIYA: [char; 10] = [
+ '୦', '୧', '୨', '୩', '୪', '୫', '୬', '୭', '୮', '୯',
+];
+static PERSIAN: [char; 10] = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
+// TODO(pcwalton): `lower-roman`, `upper-roman`
+static TELUGU: [char; 10] = [
+ '౦', '౧', '౨', '౩', '౪', '౫', '౬', '౭', '౮', '౯',
+];
+static THAI: [char; 10] = [
+ '๐', '๑', '๒', '๓', '๔', '๕', '๖', '๗', '๘', '๙',
+];
+static TIBETAN: [char; 10] = [
+ '༠', '༡', '༢', '༣', '༤', '༥', '༦', '༧', '༨', '༩',
+];
+
+// Alphabetic styles per CSS-COUNTER-STYLES § 6.2:
+static LOWER_ALPHA: [char; 26] = [
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
+ 't', 'u', 'v', 'w', 'x', 'y', 'z',
+];
+static UPPER_ALPHA: [char; 26] = [
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
+ 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+];
+static CJK_EARTHLY_BRANCH: [char; 12] = [
+ '子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥',
+];
+static CJK_HEAVENLY_STEM: [char; 10] = [
+ '甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸',
+];
+static LOWER_GREEK: [char; 24] = [
+ 'α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ', 'ι', 'κ', 'λ', 'μ', 'ν', 'ξ', 'ο', 'π',
+ 'ρ', 'σ', 'τ', 'υ', 'φ', 'χ', 'ψ', 'ω',
+];
+static HIRAGANA: [char; 48] = [
+ 'あ', 'い', 'う', 'え', 'お', 'か', 'き', 'く', 'け', 'こ', 'さ', 'し', 'す',
+ 'せ', 'そ', 'た', 'ち', 'つ', 'て', 'と', 'な', 'に', 'ぬ', 'ね', 'の', 'は',
+ 'ひ', 'ふ', 'へ', 'ほ', 'ま', 'み', 'む', 'め', 'も', 'や', 'ゆ', 'よ', 'ら',
+ 'り', 'る', 'れ', 'ろ', 'わ', 'ゐ', 'ゑ', 'を', 'ん',
+];
+static HIRAGANA_IROHA: [char; 47] = [
+ 'い', 'ろ', 'は', 'に', 'ほ', 'へ', 'と', 'ち', 'り', 'ぬ', 'る', 'を', 'わ',
+ 'か', 'よ', 'た', 'れ', 'そ', 'つ', 'ね', 'な', 'ら', 'む', 'う', 'ゐ', 'の',
+ 'お', 'く', 'や', 'ま', 'け', 'ふ', 'こ', 'え', 'て', 'あ', 'さ', 'き', 'ゆ',
+ 'め', 'み', 'し', 'ゑ', 'ひ', 'も', 'せ', 'す',
+];
+static KATAKANA: [char; 48] = [
+ 'ア', 'イ', 'ウ', 'エ', 'オ', 'カ', 'キ', 'ク', 'ケ', 'コ', 'サ', 'シ', 'ス',
+ 'セ', 'ソ', 'タ', 'チ', 'ツ', 'テ', 'ト', 'ナ', 'ニ', 'ヌ', 'ネ', 'ノ', 'ハ',
+ 'ヒ', 'フ', 'ヘ', 'ホ', 'マ', 'ミ', 'ム', 'メ', 'モ', 'ヤ', 'ユ', 'ヨ', 'ラ',
+ 'リ', 'ル', 'レ', 'ロ', 'ワ', 'ヰ', 'ヱ', 'ヲ', 'ン',
+];
+static KATAKANA_IROHA: [char; 47] = [
+ 'イ', 'ロ', 'ハ', 'ニ', 'ホ', 'ヘ', 'ト', 'チ', 'リ', 'ヌ', 'ル', 'ヲ', 'ワ',
+ 'カ', 'ヨ', 'タ', 'レ', 'ソ', 'ツ', 'ネ', 'ナ', 'ラ', 'ム', 'ウ', 'ヰ', 'ノ',
+ 'オ', 'ク', 'ヤ', 'マ', 'ケ', 'フ', 'コ', 'エ', 'テ', 'ア', 'サ', 'キ', 'ユ',
+ 'メ', 'ミ', 'シ', 'ヱ', 'ヒ', 'モ', 'セ', 'ス',
+];
+
+/// The generated content resolution traversal.
+pub struct ResolveGeneratedContent<'a> {
+ /// The layout context.
+ layout_context: &'a LayoutContext<'a>,
+ /// The counter representing an ordered list item.
+ list_item: Counter,
+ /// Named CSS counters.
+ counters: HashMap<String, Counter>,
+ /// The level of quote nesting.
+ quote: u32,
+}
+
+impl<'a> ResolveGeneratedContent<'a> {
+ /// Creates a new generated content resolution traversal.
+ pub fn new(layout_context: &'a LayoutContext) -> ResolveGeneratedContent<'a> {
+ ResolveGeneratedContent {
+ layout_context: layout_context,
+ list_item: Counter::new(),
+ counters: HashMap::new(),
+ quote: 0,
+ }
+ }
+}
+
+impl<'a> InorderFlowTraversal for ResolveGeneratedContent<'a> {
+ #[inline]
+ fn process(&mut self, flow: &mut dyn Flow, level: u32) {
+ let mut mutator = ResolveGeneratedContentFragmentMutator {
+ traversal: self,
+ level: level,
+ is_block: flow.is_block_like(),
+ incremented: false,
+ };
+ flow.mutate_fragments(&mut |fragment| mutator.mutate_fragment(fragment))
+ }
+
+ #[inline]
+ fn should_process_subtree(&mut self, flow: &mut dyn Flow) -> bool {
+ flow.base()
+ .restyle_damage
+ .intersects(ServoRestyleDamage::RESOLVE_GENERATED_CONTENT) ||
+ flow.base().flags.intersects(
+ FlowFlags::AFFECTS_COUNTERS | FlowFlags::HAS_COUNTER_AFFECTING_CHILDREN,
+ )
+ }
+}
+
+/// The object that mutates the generated content fragments.
+struct ResolveGeneratedContentFragmentMutator<'a, 'b: 'a> {
+ /// The traversal.
+ traversal: &'a mut ResolveGeneratedContent<'b>,
+ /// The level we're at in the flow tree.
+ level: u32,
+ /// Whether this flow is a block flow.
+ is_block: bool,
+ /// Whether we've incremented the counter yet.
+ incremented: bool,
+}
+
+impl<'a, 'b> ResolveGeneratedContentFragmentMutator<'a, 'b> {
+ fn mutate_fragment(&mut self, fragment: &mut Fragment) {
+ // We only reset and/or increment counters once per flow. This avoids double-incrementing
+ // counters on list items (once for the main fragment and once for the marker).
+ if !self.incremented {
+ self.reset_and_increment_counters_as_necessary(fragment);
+ }
+
+ let mut list_style_type = fragment.style().get_list().list_style_type;
+ if fragment.style().get_box().display != Display::ListItem {
+ list_style_type = ListStyleType::None
+ }
+
+ let mut new_info = None;
+ {
+ let info =
+ if let SpecificFragmentInfo::GeneratedContent(ref mut info) = fragment.specific {
+ info
+ } else {
+ return;
+ };
+
+ match **info {
+ GeneratedContentInfo::ListItem => {
+ new_info = self.traversal.list_item.render(
+ self.traversal.layout_context,
+ fragment.node,
+ fragment.pseudo.clone(),
+ fragment.style.clone(),
+ list_style_type,
+ RenderingMode::Suffix(".\u{00a0}"),
+ )
+ },
+ GeneratedContentInfo::Empty |
+ GeneratedContentInfo::ContentItem(ContentItem::String(_)) => {
+ // Nothing to do here.
+ },
+ GeneratedContentInfo::ContentItem(ContentItem::Counter(
+ ref counter_name,
+ counter_style,
+ )) => {
+ let temporary_counter = Counter::new();
+ let counter = self
+ .traversal
+ .counters
+ .get(&*counter_name.0)
+ .unwrap_or(&temporary_counter);
+ new_info = counter.render(
+ self.traversal.layout_context,
+ fragment.node,
+ fragment.pseudo.clone(),
+ fragment.style.clone(),
+ counter_style,
+ RenderingMode::Plain,
+ )
+ },
+ GeneratedContentInfo::ContentItem(ContentItem::Counters(
+ ref counter_name,
+ ref separator,
+ counter_style,
+ )) => {
+ let temporary_counter = Counter::new();
+ let counter = self
+ .traversal
+ .counters
+ .get(&*counter_name.0)
+ .unwrap_or(&temporary_counter);
+ new_info = counter.render(
+ self.traversal.layout_context,
+ fragment.node,
+ fragment.pseudo,
+ fragment.style.clone(),
+ counter_style,
+ RenderingMode::All(&separator),
+ );
+ },
+ GeneratedContentInfo::ContentItem(ContentItem::OpenQuote) => {
+ new_info = render_text(
+ self.traversal.layout_context,
+ fragment.node,
+ fragment.pseudo,
+ fragment.style.clone(),
+ self.quote(&*fragment.style, false),
+ );
+ self.traversal.quote += 1
+ },
+ GeneratedContentInfo::ContentItem(ContentItem::CloseQuote) => {
+ if self.traversal.quote >= 1 {
+ self.traversal.quote -= 1
+ }
+
+ new_info = render_text(
+ self.traversal.layout_context,
+ fragment.node,
+ fragment.pseudo,
+ fragment.style.clone(),
+ self.quote(&*fragment.style, true),
+ );
+ },
+ GeneratedContentInfo::ContentItem(ContentItem::NoOpenQuote) => {
+ self.traversal.quote += 1
+ },
+ GeneratedContentInfo::ContentItem(ContentItem::NoCloseQuote) => {
+ if self.traversal.quote >= 1 {
+ self.traversal.quote -= 1
+ }
+ },
+ GeneratedContentInfo::ContentItem(ContentItem::Url(..)) => {
+ unreachable!("Servo doesn't parse content: url(..) yet")
+ },
+ }
+ };
+
+ fragment.specific = match new_info {
+ Some(new_info) => new_info,
+ // If the fragment did not generate any content, replace it with a no-op placeholder
+ // so that it isn't processed again on the next layout. FIXME (mbrubeck): When
+ // processing an inline flow, this traversal should be allowed to insert or remove
+ // fragments. Then we can just remove these fragments rather than adding placeholders.
+ None => SpecificFragmentInfo::GeneratedContent(Box::new(GeneratedContentInfo::Empty)),
+ };
+ }
+
+ fn reset_and_increment_counters_as_necessary(&mut self, fragment: &mut Fragment) {
+ let mut list_style_type = fragment.style().get_list().list_style_type;
+ if !self.is_block || fragment.style().get_box().display != Display::ListItem {
+ list_style_type = ListStyleType::None
+ }
+
+ match list_style_type {
+ ListStyleType::Disc |
+ ListStyleType::None |
+ ListStyleType::Circle |
+ ListStyleType::Square |
+ ListStyleType::DisclosureOpen |
+ ListStyleType::DisclosureClosed => {},
+ _ => self.traversal.list_item.increment(self.level, 1),
+ }
+
+ // Truncate down counters.
+ for (_, counter) in &mut self.traversal.counters {
+ counter.truncate_to_level(self.level);
+ }
+ self.traversal.list_item.truncate_to_level(self.level);
+
+ for pair in &*fragment.style().get_counters().counter_reset {
+ let counter_name = &*pair.name.0;
+ if let Some(ref mut counter) = self.traversal.counters.get_mut(counter_name) {
+ counter.reset(self.level, pair.value);
+ continue;
+ }
+
+ let mut counter = Counter::new();
+ counter.reset(self.level, pair.value);
+ self.traversal
+ .counters
+ .insert(counter_name.to_owned(), counter);
+ }
+
+ for pair in &*fragment.style().get_counters().counter_increment {
+ let counter_name = &*pair.name.0;
+ if let Some(ref mut counter) = self.traversal.counters.get_mut(counter_name) {
+ counter.increment(self.level, pair.value);
+ continue;
+ }
+
+ let mut counter = Counter::new();
+ counter.increment(self.level, pair.value);
+ self.traversal
+ .counters
+ .insert(counter_name.to_owned(), counter);
+ }
+
+ self.incremented = true
+ }
+
+ fn quote(&self, style: &ComputedValues, close: bool) -> String {
+ let quotes = &style.get_list().quotes;
+ if quotes.0.is_empty() {
+ return String::new();
+ }
+ let pair = if self.traversal.quote as usize >= quotes.0.len() {
+ quotes.0.last().unwrap()
+ } else {
+ &quotes.0[self.traversal.quote as usize]
+ };
+ if close {
+ pair.closing.to_string()
+ } else {
+ pair.opening.to_string()
+ }
+ }
+}
+
+/// A counter per CSS 2.1 § 12.4.
+struct Counter {
+ /// The values at each level.
+ values: Vec<CounterValue>,
+}
+
+impl Counter {
+ fn new() -> Counter {
+ Counter { values: Vec::new() }
+ }
+
+ fn reset(&mut self, level: u32, value: i32) {
+ // Do we have an instance of the counter at this level? If so, just mutate it.
+ if let Some(ref mut existing_value) = self.values.last_mut() {
+ if level == existing_value.level {
+ existing_value.value = value;
+ return;
+ }
+ }
+
+ // Otherwise, push a new instance of the counter.
+ self.values.push(CounterValue {
+ level: level,
+ value: value,
+ })
+ }
+
+ fn truncate_to_level(&mut self, level: u32) {
+ if let Some(position) = self.values.iter().position(|value| value.level > level) {
+ self.values.truncate(position)
+ }
+ }
+
+ fn increment(&mut self, level: u32, amount: i32) {
+ if let Some(ref mut value) = self.values.last_mut() {
+ value.value += amount;
+ return;
+ }
+
+ self.values.push(CounterValue {
+ level: level,
+ value: amount,
+ })
+ }
+
+ fn render(
+ &self,
+ layout_context: &LayoutContext,
+ node: OpaqueNode,
+ pseudo: PseudoElementType,
+ style: crate::ServoArc<ComputedValues>,
+ list_style_type: ListStyleType,
+ mode: RenderingMode,
+ ) -> Option<SpecificFragmentInfo> {
+ let mut string = String::new();
+ match mode {
+ RenderingMode::Plain => {
+ let value = match self.values.last() {
+ Some(ref value) => value.value,
+ None => 0,
+ };
+ push_representation(value, list_style_type, &mut string)
+ },
+ RenderingMode::Suffix(suffix) => {
+ let value = match self.values.last() {
+ Some(ref value) => value.value,
+ None => 0,
+ };
+ push_representation(value, list_style_type, &mut string);
+ string.push_str(suffix)
+ },
+ RenderingMode::All(separator) => {
+ let mut first = true;
+ for value in &self.values {
+ if !first {
+ string.push_str(separator)
+ }
+ first = false;
+ push_representation(value.value, list_style_type, &mut string)
+ }
+ },
+ }
+
+ if string.is_empty() {
+ None
+ } else {
+ render_text(layout_context, node, pseudo, style, string)
+ }
+ }
+}
+
+/// How a counter value is to be rendered.
+enum RenderingMode<'a> {
+ /// The innermost counter value is rendered with no extra decoration.
+ Plain,
+ /// The innermost counter value is rendered with the given string suffix.
+ Suffix(&'a str),
+ /// All values of the counter are rendered with the given separator string between them.
+ All(&'a str),
+}
+
+/// The value of a counter at a given level.
+struct CounterValue {
+ /// The level of the flow tree that this corresponds to.
+ level: u32,
+ /// The value of the counter at this level.
+ value: i32,
+}
+
+/// Creates fragment info for a literal string.
+fn render_text(
+ layout_context: &LayoutContext,
+ node: OpaqueNode,
+ pseudo: PseudoElementType,
+ style: crate::ServoArc<ComputedValues>,
+ string: String,
+) -> Option<SpecificFragmentInfo> {
+ let mut fragments = LinkedList::new();
+ let info = SpecificFragmentInfo::UnscannedText(Box::new(UnscannedTextFragmentInfo::new(
+ string.into_boxed_str(),
+ None,
+ )));
+ fragments.push_back(Fragment::from_opaque_node_and_style(
+ node,
+ pseudo,
+ style.clone(),
+ style,
+ RestyleDamage::rebuild_and_reflow(),
+ info,
+ ));
+ // FIXME(pcwalton): This should properly handle multiple marker fragments. This could happen
+ // due to text run splitting.
+ let fragments = with_thread_local_font_context(layout_context, |font_context| {
+ TextRunScanner::new().scan_for_runs(font_context, fragments)
+ });
+ if fragments.is_empty() {
+ None
+ } else {
+ Some(fragments.fragments.into_iter().next().unwrap().specific)
+ }
+}
+
+/// Appends string that represents the value rendered using the system appropriate for the given
+/// `list-style-type` onto the given string.
+fn push_representation(value: i32, list_style_type: ListStyleType, accumulator: &mut String) {
+ match list_style_type {
+ ListStyleType::None => {},
+ ListStyleType::Disc |
+ ListStyleType::Circle |
+ ListStyleType::Square |
+ ListStyleType::DisclosureOpen |
+ ListStyleType::DisclosureClosed => accumulator.push(static_representation(list_style_type)),
+ ListStyleType::Decimal => push_numeric_representation(value, &DECIMAL, accumulator),
+ ListStyleType::ArabicIndic => {
+ push_numeric_representation(value, &ARABIC_INDIC, accumulator)
+ },
+ ListStyleType::Bengali => push_numeric_representation(value, &BENGALI, accumulator),
+ ListStyleType::Cambodian | ListStyleType::Khmer => {
+ push_numeric_representation(value, &CAMBODIAN, accumulator)
+ },
+ ListStyleType::CjkDecimal => push_numeric_representation(value, &CJK_DECIMAL, accumulator),
+ ListStyleType::Devanagari => push_numeric_representation(value, &DEVANAGARI, accumulator),
+ ListStyleType::Gujarati => push_numeric_representation(value, &GUJARATI, accumulator),
+ ListStyleType::Gurmukhi => push_numeric_representation(value, &GURMUKHI, accumulator),
+ ListStyleType::Kannada => push_numeric_representation(value, &KANNADA, accumulator),
+ ListStyleType::Lao => push_numeric_representation(value, &LAO, accumulator),
+ ListStyleType::Malayalam => push_numeric_representation(value, &MALAYALAM, accumulator),
+ ListStyleType::Mongolian => push_numeric_representation(value, &MONGOLIAN, accumulator),
+ ListStyleType::Myanmar => push_numeric_representation(value, &MYANMAR, accumulator),
+ ListStyleType::Oriya => push_numeric_representation(value, &ORIYA, accumulator),
+ ListStyleType::Persian => push_numeric_representation(value, &PERSIAN, accumulator),
+ ListStyleType::Telugu => push_numeric_representation(value, &TELUGU, accumulator),
+ ListStyleType::Thai => push_numeric_representation(value, &THAI, accumulator),
+ ListStyleType::Tibetan => push_numeric_representation(value, &TIBETAN, accumulator),
+ ListStyleType::LowerAlpha => {
+ push_alphabetic_representation(value, &LOWER_ALPHA, accumulator)
+ },
+ ListStyleType::UpperAlpha => {
+ push_alphabetic_representation(value, &UPPER_ALPHA, accumulator)
+ },
+ ListStyleType::CjkEarthlyBranch => {
+ push_alphabetic_representation(value, &CJK_EARTHLY_BRANCH, accumulator)
+ },
+ ListStyleType::CjkHeavenlyStem => {
+ push_alphabetic_representation(value, &CJK_HEAVENLY_STEM, accumulator)
+ },
+ ListStyleType::LowerGreek => {
+ push_alphabetic_representation(value, &LOWER_GREEK, accumulator)
+ },
+ ListStyleType::Hiragana => push_alphabetic_representation(value, &HIRAGANA, accumulator),
+ ListStyleType::HiraganaIroha => {
+ push_alphabetic_representation(value, &HIRAGANA_IROHA, accumulator)
+ },
+ ListStyleType::Katakana => push_alphabetic_representation(value, &KATAKANA, accumulator),
+ ListStyleType::KatakanaIroha => {
+ push_alphabetic_representation(value, &KATAKANA_IROHA, accumulator)
+ },
+ }
+}
+
+/// Returns the static character that represents the value rendered using the given list-style, if
+/// possible.
+pub fn static_representation(list_style_type: ListStyleType) -> char {
+ match list_style_type {
+ ListStyleType::Disc => '•',
+ ListStyleType::Circle => '◦',
+ ListStyleType::Square => '▪',
+ ListStyleType::DisclosureOpen => '▾',
+ ListStyleType::DisclosureClosed => '‣',
+ _ => panic!("No static representation for this list-style-type!"),
+ }
+}
+
+/// Pushes the string that represents the value rendered using the given *alphabetic system* onto
+/// the accumulator per CSS-COUNTER-STYLES § 3.1.4.
+fn push_alphabetic_representation(value: i32, system: &[char], accumulator: &mut String) {
+ let mut abs_value = handle_negative_value(value, accumulator);
+
+ let mut string: SmallVec<[char; 8]> = SmallVec::new();
+ while abs_value != 0 {
+ // Step 1.
+ abs_value = abs_value - 1;
+ // Step 2.
+ string.push(system[abs_value % system.len()]);
+ // Step 3.
+ abs_value = abs_value / system.len();
+ }
+
+ accumulator.extend(string.iter().cloned().rev())
+}
+
+/// Pushes the string that represents the value rendered using the given *numeric system* onto the
+/// accumulator per CSS-COUNTER-STYLES § 3.1.5.
+fn push_numeric_representation(value: i32, system: &[char], accumulator: &mut String) {
+ let mut abs_value = handle_negative_value(value, accumulator);
+
+ // Step 1.
+ if abs_value == 0 {
+ accumulator.push(system[0]);
+ return;
+ }
+
+ // Step 2.
+ let mut string: SmallVec<[char; 8]> = SmallVec::new();
+ while abs_value != 0 {
+ // Step 2.1.
+ string.push(system[abs_value % system.len()]);
+ // Step 2.2.
+ abs_value = abs_value / system.len();
+ }
+
+ // Step 3.
+ accumulator.extend(string.iter().cloned().rev())
+}
+
+/// If the system uses a negative sign, handle negative values per CSS-COUNTER-STYLES § 2.
+///
+/// Returns the absolute value of the counter.
+fn handle_negative_value(value: i32, accumulator: &mut String) -> usize {
+ // 3. If the counter value is negative and the counter style uses a negative sign, instead
+ // generate an initial representation using the absolute value of the counter value.
+ if value < 0 {
+ // TODO: Support different negative signs using the 'negative' descriptor.
+ // https://drafts.csswg.org/date/2015-07-16/css-counter-styles/#counter-style-negative
+ accumulator.push('-');
+ value.abs() as usize
+ } else {
+ value as usize
+ }
+}
diff --git a/components/layout_2020/incremental.rs b/components/layout_2020/incremental.rs
new file mode 100644
index 00000000000..4e313f088bb
--- /dev/null
+++ b/components/layout_2020/incremental.rs
@@ -0,0 +1,101 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+use crate::flow::{Flow, FlowFlags, GetBaseFlow};
+use style::computed_values::float::T as Float;
+use style::selector_parser::RestyleDamage;
+use style::servo::restyle_damage::ServoRestyleDamage;
+
+/// Used in a flow traversal to indicate whether this re-layout should be incremental or not.
+#[derive(Clone, Copy, PartialEq)]
+pub enum RelayoutMode {
+ Incremental,
+ Force,
+}
+
+bitflags! {
+ pub struct SpecialRestyleDamage: u8 {
+ #[doc = "If this flag is set, we need to reflow the entire document. This is more or less a \
+ temporary hack to deal with cases that we don't handle incrementally yet."]
+ const REFLOW_ENTIRE_DOCUMENT = 0x01;
+ }
+}
+
+impl dyn Flow {
+ pub fn compute_layout_damage(&mut self) -> SpecialRestyleDamage {
+ let mut special_damage = SpecialRestyleDamage::empty();
+ let is_absolutely_positioned = self
+ .base()
+ .flags
+ .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED);
+
+ // In addition to damage, we use this phase to compute whether nodes affect CSS counters.
+ let mut has_counter_affecting_children = false;
+
+ {
+ let self_base = self.mut_base();
+ // Take a snapshot of the parent damage before updating it with damage from children.
+ let parent_damage = self_base.restyle_damage;
+
+ for kid in self_base.children.iter_mut() {
+ let child_is_absolutely_positioned = kid
+ .base()
+ .flags
+ .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED);
+ kid.mut_base().restyle_damage.insert(
+ parent_damage
+ .damage_for_child(is_absolutely_positioned, child_is_absolutely_positioned),
+ );
+ {
+ let kid: &mut dyn Flow = kid;
+ special_damage.insert(kid.compute_layout_damage());
+ }
+ self_base.restyle_damage.insert(
+ kid.base()
+ .restyle_damage
+ .damage_for_parent(child_is_absolutely_positioned),
+ );
+
+ has_counter_affecting_children = has_counter_affecting_children ||
+ kid.base().flags.intersects(
+ FlowFlags::AFFECTS_COUNTERS | FlowFlags::HAS_COUNTER_AFFECTING_CHILDREN,
+ );
+ }
+ }
+
+ let self_base = self.mut_base();
+ if self_base.flags.float_kind() != Float::None &&
+ self_base
+ .restyle_damage
+ .intersects(ServoRestyleDamage::REFLOW)
+ {
+ special_damage.insert(SpecialRestyleDamage::REFLOW_ENTIRE_DOCUMENT);
+ }
+
+ if has_counter_affecting_children {
+ self_base
+ .flags
+ .insert(FlowFlags::HAS_COUNTER_AFFECTING_CHILDREN)
+ } else {
+ self_base
+ .flags
+ .remove(FlowFlags::HAS_COUNTER_AFFECTING_CHILDREN)
+ }
+
+ special_damage
+ }
+
+ pub fn reflow_entire_document(&mut self) {
+ let self_base = self.mut_base();
+ self_base
+ .restyle_damage
+ .insert(RestyleDamage::rebuild_and_reflow());
+ self_base
+ .restyle_damage
+ .remove(ServoRestyleDamage::RECONSTRUCT_FLOW);
+ for kid in self_base.children.iter_mut() {
+ kid.reflow_entire_document();
+ }
+ }
+}
diff --git a/components/layout_2020/inline.rs b/components/layout_2020/inline.rs
new file mode 100644
index 00000000000..f4dacefc5ef
--- /dev/null
+++ b/components/layout_2020/inline.rs
@@ -0,0 +1,2220 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+use crate::block::AbsoluteAssignBSizesTraversal;
+use crate::context::{LayoutContext, LayoutFontContext};
+use crate::display_list::items::{DisplayListSection, OpaqueNode};
+use crate::display_list::{
+ BorderPaintingMode, DisplayListBuildState, StackingContextCollectionState,
+};
+use crate::floats::{FloatKind, Floats, PlacementInfo};
+use crate::flow::{BaseFlow, Flow, FlowClass, ForceNonfloatedFlag};
+use crate::flow::{EarlyAbsolutePositionInfo, FlowFlags, GetBaseFlow, OpaqueFlow};
+use crate::flow_ref::FlowRef;
+use crate::fragment::FragmentFlags;
+use crate::fragment::SpecificFragmentInfo;
+use crate::fragment::{CoordinateSystem, Fragment, FragmentBorderBoxIterator, Overflow};
+use crate::layout_debug;
+use crate::model::IntrinsicISizesContribution;
+use crate::text;
+use crate::traversal::PreorderFlowTraversal;
+use crate::ServoArc;
+use app_units::{Au, MIN_AU};
+use euclid::{Point2D, Rect, Size2D};
+use gfx::font::FontMetrics;
+use gfx_traits::print_tree::PrintTree;
+use range::{Range, RangeIndex};
+use script_layout_interface::wrapper_traits::PseudoElementType;
+use servo_geometry::MaxRect;
+use std::cmp::max;
+use std::collections::VecDeque;
+use std::sync::Arc;
+use std::{fmt, i32, isize, mem};
+use style::computed_values::display::T as Display;
+use style::computed_values::overflow_x::T as StyleOverflow;
+use style::computed_values::position::T as Position;
+use style::computed_values::text_align::T as TextAlign;
+use style::computed_values::text_justify::T as TextJustify;
+use style::computed_values::white_space::T as WhiteSpace;
+use style::logical_geometry::{LogicalRect, LogicalSize, WritingMode};
+use style::properties::ComputedValues;
+use style::servo::restyle_damage::ServoRestyleDamage;
+use style::values::computed::box_::VerticalAlign;
+use style::values::generics::box_::VerticalAlignKeyword;
+use style::values::specified::text::TextOverflowSide;
+use unicode_bidi as bidi;
+
+/// `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.
+#[derive(Clone, Debug, Serialize)]
+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, 2) | [2, 3) | [3, 5) | [5, 6) |
+ /// |----------|-------------|-------------|----------|
+ /// | 'I like' | 'truffles,' | '<img> yes' | 'I do.' |
+ pub range: Range<FragmentIndex>,
+
+ /// The bidirectional embedding level runs for this line, in visual order.
+ ///
+ /// Can be set to `None` if the line is 100% left-to-right.
+ pub visual_runs: Option<Vec<(Range<FragmentIndex>, bidi::Level)>>,
+
+ /// 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 like truffles, <img></span></div>
+ /// ~~~
+ ///
+ /// ...the bounds would be:
+ ///
+ /// ~~~text
+ /// +-----------------------------------------------------------+
+ /// | ^ |
+ /// | | |
+ /// | origin.y |
+ /// | | |
+ /// | v |
+ /// |< - origin.x ->+ - - - - - - - - +---------+---- |
+ /// | | | | ^ |
+ /// | | | <img> | size.block |
+ /// | I like truffles, | | v |
+ /// | + - - - - - - - - +---------+---- |
+ /// | | | |
+ /// | |<------ size.inline ------>| |
+ /// | |
+ /// | |
+ /// +-----------------------------------------------------------+
+ /// ~~~
+ pub bounds: LogicalRect<Au>,
+
+ /// The green zone is the greatest extent from which 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>,
+
+ /// The minimum metrics for this line, as specified by the style.
+ pub minimum_metrics: LineMetrics,
+
+ /// The actual metrics for this line.
+ pub metrics: LineMetrics,
+}
+
+impl Line {
+ fn new(writing_mode: WritingMode, minimum_metrics: &LineMetrics) -> Line {
+ Line {
+ range: Range::empty(),
+ visual_runs: None,
+ bounds: LogicalRect::zero(writing_mode),
+ green_zone: LogicalSize::zero(writing_mode),
+ minimum_metrics: *minimum_metrics,
+ metrics: *minimum_metrics,
+ }
+ }
+
+ /// Returns the new metrics that this line would have if `new_fragment` were added to it.
+ ///
+ /// FIXME(pcwalton): 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_metrics_for_fragment(
+ &self,
+ new_fragment: &Fragment,
+ layout_context: &LayoutContext,
+ ) -> LineMetrics {
+ if !new_fragment.is_vertically_aligned_to_top_or_bottom() {
+ let fragment_inline_metrics =
+ new_fragment.aligned_inline_metrics(layout_context, &self.minimum_metrics, None);
+ self.metrics
+ .new_metrics_for_fragment(&fragment_inline_metrics)
+ } else {
+ self.metrics
+ }
+ }
+
+ /// Returns the new block size that this line would have if `new_fragment` were added to it.
+ /// `new_inline_metrics` represents the new inline metrics that this line would have; it can
+ /// be computed with `new_inline_metrics()`.
+ fn new_block_size_for_fragment(
+ &self,
+ new_fragment: &Fragment,
+ new_line_metrics: &LineMetrics,
+ layout_context: &LayoutContext,
+ ) -> Au {
+ let new_block_size = if new_fragment.is_vertically_aligned_to_top_or_bottom() {
+ max(
+ new_fragment
+ .aligned_inline_metrics(layout_context, &self.minimum_metrics, None)
+ .space_needed(),
+ self.minimum_metrics.space_needed(),
+ )
+ } else {
+ new_line_metrics.space_needed()
+ };
+ max(self.bounds.size.block, new_block_size)
+ }
+}
+
+int_range_index! {
+ #[derive(Serialize)]
+ #[doc = "The index of a fragment in a flattened vector of DOM elements."]
+ struct FragmentIndex(isize)
+}
+
+/// Arranges fragments into lines, splitting them up as necessary.
+struct LineBreaker {
+ /// The floats we need to flow around.
+ floats: Floats,
+ /// The resulting fragment list for the flow, consisting of possibly-broken fragments.
+ new_fragments: Vec<Fragment>,
+ /// The next fragment or fragments that we need to work on.
+ work_list: VecDeque<Fragment>,
+ /// The line we're currently working on.
+ pending_line: Line,
+ /// The lines we've already committed.
+ lines: Vec<Line>,
+ /// The index of the last known good line breaking opportunity. The opportunity will either
+ /// be inside this fragment (if it is splittable) or immediately prior to it.
+ last_known_line_breaking_opportunity: Option<FragmentIndex>,
+ /// The current position in the block direction.
+ cur_b: Au,
+ /// The computed value of the indentation for the first line (`text-indent`, CSS 2.1 § 16.1).
+ first_line_indentation: Au,
+ /// The minimum metrics for each line, as specified by the line height and font style.
+ minimum_metrics: LineMetrics,
+}
+
+impl LineBreaker {
+ /// Creates a new `LineBreaker` with a set of floats and the indentation of the first line.
+ fn new(
+ float_context: Floats,
+ first_line_indentation: Au,
+ minimum_line_metrics: &LineMetrics,
+ ) -> LineBreaker {
+ LineBreaker {
+ new_fragments: Vec::new(),
+ work_list: VecDeque::new(),
+ pending_line: Line::new(float_context.writing_mode, minimum_line_metrics),
+ floats: float_context,
+ lines: Vec::new(),
+ cur_b: Au(0),
+ last_known_line_breaking_opportunity: None,
+ first_line_indentation: first_line_indentation,
+ minimum_metrics: *minimum_line_metrics,
+ }
+ }
+
+ /// Resets the `LineBreaker` to the initial state it had after a call to `new`.
+ fn reset_scanner(&mut self) {
+ self.lines = Vec::new();
+ self.new_fragments = Vec::new();
+ self.cur_b = Au(0);
+ self.reset_line();
+ }
+
+ /// Reinitializes the pending line to blank data.
+ fn reset_line(&mut self) {
+ self.last_known_line_breaking_opportunity = None;
+ // https://github.com/rust-lang/rust/issues/49282
+ self.pending_line = Line::new(self.floats.writing_mode, &self.minimum_metrics);
+ }
+
+ /// Reflows fragments for the given inline flow.
+ fn scan_for_lines(&mut self, flow: &mut InlineFlow, layout_context: &LayoutContext) {
+ self.reset_scanner();
+
+ // Create our fragment iterator.
+ debug!(
+ "LineBreaker: scanning for lines, {} fragments",
+ flow.fragments.len()
+ );
+ let mut old_fragments = mem::replace(&mut flow.fragments, InlineFragments::new());
+ let old_fragment_iter = old_fragments.fragments.into_iter();
+
+ // TODO(pcwalton): This would likely be better as a list of dirty line
+ // indices. That way we could resynchronize if we discover during reflow
+ // that all subsequent fragments must have the same position as they had
+ // in the previous reflow. I don't know how common this case really is
+ // in practice, but it's probably worth handling.
+ self.lines = Vec::new();
+
+ // Do the reflow.
+ self.reflow_fragments(old_fragment_iter, flow, layout_context);
+
+ // Perform unicode bidirectional layout.
+ let para_level = flow.base.writing_mode.to_bidi_level();
+
+ // The text within a fragment is at a single bidi embedding level
+ // (because we split fragments on level run boundaries during flow
+ // construction), so we can build a level array with just one entry per
+ // fragment.
+ let levels: Vec<bidi::Level> = self
+ .new_fragments
+ .iter()
+ .map(|fragment| match fragment.specific {
+ SpecificFragmentInfo::ScannedText(ref info) => info.run.bidi_level,
+ _ => para_level,
+ })
+ .collect();
+
+ let mut lines = mem::replace(&mut self.lines, Vec::new());
+
+ // If everything is LTR, don't bother with reordering.
+ if bidi::level::has_rtl(&levels) {
+ // Compute and store the visual ordering of the fragments within the
+ // line.
+ for line in &mut lines {
+ let range = line.range.begin().to_usize()..line.range.end().to_usize();
+ // FIXME: Update to use BidiInfo::visual_runs, as this algorithm needs access to
+ // the original text and original BidiClass of its characters.
+ #[allow(deprecated)]
+ let runs = bidi::deprecated::visual_runs(range, &levels);
+ line.visual_runs = Some(
+ runs.iter()
+ .map(|run| {
+ let start = FragmentIndex(run.start as isize);
+ let len = FragmentIndex(run.len() as isize);
+ (Range::new(start, len), levels[run.start])
+ })
+ .collect(),
+ );
+ }
+ }
+
+ // Place the fragments back into the flow.
+ old_fragments.fragments = mem::replace(&mut self.new_fragments, vec![]);
+ flow.fragments = old_fragments;
+ flow.lines = lines;
+ }
+
+ /// Reflows the given fragments, which have been plucked out of the inline flow.
+ fn reflow_fragments<'a, I>(
+ &mut self,
+ mut old_fragment_iter: I,
+ flow: &'a InlineFlow,
+ layout_context: &LayoutContext,
+ ) where
+ I: Iterator<Item = Fragment>,
+ {
+ loop {
+ // Acquire the next fragment to lay out from the work list or fragment list, as
+ // appropriate.
+ let fragment = match self.next_unbroken_fragment(&mut old_fragment_iter) {
+ None => break,
+ Some(fragment) => fragment,
+ };
+
+ // Do not reflow truncated fragments. Reflow the original fragment only.
+ let fragment = if fragment.flags.contains(FragmentFlags::IS_ELLIPSIS) {
+ continue;
+ } else if let SpecificFragmentInfo::TruncatedFragment(info) = fragment.specific {
+ info.full
+ } else {
+ fragment
+ };
+
+ // Try to append the fragment.
+ self.reflow_fragment(fragment, flow, layout_context);
+ }
+
+ if !self.pending_line_is_empty() {
+ debug!(
+ "LineBreaker: partially full line {} at end of scanning; committing it",
+ self.lines.len()
+ );
+ self.flush_current_line()
+ }
+ }
+
+ /// Acquires a new fragment to lay out from the work list or fragment list as appropriate.
+ /// Note that you probably don't want to call this method directly in order to be incremental-
+ /// reflow-safe; try `next_unbroken_fragment` instead.
+ fn next_fragment<I>(&mut self, old_fragment_iter: &mut I) -> Option<Fragment>
+ where
+ I: Iterator<Item = Fragment>,
+ {
+ self.work_list
+ .pop_front()
+ .or_else(|| old_fragment_iter.next())
+ }
+
+ /// Acquires a new fragment to lay out from the work list or fragment list,
+ /// merging it with any subsequent fragments as appropriate. In effect, what
+ /// this method does is to return the next fragment to lay out, undoing line
+ /// break operations that any previous reflows may have performed. You
+ /// probably want to be using this method instead of `next_fragment`.
+ fn next_unbroken_fragment<I>(&mut self, old_fragment_iter: &mut I) -> Option<Fragment>
+ where
+ I: Iterator<Item = Fragment>,
+ {
+ let mut result = self.next_fragment(old_fragment_iter)?;
+
+ loop {
+ let candidate = match self.next_fragment(old_fragment_iter) {
+ None => return Some(result),
+ Some(fragment) => fragment,
+ };
+
+ let need_to_merge = match (&mut result.specific, &candidate.specific) {
+ (
+ &mut SpecificFragmentInfo::ScannedText(ref mut result_info),
+ &SpecificFragmentInfo::ScannedText(ref candidate_info),
+ ) => {
+ result.margin.inline_end == Au(0) &&
+ candidate.margin.inline_start == Au(0) &&
+ result.border_padding.inline_end == Au(0) &&
+ candidate.border_padding.inline_start == Au(0) &&
+ result_info.selected() == candidate_info.selected() &&
+ Arc::ptr_eq(&result_info.run, &candidate_info.run) &&
+ inline_contexts_are_equal(
+ &result.inline_context,
+ &candidate.inline_context,
+ )
+ },
+ _ => false,
+ };
+
+ if need_to_merge {
+ result.merge_with(candidate);
+ continue;
+ }
+
+ self.work_list.push_front(candidate);
+ return Some(result);
+ }
+ }
+
+ /// Commits a line to the list.
+ fn flush_current_line(&mut self) {
+ debug!(
+ "LineBreaker: flushing line {}: {:?}",
+ self.lines.len(),
+ self.pending_line
+ );
+ self.strip_trailing_whitespace_from_pending_line_if_necessary();
+ self.lines.push(self.pending_line.clone());
+ self.cur_b = self.pending_line.bounds.start.b + self.pending_line.bounds.size.block;
+ self.reset_line();
+ }
+
+ /// Removes trailing whitespace from the pending line if necessary. This is done right before
+ /// flushing it.
+ fn strip_trailing_whitespace_from_pending_line_if_necessary(&mut self) {
+ if self.pending_line.range.is_empty() {
+ return;
+ }
+ let last_fragment_index = self.pending_line.range.end() - FragmentIndex(1);
+ let fragment = &mut self.new_fragments[last_fragment_index.get() as usize];
+
+ let old_fragment_inline_size = fragment.border_box.size.inline;
+
+ fragment.strip_trailing_whitespace_if_necessary();
+
+ self.pending_line.bounds.size.inline +=
+ fragment.border_box.size.inline - old_fragment_inline_size;
+ }
+
+ /// 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,
+ flow: &InlineFlow,
+ first_fragment: &Fragment,
+ ceiling: Au,
+ ) -> (LogicalRect<Au>, Au) {
+ debug!(
+ "LineBreaker: trying to place first fragment of line {}; fragment size: {:?}, \
+ splittable: {}",
+ self.lines.len(),
+ first_fragment.border_box.size,
+ first_fragment.can_split()
+ );
+
+ // Initially, pretend a splittable fragment has zero inline-size. We will move it later if
+ // it has nonzero inline-size and that causes problems.
+ let placement_inline_size = if first_fragment.can_split() {
+ first_fragment.minimum_splittable_inline_size()
+ } else {
+ first_fragment.margin_box_inline_size() + self.indentation_for_pending_fragment()
+ };
+
+ // Try to place the fragment between floats.
+ let line_bounds = self.floats.place_between_floats(&PlacementInfo {
+ size: LogicalSize::new(
+ self.floats.writing_mode,
+ placement_inline_size,
+ first_fragment.border_box.size.block,
+ ),
+ ceiling: ceiling,
+ max_inline_size: flow.base.position.size.inline,
+ kind: FloatKind::Left,
+ });
+
+ let fragment_margin_box_inline_size = first_fragment.margin_box_inline_size();
+
+ // Simple case: if the fragment fits, then we can stop here.
+ if line_bounds.size.inline > fragment_margin_box_inline_size {
+ debug!("LineBreaker: fragment fits on line {}", self.lines.len());
+ return (line_bounds, fragment_margin_box_inline_size);
+ }
+
+ // If not, but we can't split the fragment, then we'll place the line here and it will
+ // overflow.
+ if !first_fragment.can_split() {
+ debug!("LineBreaker: line doesn't fit, but is unsplittable");
+ }
+
+ (line_bounds, fragment_margin_box_inline_size)
+ }
+
+ /// 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,
+ flow: &InlineFlow,
+ in_fragment: Fragment,
+ new_block_size: Au,
+ ) -> bool {
+ debug!("LineBreaker: entering float collision avoider!");
+
+ // First predict where the next line is going to be.
+ let (next_line, first_fragment_inline_size) =
+ self.initial_line_placement(flow, &in_fragment, self.pending_line.bounds.start.b);
+ 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;
+
+ debug_assert!(
+ !self.pending_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
+ }
+
+ /// Tries to append the given fragment to the line, splitting it if necessary. Commits the
+ /// current line if needed.
+ fn reflow_fragment(
+ &mut self,
+ mut fragment: Fragment,
+ flow: &InlineFlow,
+ layout_context: &LayoutContext,
+ ) {
+ // Undo any whitespace stripping from previous reflows.
+ fragment.reset_text_range_and_inline_size();
+ // Determine initial placement for the fragment if we need to.
+ //
+ // Also, determine whether we can legally break the line before, or
+ // inside, this fragment.
+ let fragment_is_line_break_opportunity = if self.pending_line_is_empty() {
+ fragment.strip_leading_whitespace_if_necessary();
+ let (line_bounds, _) = self.initial_line_placement(flow, &fragment, self.cur_b);
+ self.pending_line.bounds.start = line_bounds.start;
+ self.pending_line.green_zone = line_bounds.size;
+ false
+ } else {
+ // In case of Foo<span style="...">bar</span>, the line breaker will
+ // set the "suppress line break before" flag for the second fragment.
+ //
+ // In case of Foo<span>bar</span> the second fragment ("bar") will
+ // start _within_ a glyph run, so we also avoid breaking there
+ //
+ // is_on_glyph_run_boundary does a binary search, but this is ok
+ // because the result will be cached and reused in
+ // `calculate_split_position` later
+ if fragment.suppress_line_break_before() || !fragment.is_on_glyph_run_boundary() {
+ false
+ } else {
+ fragment.white_space().allow_wrap()
+ }
+ };
+
+ debug!(
+ "LineBreaker: trying to append to line {} \
+ (fragment size: {:?}, green zone: {:?}): {:?}",
+ self.lines.len(),
+ fragment.border_box.size,
+ self.pending_line.green_zone,
+ fragment
+ );
+
+ // NB: At this point, if `green_zone.inline <
+ // self.pending_line.bounds.size.inline` or `green_zone.block <
+ // self.pending_line.bounds.size.block`, then we committed a line that
+ // overlaps with floats.
+ let green_zone = self.pending_line.green_zone;
+ let new_line_metrics = self
+ .pending_line
+ .new_metrics_for_fragment(&fragment, layout_context);
+ let new_block_size = self.pending_line.new_block_size_for_fragment(
+ &fragment,
+ &new_line_metrics,
+ layout_context,
+ );
+ if new_block_size > green_zone.block {
+ // Uh-oh. Float collision imminent. Enter the float collision avoider!
+ if !self.avoid_floats(flow, fragment, new_block_size) {
+ self.flush_current_line();
+ }
+ return;
+ }
+
+ // Record the last known good line break opportunity if this is one.
+ if fragment_is_line_break_opportunity {
+ self.last_known_line_breaking_opportunity = Some(self.pending_line.range.end())
+ }
+
+ // If we must flush the line after finishing this fragment due to `white-space: pre`,
+ // detect that.
+ let line_flush_mode = if fragment.white_space().preserve_newlines() {
+ if fragment.requires_line_break_afterward_if_wrapping_on_newlines() {
+ LineFlushMode::Flush
+ } else {
+ LineFlushMode::No
+ }
+ } else {
+ LineFlushMode::No
+ };
+
+ // 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 indentation = self.indentation_for_pending_fragment();
+ let new_inline_size =
+ self.pending_line.bounds.size.inline + fragment.margin_box_inline_size() + indentation;
+ if new_inline_size <= green_zone.inline {
+ debug!("LineBreaker: fragment fits without splitting");
+ self.push_fragment_to_line(layout_context, fragment, line_flush_mode);
+ return;
+ }
+
+ // If the wrapping mode prevents us from splitting, then back up and split at the last
+ // known good split point.
+ if !fragment.white_space().allow_wrap() {
+ debug!(
+ "LineBreaker: fragment can't split; falling back to last known good split point"
+ );
+ self.split_line_at_last_known_good_position(layout_context, fragment, line_flush_mode);
+ return;
+ }
+
+ // Split it up!
+ let available_inline_size =
+ green_zone.inline - self.pending_line.bounds.size.inline - indentation;
+ let inline_start_fragment;
+ let inline_end_fragment;
+ let split_result = match fragment
+ .calculate_split_position(available_inline_size, self.pending_line_is_empty())
+ {
+ None => {
+ // We failed to split. Defer to the next line if we're allowed to; otherwise,
+ // rewind to the last line breaking opportunity.
+ if fragment_is_line_break_opportunity {
+ debug!("LineBreaker: fragment was unsplittable; deferring to next line");
+ self.work_list.push_front(fragment);
+ self.flush_current_line();
+ } else {
+ self.split_line_at_last_known_good_position(
+ layout_context,
+ fragment,
+ LineFlushMode::No,
+ );
+ }
+ return;
+ },
+ Some(split_result) => split_result,
+ };
+
+ inline_start_fragment = split_result
+ .inline_start
+ .as_ref()
+ .map(|x| fragment.transform_with_split_info(x, split_result.text_run.clone(), true));
+ inline_end_fragment = split_result
+ .inline_end
+ .as_ref()
+ .map(|x| fragment.transform_with_split_info(x, split_result.text_run.clone(), false));
+
+ // Push the first fragment onto the line we're working on and start off the next line with
+ // the second fragment. If there's no second fragment, the next line will start off empty.
+ match (inline_start_fragment, inline_end_fragment) {
+ (Some(mut inline_start_fragment), Some(mut inline_end_fragment)) => {
+ inline_start_fragment.border_padding.inline_end = Au(0);
+ if let Some(ref mut inline_context) = inline_start_fragment.inline_context {
+ for node in &mut inline_context.nodes {
+ node.flags
+ .remove(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT);
+ }
+ }
+ inline_start_fragment.border_box.size.inline +=
+ inline_start_fragment.border_padding.inline_start;
+
+ inline_end_fragment.border_padding.inline_start = Au(0);
+ if let Some(ref mut inline_context) = inline_end_fragment.inline_context {
+ for node in &mut inline_context.nodes {
+ node.flags
+ .remove(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT);
+ }
+ }
+ inline_end_fragment.border_box.size.inline +=
+ inline_end_fragment.border_padding.inline_end;
+
+ self.push_fragment_to_line(
+ layout_context,
+ inline_start_fragment,
+ LineFlushMode::Flush,
+ );
+ self.work_list.push_front(inline_end_fragment)
+ },
+ (Some(fragment), None) => {
+ self.push_fragment_to_line(layout_context, fragment, line_flush_mode);
+ },
+ (None, Some(fragment)) => {
+ // Yes, this can happen!
+ self.flush_current_line();
+ self.work_list.push_front(fragment)
+ },
+ (None, None) => {},
+ }
+ }
+
+ /// Pushes a fragment to the current line unconditionally, possibly truncating it and placing
+ /// an ellipsis based on the value of `text-overflow`. If `flush_line` is `Flush`, then flushes
+ /// the line afterward;
+ fn push_fragment_to_line(
+ &mut self,
+ layout_context: &LayoutContext,
+ fragment: Fragment,
+ line_flush_mode: LineFlushMode,
+ ) {
+ let indentation = self.indentation_for_pending_fragment();
+ if self.pending_line_is_empty() {
+ debug_assert!(self.new_fragments.len() <= (isize::MAX as usize));
+ self.pending_line.range.reset(
+ FragmentIndex(self.new_fragments.len() as isize),
+ FragmentIndex(0),
+ );
+ }
+
+ // Determine if an ellipsis will be necessary to account for `text-overflow`.
+ let available_inline_size = self.pending_line.green_zone.inline -
+ self.pending_line.bounds.size.inline -
+ indentation;
+
+ let ellipsis = match (
+ &fragment.style().get_text().text_overflow.second,
+ fragment.style().get_box().overflow_x,
+ ) {
+ (&TextOverflowSide::Clip, _) | (_, StyleOverflow::Visible) => None,
+ (&TextOverflowSide::Ellipsis, _) => {
+ if fragment.margin_box_inline_size() > available_inline_size {
+ Some("…".to_string())
+ } else {
+ None
+ }
+ },
+ (&TextOverflowSide::String(ref string), _) => {
+ if fragment.margin_box_inline_size() > available_inline_size {
+ Some(string.to_string())
+ } else {
+ None
+ }
+ },
+ };
+
+ if let Some(string) = ellipsis {
+ let ellipsis = fragment.transform_into_ellipsis(layout_context, string);
+ let truncated = fragment
+ .truncate_to_inline_size(available_inline_size - ellipsis.margin_box_inline_size());
+ self.push_fragment_to_line_ignoring_text_overflow(truncated, layout_context);
+ self.push_fragment_to_line_ignoring_text_overflow(ellipsis, layout_context);
+ } else {
+ self.push_fragment_to_line_ignoring_text_overflow(fragment, layout_context);
+ }
+
+ if line_flush_mode == LineFlushMode::Flush {
+ self.flush_current_line()
+ }
+ }
+
+ /// Pushes a fragment to the current line unconditionally, without placing an ellipsis in the
+ /// case of `text-overflow: ellipsis`.
+ fn push_fragment_to_line_ignoring_text_overflow(
+ &mut self,
+ fragment: Fragment,
+ layout_context: &LayoutContext,
+ ) {
+ let indentation = self.indentation_for_pending_fragment();
+ self.pending_line.range.extend_by(FragmentIndex(1));
+
+ if !fragment.is_inline_absolute() && !fragment.is_hypothetical() {
+ self.pending_line.bounds.size.inline = self.pending_line.bounds.size.inline +
+ fragment.margin_box_inline_size() +
+ indentation;
+ self.pending_line.metrics = self
+ .pending_line
+ .new_metrics_for_fragment(&fragment, layout_context);
+ self.pending_line.bounds.size.block = self.pending_line.new_block_size_for_fragment(
+ &fragment,
+ &self.pending_line.metrics,
+ layout_context,
+ );
+ }
+
+ self.new_fragments.push(fragment);
+ }
+
+ fn split_line_at_last_known_good_position(
+ &mut self,
+ layout_context: &LayoutContext,
+ cur_fragment: Fragment,
+ line_flush_mode: LineFlushMode,
+ ) {
+ let last_known_line_breaking_opportunity = match self.last_known_line_breaking_opportunity {
+ None => {
+ // No line breaking opportunity exists at all for this line. Overflow.
+ self.push_fragment_to_line(layout_context, cur_fragment, line_flush_mode);
+ return;
+ },
+ Some(last_known_line_breaking_opportunity) => last_known_line_breaking_opportunity,
+ };
+
+ self.work_list.push_front(cur_fragment);
+ for fragment_index in
+ (last_known_line_breaking_opportunity.get()..self.pending_line.range.end().get()).rev()
+ {
+ debug_assert_eq!(fragment_index, (self.new_fragments.len() as isize) - 1);
+ self.work_list.push_front(self.new_fragments.pop().unwrap());
+ }
+
+ // FIXME(pcwalton): This should actually attempt to split the last fragment if
+ // possible to do so, to handle cases like:
+ //
+ // (available width)
+ // +-------------+
+ // The alphabet
+ // (<em>abcdefghijklmnopqrstuvwxyz</em>)
+ //
+ // Here, the last known-good split point is inside the fragment containing
+ // "The alphabet (", which has already been committed by the time we get to this
+ // point. Unfortunately, the existing splitting API (`calculate_split_position`)
+ // has no concept of "split right before the last non-whitespace position". We'll
+ // need to add that feature to the API to handle this case correctly.
+ self.pending_line
+ .range
+ .extend_to(last_known_line_breaking_opportunity);
+ self.flush_current_line();
+ }
+
+ /// Returns the indentation that needs to be applied before the fragment we're reflowing.
+ fn indentation_for_pending_fragment(&self) -> Au {
+ if self.pending_line_is_empty() && self.lines.is_empty() {
+ self.first_line_indentation
+ } else {
+ Au(0)
+ }
+ }
+
+ /// Returns true if the pending line is empty and false otherwise.
+ fn pending_line_is_empty(&self) -> bool {
+ self.pending_line.range.length() == FragmentIndex(0)
+ }
+}
+
+/// Represents a list of inline fragments, including element ranges.
+#[derive(Clone, Serialize)]
+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) -> usize {
+ 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.fragments.is_empty()
+ }
+
+ /// A convenience function to return the fragment at a given index.
+ pub fn get(&self, index: usize) -> &Fragment {
+ &self.fragments[index]
+ }
+
+ /// A convenience function to return a mutable reference to the fragment at a given index.
+ pub fn get_mut(&mut self, index: usize) -> &mut Fragment {
+ &mut self.fragments[index]
+ }
+}
+
+#[allow(unsafe_code)]
+unsafe impl crate::flow::HasBaseFlow for InlineFlow {}
+
+/// Flows for inline layout.
+#[derive(Serialize)]
+#[repr(C)]
+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 metrics for each line, as specified by the line height and font style.
+ pub minimum_line_metrics: LineMetrics,
+
+ /// The amount of indentation to use on the first line. This is determined by our block parent
+ /// (because percentages are relative to the containing block, and we aren't in a position to
+ /// compute things relative to our parent's containing block).
+ pub first_line_indentation: Au,
+}
+
+impl InlineFlow {
+ pub fn from_fragments(fragments: InlineFragments, writing_mode: WritingMode) -> InlineFlow {
+ let mut flow = InlineFlow {
+ base: BaseFlow::new(None, writing_mode, ForceNonfloatedFlag::ForceNonfloated),
+ fragments: fragments,
+ lines: Vec::new(),
+ minimum_line_metrics: LineMetrics::new(Au(0), Au(0)),
+ first_line_indentation: Au(0),
+ };
+
+ if flow
+ .fragments
+ .fragments
+ .iter()
+ .any(Fragment::is_unscanned_generated_content)
+ {
+ flow.base
+ .restyle_damage
+ .insert(ServoRestyleDamage::RESOLVE_GENERATED_CONTENT);
+ }
+
+ flow
+ }
+
+ /// Sets fragment positions in the inline direction based on alignment for one line. This
+ /// performs text justification if mandated by the style.
+ fn set_inline_fragment_positions(
+ fragments: &mut InlineFragments,
+ line: &Line,
+ line_align: TextAlign,
+ indentation: Au,
+ is_last_line: bool,
+ ) {
+ // Figure out how much inline-size we have.
+ let slack_inline_size = max(Au(0), line.green_zone.inline - line.bounds.size.inline);
+
+ // Compute the value we're going to use for `text-justify`.
+ if fragments.fragments.is_empty() {
+ return;
+ }
+ let text_justify = fragments.fragments[0]
+ .style()
+ .get_inherited_text()
+ .text_justify;
+
+ // Translate `left` and `right` to logical directions.
+ let is_ltr = fragments.fragments[0].style().writing_mode.is_bidi_ltr();
+ let line_align = match (line_align, is_ltr) {
+ (TextAlign::Left, true) |
+ (TextAlign::ServoLeft, true) |
+ (TextAlign::Right, false) |
+ (TextAlign::ServoRight, false) => TextAlign::Start,
+ (TextAlign::Left, false) |
+ (TextAlign::ServoLeft, false) |
+ (TextAlign::Right, true) |
+ (TextAlign::ServoRight, true) => TextAlign::End,
+ _ => line_align,
+ };
+
+ // Set the fragment inline positions based on that alignment, and justify the text if
+ // necessary.
+ let mut inline_start_position_for_fragment = line.bounds.start.i + indentation;
+ match line_align {
+ TextAlign::Justify if !is_last_line && text_justify != TextJustify::None => {
+ InlineFlow::justify_inline_fragments(fragments, line, slack_inline_size)
+ },
+ TextAlign::Justify | TextAlign::Start => {},
+ TextAlign::Center | TextAlign::ServoCenter => {
+ inline_start_position_for_fragment =
+ inline_start_position_for_fragment + slack_inline_size.scale_by(0.5)
+ },
+ TextAlign::End => {
+ inline_start_position_for_fragment =
+ inline_start_position_for_fragment + slack_inline_size
+ },
+ TextAlign::Left | TextAlign::ServoLeft | TextAlign::Right | TextAlign::ServoRight => {
+ unreachable!()
+ },
+ }
+
+ // Lay out the fragments in visual order.
+ let run_count = match line.visual_runs {
+ Some(ref runs) => runs.len(),
+ None => 1,
+ };
+ for run_idx in 0..run_count {
+ let (range, level) = match line.visual_runs {
+ Some(ref runs) if is_ltr => runs[run_idx],
+ Some(ref runs) => runs[run_count - run_idx - 1], // reverse order for RTL runs
+ None => (line.range, bidi::Level::ltr()),
+ };
+
+ struct MaybeReverse<I> {
+ iter: I,
+ reverse: bool,
+ }
+
+ impl<I: DoubleEndedIterator> Iterator for MaybeReverse<I> {
+ type Item = I::Item;
+
+ fn next(&mut self) -> Option<I::Item> {
+ if self.reverse {
+ self.iter.next_back()
+ } else {
+ self.iter.next()
+ }
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ self.iter.size_hint()
+ }
+ }
+
+ // If the bidi embedding direction is opposite the layout direction, lay out this
+ // run in reverse order.
+ let fragment_indices = MaybeReverse {
+ iter: range.begin().get()..range.end().get(),
+ reverse: level.is_ltr() != is_ltr,
+ };
+
+ for fragment_index in fragment_indices {
+ let fragment = fragments.get_mut(fragment_index as usize);
+ inline_start_position_for_fragment =
+ inline_start_position_for_fragment + fragment.margin.inline_start;
+
+ let border_start = if fragment.style.writing_mode.is_bidi_ltr() == is_ltr {
+ inline_start_position_for_fragment
+ } else {
+ line.green_zone.inline -
+ inline_start_position_for_fragment -
+ fragment.margin.inline_end -
+ fragment.border_box.size.inline
+ };
+ fragment.border_box = LogicalRect::new(
+ fragment.style.writing_mode,
+ border_start,
+ fragment.border_box.start.b,
+ fragment.border_box.size.inline,
+ fragment.border_box.size.block,
+ );
+ fragment.update_late_computed_inline_position_if_necessary();
+
+ if !fragment.is_inline_absolute() {
+ inline_start_position_for_fragment = inline_start_position_for_fragment +
+ fragment.border_box.size.inline +
+ fragment.margin.inline_end;
+ }
+ }
+ }
+ }
+
+ /// Justifies the given set of inline fragments, distributing the `slack_inline_size` among all
+ /// of them according to the value of `text-justify`.
+ fn justify_inline_fragments(
+ fragments: &mut InlineFragments,
+ line: &Line,
+ slack_inline_size: Au,
+ ) {
+ // Fast path.
+ if slack_inline_size == Au(0) {
+ return;
+ }
+
+ // First, calculate the number of expansion opportunities (spaces, normally).
+ let mut expansion_opportunities = 0;
+ for fragment_index in line.range.each_index() {
+ let fragment = fragments.get(fragment_index.to_usize());
+ let scanned_text_fragment_info = match fragment.specific {
+ SpecificFragmentInfo::ScannedText(ref info) if !info.range.is_empty() => info,
+ _ => continue,
+ };
+ let fragment_range = scanned_text_fragment_info.range;
+
+ for slice in scanned_text_fragment_info
+ .run
+ .character_slices_in_range(&fragment_range)
+ {
+ expansion_opportunities += slice.glyphs.space_count_in_range(&slice.range)
+ }
+ }
+
+ if expansion_opportunities == 0 {
+ return;
+ }
+
+ // Then distribute all the space across the expansion opportunities.
+ let space_per_expansion_opportunity = slack_inline_size / expansion_opportunities as i32;
+ for fragment_index in line.range.each_index() {
+ let fragment = fragments.get_mut(fragment_index.to_usize());
+ let scanned_text_fragment_info = match fragment.specific {
+ SpecificFragmentInfo::ScannedText(ref mut info) if !info.range.is_empty() => info,
+ _ => continue,
+ };
+ let fragment_range = scanned_text_fragment_info.range;
+ let run = Arc::make_mut(&mut scanned_text_fragment_info.run);
+ run.extra_word_spacing = space_per_expansion_opportunity;
+
+ // Recompute the fragment's border box size.
+ let new_inline_size = run.advance_for_range(&fragment_range);
+ let new_size = LogicalSize::new(
+ fragment.style.writing_mode,
+ new_inline_size,
+ fragment.border_box.size.block,
+ );
+ fragment.border_box = LogicalRect::from_point_size(
+ fragment.style.writing_mode,
+ fragment.border_box.start,
+ new_size,
+ );
+ }
+ }
+
+ /// Sets final fragment positions in the block direction for one line.
+ fn set_block_fragment_positions(
+ fragments: &mut InlineFragments,
+ line: &Line,
+ minimum_line_metrics: &LineMetrics,
+ layout_context: &LayoutContext,
+ ) {
+ for fragment_index in line.range.each_index() {
+ let fragment = fragments.get_mut(fragment_index.to_usize());
+ let line_metrics = LineMetrics::for_line_and_fragment(line, fragment, layout_context);
+ let inline_metrics = fragment.aligned_inline_metrics(
+ layout_context,
+ minimum_line_metrics,
+ Some(&line_metrics),
+ );
+
+ // Align the top of the fragment's border box with its ascent above the baseline.
+ fragment.border_box.start.b =
+ line.bounds.start.b + line_metrics.space_above_baseline - inline_metrics.ascent;
+
+ // CSS 2.1 § 10.8: "The height of each inline-level box in the line box is
+ // calculated. For replaced elements, inline-block elements, and inline-table
+ // elements, this is the height of their margin box; for inline boxes, this is their
+ // 'line-height'."
+ //
+ // CSS 2.1 § 10.8.1: "Although margins, borders, and padding of non-replaced elements
+ // do not enter into the line box calculation, they are still rendered around inline
+ // boxes."
+ //
+ // Effectively, if the fragment is a non-replaced element (excluding inline-block), we
+ // need to align its ascent above the baseline with the top of the *content box*, not
+ // the border box. Since the code above has already aligned it to the border box, we
+ // simply need to adjust it in this case.
+ if !fragment.is_replaced_or_inline_block() {
+ fragment.border_box.start.b -= fragment.border_padding.block_start
+ }
+
+ fragment.update_late_computed_block_position_if_necessary();
+ }
+ }
+
+ /// Computes the minimum metrics for each line. This is done during flow construction.
+ ///
+ /// `style` is the style of the block.
+ pub fn minimum_line_metrics(
+ &self,
+ font_context: &mut LayoutFontContext,
+ style: &ComputedValues,
+ ) -> LineMetrics {
+ InlineFlow::minimum_line_metrics_for_fragments(
+ &self.fragments.fragments,
+ font_context,
+ style,
+ )
+ }
+
+ /// Computes the minimum line metrics for the given fragments. This is typically done during
+ /// flow construction.
+ ///
+ /// `style` is the style of the block that these fragments belong to.
+ pub fn minimum_line_metrics_for_fragments(
+ fragments: &[Fragment],
+ font_context: &mut LayoutFontContext,
+ style: &ComputedValues,
+ ) -> LineMetrics {
+ // As a special case, if this flow contains only hypothetical fragments, then the entire
+ // flow is hypothetical and takes up no space. See CSS 2.1 § 10.3.7.
+ if fragments.iter().all(Fragment::is_hypothetical) {
+ return LineMetrics::new(Au(0), Au(0));
+ }
+
+ let font_style = style.clone_font();
+ 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 = if fragments.iter().any(Fragment::is_text_or_replaced) {
+ InlineMetrics::from_font_metrics(&font_metrics, line_height)
+ } else {
+ InlineMetrics::new(Au(0), Au(0), Au(0))
+ };
+
+ let mut line_metrics = LineMetrics::new(Au(0), MIN_AU);
+ let mut largest_block_size_for_top_fragments = Au(0);
+ let mut largest_block_size_for_bottom_fragments = Au(0);
+
+ // We use `VerticalAlign::baseline()` here because `vertical-align` must
+ // not apply to the inside of inline blocks.
+ update_line_metrics_for_fragment(
+ &mut line_metrics,
+ &inline_metrics,
+ style.get_box().display,
+ VerticalAlign::baseline(),
+ &mut largest_block_size_for_top_fragments,
+ &mut largest_block_size_for_bottom_fragments,
+ );
+
+ // According to CSS 2.1 § 10.8, `line-height` of any inline element specifies the minimal
+ // height of line boxes within the element.
+ for inline_context in fragments
+ .iter()
+ .filter_map(|fragment| fragment.inline_context.as_ref())
+ {
+ for node in &inline_context.nodes {
+ let font_style = node.style.clone_font();
+ let font_metrics = text::font_metrics_for_style(font_context, font_style);
+ let line_height = text::line_height_from_style(&*node.style, &font_metrics);
+ let inline_metrics = InlineMetrics::from_font_metrics(&font_metrics, line_height);
+
+ update_line_metrics_for_fragment(
+ &mut line_metrics,
+ &inline_metrics,
+ node.style.get_box().display,
+ node.style.get_box().vertical_align,
+ &mut largest_block_size_for_top_fragments,
+ &mut largest_block_size_for_bottom_fragments,
+ );
+ }
+ }
+
+ line_metrics.space_above_baseline = max(
+ line_metrics.space_above_baseline,
+ largest_block_size_for_bottom_fragments - max(line_metrics.space_below_baseline, Au(0)),
+ );
+ line_metrics.space_below_baseline = max(
+ line_metrics.space_below_baseline,
+ largest_block_size_for_top_fragments - line_metrics.space_above_baseline,
+ );
+
+ return line_metrics;
+
+ fn update_line_metrics_for_fragment(
+ line_metrics: &mut LineMetrics,
+ inline_metrics: &InlineMetrics,
+ display_value: Display,
+ vertical_align_value: VerticalAlign,
+ largest_block_size_for_top_fragments: &mut Au,
+ largest_block_size_for_bottom_fragments: &mut Au,
+ ) {
+ // FIXME(emilio): This should probably be handled.
+ let vertical_align_value = match vertical_align_value {
+ VerticalAlign::Keyword(kw) => kw,
+ VerticalAlign::Length(..) => {
+ *line_metrics = line_metrics.new_metrics_for_fragment(inline_metrics);
+ return;
+ },
+ };
+
+ match (display_value, vertical_align_value) {
+ (Display::Inline, VerticalAlignKeyword::Top) |
+ (Display::Block, VerticalAlignKeyword::Top) |
+ (Display::InlineFlex, VerticalAlignKeyword::Top) |
+ (Display::InlineBlock, VerticalAlignKeyword::Top)
+ if inline_metrics.space_above_baseline >= Au(0) =>
+ {
+ *largest_block_size_for_top_fragments = max(
+ *largest_block_size_for_top_fragments,
+ inline_metrics.space_above_baseline + inline_metrics.space_below_baseline,
+ )
+ },
+ (Display::Inline, VerticalAlignKeyword::Bottom) |
+ (Display::Block, VerticalAlignKeyword::Bottom) |
+ (Display::InlineFlex, VerticalAlignKeyword::Bottom) |
+ (Display::InlineBlock, VerticalAlignKeyword::Bottom)
+ if inline_metrics.space_below_baseline >= Au(0) =>
+ {
+ *largest_block_size_for_bottom_fragments = max(
+ *largest_block_size_for_bottom_fragments,
+ inline_metrics.space_above_baseline + inline_metrics.space_below_baseline,
+ )
+ },
+ _ => *line_metrics = line_metrics.new_metrics_for_fragment(inline_metrics),
+ }
+ }
+ }
+
+ fn update_restyle_damage(&mut self) {
+ let mut damage = self.base.restyle_damage;
+
+ for frag in &self.fragments.fragments {
+ damage.insert(frag.restyle_damage());
+ }
+
+ self.base.restyle_damage = damage;
+ }
+
+ fn containing_block_range_for_flow_surrounding_fragment_at_index(
+ &self,
+ fragment_index: FragmentIndex,
+ ) -> Range<FragmentIndex> {
+ let mut start_index = fragment_index;
+ while start_index > FragmentIndex(0) &&
+ self.fragments.fragments[(start_index - FragmentIndex(1)).get() as usize]
+ .is_positioned()
+ {
+ start_index = start_index - FragmentIndex(1)
+ }
+
+ let mut end_index = fragment_index + FragmentIndex(1);
+ while end_index < FragmentIndex(self.fragments.fragments.len() as isize) &&
+ self.fragments.fragments[end_index.get() as usize].is_positioned()
+ {
+ end_index = end_index + FragmentIndex(1)
+ }
+
+ Range::new(start_index, end_index - start_index)
+ }
+
+ fn containing_block_range_for_flow(&self, opaque_flow: OpaqueFlow) -> Range<FragmentIndex> {
+ match self
+ .fragments
+ .fragments
+ .iter()
+ .position(|fragment| match fragment.specific {
+ SpecificFragmentInfo::InlineAbsolute(ref inline_absolute) => {
+ OpaqueFlow::from_flow(&*inline_absolute.flow_ref) == opaque_flow
+ },
+ SpecificFragmentInfo::InlineAbsoluteHypothetical(
+ ref inline_absolute_hypothetical,
+ ) => OpaqueFlow::from_flow(&*inline_absolute_hypothetical.flow_ref) == opaque_flow,
+ _ => false,
+ }) {
+ Some(index) => {
+ let index = FragmentIndex(index as isize);
+ self.containing_block_range_for_flow_surrounding_fragment_at_index(index)
+ },
+ None => {
+ // FIXME(pcwalton): This is quite wrong. We should only return the range
+ // surrounding the inline fragments that constitute the containing block. But this
+ // suffices to get Google looking right.
+ Range::new(
+ FragmentIndex(0),
+ FragmentIndex(self.fragments.fragments.len() as isize),
+ )
+ },
+ }
+ }
+
+ pub fn baseline_offset_of_last_line(&self) -> Option<Au> {
+ self.last_line_containing_real_fragments().map(|line| {
+ line.bounds.start.b + line.bounds.size.block - line.metrics.space_below_baseline
+ })
+ }
+
+ // Returns the last line that doesn't consist entirely of hypothetical boxes.
+ fn last_line_containing_real_fragments(&self) -> Option<&Line> {
+ for line in self.lines.iter().rev() {
+ if (line.range.begin().get()..line.range.end().get())
+ .any(|index| !self.fragments.fragments[index as usize].is_hypothetical())
+ {
+ return Some(line);
+ }
+ }
+ None
+ }
+
+ fn build_display_list_for_inline_fragment_at_index(
+ &mut self,
+ state: &mut DisplayListBuildState,
+ index: usize,
+ ) {
+ let fragment = self.fragments.fragments.get_mut(index).unwrap();
+ let stacking_relative_border_box = self
+ .base
+ .stacking_relative_border_box_for_display_list(fragment);
+ fragment.build_display_list(
+ state,
+ stacking_relative_border_box,
+ BorderPaintingMode::Separate,
+ DisplayListSection::Content,
+ self.base.clip,
+ None,
+ );
+ }
+}
+
+impl Flow for InlineFlow {
+ fn class(&self) -> FlowClass {
+ FlowClass::Inline
+ }
+
+ fn as_inline(&self) -> &InlineFlow {
+ self
+ }
+
+ fn as_mut_inline(&mut self) -> &mut InlineFlow {
+ self
+ }
+
+ fn bubble_inline_sizes(&mut self) {
+ self.update_restyle_damage();
+
+ let _scope = layout_debug_scope!("inline::bubble_inline_sizes {:x}", self.base.debug_id());
+
+ let writing_mode = self.base.writing_mode;
+ for kid in self.base.child_iter_mut() {
+ kid.mut_base().floats = Floats::new(writing_mode);
+ }
+
+ self.base
+ .flags
+ .remove(FlowFlags::CONTAINS_TEXT_OR_REPLACED_FRAGMENTS);
+
+ let mut intrinsic_sizes_for_flow = IntrinsicISizesContribution::new();
+ let mut intrinsic_sizes_for_inline_run = IntrinsicISizesContribution::new();
+ let mut intrinsic_sizes_for_nonbroken_run = IntrinsicISizesContribution::new();
+ for fragment in &mut self.fragments.fragments {
+ let intrinsic_sizes_for_fragment = fragment.compute_intrinsic_inline_sizes().finish();
+ match fragment.style.get_inherited_text().white_space {
+ WhiteSpace::Nowrap => intrinsic_sizes_for_nonbroken_run
+ .union_nonbreaking_inline(&intrinsic_sizes_for_fragment),
+ WhiteSpace::Pre => {
+ intrinsic_sizes_for_nonbroken_run
+ .union_nonbreaking_inline(&intrinsic_sizes_for_fragment);
+
+ // Flush the intrinsic sizes we've been gathering up in order to handle the
+ // line break, if necessary.
+ if fragment.requires_line_break_afterward_if_wrapping_on_newlines() {
+ intrinsic_sizes_for_inline_run
+ .union_inline(&intrinsic_sizes_for_nonbroken_run.finish());
+ intrinsic_sizes_for_nonbroken_run = IntrinsicISizesContribution::new();
+ intrinsic_sizes_for_flow
+ .union_block(&intrinsic_sizes_for_inline_run.finish());
+ intrinsic_sizes_for_inline_run = IntrinsicISizesContribution::new();
+ }
+ },
+ WhiteSpace::PreWrap | WhiteSpace::PreLine => {
+ // Flush the intrinsic sizes we were gathering up for the nonbroken run, if
+ // necessary.
+ intrinsic_sizes_for_inline_run
+ .union_inline(&intrinsic_sizes_for_nonbroken_run.finish());
+ intrinsic_sizes_for_nonbroken_run = IntrinsicISizesContribution::new();
+
+ intrinsic_sizes_for_nonbroken_run.union_inline(&intrinsic_sizes_for_fragment);
+
+ // Flush the intrinsic sizes we've been gathering up in order to handle the
+ // line break, if necessary.
+ if fragment.requires_line_break_afterward_if_wrapping_on_newlines() {
+ intrinsic_sizes_for_inline_run
+ .union_inline(&intrinsic_sizes_for_nonbroken_run.finish());
+ intrinsic_sizes_for_nonbroken_run = IntrinsicISizesContribution::new();
+ intrinsic_sizes_for_flow
+ .union_block(&intrinsic_sizes_for_inline_run.finish());
+ intrinsic_sizes_for_inline_run = IntrinsicISizesContribution::new();
+ }
+ },
+ WhiteSpace::Normal => {
+ // Flush the intrinsic sizes we were gathering up for the nonbroken run, if
+ // necessary.
+ intrinsic_sizes_for_inline_run
+ .union_inline(&intrinsic_sizes_for_nonbroken_run.finish());
+ intrinsic_sizes_for_nonbroken_run = IntrinsicISizesContribution::new();
+
+ intrinsic_sizes_for_nonbroken_run.union_inline(&intrinsic_sizes_for_fragment);
+ },
+ }
+
+ fragment
+ .restyle_damage
+ .remove(ServoRestyleDamage::BUBBLE_ISIZES);
+
+ if fragment.is_text_or_replaced() {
+ self.base
+ .flags
+ .insert(FlowFlags::CONTAINS_TEXT_OR_REPLACED_FRAGMENTS);
+ }
+ }
+
+ // Flush any remaining nonbroken-run and inline-run intrinsic sizes.
+ intrinsic_sizes_for_inline_run.union_inline(&intrinsic_sizes_for_nonbroken_run.finish());
+ intrinsic_sizes_for_flow.union_block(&intrinsic_sizes_for_inline_run.finish());
+
+ // Finish up the computation.
+ self.base.intrinsic_inline_sizes = intrinsic_sizes_for_flow.finish()
+ }
+
+ /// 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 {:x}", 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.block_container_inline_size;
+ let container_mode = self.base.block_container_writing_mode;
+ let container_block_size = self.base.block_container_explicit_block_size;
+ self.base.position.size.inline = inline_size;
+
+ {
+ let this = &mut *self;
+ for fragment in this.fragments.fragments.iter_mut() {
+ fragment.compute_border_and_padding(inline_size);
+ fragment.compute_block_direction_margins(inline_size);
+ fragment.compute_inline_direction_margins(inline_size);
+ fragment
+ .assign_replaced_inline_size_if_necessary(inline_size, container_block_size);
+ }
+ }
+
+ // If there are any inline-block kids, propagate explicit block and inline
+ // sizes down to them.
+ let block_container_explicit_block_size = self.base.block_container_explicit_block_size;
+ for kid in self.base.child_iter_mut() {
+ let kid_base = kid.mut_base();
+
+ kid_base.block_container_inline_size = inline_size;
+ kid_base.block_container_writing_mode = container_mode;
+ kid_base.block_container_explicit_block_size = block_container_explicit_block_size;
+ }
+ }
+
+ /// Calculate and set the block-size of this flow. See CSS 2.1 § 10.6.1.
+ /// Note that we do not need to do in-order traversal because the children
+ /// are always block formatting context.
+ fn assign_block_size(&mut self, layout_context: &LayoutContext) {
+ let _scope = layout_debug_scope!("inline::assign_block_size {:x}", self.base.debug_id());
+
+ // Divide the fragments into lines.
+ //
+ // TODO(pcwalton, #226): Get the CSS `line-height` property from the
+ // style of the containing block to determine the minimum line block
+ // size.
+ //
+ // TODO(pcwalton, #226): Get the CSS `line-height` property from each
+ // non-replaced inline element to determine its block-size for computing
+ // the line's own block-size.
+ //
+ // TODO(pcwalton): Cache the line scanner?
+ debug!(
+ "assign_block_size_inline: floats in: {:?}",
+ self.base.floats
+ );
+
+ // Assign the block-size and late-computed inline-sizes for the inline fragments.
+ for fragment in &mut self.fragments.fragments {
+ fragment.update_late_computed_replaced_inline_size_if_necessary();
+ fragment.assign_replaced_block_size_if_necessary();
+ }
+
+ // Reset our state, so that we handle incremental reflow correctly.
+ //
+ // TODO(pcwalton): Do something smarter, like Gecko and WebKit?
+ self.lines.clear();
+
+ // Determine how much indentation the first line wants.
+ let mut indentation = if self.fragments.is_empty() {
+ Au(0)
+ } else {
+ self.first_line_indentation
+ };
+
+ // Perform line breaking.
+ let mut scanner = LineBreaker::new(
+ self.base.floats.clone(),
+ indentation,
+ &self.minimum_line_metrics,
+ );
+ scanner.scan_for_lines(self, layout_context);
+
+ // Now, go through each line and lay out the fragments inside.
+ let line_count = self.lines.len();
+ for (line_index, line) in self.lines.iter_mut().enumerate() {
+ // Lay out fragments in the inline direction, and justify them if
+ // necessary.
+ InlineFlow::set_inline_fragment_positions(
+ &mut self.fragments,
+ line,
+ self.base.flags.text_align(),
+ indentation,
+ line_index + 1 == line_count,
+ );
+
+ // Compute the final positions in the block direction of each fragment.
+ InlineFlow::set_block_fragment_positions(
+ &mut self.fragments,
+ line,
+ &self.minimum_line_metrics,
+ layout_context,
+ );
+
+ // This is used to set the block-start position of the next line in
+ // the next iteration of the loop. We're no longer on the first
+ // line, so set indentation to zero.
+ indentation = Au(0)
+ }
+
+ if self.is_absolute_containing_block() {
+ // 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 containing block, which may also be an absolute flow.
+ let assign_abs_b_sizes = AbsoluteAssignBSizesTraversal(layout_context.shared_context());
+ assign_abs_b_sizes.traverse_absolute_flows(&mut *self);
+ }
+
+ self.base.position.size.block = match self.last_line_containing_real_fragments() {
+ Some(last_line) => last_line.bounds.start.b + last_line.bounds.size.block,
+ None => Au(0),
+ };
+
+ self.base.floats = scanner.floats;
+ let writing_mode = self.base.floats.writing_mode;
+ self.base.floats.translate(LogicalSize::new(
+ writing_mode,
+ Au(0),
+ -self.base.position.size.block,
+ ));
+
+ let containing_block_size =
+ LogicalSize::new(writing_mode, Au(0), self.base.position.size.block);
+ self.mutate_fragments(&mut |f: &mut Fragment| match f.specific {
+ SpecificFragmentInfo::InlineBlock(ref mut info) => {
+ let block = FlowRef::deref_mut(&mut info.flow_ref);
+ block.mut_base().early_absolute_position_info = EarlyAbsolutePositionInfo {
+ relative_containing_block_size: containing_block_size,
+ relative_containing_block_mode: writing_mode,
+ };
+ },
+ SpecificFragmentInfo::InlineAbsolute(ref mut info) => {
+ let block = FlowRef::deref_mut(&mut info.flow_ref);
+ block.mut_base().early_absolute_position_info = EarlyAbsolutePositionInfo {
+ relative_containing_block_size: containing_block_size,
+ relative_containing_block_mode: writing_mode,
+ };
+ },
+ _ => (),
+ });
+
+ self.base
+ .restyle_damage
+ .remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW);
+ for fragment in &mut self.fragments.fragments {
+ fragment
+ .restyle_damage
+ .remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW);
+ }
+ }
+
+ fn compute_stacking_relative_position(&mut self, _: &LayoutContext) {
+ // First, gather up the positions of all the containing blocks (if any).
+ //
+ // FIXME(pcwalton): This will get the absolute containing blocks inside `...` wrong in the
+ // case of something like:
+ //
+ // <span style="position: relative">
+ // Foo
+ // <span style="display: inline-block">...</span>
+ // </span>
+ let mut containing_block_positions = Vec::new();
+ let container_size = Size2D::new(self.base.block_container_inline_size, Au(0));
+ for (fragment_index, fragment) in self.fragments.fragments.iter().enumerate() {
+ match fragment.specific {
+ SpecificFragmentInfo::InlineAbsolute(_) => {
+ let containing_block_range = self
+ .containing_block_range_for_flow_surrounding_fragment_at_index(
+ FragmentIndex(fragment_index as isize),
+ );
+ let first_fragment_index = containing_block_range.begin().get() as usize;
+ debug_assert!(first_fragment_index < self.fragments.fragments.len());
+ let first_fragment = &self.fragments.fragments[first_fragment_index];
+ let padding_box_origin = (first_fragment.border_box -
+ first_fragment.style.logical_border_width())
+ .start;
+ containing_block_positions.push(
+ padding_box_origin.to_physical(self.base.writing_mode, container_size),
+ );
+ },
+ SpecificFragmentInfo::InlineBlock(_) if fragment.is_positioned() => {
+ let containing_block_range = self
+ .containing_block_range_for_flow_surrounding_fragment_at_index(
+ FragmentIndex(fragment_index as isize),
+ );
+ let first_fragment_index = containing_block_range.begin().get() as usize;
+ debug_assert!(first_fragment_index < self.fragments.fragments.len());
+ let first_fragment = &self.fragments.fragments[first_fragment_index];
+ let padding_box_origin = (first_fragment.border_box -
+ first_fragment.style.logical_border_width())
+ .start;
+ containing_block_positions.push(
+ padding_box_origin.to_physical(self.base.writing_mode, container_size),
+ );
+ },
+ _ => {},
+ }
+ }
+
+ // Then compute the positions of all of our fragments.
+ let mut containing_block_positions = containing_block_positions.iter();
+ for fragment in &mut self.fragments.fragments {
+ let stacking_relative_border_box = fragment.stacking_relative_border_box(
+ &self.base.stacking_relative_position,
+ &self
+ .base
+ .early_absolute_position_info
+ .relative_containing_block_size,
+ self.base
+ .early_absolute_position_info
+ .relative_containing_block_mode,
+ CoordinateSystem::Parent,
+ );
+ let stacking_relative_content_box =
+ fragment.stacking_relative_content_box(stacking_relative_border_box);
+
+ let is_positioned = fragment.is_positioned();
+ match fragment.specific {
+ SpecificFragmentInfo::InlineBlock(ref mut info) => {
+ let flow = FlowRef::deref_mut(&mut info.flow_ref);
+ let block_flow = flow.as_mut_block();
+ block_flow.base.late_absolute_position_info =
+ self.base.late_absolute_position_info;
+
+ let stacking_relative_position = self.base.stacking_relative_position;
+ if is_positioned {
+ let padding_box_origin = containing_block_positions.next().unwrap();
+ block_flow
+ .base
+ .late_absolute_position_info
+ .stacking_relative_position_of_absolute_containing_block =
+ *padding_box_origin + stacking_relative_position;
+ }
+
+ block_flow.base.stacking_relative_position =
+ stacking_relative_content_box.origin.to_vector();
+
+ // Write the clip in our coordinate system into the child flow. (The kid will
+ // fix it up to be in its own coordinate system if necessary.)
+ block_flow.base.clip = self.base.clip.clone()
+ },
+ SpecificFragmentInfo::InlineAbsoluteHypothetical(ref mut info) => {
+ let flow = FlowRef::deref_mut(&mut info.flow_ref);
+ let block_flow = flow.as_mut_block();
+ block_flow.base.late_absolute_position_info =
+ self.base.late_absolute_position_info;
+
+ block_flow.base.stacking_relative_position =
+ stacking_relative_border_box.origin.to_vector();
+
+ // As above, this is in our coordinate system for now.
+ block_flow.base.clip = self.base.clip.clone()
+ },
+ SpecificFragmentInfo::InlineAbsolute(ref mut info) => {
+ let flow = FlowRef::deref_mut(&mut info.flow_ref);
+ let block_flow = flow.as_mut_block();
+ block_flow.base.late_absolute_position_info =
+ self.base.late_absolute_position_info;
+
+ let stacking_relative_position = self.base.stacking_relative_position;
+ let padding_box_origin = containing_block_positions.next().unwrap();
+ block_flow
+ .base
+ .late_absolute_position_info
+ .stacking_relative_position_of_absolute_containing_block =
+ *padding_box_origin + stacking_relative_position;
+
+ block_flow.base.stacking_relative_position =
+ stacking_relative_border_box.origin.to_vector();
+
+ // As above, this is in our coordinate system for now.
+ block_flow.base.clip = self.base.clip.clone()
+ },
+ _ => {},
+ }
+ }
+ }
+
+ fn update_late_computed_inline_position_if_necessary(&mut self, _: Au) {}
+
+ fn update_late_computed_block_position_if_necessary(&mut self, _: Au) {}
+
+ fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
+ self.base.stacking_context_id = state.current_stacking_context_id;
+ self.base.clipping_and_scrolling = Some(state.current_clipping_and_scrolling);
+ self.base.clip = state
+ .clip_stack
+ .last()
+ .cloned()
+ .unwrap_or_else(Rect::max_rect);
+
+ let previous_cb_clipping_and_scrolling = state.containing_block_clipping_and_scrolling;
+
+ for fragment in self.fragments.fragments.iter_mut() {
+ state.containing_block_clipping_and_scrolling = previous_cb_clipping_and_scrolling;
+
+ let abspos_containing_block = fragment.style.get_box().position != Position::Static;
+ if abspos_containing_block {
+ state.containing_block_clipping_and_scrolling =
+ state.current_clipping_and_scrolling;
+ }
+
+ // We clear this here, but it might be set again if we create a stacking context for
+ // this fragment.
+ fragment.established_reference_frame = None;
+
+ if !fragment.collect_stacking_contexts_for_blocklike_fragment(state) {
+ if !fragment.establishes_stacking_context() {
+ fragment.stacking_context_id = state.current_stacking_context_id;
+ } else {
+ fragment.create_stacking_context_for_inline_block(&self.base, state);
+ }
+ }
+
+ // Reset the containing block clipping and scrolling before each loop iteration,
+ // so we don't pollute subsequent fragments.
+ state.containing_block_clipping_and_scrolling = previous_cb_clipping_and_scrolling;
+ }
+ }
+
+ fn build_display_list(&mut self, state: &mut DisplayListBuildState) {
+ debug!(
+ "Flow: building display list for {} inline fragments",
+ self.fragments.len()
+ );
+
+ // We iterate using an index here, because we want to avoid doing a doing
+ // a double-borrow of self (one mutable for the method call and one immutable
+ // for the self.fragments.fragment iterator itself).
+ for index in 0..self.fragments.fragments.len() {
+ let (establishes_stacking_context, stacking_context_id) = {
+ let fragment = self.fragments.fragments.get(index).unwrap();
+ (
+ self.base.stacking_context_id != fragment.stacking_context_id,
+ fragment.stacking_context_id,
+ )
+ };
+
+ let parent_stacking_context_id = state.current_stacking_context_id;
+ if establishes_stacking_context {
+ state.current_stacking_context_id = stacking_context_id;
+ }
+
+ self.build_display_list_for_inline_fragment_at_index(state, index);
+
+ if establishes_stacking_context {
+ state.current_stacking_context_id = parent_stacking_context_id
+ }
+ }
+
+ if !self.fragments.fragments.is_empty() {
+ self.base
+ .build_display_items_for_debugging_tint(state, self.fragments.fragments[0].node);
+ }
+ }
+
+ fn repair_style(&mut self, _: &ServoArc<ComputedValues>) {}
+
+ fn compute_overflow(&self) -> Overflow {
+ let mut overflow = Overflow::new();
+ let flow_size = self.base.position.size.to_physical(self.base.writing_mode);
+ let relative_containing_block_size = &self
+ .base
+ .early_absolute_position_info
+ .relative_containing_block_size;
+ for fragment in &self.fragments.fragments {
+ overflow.union(&fragment.compute_overflow(&flow_size, &relative_containing_block_size))
+ }
+ overflow
+ }
+
+ fn iterate_through_fragment_border_boxes(
+ &self,
+ iterator: &mut dyn FragmentBorderBoxIterator,
+ level: i32,
+ stacking_context_position: &Point2D<Au>,
+ ) {
+ // FIXME(#2795): Get the real container size.
+ for fragment in &self.fragments.fragments {
+ if !iterator.should_process(fragment) {
+ continue;
+ }
+
+ let stacking_relative_position = &self.base.stacking_relative_position;
+ let relative_containing_block_size = &self
+ .base
+ .early_absolute_position_info
+ .relative_containing_block_size;
+ let relative_containing_block_mode = self
+ .base
+ .early_absolute_position_info
+ .relative_containing_block_mode;
+ iterator.process(
+ fragment,
+ level,
+ &fragment
+ .stacking_relative_border_box(
+ stacking_relative_position,
+ relative_containing_block_size,
+ relative_containing_block_mode,
+ CoordinateSystem::Own,
+ )
+ .translate(&stacking_context_position.to_vector()),
+ )
+ }
+ }
+
+ fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) {
+ for fragment in &mut self.fragments.fragments {
+ (*mutator)(fragment)
+ }
+ }
+
+ fn contains_positioned_fragments(&self) -> bool {
+ self.fragments
+ .fragments
+ .iter()
+ .any(|fragment| fragment.is_positioned())
+ }
+
+ fn contains_relatively_positioned_fragments(&self) -> bool {
+ self.fragments
+ .fragments
+ .iter()
+ .any(|fragment| fragment.style.get_box().position == Position::Relative)
+ }
+
+ fn generated_containing_block_size(&self, for_flow: OpaqueFlow) -> LogicalSize<Au> {
+ let mut containing_block_size = LogicalSize::new(self.base.writing_mode, Au(0), Au(0));
+ for index in self.containing_block_range_for_flow(for_flow).each_index() {
+ let fragment = &self.fragments.fragments[index.get() as usize];
+ if fragment.is_absolutely_positioned() {
+ continue;
+ }
+ containing_block_size.inline =
+ containing_block_size.inline + fragment.border_box.size.inline;
+ containing_block_size.block =
+ max(containing_block_size.block, fragment.border_box.size.block);
+ }
+ containing_block_size
+ }
+
+ fn print_extra_flow_children(&self, print_tree: &mut PrintTree) {
+ for fragment in &self.fragments.fragments {
+ print_tree.add_item(format!("{:?}", fragment));
+ }
+ }
+}
+
+impl fmt::Debug for InlineFlow {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "{:?}({:x}) {:?}",
+ self.class(),
+ self.base.debug_id(),
+ self.base()
+ )
+ }
+}
+
+#[derive(Clone)]
+pub struct InlineFragmentNodeInfo {
+ pub address: OpaqueNode,
+ pub style: ServoArc<ComputedValues>,
+ pub selected_style: ServoArc<ComputedValues>,
+ pub pseudo: PseudoElementType,
+ pub flags: InlineFragmentNodeFlags,
+}
+
+bitflags! {
+ pub struct InlineFragmentNodeFlags: u8 {
+ const FIRST_FRAGMENT_OF_ELEMENT = 0x01;
+ const LAST_FRAGMENT_OF_ELEMENT = 0x02;
+ }
+}
+
+impl fmt::Debug for InlineFragmentNodeInfo {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{:?}", self.flags.bits())
+ }
+}
+
+#[derive(Clone)]
+pub struct InlineFragmentContext {
+ /// The list of nodes that this fragment will be inheriting styles from,
+ /// from the most deeply-nested node out.
+ pub nodes: Vec<InlineFragmentNodeInfo>,
+}
+
+impl InlineFragmentContext {
+ pub fn new() -> InlineFragmentContext {
+ InlineFragmentContext { nodes: vec![] }
+ }
+
+ #[inline]
+ pub fn contains_node(&self, node_address: OpaqueNode) -> bool {
+ self.nodes
+ .iter()
+ .position(|node| node.address == node_address)
+ .is_some()
+ }
+
+ fn ptr_eq(&self, other: &InlineFragmentContext) -> bool {
+ if self.nodes.len() != other.nodes.len() {
+ return false;
+ }
+ for (this_node, other_node) in self.nodes.iter().zip(&other.nodes) {
+ if this_node.address != other_node.address {
+ return false;
+ }
+ }
+ true
+ }
+}
+
+fn inline_contexts_are_equal(
+ inline_context_a: &Option<InlineFragmentContext>,
+ inline_context_b: &Option<InlineFragmentContext>,
+) -> bool {
+ match (inline_context_a, inline_context_b) {
+ (&Some(ref inline_context_a), &Some(ref inline_context_b)) => {
+ inline_context_a.ptr_eq(inline_context_b)
+ },
+ (&None, &None) => true,
+ (&Some(_), &None) | (&None, &Some(_)) => false,
+ }
+}
+
+/// Ascent and space needed above and below the baseline for a fragment. See CSS 2.1 § 10.8.1.
+///
+/// Descent is not included in this structure because it can be computed from the fragment's
+/// border/content box and the ascent.
+#[derive(Clone, Copy, Debug, Serialize)]
+pub struct InlineMetrics {
+ /// The amount of space above the baseline needed for this fragment.
+ pub space_above_baseline: Au,
+ /// The amount of space below the baseline needed for this fragment.
+ pub space_below_baseline: Au,
+ /// The distance from the baseline to the top of this fragment. This can differ from
+ /// `block_size_above_baseline` if the fragment needs some empty space above it due to
+ /// line-height, etc.
+ pub ascent: Au,
+}
+
+impl InlineMetrics {
+ /// Creates a new set of inline metrics.
+ pub fn new(space_above_baseline: Au, space_below_baseline: Au, ascent: Au) -> InlineMetrics {
+ InlineMetrics {
+ space_above_baseline: space_above_baseline,
+ space_below_baseline: space_below_baseline,
+ ascent: ascent,
+ }
+ }
+
+ /// 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);
+
+ // Calculating the half leading here and then using leading - half_leading
+ // below ensure that we don't introduce any rounding accuracy issues here.
+ // The invariant is that the resulting total line height must exactly
+ // equal the requested line_height.
+ let half_leading = leading.scale_by(0.5);
+ InlineMetrics {
+ space_above_baseline: font_metrics.ascent + half_leading,
+ space_below_baseline: font_metrics.descent + leading - half_leading,
+ ascent: font_metrics.ascent,
+ }
+ }
+
+ /// Returns the sum of the space needed above and below the baseline.
+ fn space_needed(&self) -> Au {
+ self.space_above_baseline + self.space_below_baseline
+ }
+}
+
+#[derive(Clone, Copy, PartialEq)]
+enum LineFlushMode {
+ No,
+ Flush,
+}
+
+#[derive(Clone, Copy, Debug, Serialize)]
+pub struct LineMetrics {
+ pub space_above_baseline: Au,
+ pub space_below_baseline: Au,
+}
+
+impl LineMetrics {
+ pub fn new(space_above_baseline: Au, space_below_baseline: Au) -> LineMetrics {
+ LineMetrics {
+ space_above_baseline: space_above_baseline,
+ space_below_baseline: space_below_baseline,
+ }
+ }
+
+ /// Returns the line metrics that result from combining the line that these metrics represent
+ /// with a fragment with the given metrics.
+ fn new_metrics_for_fragment(&self, fragment_inline_metrics: &InlineMetrics) -> LineMetrics {
+ LineMetrics {
+ space_above_baseline: max(
+ self.space_above_baseline,
+ fragment_inline_metrics.space_above_baseline,
+ ),
+ space_below_baseline: max(
+ self.space_below_baseline,
+ fragment_inline_metrics.space_below_baseline,
+ ),
+ }
+ }
+
+ fn for_line_and_fragment(
+ line: &Line,
+ fragment: &Fragment,
+ layout_context: &LayoutContext,
+ ) -> LineMetrics {
+ if !fragment.is_hypothetical() {
+ let space_above_baseline = line.metrics.space_above_baseline;
+ return LineMetrics {
+ space_above_baseline: space_above_baseline,
+ space_below_baseline: line.bounds.size.block - space_above_baseline,
+ };
+ }
+
+ let hypothetical_line_metrics = line.new_metrics_for_fragment(fragment, layout_context);
+ let hypothetical_block_size =
+ line.new_block_size_for_fragment(fragment, &hypothetical_line_metrics, layout_context);
+ let hypothetical_space_above_baseline = hypothetical_line_metrics.space_above_baseline;
+ LineMetrics {
+ space_above_baseline: hypothetical_space_above_baseline,
+ space_below_baseline: hypothetical_block_size - hypothetical_space_above_baseline,
+ }
+ }
+
+ /// Returns the sum of the space needed above and below the baseline.
+ pub fn space_needed(&self) -> Au {
+ self.space_above_baseline + self.space_below_baseline
+ }
+}
diff --git a/components/layout_2020/layout_debug.rs b/components/layout_2020/layout_debug.rs
new file mode 100644
index 00000000000..d3af6b4d8d5
--- /dev/null
+++ b/components/layout_2020/layout_debug.rs
@@ -0,0 +1,124 @@
+/* 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 https://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.
+
+use crate::flow::GetBaseFlow;
+use crate::flow_ref::FlowRef;
+use serde_json::{to_string, to_value, Value};
+use std::borrow::ToOwned;
+use std::cell::RefCell;
+use std::fs::File;
+use std::io::Write;
+#[cfg(debug_assertions)]
+use std::sync::atomic::{AtomicUsize, Ordering};
+
+thread_local!(static STATE_KEY: RefCell<Option<State>> = RefCell::new(None));
+
+#[cfg(debug_assertions)]
+static DEBUG_ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
+
+pub struct Scope;
+
+#[macro_export]
+macro_rules! layout_debug_scope(
+ ($($arg:tt)*) => (
+ if cfg!(debug_assertions) {
+ layout_debug::Scope::new(format!($($arg)*))
+ } else {
+ layout_debug::Scope
+ }
+ )
+);
+
+#[derive(Serialize)]
+struct ScopeData {
+ name: String,
+ pre: Value,
+ post: Value,
+ children: Vec<Box<ScopeData>>,
+}
+
+impl ScopeData {
+ fn new(name: String, pre: Value) -> ScopeData {
+ ScopeData {
+ name: name,
+ pre: pre,
+ post: Value::Null,
+ 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 {
+ STATE_KEY.with(|ref r| {
+ if let Some(ref mut state) = *r.borrow_mut() {
+ let flow_trace = to_value(&state.flow_root.base()).unwrap();
+ let data = Box::new(ScopeData::new(name.clone(), flow_trace));
+ state.scope_stack.push(data);
+ }
+ });
+ Scope
+ }
+}
+
+#[cfg(debug_assertions)]
+impl Drop for Scope {
+ fn drop(&mut self) {
+ STATE_KEY.with(|ref r| {
+ if let Some(ref mut state) = *r.borrow_mut() {
+ let mut current_scope = state.scope_stack.pop().unwrap();
+ current_scope.post = to_value(&state.flow_root.base()).unwrap();
+ let previous_scope = state.scope_stack.last_mut().unwrap();
+ previous_scope.children.push(current_scope);
+ }
+ });
+ }
+}
+
+/// Generate a unique ID. This is used for items such as Fragment
+/// which are often reallocated but represent essentially the
+/// same data.
+#[cfg(debug_assertions)]
+pub fn generate_unique_debug_id() -> u16 {
+ DEBUG_ID_COUNTER.fetch_add(1, Ordering::SeqCst) as u16
+}
+
+/// 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.with(|ref r| r.borrow().is_none()));
+
+ STATE_KEY.with(|ref r| {
+ let flow_trace = to_value(&flow_root.base()).unwrap();
+ let state = State {
+ scope_stack: vec![Box::new(ScopeData::new("root".to_owned(), flow_trace))],
+ flow_root: flow_root.clone(),
+ };
+ *r.borrow_mut() = Some(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(generation: u32) {
+ let mut thread_state = STATE_KEY.with(|ref r| r.borrow_mut().take().unwrap());
+ assert_eq!(thread_state.scope_stack.len(), 1);
+ let mut root_scope = thread_state.scope_stack.pop().unwrap();
+ root_scope.post = to_value(&thread_state.flow_root.base()).unwrap();
+
+ let result = to_string(&root_scope).unwrap();
+ let mut file = File::create(format!("layout_trace-{}.json", generation)).unwrap();
+ file.write_all(result.as_bytes()).unwrap();
+}
diff --git a/components/layout_2020/lib.rs b/components/layout_2020/lib.rs
index daa3e8897c2..f8afdc79190 100644
--- a/components/layout_2020/lib.rs
+++ b/components/layout_2020/lib.rs
@@ -1,3 +1,63 @@
/* 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 https://mozilla.org/MPL/2.0/. */
+
+#![deny(unsafe_code)]
+
+#[macro_use]
+extern crate bitflags;
+#[macro_use]
+extern crate html5ever;
+#[macro_use]
+extern crate log;
+#[macro_use]
+extern crate range;
+#[macro_use]
+extern crate serde;
+
+#[macro_use]
+pub mod layout_debug;
+
+pub mod animation;
+mod block;
+pub mod construct;
+pub mod context;
+pub mod data;
+pub mod display_list;
+mod flex;
+mod floats;
+pub mod flow;
+mod flow_list;
+pub mod flow_ref;
+mod fragment;
+mod generated_content;
+pub mod incremental;
+mod inline;
+mod linked_list;
+mod list_item;
+mod model;
+mod multicol;
+pub mod opaque_node;
+pub mod parallel;
+mod persistent_list;
+pub mod query;
+pub mod sequential;
+mod table;
+mod table_caption;
+mod table_cell;
+mod table_colgroup;
+mod table_row;
+mod table_rowgroup;
+mod table_wrapper;
+mod text;
+pub mod traversal;
+pub mod wrapper;
+
+// For unit tests:
+pub use self::data::LayoutData;
+pub use crate::fragment::Fragment;
+pub use crate::fragment::SpecificFragmentInfo;
+
+// We can't use servo_arc for everything in layout, because the Flow stuff uses
+// weak references.
+use servo_arc::Arc as ServoArc;
diff --git a/components/layout_2020/linked_list.rs b/components/layout_2020/linked_list.rs
new file mode 100644
index 00000000000..09e605a3d31
--- /dev/null
+++ b/components/layout_2020/linked_list.rs
@@ -0,0 +1,21 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+//! Utility functions for doubly-linked lists.
+
+use std::collections::LinkedList;
+use std::mem;
+
+/// Splits the head off a list in O(1) time, and returns the head.
+pub fn split_off_head<T>(list: &mut LinkedList<T>) -> LinkedList<T> {
+ let tail = list.split_off(1);
+ mem::replace(list, tail)
+}
+
+/// Prepends the items in the other list to this one, leaving the other list empty.
+#[inline]
+pub fn prepend_from<T>(this: &mut LinkedList<T>, other: &mut LinkedList<T>) {
+ other.append(this);
+ mem::swap(this, other);
+}
diff --git a/components/layout_2020/list_item.rs b/components/layout_2020/list_item.rs
new file mode 100644
index 00000000000..3416ff444b3
--- /dev/null
+++ b/components/layout_2020/list_item.rs
@@ -0,0 +1,323 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+//! Layout for elements with a CSS `display` property of `list-item`. These elements consist of a
+//! block and an extra inline fragment for the marker.
+
+use crate::block::BlockFlow;
+use crate::context::{with_thread_local_font_context, LayoutContext};
+use crate::display_list::items::DisplayListSection;
+use crate::display_list::{
+ BorderPaintingMode, DisplayListBuildState, StackingContextCollectionState,
+};
+use crate::floats::FloatKind;
+use crate::flow::{Flow, FlowClass, OpaqueFlow};
+use crate::fragment::Overflow;
+use crate::fragment::{
+ CoordinateSystem, Fragment, FragmentBorderBoxIterator, GeneratedContentInfo,
+};
+use crate::generated_content;
+use crate::inline::InlineFlow;
+use app_units::Au;
+use euclid::Point2D;
+use style::computed_values::list_style_type::T as ListStyleType;
+use style::computed_values::position::T as Position;
+use style::logical_geometry::LogicalSize;
+use style::properties::ComputedValues;
+use style::servo::restyle_damage::ServoRestyleDamage;
+
+#[allow(unsafe_code)]
+unsafe impl crate::flow::HasBaseFlow for ListItemFlow {}
+
+/// A block with the CSS `display` property equal to `list-item`.
+#[derive(Debug)]
+#[repr(C)]
+pub struct ListItemFlow {
+ /// Data common to all block flows.
+ pub block_flow: BlockFlow,
+ /// The marker, if outside. (Markers that are inside are instead just fragments on the interior
+ /// `InlineFlow`.)
+ pub marker_fragments: Vec<Fragment>,
+}
+
+impl ListItemFlow {
+ pub fn from_fragments_and_flotation(
+ main_fragment: Fragment,
+ marker_fragments: Vec<Fragment>,
+ flotation: Option<FloatKind>,
+ ) -> ListItemFlow {
+ let mut this = ListItemFlow {
+ block_flow: BlockFlow::from_fragment_and_float_kind(main_fragment, flotation),
+ marker_fragments: marker_fragments,
+ };
+
+ if let Some(ref marker) = this.marker_fragments.first() {
+ match marker.style().get_list().list_style_type {
+ ListStyleType::Disc |
+ ListStyleType::None |
+ ListStyleType::Circle |
+ ListStyleType::Square |
+ ListStyleType::DisclosureOpen |
+ ListStyleType::DisclosureClosed => {},
+ _ => this
+ .block_flow
+ .base
+ .restyle_damage
+ .insert(ServoRestyleDamage::RESOLVE_GENERATED_CONTENT),
+ }
+ }
+
+ this
+ }
+
+ /// Assign inline size and position for the marker. This is done during the `assign_block_size`
+ /// traversal because floats will impact the marker position. Therefore we need to have already
+ /// called `assign_block_size` on the list item's block flow, in order to know which floats
+ /// impact the position.
+ ///
+ /// Per CSS 2.1 § 12.5.1, the marker position is not precisely specified, but it must be on the
+ /// left side of the content (for ltr direction). However, flowing the marker around floats
+ /// matches the rendering of Gecko and Blink.
+ fn assign_marker_inline_sizes(&mut self, layout_context: &LayoutContext) {
+ let base = &self.block_flow.base;
+ let available_rect = base.floats.available_rect(
+ -base.position.size.block,
+ base.position.size.block,
+ base.block_container_inline_size,
+ );
+ let mut marker_inline_start = available_rect
+ .unwrap_or(self.block_flow.fragment.border_box)
+ .start
+ .i;
+
+ for marker in self.marker_fragments.iter_mut().rev() {
+ let container_block_size = self
+ .block_flow
+ .explicit_block_containing_size(layout_context.shared_context());
+ marker.assign_replaced_inline_size_if_necessary(
+ base.block_container_inline_size,
+ container_block_size,
+ );
+
+ // Do this now. There's no need to do this in bubble-widths, since markers do not
+ // contribute to the inline size of this flow.
+ let intrinsic_inline_sizes = marker.compute_intrinsic_inline_sizes();
+
+ marker.border_box.size.inline = intrinsic_inline_sizes
+ .content_intrinsic_sizes
+ .preferred_inline_size;
+ marker_inline_start = marker_inline_start - marker.border_box.size.inline;
+ marker.border_box.start.i = marker_inline_start;
+ }
+ }
+
+ fn assign_marker_block_sizes(&mut self, layout_context: &LayoutContext) {
+ // FIXME(pcwalton): Do this during flow construction, like `InlineFlow` does?
+ let marker_line_metrics = with_thread_local_font_context(layout_context, |font_context| {
+ InlineFlow::minimum_line_metrics_for_fragments(
+ &self.marker_fragments,
+ font_context,
+ &*self.block_flow.fragment.style,
+ )
+ });
+
+ for marker in &mut self.marker_fragments {
+ marker.assign_replaced_block_size_if_necessary();
+ let marker_inline_metrics = marker.aligned_inline_metrics(
+ layout_context,
+ &marker_line_metrics,
+ Some(&marker_line_metrics),
+ );
+ marker.border_box.start.b =
+ marker_line_metrics.space_above_baseline - marker_inline_metrics.ascent;
+ }
+ }
+}
+
+impl Flow for ListItemFlow {
+ fn class(&self) -> FlowClass {
+ FlowClass::ListItem
+ }
+
+ fn as_mut_block(&mut self) -> &mut BlockFlow {
+ &mut self.block_flow
+ }
+
+ fn as_block(&self) -> &BlockFlow {
+ &self.block_flow
+ }
+
+ fn bubble_inline_sizes(&mut self) {
+ // The marker contributes no intrinsic inline-size, so…
+ self.block_flow.bubble_inline_sizes()
+ }
+
+ fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
+ self.block_flow.assign_inline_sizes(layout_context);
+ }
+
+ fn assign_block_size(&mut self, layout_context: &LayoutContext) {
+ self.block_flow.assign_block_size(layout_context);
+ self.assign_marker_inline_sizes(layout_context);
+ self.assign_marker_block_sizes(layout_context);
+ }
+
+ fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
+ self.block_flow
+ .compute_stacking_relative_position(layout_context)
+ }
+
+ fn place_float_if_applicable<'a>(&mut self) {
+ self.block_flow.place_float_if_applicable()
+ }
+
+ fn contains_roots_of_absolute_flow_tree(&self) -> bool {
+ self.block_flow.contains_roots_of_absolute_flow_tree()
+ }
+
+ fn is_absolute_containing_block(&self) -> bool {
+ self.block_flow.is_absolute_containing_block()
+ }
+
+ fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
+ self.block_flow
+ .update_late_computed_inline_position_if_necessary(inline_position)
+ }
+
+ fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
+ self.block_flow
+ .update_late_computed_block_position_if_necessary(block_position)
+ }
+
+ fn build_display_list(&mut self, state: &mut DisplayListBuildState) {
+ // Draw the marker, if applicable.
+ for marker in &mut self.marker_fragments {
+ let stacking_relative_border_box = self
+ .block_flow
+ .base
+ .stacking_relative_border_box_for_display_list(marker);
+ marker.build_display_list(
+ state,
+ stacking_relative_border_box,
+ BorderPaintingMode::Separate,
+ DisplayListSection::Content,
+ self.block_flow.base.clip,
+ None,
+ );
+ }
+
+ // Draw the rest of the block.
+ self.block_flow
+ .build_display_list_for_block(state, BorderPaintingMode::Separate)
+ }
+
+ fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
+ self.block_flow.collect_stacking_contexts(state);
+ }
+
+ fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>) {
+ self.block_flow.repair_style(new_style)
+ }
+
+ fn compute_overflow(&self) -> Overflow {
+ let mut overflow = self.block_flow.compute_overflow();
+ let flow_size = self
+ .block_flow
+ .base
+ .position
+ .size
+ .to_physical(self.block_flow.base.writing_mode);
+ let relative_containing_block_size = &self
+ .block_flow
+ .base
+ .early_absolute_position_info
+ .relative_containing_block_size;
+
+ for fragment in &self.marker_fragments {
+ overflow.union(&fragment.compute_overflow(&flow_size, &relative_containing_block_size))
+ }
+ overflow
+ }
+
+ fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
+ self.block_flow.generated_containing_block_size(flow)
+ }
+
+ /// The 'position' property of this flow.
+ fn positioning(&self) -> Position {
+ self.block_flow.positioning()
+ }
+
+ fn iterate_through_fragment_border_boxes(
+ &self,
+ iterator: &mut dyn FragmentBorderBoxIterator,
+ level: i32,
+ stacking_context_position: &Point2D<Au>,
+ ) {
+ self.block_flow.iterate_through_fragment_border_boxes(
+ iterator,
+ level,
+ stacking_context_position,
+ );
+
+ for marker in &self.marker_fragments {
+ if iterator.should_process(marker) {
+ iterator.process(
+ marker,
+ level,
+ &marker
+ .stacking_relative_border_box(
+ &self.block_flow.base.stacking_relative_position,
+ &self
+ .block_flow
+ .base
+ .early_absolute_position_info
+ .relative_containing_block_size,
+ self.block_flow
+ .base
+ .early_absolute_position_info
+ .relative_containing_block_mode,
+ CoordinateSystem::Own,
+ )
+ .translate(&stacking_context_position.to_vector()),
+ );
+ }
+ }
+ }
+
+ fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) {
+ self.block_flow.mutate_fragments(mutator);
+
+ for marker in &mut self.marker_fragments {
+ (*mutator)(marker)
+ }
+ }
+}
+
+/// The kind of content that `list-style-type` results in.
+pub enum ListStyleTypeContent {
+ None,
+ StaticText(char),
+ GeneratedContent(Box<GeneratedContentInfo>),
+}
+
+impl ListStyleTypeContent {
+ /// Returns the content to be used for the given value of the `list-style-type` property.
+ pub fn from_list_style_type(list_style_type: ListStyleType) -> ListStyleTypeContent {
+ // Just to keep things simple, use a nonbreaking space (Unicode 0xa0) to provide the marker
+ // separation.
+ match list_style_type {
+ ListStyleType::None => ListStyleTypeContent::None,
+ ListStyleType::Disc |
+ ListStyleType::Circle |
+ ListStyleType::Square |
+ ListStyleType::DisclosureOpen |
+ ListStyleType::DisclosureClosed => {
+ let text = generated_content::static_representation(list_style_type);
+ ListStyleTypeContent::StaticText(text)
+ },
+ _ => ListStyleTypeContent::GeneratedContent(Box::new(GeneratedContentInfo::ListItem)),
+ }
+ }
+}
diff --git a/components/layout_2020/model.rs b/components/layout_2020/model.rs
new file mode 100644
index 00000000000..17c3b625af3
--- /dev/null
+++ b/components/layout_2020/model.rs
@@ -0,0 +1,609 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+//! Borders, padding, and margins.
+
+use crate::fragment::Fragment;
+use app_units::Au;
+use euclid::SideOffsets2D;
+use std::cmp::{max, min};
+use std::fmt;
+use style::logical_geometry::{LogicalMargin, WritingMode};
+use style::properties::ComputedValues;
+use style::values::computed::MaxSize;
+use style::values::computed::{LengthPercentageOrAuto, Size};
+
+/// A collapsible margin. See CSS 2.1 § 8.3.1.
+#[derive(Clone, Copy, Debug)]
+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 = max(self.most_positive, other.most_positive);
+ self.most_negative = 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.
+#[derive(Clone, Copy, Debug)]
+pub enum CollapsibleMargins {
+ /// Margins may not collapse with this flow.
+ None(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.
+ Collapse(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
+ CollapseThrough(AdjoiningMargins),
+}
+
+impl CollapsibleMargins {
+ pub fn new() -> CollapsibleMargins {
+ CollapsibleMargins::None(Au(0), Au(0))
+ }
+
+ /// Returns the amount of margin that should be applied in a noncollapsible context. This is
+ /// currently used to apply block-start margin for hypothetical boxes, since we do not collapse
+ /// margins of hypothetical boxes.
+ pub fn block_start_margin_for_noncollapsible_context(&self) -> Au {
+ match *self {
+ CollapsibleMargins::None(block_start, _) => block_start,
+ CollapsibleMargins::Collapse(ref block_start, _) |
+ CollapsibleMargins::CollapseThrough(ref block_start) => block_start.collapse(),
+ }
+ }
+
+ pub fn block_end_margin_for_noncollapsible_context(&self) -> Au {
+ match *self {
+ CollapsibleMargins::None(_, block_end) => block_end,
+ CollapsibleMargins::Collapse(_, ref block_end) |
+ CollapsibleMargins::CollapseThrough(ref block_end) => block_end.collapse(),
+ }
+ }
+}
+
+enum FinalMarginState {
+ MarginsCollapseThrough,
+ BottomMarginCollapses,
+}
+
+pub struct MarginCollapseInfo {
+ pub state: MarginCollapseState,
+ pub block_start_margin: AdjoiningMargins,
+ pub margin_in: AdjoiningMargins,
+}
+
+impl MarginCollapseInfo {
+ pub fn initialize_block_start_margin(
+ fragment: &Fragment,
+ can_collapse_block_start_margin_with_kids: bool,
+ ) -> MarginCollapseInfo {
+ MarginCollapseInfo {
+ state: if can_collapse_block_start_margin_with_kids {
+ MarginCollapseState::AccumulatingCollapsibleTopMargin
+ } else {
+ MarginCollapseState::AccumulatingMarginIn
+ },
+ block_start_margin: AdjoiningMargins::from_margin(fragment.margin.block_start),
+ margin_in: AdjoiningMargins::new(),
+ }
+ }
+
+ pub fn finish_and_compute_collapsible_margins(
+ mut self,
+ fragment: &Fragment,
+ containing_block_size: Option<Au>,
+ can_collapse_block_end_margin_with_kids: bool,
+ mut may_collapse_through: bool,
+ ) -> (CollapsibleMargins, Au) {
+ let state = match self.state {
+ MarginCollapseState::AccumulatingCollapsibleTopMargin => {
+ may_collapse_through = may_collapse_through &&
+ match fragment.style().content_block_size() {
+ Size::Auto => true,
+ Size::LengthPercentage(ref lp) => {
+ lp.is_definitely_zero() ||
+ lp.maybe_to_used_value(containing_block_size).is_none()
+ },
+ };
+
+ if may_collapse_through {
+ if fragment.style.min_block_size().is_auto() ||
+ fragment.style().min_block_size().is_definitely_zero()
+ {
+ FinalMarginState::MarginsCollapseThrough
+ } else {
+ // If the fragment has non-zero min-block-size, margins may not
+ // collapse through it.
+ FinalMarginState::BottomMarginCollapses
+ }
+ } else {
+ // If the fragment has an explicitly specified block-size, margins may not
+ // collapse through it.
+ FinalMarginState::BottomMarginCollapses
+ }
+ },
+ MarginCollapseState::AccumulatingMarginIn => FinalMarginState::BottomMarginCollapses,
+ };
+
+ // 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 {
+ FinalMarginState::MarginsCollapseThrough => {
+ let advance = self.block_start_margin.collapse();
+ self.margin_in
+ .union(AdjoiningMargins::from_margin(block_end_margin));
+ (
+ CollapsibleMargins::Collapse(self.block_start_margin, self.margin_in),
+ advance,
+ )
+ },
+ FinalMarginState::BottomMarginCollapses => {
+ let advance = self.margin_in.collapse();
+ self.margin_in
+ .union(AdjoiningMargins::from_margin(block_end_margin));
+ (
+ CollapsibleMargins::Collapse(self.block_start_margin, self.margin_in),
+ advance,
+ )
+ },
+ }
+ } else {
+ match state {
+ FinalMarginState::MarginsCollapseThrough => {
+ self.block_start_margin
+ .union(AdjoiningMargins::from_margin(block_end_margin));
+ (
+ CollapsibleMargins::CollapseThrough(self.block_start_margin),
+ Au(0),
+ )
+ },
+ FinalMarginState::BottomMarginCollapses => {
+ self.margin_in
+ .union(AdjoiningMargins::from_margin(block_end_margin));
+ (
+ CollapsibleMargins::Collapse(self.block_start_margin, self.margin_in),
+ Au(0),
+ )
+ },
+ }
+ }
+ }
+
+ pub fn current_float_ceiling(&mut self) -> Au {
+ match self.state {
+ MarginCollapseState::AccumulatingCollapsibleTopMargin => {
+ // We do not include the top margin in the float ceiling, because the float flow
+ // needs to be positioned relative to our *border box*, not our margin box. See
+ // `tests/ref/float_under_top_margin_a.html`.
+ Au(0)
+ },
+ MarginCollapseState::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,
+ can_collapse_block_start_margin: bool,
+ ) -> Au {
+ if !can_collapse_block_start_margin {
+ self.state = MarginCollapseState::AccumulatingMarginIn
+ }
+
+ match (self.state, *child_collapsible_margins) {
+ (
+ MarginCollapseState::AccumulatingCollapsibleTopMargin,
+ CollapsibleMargins::None(block_start, _),
+ ) => {
+ self.state = MarginCollapseState::AccumulatingMarginIn;
+ block_start
+ },
+ (
+ MarginCollapseState::AccumulatingCollapsibleTopMargin,
+ CollapsibleMargins::Collapse(block_start, _),
+ ) => {
+ self.block_start_margin.union(block_start);
+ self.state = MarginCollapseState::AccumulatingMarginIn;
+ Au(0)
+ },
+ (
+ MarginCollapseState::AccumulatingMarginIn,
+ CollapsibleMargins::None(block_start, _),
+ ) => {
+ let previous_margin_value = self.margin_in.collapse();
+ self.margin_in = AdjoiningMargins::new();
+ previous_margin_value + block_start
+ },
+ (
+ MarginCollapseState::AccumulatingMarginIn,
+ CollapsibleMargins::Collapse(block_start, _),
+ ) => {
+ self.margin_in.union(block_start);
+ let margin_value = self.margin_in.collapse();
+ self.margin_in = AdjoiningMargins::new();
+ margin_value
+ },
+ (_, CollapsibleMargins::CollapseThrough(_)) => {
+ // 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) {
+ (
+ MarginCollapseState::AccumulatingCollapsibleTopMargin,
+ CollapsibleMargins::None(..),
+ ) |
+ (
+ MarginCollapseState::AccumulatingCollapsibleTopMargin,
+ CollapsibleMargins::Collapse(..),
+ ) => {
+ // Can't happen because the state will have been replaced with
+ // `MarginCollapseState::AccumulatingMarginIn` above.
+ panic!("should not be accumulating collapsible block_start margins anymore!")
+ },
+ (
+ MarginCollapseState::AccumulatingCollapsibleTopMargin,
+ CollapsibleMargins::CollapseThrough(margin),
+ ) => {
+ self.block_start_margin.union(margin);
+ Au(0)
+ },
+ (MarginCollapseState::AccumulatingMarginIn, CollapsibleMargins::None(_, block_end)) => {
+ assert_eq!(self.margin_in.most_positive, Au(0));
+ assert_eq!(self.margin_in.most_negative, Au(0));
+ block_end
+ },
+ (
+ MarginCollapseState::AccumulatingMarginIn,
+ CollapsibleMargins::Collapse(_, block_end),
+ ) |
+ (
+ MarginCollapseState::AccumulatingMarginIn,
+ CollapsibleMargins::CollapseThrough(block_end),
+ ) => {
+ self.margin_in.union(block_end);
+ Au(0)
+ },
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug)]
+pub enum MarginCollapseState {
+ /// We are accumulating margin on the logical top of this flow.
+ AccumulatingCollapsibleTopMargin,
+ /// We are accumulating margin between two blocks.
+ AccumulatingMarginIn,
+}
+
+/// Intrinsic inline-sizes, which consist of minimum and preferred.
+#[derive(Clone, Copy, Serialize)]
+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,
+}
+
+impl fmt::Debug for IntrinsicISizes {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "min={:?}, pref={:?}",
+ self.minimum_inline_size, self.preferred_inline_size
+ )
+ }
+}
+
+impl IntrinsicISizes {
+ pub fn new() -> IntrinsicISizes {
+ IntrinsicISizes {
+ minimum_inline_size: Au(0),
+ preferred_inline_size: Au(0),
+ }
+ }
+}
+
+/// The temporary result of the computation of intrinsic inline-sizes.
+#[derive(Debug)]
+pub struct IntrinsicISizesContribution {
+ /// Intrinsic sizes for the content only (not counting borders, padding, or margins).
+ pub content_intrinsic_sizes: IntrinsicISizes,
+ /// The inline size of borders and padding, as well as margins if appropriate.
+ pub surrounding_size: Au,
+}
+
+impl IntrinsicISizesContribution {
+ /// Creates and initializes an inline size computation with all sizes set to zero.
+ pub fn new() -> IntrinsicISizesContribution {
+ IntrinsicISizesContribution {
+ content_intrinsic_sizes: IntrinsicISizes::new(),
+ surrounding_size: Au(0),
+ }
+ }
+
+ /// Adds the content intrinsic sizes and the surrounding size together to yield the final
+ /// intrinsic size computation.
+ pub fn finish(self) -> IntrinsicISizes {
+ IntrinsicISizes {
+ minimum_inline_size: self.content_intrinsic_sizes.minimum_inline_size +
+ self.surrounding_size,
+ preferred_inline_size: self.content_intrinsic_sizes.preferred_inline_size +
+ self.surrounding_size,
+ }
+ }
+
+ /// Updates the computation so that the minimum is the maximum of the current minimum and the
+ /// given minimum and the preferred is the sum of the current preferred and the given
+ /// preferred. This is used when laying out fragments in the inline direction.
+ ///
+ /// FIXME(pcwalton): This is incorrect when the inline fragment contains forced line breaks
+ /// (e.g. `<br>` or `white-space: pre`).
+ pub fn union_inline(&mut self, sizes: &IntrinsicISizes) {
+ self.content_intrinsic_sizes.minimum_inline_size = max(
+ self.content_intrinsic_sizes.minimum_inline_size,
+ sizes.minimum_inline_size,
+ );
+ self.content_intrinsic_sizes.preferred_inline_size =
+ self.content_intrinsic_sizes.preferred_inline_size + sizes.preferred_inline_size
+ }
+
+ /// Updates the computation so that the minimum is the sum of the current minimum and the
+ /// given minimum and the preferred is the sum of the current preferred and the given
+ /// preferred. This is used when laying out fragments in the inline direction when
+ /// `white-space` is `pre` or `nowrap`.
+ pub fn union_nonbreaking_inline(&mut self, sizes: &IntrinsicISizes) {
+ self.content_intrinsic_sizes.minimum_inline_size =
+ self.content_intrinsic_sizes.minimum_inline_size + sizes.minimum_inline_size;
+ self.content_intrinsic_sizes.preferred_inline_size =
+ self.content_intrinsic_sizes.preferred_inline_size + sizes.preferred_inline_size
+ }
+
+ /// Updates the computation so that the minimum is the maximum of the current minimum and the
+ /// given minimum and the preferred is the maximum of the current preferred and the given
+ /// preferred. This can be useful when laying out fragments in the block direction (but note
+ /// that it does not take floats into account, so `BlockFlow` does not use it).
+ ///
+ /// This is used when contributing the intrinsic sizes for individual fragments.
+ pub fn union_block(&mut self, sizes: &IntrinsicISizes) {
+ self.content_intrinsic_sizes.minimum_inline_size = max(
+ self.content_intrinsic_sizes.minimum_inline_size,
+ sizes.minimum_inline_size,
+ );
+ self.content_intrinsic_sizes.preferred_inline_size = max(
+ self.content_intrinsic_sizes.preferred_inline_size,
+ sizes.preferred_inline_size,
+ )
+ }
+}
+
+/// Useful helper data type when computing values for blocks and positioned elements.
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum MaybeAuto {
+ Auto,
+ Specified(Au),
+}
+
+impl MaybeAuto {
+ #[inline]
+ pub fn from_style(length: LengthPercentageOrAuto, containing_length: Au) -> MaybeAuto {
+ match length {
+ LengthPercentageOrAuto::Auto => MaybeAuto::Auto,
+ LengthPercentageOrAuto::LengthPercentage(ref lp) => {
+ MaybeAuto::Specified(lp.to_used_value(containing_length))
+ },
+ }
+ }
+
+ #[inline]
+ pub fn from_option(au: Option<Au>) -> MaybeAuto {
+ match au {
+ Some(l) => MaybeAuto::Specified(l),
+ _ => MaybeAuto::Auto,
+ }
+ }
+
+ #[inline]
+ pub fn to_option(&self) -> Option<Au> {
+ match *self {
+ MaybeAuto::Specified(value) => Some(value),
+ MaybeAuto::Auto => None,
+ }
+ }
+
+ #[inline]
+ pub fn specified_or_default(&self, default: Au) -> Au {
+ match *self {
+ MaybeAuto::Auto => default,
+ MaybeAuto::Specified(value) => value,
+ }
+ }
+
+ #[inline]
+ pub fn specified_or_zero(&self) -> Au {
+ self.specified_or_default(Au::new(0))
+ }
+
+ #[inline]
+ pub fn is_auto(&self) -> bool {
+ match *self {
+ MaybeAuto::Auto => true,
+ MaybeAuto::Specified(..) => false,
+ }
+ }
+
+ #[inline]
+ pub fn map<F>(&self, mapper: F) -> MaybeAuto
+ where
+ F: FnOnce(Au) -> Au,
+ {
+ match *self {
+ MaybeAuto::Auto => MaybeAuto::Auto,
+ MaybeAuto::Specified(value) => MaybeAuto::Specified(mapper(value)),
+ }
+ }
+}
+
+/// Receive an optional container size and return used value for width or height.
+///
+/// `style_length`: content size as given in the CSS.
+pub fn style_length(style_length: Size, container_size: Option<Au>) -> MaybeAuto {
+ match style_length {
+ Size::Auto => MaybeAuto::Auto,
+ Size::LengthPercentage(ref lp) => {
+ MaybeAuto::from_option(lp.0.maybe_to_used_value(container_size))
+ },
+ }
+}
+
+#[inline]
+pub fn padding_from_style(
+ style: &ComputedValues,
+ containing_block_inline_size: Au,
+ writing_mode: WritingMode,
+) -> LogicalMargin<Au> {
+ let padding_style = style.get_padding();
+ LogicalMargin::from_physical(
+ writing_mode,
+ SideOffsets2D::new(
+ padding_style
+ .padding_top
+ .to_used_value(containing_block_inline_size),
+ padding_style
+ .padding_right
+ .to_used_value(containing_block_inline_size),
+ padding_style
+ .padding_bottom
+ .to_used_value(containing_block_inline_size),
+ padding_style
+ .padding_left
+ .to_used_value(containing_block_inline_size),
+ ),
+ )
+}
+
+/// Returns the explicitly-specified margin lengths from the given style. Percentage and auto
+/// margins are returned as zero.
+///
+/// This is used when calculating intrinsic inline sizes.
+#[inline]
+pub fn specified_margin_from_style(
+ style: &ComputedValues,
+ writing_mode: WritingMode,
+) -> LogicalMargin<Au> {
+ let margin_style = style.get_margin();
+ LogicalMargin::from_physical(
+ writing_mode,
+ SideOffsets2D::new(
+ MaybeAuto::from_style(margin_style.margin_top, Au(0)).specified_or_zero(),
+ MaybeAuto::from_style(margin_style.margin_right, Au(0)).specified_or_zero(),
+ MaybeAuto::from_style(margin_style.margin_bottom, Au(0)).specified_or_zero(),
+ MaybeAuto::from_style(margin_style.margin_left, Au(0)).specified_or_zero(),
+ ),
+ )
+}
+
+/// A min-size and max-size constraint. The constructor has a optional `border`
+/// parameter, and when it is present the constraint will be subtracted. This is
+/// used to adjust the constraint for `box-sizing: border-box`, and when you do so
+/// make sure the size you want to clamp is intended to be used for content box.
+#[derive(Clone, Copy, Debug, Serialize)]
+pub struct SizeConstraint {
+ min_size: Au,
+ max_size: Option<Au>,
+}
+
+impl SizeConstraint {
+ /// Create a `SizeConstraint` for an axis.
+ pub fn new(
+ container_size: Option<Au>,
+ min_size: Size,
+ max_size: MaxSize,
+ border: Option<Au>,
+ ) -> SizeConstraint {
+ let mut min_size = match min_size {
+ Size::Auto => Au(0),
+ Size::LengthPercentage(ref lp) => {
+ lp.maybe_to_used_value(container_size).unwrap_or(Au(0))
+ },
+ };
+
+ let mut max_size = match max_size {
+ MaxSize::None => None,
+ MaxSize::LengthPercentage(ref lp) => lp.maybe_to_used_value(container_size),
+ };
+
+ // Make sure max size is not smaller than min size.
+ max_size = max_size.map(|x| max(x, min_size));
+
+ if let Some(border) = border {
+ min_size = max(min_size - border, Au(0));
+ max_size = max_size.map(|x| max(x - border, Au(0)));
+ }
+
+ SizeConstraint { min_size, max_size }
+ }
+
+ /// Clamp the given size by the given min size and max size constraint.
+ pub fn clamp(&self, other: Au) -> Au {
+ if other < self.min_size {
+ self.min_size
+ } else {
+ match self.max_size {
+ Some(max_size) if max_size < other => max_size,
+ _ => other,
+ }
+ }
+ }
+}
diff --git a/components/layout_2020/multicol.rs b/components/layout_2020/multicol.rs
new file mode 100644
index 00000000000..d49e5f1d79f
--- /dev/null
+++ b/components/layout_2020/multicol.rs
@@ -0,0 +1,384 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+//! CSS Multi-column layout http://dev.w3.org/csswg/css-multicol/
+
+use crate::block::BlockFlow;
+use crate::context::LayoutContext;
+use crate::display_list::{DisplayListBuildState, StackingContextCollectionState};
+use crate::floats::FloatKind;
+use crate::flow::{Flow, FlowClass, FragmentationContext, GetBaseFlow, OpaqueFlow};
+use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
+use crate::ServoArc;
+use app_units::Au;
+use euclid::{Point2D, Vector2D};
+use gfx_traits::print_tree::PrintTree;
+use std::cmp::{max, min};
+use std::fmt;
+use std::sync::Arc;
+use style::logical_geometry::LogicalSize;
+use style::properties::ComputedValues;
+use style::values::computed::length::{
+ MaxSize, NonNegativeLengthOrAuto, NonNegativeLengthPercentageOrNormal, Size,
+};
+use style::values::generics::column::ColumnCount;
+
+#[allow(unsafe_code)]
+unsafe impl crate::flow::HasBaseFlow for MulticolFlow {}
+
+#[repr(C)]
+pub struct MulticolFlow {
+ pub block_flow: BlockFlow,
+
+ /// Length between the inline-start edge of a column and that of the next.
+ /// That is, the used column-width + used column-gap.
+ pub column_pitch: Au,
+}
+
+#[allow(unsafe_code)]
+unsafe impl crate::flow::HasBaseFlow for MulticolColumnFlow {}
+
+#[repr(C)]
+pub struct MulticolColumnFlow {
+ pub block_flow: BlockFlow,
+}
+
+impl MulticolFlow {
+ pub fn from_fragment(fragment: Fragment, float_kind: Option<FloatKind>) -> MulticolFlow {
+ MulticolFlow {
+ block_flow: BlockFlow::from_fragment_and_float_kind(fragment, float_kind),
+ column_pitch: Au(0),
+ }
+ }
+}
+
+impl MulticolColumnFlow {
+ pub fn from_fragment(fragment: Fragment) -> MulticolColumnFlow {
+ MulticolColumnFlow {
+ block_flow: BlockFlow::from_fragment(fragment),
+ }
+ }
+}
+
+impl Flow for MulticolFlow {
+ fn class(&self) -> FlowClass {
+ FlowClass::Multicol
+ }
+
+ fn as_mut_block(&mut self) -> &mut BlockFlow {
+ &mut self.block_flow
+ }
+
+ fn as_block(&self) -> &BlockFlow {
+ &self.block_flow
+ }
+
+ fn bubble_inline_sizes(&mut self) {
+ // FIXME(SimonSapin) http://dev.w3.org/csswg/css-sizing/#multicol-intrinsic
+ self.block_flow.bubble_inline_sizes();
+ }
+
+ fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
+ debug!(
+ "assign_inline_sizes({}): assigning inline_size for flow",
+ "multicol"
+ );
+ let shared_context = layout_context.shared_context();
+ self.block_flow.compute_inline_sizes(shared_context);
+
+ // Move in from the inline-start border edge.
+ let inline_start_content_edge = self.block_flow.fragment.border_box.start.i +
+ self.block_flow.fragment.border_padding.inline_start;
+
+ // Distance from the inline-end margin edge to the inline-end content edge.
+ let inline_end_content_edge = self.block_flow.fragment.margin.inline_end +
+ self.block_flow.fragment.border_padding.inline_end;
+
+ self.block_flow.assign_inline_sizes(layout_context);
+ let padding_and_borders = self.block_flow.fragment.border_padding.inline_start_end();
+ let content_inline_size =
+ self.block_flow.fragment.border_box.size.inline - padding_and_borders;
+ let column_width;
+ {
+ let style = &self.block_flow.fragment.style;
+ let column_gap = match style.get_position().column_gap {
+ NonNegativeLengthPercentageOrNormal::LengthPercentage(len) => {
+ len.0.to_pixel_length(content_inline_size).into()
+ },
+ NonNegativeLengthPercentageOrNormal::Normal => {
+ self.block_flow.fragment.style.get_font().font_size.size()
+ },
+ };
+
+ let column_style = style.get_column();
+ let mut column_count;
+ if let NonNegativeLengthOrAuto::LengthPercentage(column_width) =
+ column_style.column_width
+ {
+ let column_width = Au::from(column_width);
+ column_count = max(
+ 1,
+ (content_inline_size + column_gap).0 / (column_width + column_gap).0,
+ );
+ if let ColumnCount::Integer(specified_column_count) = column_style.column_count {
+ column_count = min(column_count, specified_column_count.0 as i32);
+ }
+ } else {
+ column_count = match column_style.column_count {
+ ColumnCount::Integer(n) => n.0,
+ _ => unreachable!(),
+ }
+ }
+ column_width = max(
+ Au(0),
+ (content_inline_size + column_gap) / column_count - column_gap,
+ );
+ self.column_pitch = column_width + column_gap;
+ }
+
+ self.block_flow.fragment.border_box.size.inline = content_inline_size + padding_and_borders;
+
+ self.block_flow.propagate_assigned_inline_size_to_children(
+ shared_context,
+ inline_start_content_edge,
+ inline_end_content_edge,
+ column_width,
+ |_, _, _, _, _, _| {},
+ );
+ }
+
+ fn assign_block_size(&mut self, ctx: &LayoutContext) {
+ debug!("assign_block_size: assigning block_size for multicol");
+
+ let fragmentation_context = Some(FragmentationContext {
+ this_fragment_is_empty: true,
+ available_block_size: {
+ let style = &self.block_flow.fragment.style;
+ let size = match style.content_block_size() {
+ Size::Auto => None,
+ Size::LengthPercentage(ref lp) => lp.maybe_to_used_value(None),
+ };
+ let size = size.or_else(|| match style.max_block_size() {
+ MaxSize::None => None,
+ MaxSize::LengthPercentage(ref lp) => lp.maybe_to_used_value(None),
+ });
+
+ size.unwrap_or_else(|| {
+ // FIXME: do column balancing instead
+ // FIXME: (until column balancing) substract margins/borders/padding
+ LogicalSize::from_physical(
+ self.block_flow.base.writing_mode,
+ ctx.shared_context().viewport_size(),
+ )
+ .block
+ })
+ },
+ });
+
+ // Before layout, everything is in a single "column"
+ assert_eq!(self.block_flow.base.children.len(), 1);
+ let mut column = self.block_flow.base.children.pop_front_arc().unwrap();
+
+ // Pretend there is no children for this:
+ self.block_flow.assign_block_size(ctx);
+
+ loop {
+ let remaining = Arc::get_mut(&mut column)
+ .unwrap()
+ .fragment(ctx, fragmentation_context);
+ self.block_flow.base.children.push_back_arc(column);
+ column = match remaining {
+ Some(remaining) => remaining,
+ None => break,
+ };
+ }
+ }
+
+ fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
+ self.block_flow
+ .compute_stacking_relative_position(layout_context);
+ let pitch = LogicalSize::new(self.block_flow.base.writing_mode, self.column_pitch, Au(0));
+ let pitch = pitch.to_physical(self.block_flow.base.writing_mode);
+ for (i, child) in self.block_flow.base.children.iter_mut().enumerate() {
+ let point = &mut child.mut_base().stacking_relative_position;
+ *point = *point + Vector2D::new(pitch.width * i as i32, pitch.height * i as i32);
+ }
+ }
+
+ fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
+ self.block_flow
+ .update_late_computed_inline_position_if_necessary(inline_position)
+ }
+
+ fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
+ self.block_flow
+ .update_late_computed_block_position_if_necessary(block_position)
+ }
+
+ fn build_display_list(&mut self, state: &mut DisplayListBuildState) {
+ debug!("build_display_list_multicol");
+ self.block_flow.build_display_list(state);
+ }
+
+ fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
+ self.block_flow.collect_stacking_contexts(state);
+ }
+
+ fn repair_style(&mut self, new_style: &ServoArc<ComputedValues>) {
+ self.block_flow.repair_style(new_style)
+ }
+
+ fn compute_overflow(&self) -> Overflow {
+ self.block_flow.compute_overflow()
+ }
+
+ fn contains_roots_of_absolute_flow_tree(&self) -> bool {
+ self.block_flow.contains_roots_of_absolute_flow_tree()
+ }
+
+ fn is_absolute_containing_block(&self) -> bool {
+ self.block_flow.is_absolute_containing_block()
+ }
+
+ fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
+ self.block_flow.generated_containing_block_size(flow)
+ }
+
+ fn iterate_through_fragment_border_boxes(
+ &self,
+ iterator: &mut dyn FragmentBorderBoxIterator,
+ level: i32,
+ stacking_context_position: &Point2D<Au>,
+ ) {
+ self.block_flow.iterate_through_fragment_border_boxes(
+ iterator,
+ level,
+ stacking_context_position,
+ );
+ }
+
+ fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) {
+ self.block_flow.mutate_fragments(mutator);
+ }
+
+ fn print_extra_flow_children(&self, print_tree: &mut PrintTree) {
+ self.block_flow.print_extra_flow_children(print_tree);
+ }
+}
+
+impl Flow for MulticolColumnFlow {
+ fn class(&self) -> FlowClass {
+ FlowClass::MulticolColumn
+ }
+
+ fn as_mut_block(&mut self) -> &mut BlockFlow {
+ &mut self.block_flow
+ }
+
+ fn as_block(&self) -> &BlockFlow {
+ &self.block_flow
+ }
+
+ fn bubble_inline_sizes(&mut self) {
+ self.block_flow.bubble_inline_sizes();
+ }
+
+ fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
+ debug!(
+ "assign_inline_sizes({}): assigning inline_size for flow",
+ "multicol column"
+ );
+ self.block_flow.assign_inline_sizes(layout_context);
+ }
+
+ fn assign_block_size(&mut self, ctx: &LayoutContext) {
+ debug!("assign_block_size: assigning block_size for multicol column");
+ self.block_flow.assign_block_size(ctx);
+ }
+
+ fn fragment(
+ &mut self,
+ layout_context: &LayoutContext,
+ fragmentation_context: Option<FragmentationContext>,
+ ) -> Option<Arc<dyn Flow>> {
+ Flow::fragment(&mut self.block_flow, layout_context, fragmentation_context)
+ }
+
+ fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
+ self.block_flow
+ .compute_stacking_relative_position(layout_context)
+ }
+
+ fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
+ self.block_flow
+ .update_late_computed_inline_position_if_necessary(inline_position)
+ }
+
+ fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
+ self.block_flow
+ .update_late_computed_block_position_if_necessary(block_position)
+ }
+
+ fn build_display_list(&mut self, state: &mut DisplayListBuildState) {
+ debug!("build_display_list_multicol column");
+ self.block_flow.build_display_list(state);
+ }
+
+ fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
+ self.block_flow.collect_stacking_contexts(state);
+ }
+
+ fn repair_style(&mut self, new_style: &ServoArc<ComputedValues>) {
+ self.block_flow.repair_style(new_style)
+ }
+
+ fn compute_overflow(&self) -> Overflow {
+ self.block_flow.compute_overflow()
+ }
+
+ fn contains_roots_of_absolute_flow_tree(&self) -> bool {
+ self.block_flow.contains_roots_of_absolute_flow_tree()
+ }
+
+ fn is_absolute_containing_block(&self) -> bool {
+ self.block_flow.is_absolute_containing_block()
+ }
+
+ fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
+ self.block_flow.generated_containing_block_size(flow)
+ }
+
+ fn iterate_through_fragment_border_boxes(
+ &self,
+ iterator: &mut dyn FragmentBorderBoxIterator,
+ level: i32,
+ stacking_context_position: &Point2D<Au>,
+ ) {
+ self.block_flow.iterate_through_fragment_border_boxes(
+ iterator,
+ level,
+ stacking_context_position,
+ );
+ }
+
+ fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) {
+ self.block_flow.mutate_fragments(mutator);
+ }
+
+ fn print_extra_flow_children(&self, print_tree: &mut PrintTree) {
+ self.block_flow.print_extra_flow_children(print_tree);
+ }
+}
+
+impl fmt::Debug for MulticolFlow {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "MulticolFlow: {:?}", self.block_flow)
+ }
+}
+
+impl fmt::Debug for MulticolColumnFlow {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "MulticolColumnFlow: {:?}", self.block_flow)
+ }
+}
diff --git a/components/layout_2020/opaque_node.rs b/components/layout_2020/opaque_node.rs
new file mode 100644
index 00000000000..b48820052e9
--- /dev/null
+++ b/components/layout_2020/opaque_node.rs
@@ -0,0 +1,19 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+use crate::display_list::items::OpaqueNode;
+use libc::c_void;
+use script_traits::UntrustedNodeAddress;
+
+pub trait OpaqueNodeMethods {
+ /// 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 to_untrusted_node_address(&self) -> UntrustedNodeAddress {
+ UntrustedNodeAddress(self.0 as *const c_void)
+ }
+}
diff --git a/components/layout_2020/parallel.rs b/components/layout_2020/parallel.rs
new file mode 100644
index 00000000000..4ad10b3db11
--- /dev/null
+++ b/components/layout_2020/parallel.rs
@@ -0,0 +1,232 @@
+/* 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 https://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.
+
+#![allow(unsafe_code)]
+
+use crate::block::BlockFlow;
+use crate::context::LayoutContext;
+use crate::flow::{Flow, GetBaseFlow};
+use crate::flow_ref::FlowRef;
+use crate::traversal::{AssignBSizes, AssignISizes, BubbleISizes};
+use crate::traversal::{PostorderFlowTraversal, PreorderFlowTraversal};
+use profile_traits::time::{self, profile, TimerMetadata};
+use servo_config::opts;
+use smallvec::SmallVec;
+use std::mem;
+use std::ptr;
+use std::sync::atomic::{AtomicIsize, Ordering};
+
+/// Traversal chunk size.
+const CHUNK_SIZE: usize = 16;
+
+pub type FlowList = SmallVec<[UnsafeFlow; CHUNK_SIZE]>;
+
+/// Vtable + pointer representation of a Flow trait object.
+#[derive(Clone, Copy, Eq, PartialEq)]
+pub struct UnsafeFlow(*const dyn Flow);
+
+unsafe impl Sync for UnsafeFlow {}
+unsafe impl Send for UnsafeFlow {}
+
+fn null_unsafe_flow() -> UnsafeFlow {
+ UnsafeFlow(ptr::null::<BlockFlow>())
+}
+
+pub fn mut_owned_flow_to_unsafe_flow(flow: *mut FlowRef) -> UnsafeFlow {
+ unsafe { UnsafeFlow(&**flow) }
+}
+
+/// Information that we need stored in each flow.
+pub struct FlowParallelInfo {
+ /// The number of children that still need work done.
+ pub children_count: AtomicIsize,
+ /// The address of the parent flow.
+ pub parent: UnsafeFlow,
+}
+
+impl FlowParallelInfo {
+ pub fn new() -> FlowParallelInfo {
+ FlowParallelInfo {
+ children_count: AtomicIsize::new(0),
+ parent: null_unsafe_flow(),
+ }
+ }
+}
+
+/// 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.
+///
+/// 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 bottom_up_flow(mut unsafe_flow: UnsafeFlow, assign_bsize_traversal: &AssignBSizes) {
+ loop {
+ // Get a real flow.
+ let flow: &mut dyn Flow = unsafe { mem::transmute(unsafe_flow) };
+
+ // Perform the appropriate traversal.
+ if assign_bsize_traversal.should_process(flow) {
+ assign_bsize_traversal.process(flow);
+ }
+
+ let base = flow.mut_base();
+
+ // Reset the count of children for the next layout traversal.
+ base.parallel
+ .children_count
+ .store(base.children.len() as isize, Ordering::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 dyn Flow = unsafe { &mut *(unsafe_parent.0 as *mut dyn Flow) };
+ let parent_base = parent.mut_base();
+ if parent_base
+ .parallel
+ .children_count
+ .fetch_sub(1, Ordering::Relaxed) ==
+ 1
+ {
+ // We were the last child of our parent. Reflow our parent.
+ unsafe_flow = unsafe_parent
+ } else {
+ // Stop.
+ break;
+ }
+ }
+}
+
+fn top_down_flow<'scope>(
+ unsafe_flows: &[UnsafeFlow],
+ pool: &'scope rayon::ThreadPool,
+ scope: &rayon::ScopeFifo<'scope>,
+ assign_isize_traversal: &'scope AssignISizes,
+ assign_bsize_traversal: &'scope AssignBSizes,
+) {
+ let mut discovered_child_flows = FlowList::new();
+
+ for unsafe_flow in unsafe_flows {
+ let mut had_children = false;
+ unsafe {
+ // Get a real flow.
+ let flow: &mut dyn Flow = mem::transmute(*unsafe_flow);
+ flow.mut_base().thread_id = pool.current_thread_index().unwrap() as u8;
+
+ if assign_isize_traversal.should_process(flow) {
+ // Perform the appropriate traversal.
+ assign_isize_traversal.process(flow);
+ }
+
+ // Possibly enqueue the children.
+ for kid in flow.mut_base().child_iter_mut() {
+ had_children = true;
+ discovered_child_flows.push(UnsafeFlow(kid));
+ }
+ }
+
+ // If there were no more children, start assigning block-sizes.
+ if !had_children {
+ bottom_up_flow(*unsafe_flow, &assign_bsize_traversal)
+ }
+ }
+
+ if discovered_child_flows.is_empty() {
+ return;
+ }
+
+ if discovered_child_flows.len() <= CHUNK_SIZE {
+ // We can handle all the children in this work unit.
+ top_down_flow(
+ &discovered_child_flows,
+ pool,
+ scope,
+ &assign_isize_traversal,
+ &assign_bsize_traversal,
+ );
+ } else {
+ // Spawn a new work unit for each chunk after the first.
+ let mut chunks = discovered_child_flows.chunks(CHUNK_SIZE);
+ let first_chunk = chunks.next();
+ for chunk in chunks {
+ let nodes = chunk.iter().cloned().collect::<FlowList>();
+ scope.spawn_fifo(move |scope| {
+ top_down_flow(
+ &nodes,
+ pool,
+ scope,
+ &assign_isize_traversal,
+ &assign_bsize_traversal,
+ );
+ });
+ }
+ if let Some(chunk) = first_chunk {
+ top_down_flow(
+ chunk,
+ pool,
+ scope,
+ &assign_isize_traversal,
+ &assign_bsize_traversal,
+ );
+ }
+ }
+}
+
+/// Run the main layout passes in parallel.
+pub fn reflow(
+ root: &mut dyn Flow,
+ profiler_metadata: Option<TimerMetadata>,
+ time_profiler_chan: time::ProfilerChan,
+ context: &LayoutContext,
+ queue: &rayon::ThreadPool,
+) {
+ if opts::get().bubble_inline_sizes_separately {
+ let bubble_inline_sizes = BubbleISizes {
+ layout_context: &context,
+ };
+ bubble_inline_sizes.traverse(root);
+ }
+
+ let assign_isize_traversal = &AssignISizes {
+ layout_context: &context,
+ };
+ let assign_bsize_traversal = &AssignBSizes {
+ layout_context: &context,
+ };
+ let nodes = [UnsafeFlow(root)];
+
+ queue.install(move || {
+ rayon::scope_fifo(move |scope| {
+ profile(
+ time::ProfilerCategory::LayoutParallelWarmup,
+ profiler_metadata,
+ time_profiler_chan,
+ move || {
+ top_down_flow(
+ &nodes,
+ queue,
+ scope,
+ assign_isize_traversal,
+ assign_bsize_traversal,
+ );
+ },
+ );
+ });
+ });
+}
diff --git a/components/layout_2020/persistent_list.rs b/components/layout_2020/persistent_list.rs
new file mode 100644
index 00000000000..16bbc319ea0
--- /dev/null
+++ b/components/layout_2020/persistent_list.rs
@@ -0,0 +1,101 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+//! A persistent, thread-safe singly-linked list.
+
+use std::sync::Arc;
+
+pub struct PersistentList<T> {
+ head: PersistentListLink<T>,
+ length: usize,
+}
+
+struct PersistentListEntry<T> {
+ value: T,
+ next: PersistentListLink<T>,
+}
+
+type PersistentListLink<T> = Option<Arc<PersistentListEntry<T>>>;
+
+impl<T> PersistentList<T>
+where
+ T: Send + Sync,
+{
+ #[inline]
+ pub fn new() -> PersistentList<T> {
+ PersistentList {
+ head: None,
+ length: 0,
+ }
+ }
+
+ #[inline]
+ pub fn len(&self) -> usize {
+ self.length
+ }
+
+ #[inline]
+ pub fn front(&self) -> Option<&T> {
+ self.head.as_ref().map(|head| &head.value)
+ }
+
+ #[inline]
+ pub fn prepend_elem(&self, value: T) -> PersistentList<T> {
+ PersistentList {
+ head: Some(Arc::new(PersistentListEntry {
+ value: value,
+ next: self.head.clone(),
+ })),
+ length: self.length + 1,
+ }
+ }
+
+ #[inline]
+ pub fn iter(&self) -> PersistentListIterator<T> {
+ // This could clone (and would not need the lifetime if it did), but then it would incur
+ // atomic operations on every call to `.next()`. Bad.
+ PersistentListIterator {
+ entry: self.head.as_ref().map(|head| &**head),
+ }
+ }
+}
+
+impl<T> Clone for PersistentList<T>
+where
+ T: Send + Sync,
+{
+ fn clone(&self) -> PersistentList<T> {
+ // This establishes the persistent nature of this list: we can clone a list by just cloning
+ // its head.
+ PersistentList {
+ head: self.head.clone(),
+ length: self.length,
+ }
+ }
+}
+
+pub struct PersistentListIterator<'a, T>
+where
+ T: Send + Sync,
+{
+ entry: Option<&'a PersistentListEntry<T>>,
+}
+
+impl<'a, T> Iterator for PersistentListIterator<'a, T>
+where
+ T: Send + Sync + 'static,
+{
+ type Item = &'a T;
+
+ #[inline]
+ fn next(&mut self) -> Option<&'a T> {
+ let entry = self.entry?;
+ let value = &entry.value;
+ self.entry = match entry.next {
+ None => None,
+ Some(ref entry) => Some(&**entry),
+ };
+ Some(value)
+ }
+}
diff --git a/components/layout_2020/query.rs b/components/layout_2020/query.rs
new file mode 100644
index 00000000000..789056753d5
--- /dev/null
+++ b/components/layout_2020/query.rs
@@ -0,0 +1,1111 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+//! Utilities for querying the layout, as needed by the layout thread.
+
+use crate::construct::ConstructionResult;
+use crate::context::LayoutContext;
+use crate::display_list::items::{DisplayList, OpaqueNode, ScrollOffsetMap};
+use crate::display_list::IndexableText;
+use crate::flow::{Flow, GetBaseFlow};
+use crate::fragment::{Fragment, FragmentBorderBoxIterator, SpecificFragmentInfo};
+use crate::inline::InlineFragmentNodeFlags;
+use crate::opaque_node::OpaqueNodeMethods;
+use crate::sequential;
+use crate::wrapper::LayoutNodeLayoutData;
+use app_units::Au;
+use euclid::{Point2D, Rect, Size2D, Vector2D};
+use ipc_channel::ipc::IpcSender;
+use msg::constellation_msg::PipelineId;
+use script_layout_interface::rpc::TextIndexResponse;
+use script_layout_interface::rpc::{ContentBoxResponse, ContentBoxesResponse, LayoutRPC};
+use script_layout_interface::rpc::{NodeGeometryResponse, NodeScrollIdResponse};
+use script_layout_interface::rpc::{OffsetParentResponse, ResolvedStyleResponse, StyleResponse};
+use script_layout_interface::wrapper_traits::{
+ LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode,
+};
+use script_layout_interface::StyleData;
+use script_layout_interface::{LayoutElementType, LayoutNodeType};
+use script_traits::LayoutMsg as ConstellationMsg;
+use script_traits::UntrustedNodeAddress;
+use std::cmp::{max, min};
+use std::ops::Deref;
+use std::sync::{Arc, Mutex};
+use style::computed_values::display::T as Display;
+use style::computed_values::position::T as Position;
+use style::computed_values::visibility::T as Visibility;
+use style::context::{StyleContext, ThreadLocalStyleContext};
+use style::dom::TElement;
+use style::logical_geometry::{BlockFlowDirection, InlineBaseDirection, WritingMode};
+use style::properties::{style_structs, LonghandId, PropertyDeclarationId, PropertyId};
+use style::selector_parser::PseudoElement;
+use style_traits::ToCss;
+use webrender_api::ExternalScrollId;
+
+/// Mutable data belonging to the LayoutThread.
+///
+/// This needs to be protected by a mutex so we can do fast RPCs.
+pub struct LayoutThreadData {
+ /// The channel on which messages can be sent to the constellation.
+ pub constellation_chan: IpcSender<ConstellationMsg>,
+
+ /// The root stacking context.
+ pub display_list: Option<DisplayList>,
+
+ pub indexable_text: IndexableText,
+
+ /// A queued response for the union of the content boxes of a node.
+ pub content_box_response: Option<Rect<Au>>,
+
+ /// A queued response for the content boxes of a node.
+ pub content_boxes_response: Vec<Rect<Au>>,
+
+ /// A queued response for the client {top, left, width, height} of a node in pixels.
+ pub client_rect_response: Rect<i32>,
+
+ /// A queued response for the scroll id for a given node.
+ pub scroll_id_response: Option<ExternalScrollId>,
+
+ /// A queued response for the scroll {top, left, width, height} of a node in pixels.
+ pub scroll_area_response: Rect<i32>,
+
+ /// A queued response for the resolved style property of an element.
+ pub resolved_style_response: String,
+
+ /// A queued response for the offset parent/rect of a node.
+ pub offset_parent_response: OffsetParentResponse,
+
+ /// A queued response for the style of a node.
+ pub style_response: StyleResponse,
+
+ /// Scroll offsets of scrolling regions.
+ pub scroll_offsets: ScrollOffsetMap,
+
+ /// Index in a text fragment. We need this do determine the insertion point.
+ pub text_index_response: TextIndexResponse,
+
+ /// A queued response for the list of nodes at a given point.
+ pub nodes_from_point_response: Vec<UntrustedNodeAddress>,
+
+ /// A queued response for the inner text of a given element.
+ pub element_inner_text_response: String,
+}
+
+pub struct LayoutRPCImpl(pub Arc<Mutex<LayoutThreadData>>);
+
+// https://drafts.csswg.org/cssom-view/#overflow-directions
+fn overflow_direction(writing_mode: &WritingMode) -> OverflowDirection {
+ match (
+ writing_mode.block_flow_direction(),
+ writing_mode.inline_base_direction(),
+ ) {
+ (BlockFlowDirection::TopToBottom, InlineBaseDirection::LeftToRight) |
+ (BlockFlowDirection::LeftToRight, InlineBaseDirection::LeftToRight) => {
+ OverflowDirection::RightAndDown
+ },
+ (BlockFlowDirection::TopToBottom, InlineBaseDirection::RightToLeft) |
+ (BlockFlowDirection::RightToLeft, InlineBaseDirection::LeftToRight) => {
+ OverflowDirection::LeftAndDown
+ },
+ (BlockFlowDirection::RightToLeft, InlineBaseDirection::RightToLeft) => {
+ OverflowDirection::LeftAndUp
+ },
+ (BlockFlowDirection::LeftToRight, InlineBaseDirection::RightToLeft) => {
+ OverflowDirection::RightAndUp
+ },
+ }
+}
+
+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) -> ContentBoxResponse {
+ let &LayoutRPCImpl(ref rw_data) = self;
+ let rw_data = rw_data.lock().unwrap();
+ ContentBoxResponse(rw_data.content_box_response)
+ }
+
+ /// Requests the dimensions of all the content boxes, as in the `getClientRects()` call.
+ fn content_boxes(&self) -> ContentBoxesResponse {
+ let &LayoutRPCImpl(ref rw_data) = self;
+ let rw_data = rw_data.lock().unwrap();
+ ContentBoxesResponse(rw_data.content_boxes_response.clone())
+ }
+
+ fn nodes_from_point_response(&self) -> Vec<UntrustedNodeAddress> {
+ let &LayoutRPCImpl(ref rw_data) = self;
+ let rw_data = rw_data.lock().unwrap();
+ rw_data.nodes_from_point_response.clone()
+ }
+
+ fn node_geometry(&self) -> NodeGeometryResponse {
+ let &LayoutRPCImpl(ref rw_data) = self;
+ let rw_data = rw_data.lock().unwrap();
+ NodeGeometryResponse {
+ client_rect: rw_data.client_rect_response,
+ }
+ }
+
+ fn node_scroll_area(&self) -> NodeGeometryResponse {
+ NodeGeometryResponse {
+ client_rect: self.0.lock().unwrap().scroll_area_response,
+ }
+ }
+
+ fn node_scroll_id(&self) -> NodeScrollIdResponse {
+ NodeScrollIdResponse(
+ self.0
+ .lock()
+ .unwrap()
+ .scroll_id_response
+ .expect("scroll id is not correctly fetched"),
+ )
+ }
+
+ /// Retrieves the resolved value for a CSS style property.
+ fn resolved_style(&self) -> ResolvedStyleResponse {
+ let &LayoutRPCImpl(ref rw_data) = self;
+ let rw_data = rw_data.lock().unwrap();
+ ResolvedStyleResponse(rw_data.resolved_style_response.clone())
+ }
+
+ fn offset_parent(&self) -> OffsetParentResponse {
+ let &LayoutRPCImpl(ref rw_data) = self;
+ let rw_data = rw_data.lock().unwrap();
+ rw_data.offset_parent_response.clone()
+ }
+
+ fn style(&self) -> StyleResponse {
+ let &LayoutRPCImpl(ref rw_data) = self;
+ let rw_data = rw_data.lock().unwrap();
+ rw_data.style_response.clone()
+ }
+
+ fn text_index(&self) -> TextIndexResponse {
+ let &LayoutRPCImpl(ref rw_data) = self;
+ let rw_data = rw_data.lock().unwrap();
+ rw_data.text_index_response.clone()
+ }
+
+ fn element_inner_text(&self) -> String {
+ let &LayoutRPCImpl(ref rw_data) = self;
+ let rw_data = rw_data.lock().unwrap();
+ rw_data.element_inner_text_response.clone()
+ }
+}
+
+struct UnioningFragmentBorderBoxIterator {
+ node_address: OpaqueNode,
+ rect: Option<Rect<Au>>,
+}
+
+impl UnioningFragmentBorderBoxIterator {
+ fn new(node_address: OpaqueNode) -> UnioningFragmentBorderBoxIterator {
+ UnioningFragmentBorderBoxIterator {
+ node_address: node_address,
+ rect: None,
+ }
+ }
+}
+
+impl FragmentBorderBoxIterator for UnioningFragmentBorderBoxIterator {
+ fn process(&mut self, _: &Fragment, _: i32, border_box: &Rect<Au>) {
+ self.rect = match self.rect {
+ Some(rect) => Some(rect.union(border_box)),
+ None => Some(*border_box),
+ };
+ }
+
+ fn should_process(&mut self, fragment: &Fragment) -> bool {
+ fragment.contains_node(self.node_address)
+ }
+}
+
+struct CollectingFragmentBorderBoxIterator {
+ node_address: OpaqueNode,
+ rects: Vec<Rect<Au>>,
+}
+
+impl CollectingFragmentBorderBoxIterator {
+ fn new(node_address: OpaqueNode) -> CollectingFragmentBorderBoxIterator {
+ CollectingFragmentBorderBoxIterator {
+ node_address: node_address,
+ rects: Vec::new(),
+ }
+ }
+}
+
+impl FragmentBorderBoxIterator for CollectingFragmentBorderBoxIterator {
+ fn process(&mut self, _: &Fragment, _: i32, border_box: &Rect<Au>) {
+ self.rects.push(*border_box);
+ }
+
+ fn should_process(&mut self, fragment: &Fragment) -> bool {
+ fragment.contains_node(self.node_address)
+ }
+}
+
+enum Side {
+ Left,
+ Right,
+ Bottom,
+ Top,
+}
+
+enum MarginPadding {
+ Margin,
+ Padding,
+}
+
+enum PositionProperty {
+ Left,
+ Right,
+ Top,
+ Bottom,
+ Width,
+ Height,
+}
+
+#[derive(Debug)]
+enum OverflowDirection {
+ RightAndDown,
+ LeftAndDown,
+ LeftAndUp,
+ RightAndUp,
+}
+
+struct PositionRetrievingFragmentBorderBoxIterator {
+ node_address: OpaqueNode,
+ result: Option<Au>,
+ position: Point2D<Au>,
+ property: PositionProperty,
+}
+
+impl PositionRetrievingFragmentBorderBoxIterator {
+ fn new(
+ node_address: OpaqueNode,
+ property: PositionProperty,
+ position: Point2D<Au>,
+ ) -> PositionRetrievingFragmentBorderBoxIterator {
+ PositionRetrievingFragmentBorderBoxIterator {
+ node_address: node_address,
+ position: position,
+ property: property,
+ result: None,
+ }
+ }
+}
+
+impl FragmentBorderBoxIterator for PositionRetrievingFragmentBorderBoxIterator {
+ fn process(&mut self, fragment: &Fragment, _: i32, border_box: &Rect<Au>) {
+ let border_padding = fragment
+ .border_padding
+ .to_physical(fragment.style.writing_mode);
+ self.result = Some(match self.property {
+ PositionProperty::Left => self.position.x,
+ PositionProperty::Top => self.position.y,
+ PositionProperty::Width => border_box.size.width - border_padding.horizontal(),
+ PositionProperty::Height => border_box.size.height - border_padding.vertical(),
+ // TODO: the following 2 calculations are completely wrong.
+ // They should return the difference between the parent's and this
+ // fragment's border boxes.
+ PositionProperty::Right => border_box.max_x() + self.position.x,
+ PositionProperty::Bottom => border_box.max_y() + self.position.y,
+ });
+ }
+
+ fn should_process(&mut self, fragment: &Fragment) -> bool {
+ fragment.contains_node(self.node_address)
+ }
+}
+
+struct MarginRetrievingFragmentBorderBoxIterator {
+ node_address: OpaqueNode,
+ result: Option<Au>,
+ writing_mode: WritingMode,
+ margin_padding: MarginPadding,
+ side: Side,
+}
+
+impl MarginRetrievingFragmentBorderBoxIterator {
+ fn new(
+ node_address: OpaqueNode,
+ side: Side,
+ margin_padding: MarginPadding,
+ writing_mode: WritingMode,
+ ) -> MarginRetrievingFragmentBorderBoxIterator {
+ MarginRetrievingFragmentBorderBoxIterator {
+ node_address: node_address,
+ side: side,
+ margin_padding: margin_padding,
+ result: None,
+ writing_mode: writing_mode,
+ }
+ }
+}
+
+impl FragmentBorderBoxIterator for MarginRetrievingFragmentBorderBoxIterator {
+ fn process(&mut self, fragment: &Fragment, _: i32, _: &Rect<Au>) {
+ let rect = match self.margin_padding {
+ MarginPadding::Margin => &fragment.margin,
+ MarginPadding::Padding => &fragment.border_padding,
+ };
+ self.result = Some(match self.side {
+ Side::Left => rect.left(self.writing_mode),
+ Side::Right => rect.right(self.writing_mode),
+ Side::Bottom => rect.bottom(self.writing_mode),
+ Side::Top => rect.top(self.writing_mode),
+ });
+ }
+
+ fn should_process(&mut self, fragment: &Fragment) -> bool {
+ fragment.contains_node(self.node_address)
+ }
+}
+
+pub fn process_content_box_request(
+ requested_node: OpaqueNode,
+ layout_root: &mut dyn Flow,
+) -> Option<Rect<Au>> {
+ // FIXME(pcwalton): This has not been updated to handle the stacking context relative
+ // stuff. So the position is wrong in most cases.
+ let mut iterator = UnioningFragmentBorderBoxIterator::new(requested_node);
+ sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, &mut iterator);
+ iterator.rect
+}
+
+pub fn process_content_boxes_request(
+ requested_node: OpaqueNode,
+ layout_root: &mut dyn Flow,
+) -> Vec<Rect<Au>> {
+ // FIXME(pcwalton): This has not been updated to handle the stacking context relative
+ // stuff. So the position is wrong in most cases.
+ let mut iterator = CollectingFragmentBorderBoxIterator::new(requested_node);
+ sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, &mut iterator);
+ iterator.rects
+}
+
+struct FragmentLocatingFragmentIterator {
+ node_address: OpaqueNode,
+ client_rect: Rect<i32>,
+}
+
+impl FragmentLocatingFragmentIterator {
+ fn new(node_address: OpaqueNode) -> FragmentLocatingFragmentIterator {
+ FragmentLocatingFragmentIterator {
+ node_address: node_address,
+ client_rect: Rect::zero(),
+ }
+ }
+}
+
+struct UnioningFragmentScrollAreaIterator {
+ node_address: OpaqueNode,
+ union_rect: Rect<i32>,
+ origin_rect: Rect<i32>,
+ level: Option<i32>,
+ is_child: bool,
+ overflow_direction: OverflowDirection,
+}
+
+impl UnioningFragmentScrollAreaIterator {
+ fn new(node_address: OpaqueNode) -> UnioningFragmentScrollAreaIterator {
+ UnioningFragmentScrollAreaIterator {
+ node_address: node_address,
+ union_rect: Rect::zero(),
+ origin_rect: Rect::zero(),
+ level: None,
+ is_child: false,
+ // FIXME(#20867)
+ overflow_direction: OverflowDirection::RightAndDown,
+ }
+ }
+}
+
+struct NodeOffsetBoxInfo {
+ offset: Point2D<Au>,
+ rectangle: Rect<Au>,
+}
+
+struct ParentBorderBoxInfo {
+ node_address: OpaqueNode,
+ origin: Point2D<Au>,
+}
+
+struct ParentOffsetBorderBoxIterator {
+ node_address: OpaqueNode,
+ has_processed_node: bool,
+ node_offset_box: Option<NodeOffsetBoxInfo>,
+ parent_nodes: Vec<Option<ParentBorderBoxInfo>>,
+}
+
+impl ParentOffsetBorderBoxIterator {
+ fn new(node_address: OpaqueNode) -> ParentOffsetBorderBoxIterator {
+ ParentOffsetBorderBoxIterator {
+ node_address: node_address,
+ has_processed_node: false,
+ node_offset_box: None,
+ parent_nodes: Vec::new(),
+ }
+ }
+}
+
+impl FragmentBorderBoxIterator for FragmentLocatingFragmentIterator {
+ fn process(&mut self, fragment: &Fragment, _: i32, border_box: &Rect<Au>) {
+ let style_structs::Border {
+ border_top_width: top_width,
+ border_right_width: right_width,
+ border_bottom_width: bottom_width,
+ border_left_width: left_width,
+ ..
+ } = *fragment.style.get_border();
+ let (left_width, right_width) = (left_width.px(), right_width.px());
+ let (top_width, bottom_width) = (top_width.px(), bottom_width.px());
+ self.client_rect.origin.y = top_width as i32;
+ self.client_rect.origin.x = left_width as i32;
+ self.client_rect.size.width =
+ (border_box.size.width.to_f32_px() - left_width - right_width) as i32;
+ self.client_rect.size.height =
+ (border_box.size.height.to_f32_px() - top_width - bottom_width) as i32;
+ }
+
+ fn should_process(&mut self, fragment: &Fragment) -> bool {
+ fragment.node == self.node_address
+ }
+}
+
+// https://drafts.csswg.org/cssom-view/#scrolling-area
+impl FragmentBorderBoxIterator for UnioningFragmentScrollAreaIterator {
+ fn process(&mut self, fragment: &Fragment, level: i32, border_box: &Rect<Au>) {
+ // In cases in which smaller child elements contain less padding than the parent
+ // the a union of the two elements padding rectangles could result in an unwanted
+ // increase in size. To work around this, we store the original elements padding
+ // rectangle as `origin_rect` and the union of all child elements padding and
+ // margin rectangles as `union_rect`.
+ let style_structs::Border {
+ border_top_width: top_border,
+ border_right_width: right_border,
+ border_bottom_width: bottom_border,
+ border_left_width: left_border,
+ ..
+ } = *fragment.style.get_border();
+ let (left_border, right_border) = (left_border.px(), right_border.px());
+ let (top_border, bottom_border) = (top_border.px(), bottom_border.px());
+ let right_padding = (border_box.size.width.to_f32_px() - right_border - left_border) as i32;
+ let bottom_padding =
+ (border_box.size.height.to_f32_px() - bottom_border - top_border) as i32;
+ let top_padding = top_border as i32;
+ let left_padding = left_border as i32;
+
+ match self.level {
+ Some(start_level) if level <= start_level => {
+ self.is_child = false;
+ },
+ Some(_) => {
+ let padding = Rect::new(
+ Point2D::new(left_padding, top_padding),
+ Size2D::new(right_padding, bottom_padding),
+ );
+ let top_margin = fragment.margin.top(fragment.style.writing_mode).to_px();
+ let left_margin = fragment.margin.left(fragment.style.writing_mode).to_px();
+ let bottom_margin = fragment.margin.bottom(fragment.style.writing_mode).to_px();
+ let right_margin = fragment.margin.right(fragment.style.writing_mode).to_px();
+ let margin = Rect::new(
+ Point2D::new(left_margin, top_margin),
+ Size2D::new(right_margin, bottom_margin),
+ );
+ self.union_rect = self.union_rect.union(&margin).union(&padding);
+ },
+ None => {
+ self.level = Some(level);
+ self.is_child = true;
+ self.overflow_direction = overflow_direction(&fragment.style.writing_mode);
+ self.origin_rect = Rect::new(
+ Point2D::new(left_padding, top_padding),
+ Size2D::new(right_padding, bottom_padding),
+ );
+ },
+ };
+ }
+
+ fn should_process(&mut self, fragment: &Fragment) -> bool {
+ fragment.contains_node(self.node_address) || self.is_child
+ }
+}
+
+// https://drafts.csswg.org/cssom-view/#extensions-to-the-htmlelement-interface
+impl FragmentBorderBoxIterator for ParentOffsetBorderBoxIterator {
+ fn process(&mut self, fragment: &Fragment, level: i32, border_box: &Rect<Au>) {
+ if self.node_offset_box.is_none() {
+ // We haven't found the node yet, so we're still looking
+ // for its parent. Remove all nodes at this level or
+ // higher, as they can't be parents of this node.
+ self.parent_nodes.truncate(level as usize);
+ assert_eq!(
+ self.parent_nodes.len(),
+ level as usize,
+ "Skipped at least one level in the flow tree!"
+ );
+ }
+
+ if !fragment.is_primary_fragment() {
+ // This fragment doesn't correspond to anything worth
+ // taking measurements from.
+
+ if self.node_offset_box.is_none() {
+ // If this is the only fragment in the flow, we need to
+ // do this to avoid failing the above assertion.
+ self.parent_nodes.push(None);
+ }
+
+ return;
+ }
+
+ if fragment.node == self.node_address {
+ // Found the fragment in the flow tree that matches the
+ // DOM node being looked for.
+
+ assert!(
+ self.node_offset_box.is_none(),
+ "Node was being treated as inline, but it has an associated fragment!"
+ );
+
+ self.has_processed_node = true;
+ self.node_offset_box = Some(NodeOffsetBoxInfo {
+ offset: border_box.origin,
+ rectangle: *border_box,
+ });
+
+ // offsetParent returns null if the node is fixed.
+ if fragment.style.get_box().position == Position::Fixed {
+ self.parent_nodes.clear();
+ }
+ } else if let Some(node) = fragment.inline_context.as_ref().and_then(|inline_context| {
+ inline_context
+ .nodes
+ .iter()
+ .find(|node| node.address == self.node_address)
+ }) {
+ // TODO: Handle cases where the `offsetParent` is an inline
+ // element. This will likely be impossible until
+ // https://github.com/servo/servo/issues/13982 is fixed.
+
+ // Found a fragment in the flow tree whose inline context
+ // contains the DOM node we're looking for, i.e. the node
+ // is inline and contains this fragment.
+ match self.node_offset_box {
+ Some(NodeOffsetBoxInfo {
+ ref mut rectangle, ..
+ }) => {
+ *rectangle = rectangle.union(border_box);
+ },
+ None => {
+ // https://github.com/servo/servo/issues/13982 will
+ // cause this assertion to fail sometimes, so it's
+ // commented out for now.
+ /*assert!(node.flags.contains(FIRST_FRAGMENT_OF_ELEMENT),
+ "First fragment of inline node found wasn't its first fragment!");*/
+
+ self.node_offset_box = Some(NodeOffsetBoxInfo {
+ offset: border_box.origin,
+ rectangle: *border_box,
+ });
+ },
+ }
+
+ if node
+ .flags
+ .contains(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT)
+ {
+ self.has_processed_node = true;
+ }
+ } else if self.node_offset_box.is_none() {
+ // TODO(gw): Is there a less fragile way of checking whether this
+ // fragment is the body element, rather than just checking that
+ // it's at level 1 (below the root node)?
+ let is_body_element = level == 1;
+
+ let is_valid_parent = match (
+ is_body_element,
+ fragment.style.get_box().position,
+ &fragment.specific,
+ ) {
+ // Spec says it's valid if any of these are true:
+ // 1) Is the body element
+ // 2) Is static position *and* is a table or table cell
+ // 3) Is not static position
+ (true, _, _) |
+ (false, Position::Static, &SpecificFragmentInfo::Table) |
+ (false, Position::Static, &SpecificFragmentInfo::TableCell) |
+ (false, Position::Sticky, _) |
+ (false, Position::Absolute, _) |
+ (false, Position::Relative, _) |
+ (false, Position::Fixed, _) => true,
+
+ // Otherwise, it's not a valid parent
+ (false, Position::Static, _) => false,
+ };
+
+ let parent_info = if is_valid_parent {
+ let border_width = fragment
+ .border_width()
+ .to_physical(fragment.style.writing_mode);
+
+ Some(ParentBorderBoxInfo {
+ node_address: fragment.node,
+ origin: border_box.origin + Vector2D::new(border_width.left, border_width.top),
+ })
+ } else {
+ None
+ };
+
+ self.parent_nodes.push(parent_info);
+ }
+ }
+
+ fn should_process(&mut self, _: &Fragment) -> bool {
+ !self.has_processed_node
+ }
+}
+
+pub fn process_node_geometry_request(
+ requested_node: OpaqueNode,
+ layout_root: &mut dyn Flow,
+) -> Rect<i32> {
+ let mut iterator = FragmentLocatingFragmentIterator::new(requested_node);
+ sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, &mut iterator);
+ iterator.client_rect
+}
+
+pub fn process_node_scroll_id_request<N: LayoutNode>(
+ id: PipelineId,
+ requested_node: N,
+) -> ExternalScrollId {
+ let layout_node = requested_node.to_threadsafe();
+ layout_node.generate_scroll_id(id)
+}
+
+/// https://drafts.csswg.org/cssom-view/#scrolling-area
+pub fn process_node_scroll_area_request(
+ requested_node: OpaqueNode,
+ layout_root: &mut dyn Flow,
+) -> Rect<i32> {
+ let mut iterator = UnioningFragmentScrollAreaIterator::new(requested_node);
+ sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, &mut iterator);
+ match iterator.overflow_direction {
+ OverflowDirection::RightAndDown => {
+ let right = max(
+ iterator.union_rect.size.width,
+ iterator.origin_rect.size.width,
+ );
+ let bottom = max(
+ iterator.union_rect.size.height,
+ iterator.origin_rect.size.height,
+ );
+ Rect::new(iterator.origin_rect.origin, Size2D::new(right, bottom))
+ },
+ OverflowDirection::LeftAndDown => {
+ let bottom = max(
+ iterator.union_rect.size.height,
+ iterator.origin_rect.size.height,
+ );
+ let left = min(iterator.union_rect.origin.x, iterator.origin_rect.origin.x);
+ Rect::new(
+ Point2D::new(left, iterator.origin_rect.origin.y),
+ Size2D::new(iterator.origin_rect.size.width, bottom),
+ )
+ },
+ OverflowDirection::LeftAndUp => {
+ let top = min(iterator.union_rect.origin.y, iterator.origin_rect.origin.y);
+ let left = min(iterator.union_rect.origin.x, iterator.origin_rect.origin.x);
+ Rect::new(Point2D::new(left, top), iterator.origin_rect.size)
+ },
+ OverflowDirection::RightAndUp => {
+ let top = min(iterator.union_rect.origin.y, iterator.origin_rect.origin.y);
+ let right = max(
+ iterator.union_rect.size.width,
+ iterator.origin_rect.size.width,
+ );
+ Rect::new(
+ Point2D::new(iterator.origin_rect.origin.x, top),
+ Size2D::new(right, iterator.origin_rect.size.height),
+ )
+ },
+ }
+}
+
+/// Return the resolved value of property for a given (pseudo)element.
+/// <https://drafts.csswg.org/cssom/#resolved-value>
+pub fn process_resolved_style_request<'a, N>(
+ context: &LayoutContext,
+ node: N,
+ pseudo: &Option<PseudoElement>,
+ property: &PropertyId,
+ layout_root: &mut dyn Flow,
+) -> String
+where
+ N: LayoutNode,
+{
+ use style::stylist::RuleInclusion;
+ use style::traversal::resolve_style;
+
+ let element = node.as_element().unwrap();
+
+ // We call process_resolved_style_request after performing a whole-document
+ // traversal, so in the common case, the element is styled.
+ if element.get_data().is_some() {
+ return process_resolved_style_request_internal(node, pseudo, property, layout_root);
+ }
+
+ // In a display: none subtree. No pseudo-element exists.
+ if pseudo.is_some() {
+ return String::new();
+ }
+
+ let mut tlc = ThreadLocalStyleContext::new(&context.style_context);
+ let mut context = StyleContext {
+ shared: &context.style_context,
+ thread_local: &mut tlc,
+ };
+
+ let styles = resolve_style(&mut context, element, RuleInclusion::All, pseudo.as_ref());
+ let style = styles.primary();
+ let longhand_id = match *property {
+ PropertyId::LonghandAlias(id, _) | PropertyId::Longhand(id) => id,
+ // Firefox returns blank strings for the computed value of shorthands,
+ // so this should be web-compatible.
+ PropertyId::ShorthandAlias(..) | PropertyId::Shorthand(_) => return String::new(),
+ PropertyId::Custom(ref name) => {
+ return style.computed_value_to_string(PropertyDeclarationId::Custom(name));
+ },
+ };
+
+ // No need to care about used values here, since we're on a display: none
+ // subtree, use the resolved value.
+ style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id))
+}
+
+/// The primary resolution logic, which assumes that the element is styled.
+fn process_resolved_style_request_internal<'a, N>(
+ requested_node: N,
+ pseudo: &Option<PseudoElement>,
+ property: &PropertyId,
+ layout_root: &mut dyn Flow,
+) -> String
+where
+ N: LayoutNode,
+{
+ let layout_el = requested_node.to_threadsafe().as_element().unwrap();
+ let layout_el = match *pseudo {
+ Some(PseudoElement::Before) => layout_el.get_before_pseudo(),
+ Some(PseudoElement::After) => layout_el.get_after_pseudo(),
+ Some(PseudoElement::DetailsSummary) |
+ Some(PseudoElement::DetailsContent) |
+ Some(PseudoElement::Selection) => None,
+ // FIXME(emilio): What about the other pseudos? Probably they shouldn't
+ // just return the element's style!
+ _ => Some(layout_el),
+ };
+
+ let layout_el = match layout_el {
+ None => {
+ // The pseudo doesn't exist, return nothing. Chrome seems to query
+ // the element itself in this case, Firefox uses the resolved value.
+ // https://www.w3.org/Bugs/Public/show_bug.cgi?id=29006
+ return String::new();
+ },
+ Some(layout_el) => layout_el,
+ };
+
+ let style = &*layout_el.resolved_style();
+ let longhand_id = match *property {
+ PropertyId::LonghandAlias(id, _) | PropertyId::Longhand(id) => id,
+ // Firefox returns blank strings for the computed value of shorthands,
+ // so this should be web-compatible.
+ PropertyId::ShorthandAlias(..) | PropertyId::Shorthand(_) => return String::new(),
+ PropertyId::Custom(ref name) => {
+ return style.computed_value_to_string(PropertyDeclarationId::Custom(name));
+ },
+ };
+
+ let positioned = match style.get_box().position {
+ Position::Relative | Position::Sticky | Position::Fixed | Position::Absolute => true,
+ _ => false,
+ };
+
+ //TODO: determine whether requested property applies to the element.
+ // eg. width does not apply to non-replaced inline elements.
+ // Existing browsers disagree about when left/top/right/bottom apply
+ // (Chrome seems to think they never apply and always returns resolved values).
+ // There are probably other quirks.
+ let applies = true;
+
+ fn used_value_for_position_property<N: LayoutNode>(
+ layout_el: <N::ConcreteThreadSafeLayoutNode as ThreadSafeLayoutNode>::ConcreteThreadSafeLayoutElement,
+ layout_root: &mut dyn Flow,
+ requested_node: N,
+ longhand_id: LonghandId,
+ ) -> String {
+ let maybe_data = layout_el.borrow_layout_data();
+ let position = maybe_data.map_or(Point2D::zero(), |data| {
+ match (*data).flow_construction_result {
+ ConstructionResult::Flow(ref flow_ref, _) => flow_ref
+ .deref()
+ .base()
+ .stacking_relative_position
+ .to_point(),
+ // TODO(dzbarsky) search parents until we find node with a flow ref.
+ // https://github.com/servo/servo/issues/8307
+ _ => Point2D::zero(),
+ }
+ });
+ let property = match longhand_id {
+ LonghandId::Bottom => PositionProperty::Bottom,
+ LonghandId::Top => PositionProperty::Top,
+ LonghandId::Left => PositionProperty::Left,
+ LonghandId::Right => PositionProperty::Right,
+ LonghandId::Width => PositionProperty::Width,
+ LonghandId::Height => PositionProperty::Height,
+ _ => unreachable!(),
+ };
+ let mut iterator = PositionRetrievingFragmentBorderBoxIterator::new(
+ requested_node.opaque(),
+ property,
+ position,
+ );
+ sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, &mut iterator);
+ iterator
+ .result
+ .map(|r| r.to_css_string())
+ .unwrap_or(String::new())
+ }
+
+ // TODO: we will return neither the computed nor used value for margin and padding.
+ match longhand_id {
+ LonghandId::MarginBottom |
+ LonghandId::MarginTop |
+ LonghandId::MarginLeft |
+ LonghandId::MarginRight |
+ LonghandId::PaddingBottom |
+ LonghandId::PaddingTop |
+ LonghandId::PaddingLeft |
+ LonghandId::PaddingRight
+ if applies && style.get_box().display != Display::None =>
+ {
+ let (margin_padding, side) = match longhand_id {
+ LonghandId::MarginBottom => (MarginPadding::Margin, Side::Bottom),
+ LonghandId::MarginTop => (MarginPadding::Margin, Side::Top),
+ LonghandId::MarginLeft => (MarginPadding::Margin, Side::Left),
+ LonghandId::MarginRight => (MarginPadding::Margin, Side::Right),
+ LonghandId::PaddingBottom => (MarginPadding::Padding, Side::Bottom),
+ LonghandId::PaddingTop => (MarginPadding::Padding, Side::Top),
+ LonghandId::PaddingLeft => (MarginPadding::Padding, Side::Left),
+ LonghandId::PaddingRight => (MarginPadding::Padding, Side::Right),
+ _ => unreachable!(),
+ };
+ let mut iterator = MarginRetrievingFragmentBorderBoxIterator::new(
+ requested_node.opaque(),
+ side,
+ margin_padding,
+ style.writing_mode,
+ );
+ sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, &mut iterator);
+ iterator
+ .result
+ .map(|r| r.to_css_string())
+ .unwrap_or(String::new())
+ }
+
+ LonghandId::Bottom | LonghandId::Top | LonghandId::Right | LonghandId::Left
+ if applies && positioned && style.get_box().display != Display::None =>
+ {
+ used_value_for_position_property(layout_el, layout_root, requested_node, longhand_id)
+ },
+ LonghandId::Width | LonghandId::Height
+ if applies && style.get_box().display != Display::None =>
+ {
+ used_value_for_position_property(layout_el, layout_root, requested_node, longhand_id)
+ },
+ // FIXME: implement used value computation for line-height
+ _ => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)),
+ }
+}
+
+pub fn process_offset_parent_query(
+ requested_node: OpaqueNode,
+ layout_root: &mut dyn Flow,
+) -> OffsetParentResponse {
+ let mut iterator = ParentOffsetBorderBoxIterator::new(requested_node);
+ sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, &mut iterator);
+
+ let node_offset_box = iterator.node_offset_box;
+ let parent_info = iterator
+ .parent_nodes
+ .into_iter()
+ .rev()
+ .filter_map(|info| info)
+ .next();
+ match (node_offset_box, parent_info) {
+ (Some(node_offset_box), Some(parent_info)) => {
+ let origin = node_offset_box.offset - parent_info.origin.to_vector();
+ let size = node_offset_box.rectangle.size;
+ OffsetParentResponse {
+ node_address: Some(parent_info.node_address.to_untrusted_node_address()),
+ rect: Rect::new(origin, size),
+ }
+ },
+ _ => OffsetParentResponse::empty(),
+ }
+}
+
+pub fn process_style_query<N: LayoutNode>(requested_node: N) -> StyleResponse {
+ let element = requested_node.as_element().unwrap();
+ let data = element.borrow_data();
+
+ StyleResponse(data.map(|d| d.styles.primary().clone()))
+}
+
+enum InnerTextItem {
+ Text(String),
+ RequiredLineBreakCount(u32),
+}
+
+// https://html.spec.whatwg.org/multipage/#the-innertext-idl-attribute
+pub fn process_element_inner_text_query<N: LayoutNode>(
+ node: N,
+ indexable_text: &IndexableText,
+) -> String {
+ // Step 1.
+ let mut results = Vec::new();
+ // Step 2.
+ inner_text_collection_steps(node, indexable_text, &mut results);
+ let mut max_req_line_break_count = 0;
+ let mut inner_text = Vec::new();
+ for item in results {
+ match item {
+ InnerTextItem::Text(s) => {
+ if max_req_line_break_count > 0 {
+ // Step 5.
+ for _ in 0..max_req_line_break_count {
+ inner_text.push("\u{000A}".to_owned());
+ }
+ max_req_line_break_count = 0;
+ }
+ // Step 3.
+ if !s.is_empty() {
+ inner_text.push(s.to_owned());
+ }
+ },
+ InnerTextItem::RequiredLineBreakCount(count) => {
+ // Step 4.
+ if inner_text.len() == 0 {
+ // Remove required line break count at the start.
+ continue;
+ }
+ // Store the count if it's the max of this run,
+ // but it may be ignored if no text item is found afterwards,
+ // which means that these are consecutive line breaks at the end.
+ if count > max_req_line_break_count {
+ max_req_line_break_count = count;
+ }
+ },
+ }
+ }
+ inner_text.into_iter().collect()
+}
+
+// https://html.spec.whatwg.org/multipage/#inner-text-collection-steps
+#[allow(unsafe_code)]
+fn inner_text_collection_steps<N: LayoutNode>(
+ node: N,
+ indexable_text: &IndexableText,
+ results: &mut Vec<InnerTextItem>,
+) {
+ let mut items = Vec::new();
+ for child in node.traverse_preorder() {
+ let node = match child.type_id() {
+ LayoutNodeType::Text => child.parent_node().unwrap(),
+ _ => child,
+ };
+
+ let element_data = unsafe {
+ node.get_style_and_layout_data()
+ .map(|d| &(*(d.ptr.as_ptr() as *mut StyleData)).element_data)
+ };
+
+ if element_data.is_none() {
+ continue;
+ }
+
+ let style = match element_data.unwrap().borrow().styles.get_primary() {
+ None => continue,
+ Some(style) => style.clone(),
+ };
+
+ // Step 2.
+ if style.get_inherited_box().visibility != Visibility::Visible {
+ continue;
+ }
+
+ // Step 3.
+ let display = style.get_box().display;
+ if !child.is_connected() || display == Display::None {
+ continue;
+ }
+
+ match child.type_id() {
+ LayoutNodeType::Text => {
+ // Step 4.
+ if let Some(text_content) = indexable_text.get(child.opaque()) {
+ for content in text_content {
+ items.push(InnerTextItem::Text(content.text_run.text.to_string()));
+ }
+ }
+ },
+ LayoutNodeType::Element(LayoutElementType::HTMLBRElement) => {
+ // Step 5.
+ items.push(InnerTextItem::Text(String::from(
+ "\u{000A}", /* line feed */
+ )));
+ },
+ LayoutNodeType::Element(LayoutElementType::HTMLParagraphElement) => {
+ // Step 8.
+ items.insert(0, InnerTextItem::RequiredLineBreakCount(2));
+ items.push(InnerTextItem::RequiredLineBreakCount(2));
+ },
+ _ => {},
+ }
+
+ match display {
+ Display::TableCell if !is_last_table_cell() => {
+ // Step 6.
+ items.push(InnerTextItem::Text(String::from("\u{0009}" /* tab */)));
+ },
+ Display::TableRow if !is_last_table_row() => {
+ // Step 7.
+ items.push(InnerTextItem::Text(String::from(
+ "\u{000A}", /* line feed */
+ )));
+ },
+ Display::Block | Display::Flex | Display::TableCaption | Display::Table => {
+ // Step 9.
+ items.insert(0, InnerTextItem::RequiredLineBreakCount(1));
+ items.push(InnerTextItem::RequiredLineBreakCount(1));
+ },
+ _ => {},
+ }
+ }
+
+ results.append(&mut items);
+}
+
+fn is_last_table_cell() -> bool {
+ // FIXME(ferjm) Implement this.
+ false
+}
+
+fn is_last_table_row() -> bool {
+ // FIXME(ferjm) Implement this.
+ false
+}
diff --git a/components/layout_2020/sequential.rs b/components/layout_2020/sequential.rs
new file mode 100644
index 00000000000..16dd3b8d4da
--- /dev/null
+++ b/components/layout_2020/sequential.rs
@@ -0,0 +1,193 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+//! Implements sequential traversals over the DOM and flow trees.
+
+use crate::context::LayoutContext;
+use crate::display_list::items::{self, CommonDisplayItem, DisplayItem, DisplayListSection};
+use crate::display_list::{DisplayListBuildState, StackingContextCollectionState};
+use crate::floats::SpeculatedFloatPlacement;
+use crate::flow::{Flow, FlowFlags, GetBaseFlow, ImmutableFlowUtils};
+use crate::fragment::{CoordinateSystem, FragmentBorderBoxIterator};
+use crate::generated_content::ResolveGeneratedContent;
+use crate::incremental::RelayoutMode;
+use crate::traversal::{AssignBSizes, AssignISizes, BubbleISizes, BuildDisplayList};
+use crate::traversal::{InorderFlowTraversal, PostorderFlowTraversal, PreorderFlowTraversal};
+use app_units::Au;
+use euclid::{Point2D, Rect, Size2D, Vector2D};
+use servo_config::opts;
+use style::servo::restyle_damage::ServoRestyleDamage;
+use webrender_api::units::LayoutPoint;
+
+pub fn resolve_generated_content(root: &mut dyn Flow, layout_context: &LayoutContext) {
+ ResolveGeneratedContent::new(&layout_context).traverse(root, 0);
+}
+
+/// Run the main layout passes sequentially.
+pub fn reflow(root: &mut dyn Flow, layout_context: &LayoutContext, relayout_mode: RelayoutMode) {
+ fn doit(
+ flow: &mut dyn Flow,
+ assign_inline_sizes: AssignISizes,
+ assign_block_sizes: AssignBSizes,
+ relayout_mode: RelayoutMode,
+ ) {
+ // Force reflow children during this traversal. This is needed when we failed
+ // the float speculation of a block formatting context and need to fix it.
+ if relayout_mode == RelayoutMode::Force {
+ flow.mut_base()
+ .restyle_damage
+ .insert(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW);
+ }
+
+ if assign_inline_sizes.should_process(flow) {
+ assign_inline_sizes.process(flow);
+ }
+
+ for kid in flow.mut_base().child_iter_mut() {
+ doit(kid, assign_inline_sizes, assign_block_sizes, relayout_mode);
+ }
+
+ if assign_block_sizes.should_process(flow) {
+ assign_block_sizes.process(flow);
+ }
+ }
+
+ if opts::get().bubble_inline_sizes_separately {
+ let bubble_inline_sizes = BubbleISizes {
+ layout_context: &layout_context,
+ };
+ bubble_inline_sizes.traverse(root);
+ }
+
+ let assign_inline_sizes = AssignISizes {
+ layout_context: &layout_context,
+ };
+ let assign_block_sizes = AssignBSizes {
+ layout_context: &layout_context,
+ };
+
+ doit(root, assign_inline_sizes, assign_block_sizes, relayout_mode);
+}
+
+pub fn build_display_list_for_subtree<'a>(
+ flow_root: &mut dyn Flow,
+ layout_context: &'a LayoutContext,
+ background_color: webrender_api::ColorF,
+ client_size: Size2D<Au>,
+) -> DisplayListBuildState<'a> {
+ let mut state = StackingContextCollectionState::new(layout_context.id);
+ flow_root.collect_stacking_contexts(&mut state);
+
+ let mut state = DisplayListBuildState::new(layout_context, state);
+
+ // Create a base rectangle for the page background based on the root
+ // background color.
+ let base = state.create_base_display_item(
+ Rect::new(Point2D::new(Au::new(0), Au::new(0)), client_size),
+ flow_root.as_block().fragment.node,
+ None,
+ DisplayListSection::BackgroundAndBorders,
+ );
+ state.add_display_item(DisplayItem::Rectangle(CommonDisplayItem::new(
+ base,
+ webrender_api::RectangleDisplayItem {
+ color: background_color,
+ common: items::empty_common_item_properties(),
+ },
+ )));
+
+ let mut build_display_list = BuildDisplayList { state: state };
+ build_display_list.traverse(flow_root);
+ build_display_list.state
+}
+
+pub fn iterate_through_flow_tree_fragment_border_boxes(
+ root: &mut dyn Flow,
+ iterator: &mut dyn FragmentBorderBoxIterator,
+) {
+ fn doit(
+ flow: &mut dyn Flow,
+ level: i32,
+ iterator: &mut dyn FragmentBorderBoxIterator,
+ stacking_context_position: &Point2D<Au>,
+ ) {
+ flow.iterate_through_fragment_border_boxes(iterator, level, stacking_context_position);
+
+ for kid in flow.mut_base().child_iter_mut() {
+ let mut stacking_context_position = *stacking_context_position;
+ if kid.is_block_flow() && kid.as_block().fragment.establishes_stacking_context() {
+ stacking_context_position =
+ Point2D::new(kid.as_block().fragment.margin.inline_start, Au(0)) +
+ kid.base().stacking_relative_position +
+ stacking_context_position.to_vector();
+ let relative_position = kid
+ .as_block()
+ .stacking_relative_border_box(CoordinateSystem::Own);
+ if let Some(matrix) = kid.as_block().fragment.transform_matrix(&relative_position) {
+ let transform_matrix = matrix.transform_point2d(&LayoutPoint::zero()).unwrap();
+ stacking_context_position = stacking_context_position +
+ Vector2D::new(
+ Au::from_f32_px(transform_matrix.x),
+ Au::from_f32_px(transform_matrix.y),
+ )
+ }
+ }
+ doit(kid, level + 1, iterator, &stacking_context_position);
+ }
+ }
+
+ doit(root, 0, iterator, &Point2D::zero());
+}
+
+pub fn store_overflow(layout_context: &LayoutContext, flow: &mut dyn Flow) {
+ if !flow
+ .base()
+ .restyle_damage
+ .contains(ServoRestyleDamage::STORE_OVERFLOW)
+ {
+ return;
+ }
+
+ for kid in flow.mut_base().child_iter_mut() {
+ store_overflow(layout_context, kid);
+ }
+
+ flow.store_overflow(layout_context);
+
+ flow.mut_base()
+ .restyle_damage
+ .remove(ServoRestyleDamage::STORE_OVERFLOW);
+}
+
+/// Guesses how much inline size will be taken up by floats on the left and right sides of the
+/// given flow. This is needed to speculatively calculate the inline sizes of block formatting
+/// contexts. The speculation typically succeeds, but if it doesn't we have to lay it out again.
+pub fn guess_float_placement(flow: &mut dyn Flow) {
+ if !flow
+ .base()
+ .restyle_damage
+ .intersects(ServoRestyleDamage::REFLOW)
+ {
+ return;
+ }
+
+ let mut floats_in = SpeculatedFloatPlacement::compute_floats_in_for_first_child(flow);
+ for kid in flow.mut_base().child_iter_mut() {
+ if kid
+ .base()
+ .flags
+ .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED)
+ {
+ // Do not propagate floats in or out, but do propogate between kids.
+ guess_float_placement(kid);
+ } else {
+ floats_in.compute_floats_in(kid);
+ kid.mut_base().speculated_float_placement_in = floats_in;
+ guess_float_placement(kid);
+ floats_in = kid.base().speculated_float_placement_out;
+ }
+ }
+ floats_in.compute_floats_out(flow);
+ flow.mut_base().speculated_float_placement_out = floats_in
+}
diff --git a/components/layout_2020/table.rs b/components/layout_2020/table.rs
new file mode 100644
index 00000000000..9f6219f0980
--- /dev/null
+++ b/components/layout_2020/table.rs
@@ -0,0 +1,1378 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+//! CSS table formatting contexts.
+
+use crate::block::{BlockFlow, CandidateBSizeIterator, ISizeAndMarginsComputer};
+use crate::block::{ISizeConstraintInput, ISizeConstraintSolution};
+use crate::context::LayoutContext;
+use crate::display_list::{
+ BorderPaintingMode, DisplayListBuildState, StackingContextCollectionFlags,
+ StackingContextCollectionState,
+};
+use crate::flow::{
+ BaseFlow, EarlyAbsolutePositionInfo, Flow, FlowClass, GetBaseFlow, ImmutableFlowUtils,
+ OpaqueFlow,
+};
+use crate::flow_list::{FlowListIterator, MutFlowListIterator};
+use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
+use crate::layout_debug;
+use crate::model::{IntrinsicISizes, IntrinsicISizesContribution, MaybeAuto};
+use crate::table_cell::TableCellFlow;
+use crate::table_row::{self, CellIntrinsicInlineSize, CollapsedBorder, CollapsedBorderProvenance};
+use crate::table_row::{TableRowFlow, TableRowSizeData};
+use crate::table_wrapper::TableLayout;
+use app_units::Au;
+use euclid::Point2D;
+use gfx_traits::print_tree::PrintTree;
+use std::{cmp, fmt};
+use style::computed_values::{border_collapse, border_spacing, table_layout};
+use style::context::SharedStyleContext;
+use style::logical_geometry::LogicalSize;
+use style::properties::style_structs::Background;
+use style::properties::ComputedValues;
+use style::servo::restyle_damage::ServoRestyleDamage;
+use style::values::computed::Size;
+use style::values::CSSFloat;
+
+#[allow(unsafe_code)]
+unsafe impl crate::flow::HasBaseFlow for TableFlow {}
+
+/// 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.
+#[derive(Serialize)]
+#[repr(C)]
+pub struct TableFlow {
+ pub block_flow: BlockFlow,
+
+ /// Information about the intrinsic inline-sizes of each column, computed bottom-up during
+ /// intrinsic inline-size bubbling.
+ pub column_intrinsic_inline_sizes: Vec<ColumnIntrinsicInlineSize>,
+
+ /// Information about the actual inline sizes of each column, computed top-down during actual
+ /// inline-size bubbling.
+ pub column_computed_inline_sizes: Vec<ColumnComputedInlineSize>,
+
+ /// The final width of the borders in the inline direction for each cell, computed by the
+ /// entire table and pushed down into each row during inline size computation.
+ pub collapsed_inline_direction_border_widths_for_table: Vec<Au>,
+
+ /// The final width of the borders in the block direction for each cell, computed by the
+ /// entire table and pushed down into each row during inline size computation.
+ pub collapsed_block_direction_border_widths_for_table: Vec<Au>,
+
+ /// Table-layout property
+ pub table_layout: TableLayout,
+}
+
+impl TableFlow {
+ pub fn from_fragment(fragment: Fragment) -> TableFlow {
+ let mut block_flow = BlockFlow::from_fragment(fragment);
+ let table_layout =
+ if block_flow.fragment().style().get_table().table_layout == table_layout::T::Fixed {
+ TableLayout::Fixed
+ } else {
+ TableLayout::Auto
+ };
+ TableFlow {
+ block_flow: block_flow,
+ column_intrinsic_inline_sizes: Vec::new(),
+ column_computed_inline_sizes: Vec::new(),
+ collapsed_inline_direction_border_widths_for_table: Vec::new(),
+ collapsed_block_direction_border_widths_for_table: Vec::new(),
+ table_layout: table_layout,
+ }
+ }
+
+ /// Update the corresponding value of `self_inline_sizes` if a value of `kid_inline_sizes` has
+ /// a larger value than one of `self_inline_sizes`. Returns the minimum and preferred inline
+ /// sizes.
+ fn update_automatic_column_inline_sizes(
+ parent_inline_sizes: &mut Vec<ColumnIntrinsicInlineSize>,
+ child_cell_inline_sizes: &[CellIntrinsicInlineSize],
+ surrounding_size: Au,
+ ) -> IntrinsicISizes {
+ let mut total_inline_sizes = IntrinsicISizes {
+ minimum_inline_size: surrounding_size,
+ preferred_inline_size: surrounding_size,
+ };
+ let mut column_index = 0;
+ let mut incoming_rowspan = vec![];
+
+ for child_cell_inline_size in child_cell_inline_sizes {
+ // Skip any column occupied by a cell from a previous row.
+ while column_index < incoming_rowspan.len() && incoming_rowspan[column_index] != 1 {
+ if incoming_rowspan[column_index] > 1 {
+ incoming_rowspan[column_index] -= 1;
+ }
+ column_index += 1;
+ }
+ for _ in 0..child_cell_inline_size.column_span {
+ if column_index < parent_inline_sizes.len() {
+ // We already have some intrinsic size information for this column. Merge it in
+ // according to the rules specified in INTRINSIC § 4.
+ let parent_sizes = &mut parent_inline_sizes[column_index];
+ if child_cell_inline_size.column_span > 1 {
+ // TODO(pcwalton): Perform the recursive algorithm specified in INTRINSIC §
+ // 4. For now we make this column contribute no width.
+ } else {
+ let column_size = &child_cell_inline_size.column_size;
+ *parent_sizes = ColumnIntrinsicInlineSize {
+ minimum_length: cmp::max(
+ parent_sizes.minimum_length,
+ column_size.minimum_length,
+ ),
+ percentage: parent_sizes.greatest_percentage(column_size),
+ preferred: cmp::max(parent_sizes.preferred, column_size.preferred),
+ constrained: parent_sizes.constrained || column_size.constrained,
+ }
+ }
+ } else {
+ // We discovered a new column. Initialize its data.
+ debug_assert_eq!(column_index, parent_inline_sizes.len());
+ if child_cell_inline_size.column_span > 1 {
+ // TODO(pcwalton): Perform the recursive algorithm specified in INTRINSIC §
+ // 4. For now we make this column contribute no width.
+ parent_inline_sizes.push(ColumnIntrinsicInlineSize::new())
+ } else {
+ parent_inline_sizes.push(child_cell_inline_size.column_size)
+ }
+ }
+
+ total_inline_sizes.minimum_inline_size +=
+ parent_inline_sizes[column_index].minimum_length;
+ total_inline_sizes.preferred_inline_size +=
+ parent_inline_sizes[column_index].preferred;
+
+ // If this cell spans later rows, record its rowspan.
+ if child_cell_inline_size.row_span > 1 {
+ if incoming_rowspan.len() < column_index + 1 {
+ incoming_rowspan.resize(column_index + 1, 0);
+ }
+ incoming_rowspan[column_index] = child_cell_inline_size.row_span;
+ }
+
+ column_index += 1
+ }
+ }
+
+ total_inline_sizes
+ }
+
+ /// Updates the minimum and preferred inline-size calculation for a single row. This is
+ /// factored out into a separate function because we process children of rowgroups too.
+ fn update_column_inline_sizes_for_row(
+ row: &TableRowFlow,
+ column_inline_sizes: &mut Vec<ColumnIntrinsicInlineSize>,
+ computation: &mut IntrinsicISizesContribution,
+ first_row: bool,
+ table_layout: TableLayout,
+ surrounding_inline_size: Au,
+ ) {
+ // Read column inline-sizes from the table-row, and assign inline-size=0 for the columns
+ // not defined in the column group.
+ //
+ // FIXME: Need to read inline-sizes from either table-header-group OR the first table-row.
+ match table_layout {
+ TableLayout::Fixed => {
+ // Fixed table layout only looks at the first row.
+ //
+ // FIXME(pcwalton): This is really inefficient. We should stop after the first row!
+ if first_row {
+ for cell_inline_size in &row.cell_intrinsic_inline_sizes {
+ column_inline_sizes.push(cell_inline_size.column_size);
+ }
+ }
+ },
+ TableLayout::Auto => {
+ computation.union_block(&TableFlow::update_automatic_column_inline_sizes(
+ column_inline_sizes,
+ &row.cell_intrinsic_inline_sizes,
+ surrounding_inline_size,
+ ))
+ },
+ }
+ }
+
+ /// Returns the effective spacing per cell, taking the value of `border-collapse` into account.
+ pub fn spacing(&self) -> border_spacing::T {
+ let style = self.block_flow.fragment.style();
+ match style.get_inherited_table().border_collapse {
+ border_collapse::T::Separate => style.get_inherited_table().border_spacing,
+ border_collapse::T::Collapse => border_spacing::T::zero(),
+ }
+ }
+
+ pub fn total_horizontal_spacing(&self) -> Au {
+ let num_columns = self.column_intrinsic_inline_sizes.len();
+ if num_columns == 0 {
+ return Au(0);
+ }
+ self.spacing().horizontal() * (num_columns as i32 + 1)
+ }
+
+ fn column_styles(&self) -> Vec<ColumnStyle> {
+ let mut styles = vec![];
+ for group in self
+ .block_flow
+ .base
+ .child_iter()
+ .filter(|kid| kid.is_table_colgroup())
+ {
+ // XXXManishearth these as_foo methods should return options
+ // so that we can filter_map
+ let group = group.as_table_colgroup();
+ let colgroup_style = group.fragment.as_ref().map(|f| f.style());
+
+ // The colgroup's span attribute is only relevant when
+ // it has no children
+ // https://html.spec.whatwg.org/multipage/#forming-a-table
+ if group.cols.is_empty() {
+ let span = group
+ .fragment
+ .as_ref()
+ .map(|f| f.column_span())
+ .unwrap_or(1);
+ styles.push(ColumnStyle {
+ span,
+ colgroup_style,
+ col_style: None,
+ });
+ } else {
+ for col in &group.cols {
+ // XXXManishearth Arc-cloning colgroup_style is suboptimal
+ styles.push(ColumnStyle {
+ span: col.column_span(),
+ colgroup_style: colgroup_style,
+ col_style: Some(col.style()),
+ })
+ }
+ }
+ }
+ styles
+ }
+}
+
+impl Flow for TableFlow {
+ fn class(&self) -> FlowClass {
+ FlowClass::Table
+ }
+
+ fn as_mut_table(&mut self) -> &mut TableFlow {
+ self
+ }
+
+ fn as_table(&self) -> &TableFlow {
+ self
+ }
+
+ fn as_mut_block(&mut self) -> &mut BlockFlow {
+ &mut self.block_flow
+ }
+
+ fn as_block(&self) -> &BlockFlow {
+ &self.block_flow
+ }
+
+ fn mark_as_root(&mut self) {
+ self.block_flow.mark_as_root();
+ }
+
+ /// 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) {
+ let _scope = layout_debug_scope!(
+ "table::bubble_inline_sizes {:x}",
+ self.block_flow.base.debug_id()
+ );
+
+ // Get column inline sizes from colgroups
+ for kid in self
+ .block_flow
+ .base
+ .child_iter_mut()
+ .filter(|kid| kid.is_table_colgroup())
+ {
+ for specified_inline_size in &kid.as_mut_table_colgroup().inline_sizes {
+ self.column_intrinsic_inline_sizes
+ .push(ColumnIntrinsicInlineSize {
+ minimum_length: match *specified_inline_size {
+ Size::Auto => Au(0),
+ Size::LengthPercentage(ref lp) => {
+ lp.maybe_to_used_value(None).unwrap_or(Au(0))
+ },
+ },
+ percentage: match *specified_inline_size {
+ Size::Auto => 0.0,
+ Size::LengthPercentage(ref lp) => {
+ lp.0.as_percentage().map_or(0.0, |p| p.0)
+ },
+ },
+ preferred: Au(0),
+ constrained: false,
+ })
+ }
+ }
+
+ self.collapsed_inline_direction_border_widths_for_table = Vec::new();
+ self.collapsed_block_direction_border_widths_for_table = vec![Au(0)];
+
+ let collapsing_borders = self
+ .block_flow
+ .fragment
+ .style
+ .get_inherited_table()
+ .border_collapse ==
+ border_collapse::T::Collapse;
+ let table_inline_collapsed_borders = if collapsing_borders {
+ Some(TableInlineCollapsedBorders {
+ start: CollapsedBorder::inline_start(
+ &*self.block_flow.fragment.style,
+ CollapsedBorderProvenance::FromTable,
+ ),
+ end: CollapsedBorder::inline_end(
+ &*self.block_flow.fragment.style,
+ CollapsedBorderProvenance::FromTable,
+ ),
+ })
+ } else {
+ None
+ };
+
+ let mut computation = IntrinsicISizesContribution::new();
+ let mut previous_collapsed_block_end_borders =
+ PreviousBlockCollapsedBorders::FromTable(CollapsedBorder::block_start(
+ &*self.block_flow.fragment.style,
+ CollapsedBorderProvenance::FromTable,
+ ));
+ let mut first_row = true;
+ let (border_padding, _) = self.block_flow.fragment.surrounding_intrinsic_inline_size();
+
+ {
+ let mut iterator = TableRowIterator::new(&mut self.block_flow.base).peekable();
+ while let Some(row) = iterator.next() {
+ TableFlow::update_column_inline_sizes_for_row(
+ row,
+ &mut self.column_intrinsic_inline_sizes,
+ &mut computation,
+ first_row,
+ self.table_layout,
+ border_padding,
+ );
+ if collapsing_borders {
+ let next_index_and_sibling = iterator.peek();
+ let next_collapsed_borders_in_block_direction = match next_index_and_sibling {
+ Some(next_sibling) => NextBlockCollapsedBorders::FromNextRow(
+ &next_sibling
+ .as_table_row()
+ .preliminary_collapsed_borders
+ .block_start,
+ ),
+ None => NextBlockCollapsedBorders::FromTable(CollapsedBorder::block_end(
+ &*self.block_flow.fragment.style,
+ CollapsedBorderProvenance::FromTable,
+ )),
+ };
+ perform_border_collapse_for_row(
+ row,
+ table_inline_collapsed_borders.as_ref().unwrap(),
+ previous_collapsed_block_end_borders,
+ next_collapsed_borders_in_block_direction,
+ &mut self.collapsed_inline_direction_border_widths_for_table,
+ &mut self.collapsed_block_direction_border_widths_for_table,
+ );
+ previous_collapsed_block_end_borders =
+ PreviousBlockCollapsedBorders::FromPreviousRow(
+ row.final_collapsed_borders.block_end.clone(),
+ );
+ }
+ first_row = false
+ }
+ }
+
+ let total_horizontal_spacing = self.total_horizontal_spacing();
+ let mut style_specified_intrinsic_inline_size = self
+ .block_flow
+ .fragment
+ .style_specified_intrinsic_inline_size()
+ .finish();
+ style_specified_intrinsic_inline_size.minimum_inline_size -= total_horizontal_spacing;
+ style_specified_intrinsic_inline_size.preferred_inline_size -= total_horizontal_spacing;
+ computation.union_block(&style_specified_intrinsic_inline_size);
+ computation.surrounding_size += total_horizontal_spacing;
+
+ self.block_flow.base.intrinsic_inline_sizes = computation.finish()
+ }
+
+ /// 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, layout_context: &LayoutContext) {
+ let _scope = layout_debug_scope!(
+ "table::assign_inline_sizes {:x}",
+ self.block_flow.base.debug_id()
+ );
+ debug!(
+ "assign_inline_sizes({}): assigning inline_size for flow",
+ "table"
+ );
+
+ let shared_context = layout_context.shared_context();
+ // The position was set to the containing block by the flow's parent.
+ // FIXME: The code for distributing column widths should really be placed under table_wrapper.rs.
+ let containing_block_inline_size = self.block_flow.base.block_container_inline_size;
+
+ let mut constrained_column_inline_sizes_indices = vec![];
+ let mut unspecified_inline_sizes_indices = vec![];
+ for (idx, column_inline_size) in self.column_intrinsic_inline_sizes.iter().enumerate() {
+ if column_inline_size.constrained {
+ constrained_column_inline_sizes_indices.push(idx);
+ } else if column_inline_size.percentage == 0.0 {
+ unspecified_inline_sizes_indices.push(idx);
+ }
+ }
+
+ let inline_size_computer = InternalTable;
+ inline_size_computer.compute_used_inline_size(
+ &mut self.block_flow,
+ shared_context,
+ containing_block_inline_size,
+ );
+
+ let inline_start_content_edge = self.block_flow.fragment.border_padding.inline_start;
+ let inline_end_content_edge = self.block_flow.fragment.border_padding.inline_end;
+ let padding_and_borders = self.block_flow.fragment.border_padding.inline_start_end();
+ let spacing_per_cell = self.spacing();
+ let total_horizontal_spacing = self.total_horizontal_spacing();
+ let content_inline_size = self.block_flow.fragment.border_box.size.inline -
+ padding_and_borders -
+ total_horizontal_spacing;
+ let mut remaining_inline_size = content_inline_size;
+
+ match self.table_layout {
+ TableLayout::Fixed => {
+ self.column_computed_inline_sizes.clear();
+
+ // https://drafts.csswg.org/css2/tables.html#fixed-table-layout
+ for column_inline_size in &self.column_intrinsic_inline_sizes {
+ if column_inline_size.constrained {
+ self.column_computed_inline_sizes
+ .push(ColumnComputedInlineSize {
+ size: column_inline_size.minimum_length,
+ });
+ remaining_inline_size -= column_inline_size.minimum_length;
+ } else if column_inline_size.percentage != 0.0 {
+ let size = remaining_inline_size.scale_by(column_inline_size.percentage);
+ self.column_computed_inline_sizes
+ .push(ColumnComputedInlineSize { size: size });
+ remaining_inline_size -= size;
+ } else {
+ // Set the size to 0 now, distribute the remaining widths later
+ self.column_computed_inline_sizes
+ .push(ColumnComputedInlineSize { size: Au(0) });
+ }
+ }
+
+ // Distribute remaining content inline size
+ if unspecified_inline_sizes_indices.len() > 0 {
+ for &index in &unspecified_inline_sizes_indices {
+ self.column_computed_inline_sizes[index].size = remaining_inline_size
+ .scale_by(1.0 / unspecified_inline_sizes_indices.len() as f32);
+ }
+ } else {
+ let total_minimum_size = self
+ .column_intrinsic_inline_sizes
+ .iter()
+ .filter(|size| size.constrained)
+ .map(|size| size.minimum_length.0 as f32)
+ .sum::<f32>();
+
+ for &index in &constrained_column_inline_sizes_indices {
+ let inline_size = self.column_computed_inline_sizes[index].size.0;
+ self.column_computed_inline_sizes[index].size +=
+ remaining_inline_size.scale_by(inline_size as f32 / total_minimum_size);
+ }
+ }
+ },
+ _ => {
+ // The table wrapper already computed the inline-sizes and propagated them down
+ // to us.
+ },
+ }
+
+ let column_computed_inline_sizes = &self.column_computed_inline_sizes;
+ let collapsed_inline_direction_border_widths_for_table =
+ &self.collapsed_inline_direction_border_widths_for_table;
+ let mut collapsed_block_direction_border_widths_for_table = self
+ .collapsed_block_direction_border_widths_for_table
+ .iter()
+ .peekable();
+ let mut incoming_rowspan = vec![];
+ self.block_flow.propagate_assigned_inline_size_to_children(
+ shared_context,
+ inline_start_content_edge,
+ inline_end_content_edge,
+ content_inline_size,
+ |child_flow,
+ _child_index,
+ _content_inline_size,
+ writing_mode,
+ _inline_start_margin_edge,
+ _inline_end_margin_edge| {
+ table_row::propagate_column_inline_sizes_to_child(
+ child_flow,
+ writing_mode,
+ column_computed_inline_sizes,
+ &spacing_per_cell,
+ &mut incoming_rowspan,
+ );
+ if child_flow.is_table_row() {
+ let child_table_row = child_flow.as_mut_table_row();
+ child_table_row.populate_collapsed_border_spacing(
+ collapsed_inline_direction_border_widths_for_table,
+ &mut collapsed_block_direction_border_widths_for_table,
+ );
+ } else if child_flow.is_table_rowgroup() {
+ let child_table_rowgroup = child_flow.as_mut_table_rowgroup();
+ child_table_rowgroup.populate_collapsed_border_spacing(
+ collapsed_inline_direction_border_widths_for_table,
+ &mut collapsed_block_direction_border_widths_for_table,
+ );
+ }
+ },
+ );
+ }
+
+ fn assign_block_size(&mut self, lc: &LayoutContext) {
+ debug!("assign_block_size: assigning block_size for table");
+ let vertical_spacing = self.spacing().vertical();
+ self.block_flow
+ .assign_block_size_for_table_like_flow(vertical_spacing, lc)
+ }
+
+ fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
+ self.block_flow
+ .compute_stacking_relative_position(layout_context)
+ }
+
+ fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
+ self.block_flow.generated_containing_block_size(flow)
+ }
+
+ fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
+ self.block_flow
+ .update_late_computed_inline_position_if_necessary(inline_position)
+ }
+
+ fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
+ self.block_flow
+ .update_late_computed_block_position_if_necessary(block_position)
+ }
+
+ fn build_display_list(&mut self, state: &mut DisplayListBuildState) {
+ let border_painting_mode = match self
+ .block_flow
+ .fragment
+ .style
+ .get_inherited_table()
+ .border_collapse
+ {
+ border_collapse::T::Separate => BorderPaintingMode::Separate,
+ border_collapse::T::Collapse => BorderPaintingMode::Hidden,
+ };
+
+ self.block_flow
+ .build_display_list_for_block(state, border_painting_mode);
+
+ let iter = TableCellStyleIterator::new(&self);
+ for style in iter {
+ style.build_display_list(state)
+ }
+ }
+
+ fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
+ // Stacking contexts are collected by the table wrapper.
+ self.block_flow.collect_stacking_contexts_for_block(
+ state,
+ StackingContextCollectionFlags::NEVER_CREATES_STACKING_CONTEXT,
+ );
+ }
+
+ fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>) {
+ self.block_flow.repair_style(new_style)
+ }
+
+ fn compute_overflow(&self) -> Overflow {
+ self.block_flow.compute_overflow()
+ }
+
+ fn iterate_through_fragment_border_boxes(
+ &self,
+ iterator: &mut dyn FragmentBorderBoxIterator,
+ level: i32,
+ stacking_context_position: &Point2D<Au>,
+ ) {
+ self.block_flow.iterate_through_fragment_border_boxes(
+ iterator,
+ level,
+ stacking_context_position,
+ )
+ }
+
+ fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) {
+ self.block_flow.mutate_fragments(mutator)
+ }
+
+ fn print_extra_flow_children(&self, print_tree: &mut PrintTree) {
+ self.block_flow.print_extra_flow_children(print_tree);
+ }
+}
+
+#[derive(Debug)]
+struct ColumnStyle<'table> {
+ span: u32,
+ colgroup_style: Option<&'table ComputedValues>,
+ col_style: Option<&'table ComputedValues>,
+}
+
+impl fmt::Debug 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,
+ shared_context: &SharedStyleContext,
+ parent_flow_inline_size: Au,
+ ) {
+ let mut input = self.compute_inline_size_constraint_inputs(
+ block,
+ parent_flow_inline_size,
+ shared_context,
+ );
+
+ // Tables are always at least as wide as their minimum inline size.
+ let minimum_inline_size = block.base.intrinsic_inline_sizes.minimum_inline_size -
+ block.fragment.border_padding.inline_start_end();
+ input.available_inline_size = cmp::max(input.available_inline_size, minimum_inline_size);
+
+ 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(0), Au(0))
+ }
+}
+
+/// Information about the intrinsic inline sizes of columns within a table.
+///
+/// During table inline-size bubbling, we might need to store both a percentage constraint and a
+/// specific width constraint. For instance, one cell might say that it wants to be 100 pixels wide
+/// in the inline direction and another cell might say that it wants to take up 20% of the inline-
+/// size of the table. Now because we bubble up these constraints during the bubble-inline-sizes
+/// phase of layout, we don't know yet how wide the table is ultimately going to be in the inline
+/// direction. As we need to pick the maximum width of all cells for a column (in this case, the
+/// maximum of 100 pixels and 20% of the table), the preceding constraint means that we must
+/// potentially store both a specified width *and* a specified percentage, so that the inline-size
+/// assignment phase of layout will know which one to pick.
+#[derive(Clone, Copy, Debug, Serialize)]
+pub struct ColumnIntrinsicInlineSize {
+ /// The preferred intrinsic inline size.
+ pub preferred: Au,
+ /// The largest specified size of this column as a length.
+ pub minimum_length: Au,
+ /// The largest specified size of this column as a percentage (`width` property).
+ pub percentage: CSSFloat,
+ /// Whether the column inline size is *constrained* per INTRINSIC § 4.1.
+ pub constrained: bool,
+}
+
+impl ColumnIntrinsicInlineSize {
+ /// Returns a newly-initialized `ColumnIntrinsicInlineSize` with all fields blank.
+ pub fn new() -> ColumnIntrinsicInlineSize {
+ ColumnIntrinsicInlineSize {
+ preferred: Au(0),
+ minimum_length: Au(0),
+ percentage: 0.0,
+ constrained: false,
+ }
+ }
+
+ /// Returns the higher of the two percentages specified in `self` and `other`.
+ pub fn greatest_percentage(&self, other: &ColumnIntrinsicInlineSize) -> CSSFloat {
+ if self.percentage > other.percentage {
+ self.percentage
+ } else {
+ other.percentage
+ }
+ }
+}
+
+/// The actual inline size for each column.
+///
+/// TODO(pcwalton): There will probably be some `border-collapse`-related info in here too
+/// eventually.
+#[derive(Clone, Copy, Debug, Serialize)]
+pub struct ColumnComputedInlineSize {
+ /// The computed size of this inline column.
+ pub size: Au,
+}
+
+pub trait VecExt<T> {
+ fn push_or_set(&mut self, index: usize, value: T) -> &mut T;
+ fn get_mut_or_push(&mut self, index: usize, zero: T) -> &mut T;
+}
+
+impl<T> VecExt<T> for Vec<T> {
+ fn push_or_set(&mut self, index: usize, value: T) -> &mut T {
+ if index < self.len() {
+ self[index] = value
+ } else {
+ debug_assert_eq!(index, self.len());
+ self.push(value)
+ }
+ &mut self[index]
+ }
+
+ fn get_mut_or_push(&mut self, index: usize, zero: T) -> &mut T {
+ if index >= self.len() {
+ debug_assert_eq!(index, self.len());
+ self.push(zero)
+ }
+ &mut self[index]
+ }
+}
+
+/// Updates the border styles in the block direction for a single row. This function should
+/// only be called if border collapsing is on. It is factored out into a separate function
+/// because we process children of rowgroups too.
+fn perform_border_collapse_for_row(
+ child_table_row: &mut TableRowFlow,
+ table_inline_borders: &TableInlineCollapsedBorders,
+ previous_block_borders: PreviousBlockCollapsedBorders,
+ next_block_borders: NextBlockCollapsedBorders,
+ inline_spacing: &mut Vec<Au>,
+ block_spacing: &mut Vec<Au>,
+) {
+ // TODO mbrubeck: Take rowspan and colspan into account.
+ let number_of_borders_inline_direction =
+ child_table_row.preliminary_collapsed_borders.inline.len();
+ // Compute interior inline borders.
+ for (i, this_inline_border) in child_table_row
+ .preliminary_collapsed_borders
+ .inline
+ .iter_mut()
+ .enumerate()
+ {
+ child_table_row
+ .final_collapsed_borders
+ .inline
+ .push_or_set(i, *this_inline_border);
+ if i == 0 {
+ child_table_row.final_collapsed_borders.inline[i].combine(&table_inline_borders.start);
+ } else if i + 1 == number_of_borders_inline_direction {
+ child_table_row.final_collapsed_borders.inline[i].combine(&table_inline_borders.end);
+ }
+
+ let inline_spacing = inline_spacing.get_mut_or_push(i, Au(0));
+ *inline_spacing = cmp::max(
+ *inline_spacing,
+ child_table_row.final_collapsed_borders.inline[i].width,
+ )
+ }
+
+ // Compute block-start borders.
+ let block_start_borders = &mut child_table_row.final_collapsed_borders.block_start;
+ *block_start_borders = child_table_row
+ .preliminary_collapsed_borders
+ .block_start
+ .clone();
+ for (i, this_border) in block_start_borders.iter_mut().enumerate() {
+ match previous_block_borders {
+ PreviousBlockCollapsedBorders::FromPreviousRow(ref previous_block_borders) => {
+ if previous_block_borders.len() > i {
+ this_border.combine(&previous_block_borders[i]);
+ }
+ },
+ PreviousBlockCollapsedBorders::FromTable(table_border) => {
+ this_border.combine(&table_border);
+ },
+ }
+ }
+
+ // Compute block-end borders.
+ let next_block = &mut child_table_row.final_collapsed_borders.block_end;
+ block_spacing.push(Au(0));
+ let block_spacing = block_spacing.last_mut().unwrap();
+ for (i, this_block_border) in child_table_row
+ .preliminary_collapsed_borders
+ .block_end
+ .iter()
+ .enumerate()
+ {
+ let next_block = next_block.push_or_set(i, *this_block_border);
+ match next_block_borders {
+ NextBlockCollapsedBorders::FromNextRow(next_block_borders) => {
+ if next_block_borders.len() > i {
+ next_block.combine(&next_block_borders[i])
+ }
+ },
+ NextBlockCollapsedBorders::FromTable(ref next_block_borders) => {
+ next_block.combine(next_block_borders);
+ },
+ }
+ *block_spacing = cmp::max(*block_spacing, next_block.width)
+ }
+}
+
+/// Encapsulates functionality shared among all table-like flows: for now, tables and table
+/// rowgroups.
+pub trait TableLikeFlow {
+ /// Lays out the rows of a table.
+ fn assign_block_size_for_table_like_flow(
+ &mut self,
+ block_direction_spacing: Au,
+ layout_context: &LayoutContext,
+ );
+}
+
+impl TableLikeFlow for BlockFlow {
+ fn assign_block_size_for_table_like_flow(
+ &mut self,
+ block_direction_spacing: Au,
+ layout_context: &LayoutContext,
+ ) {
+ debug_assert!(
+ self.fragment.style.get_inherited_table().border_collapse ==
+ border_collapse::T::Separate ||
+ block_direction_spacing == Au(0)
+ );
+
+ fn border_spacing_for_row(
+ fragment: &Fragment,
+ row: &TableRowFlow,
+ block_direction_spacing: Au,
+ ) -> Au {
+ match fragment.style.get_inherited_table().border_collapse {
+ border_collapse::T::Separate => block_direction_spacing,
+ border_collapse::T::Collapse => row.collapsed_border_spacing.block_start,
+ }
+ }
+
+ if self
+ .base
+ .restyle_damage
+ .contains(ServoRestyleDamage::REFLOW)
+ {
+ let mut sizes = vec![Default::default()];
+ // The amount of border spacing up to and including this row,
+ // but not including the spacing beneath it
+ let mut cumulative_border_spacing = Au(0);
+ let mut incoming_rowspan_data = vec![];
+ let mut rowgroup_id = 0;
+ let mut first = true;
+
+ // First pass: Compute block-direction border spacings
+ // XXXManishearth this can be done in tandem with the second pass,
+ // provided we never hit any rowspan cases
+ for kid in self.base.child_iter_mut() {
+ if kid.is_table_row() {
+ // skip the first row, it is accounted for
+ if first {
+ first = false;
+ continue;
+ }
+ cumulative_border_spacing += border_spacing_for_row(
+ &self.fragment,
+ kid.as_table_row(),
+ block_direction_spacing,
+ );
+ sizes.push(TableRowSizeData {
+ // we haven't calculated sizes yet
+ size: Au(0),
+ cumulative_border_spacing,
+ rowgroup_id,
+ });
+ } else if kid.is_table_rowgroup() && !first {
+ rowgroup_id += 1;
+ }
+ }
+
+ // Second pass: Compute row block sizes
+ // [expensive: iterates over cells]
+ let mut i = 0;
+ for kid in self.base.child_iter_mut() {
+ if kid.is_table_row() {
+ let size = kid.as_mut_table_row().compute_block_size_table_row_base(
+ layout_context,
+ &mut incoming_rowspan_data,
+ &sizes,
+ i,
+ );
+ sizes[i].size = size;
+ i += 1;
+ }
+ }
+
+ // Our current border-box position.
+ let block_start_border_padding = self.fragment.border_padding.block_start;
+ let mut current_block_offset = block_start_border_padding;
+ let mut has_rows = false;
+
+ // Third pass: Assign block sizes and positions to rows, cells, and other children
+ // [expensive: iterates over cells]
+ // At this point, `current_block_offset` is at the content edge of our box. Now iterate
+ // over children.
+ let mut i = 0;
+ for kid in self.base.child_iter_mut() {
+ if kid.is_table_row() {
+ has_rows = true;
+ let row = kid.as_mut_table_row();
+ row.assign_block_size_to_self_and_children(&sizes, i);
+ row.mut_base().restyle_damage.remove(
+ ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW,
+ );
+ current_block_offset = current_block_offset +
+ border_spacing_for_row(&self.fragment, row, block_direction_spacing);
+ i += 1;
+ }
+
+ // At this point, `current_block_offset` is at the border edge of the child.
+ kid.mut_base().position.start.b = current_block_offset;
+
+ // 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 = kid.mut_base();
+ current_block_offset = current_block_offset + kid_base.position.size.block;
+ }
+
+ // Compute any explicitly-specified block size.
+ // Can't use `for` because we assign to
+ // `candidate_block_size_iterator.candidate_value`.
+ let mut block_size = current_block_offset - block_start_border_padding;
+ let mut candidate_block_size_iterator = CandidateBSizeIterator::new(
+ &self.fragment,
+ self.base.block_container_explicit_block_size,
+ );
+ while let Some(candidate_block_size) = candidate_block_size_iterator.next() {
+ candidate_block_size_iterator.candidate_value = match candidate_block_size {
+ MaybeAuto::Auto => block_size,
+ MaybeAuto::Specified(value) => value,
+ };
+ }
+
+ // Adjust `current_block_offset` as necessary to account for the explicitly-specified
+ // block-size.
+ block_size = candidate_block_size_iterator.candidate_value;
+ let delta = block_size - (current_block_offset - block_start_border_padding);
+ current_block_offset = current_block_offset + delta;
+
+ // Take border, padding, and spacing into account.
+ let block_end_offset = self.fragment.border_padding.block_end +
+ if has_rows {
+ block_direction_spacing
+ } else {
+ Au(0)
+ };
+ current_block_offset = current_block_offset + block_end_offset;
+
+ // Now that `current_block_offset` is at the block-end of the border box, compute the
+ // final border box position.
+ self.fragment.border_box.size.block = current_block_offset;
+ self.fragment.border_box.start.b = Au(0);
+ self.base.position.size.block = current_block_offset;
+
+ // Fourth pass: Assign absolute position info
+ // Write in the size of the relative containing block for children. (This information
+ // is also needed to handle RTL.)
+ for kid in self.base.child_iter_mut() {
+ kid.mut_base().early_absolute_position_info = EarlyAbsolutePositionInfo {
+ relative_containing_block_size: self.fragment.content_box().size,
+ relative_containing_block_mode: self.fragment.style().writing_mode,
+ };
+ }
+ }
+
+ self.base
+ .restyle_damage
+ .remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW);
+ }
+}
+
+/// Inline collapsed borders for the table itself.
+#[derive(Debug)]
+struct TableInlineCollapsedBorders {
+ /// The table border at the start of the inline direction.
+ start: CollapsedBorder,
+ /// The table border at the end of the inline direction.
+ end: CollapsedBorder,
+}
+
+enum PreviousBlockCollapsedBorders {
+ FromPreviousRow(Vec<CollapsedBorder>),
+ FromTable(CollapsedBorder),
+}
+
+enum NextBlockCollapsedBorders<'a> {
+ FromNextRow(&'a [CollapsedBorder]),
+ FromTable(CollapsedBorder),
+}
+
+/// Iterator over all the rows of a table, which also
+/// provides the Fragment for rowgroups if any
+struct TableRowAndGroupIterator<'a> {
+ kids: FlowListIterator<'a>,
+ group: Option<(&'a Fragment, FlowListIterator<'a>)>,
+}
+
+impl<'a> TableRowAndGroupIterator<'a> {
+ fn new(base: &'a BaseFlow) -> Self {
+ TableRowAndGroupIterator {
+ kids: base.child_iter(),
+ group: None,
+ }
+ }
+}
+
+impl<'a> Iterator for TableRowAndGroupIterator<'a> {
+ type Item = (Option<&'a Fragment>, &'a TableRowFlow);
+ #[inline]
+ fn next(&mut self) -> Option<Self::Item> {
+ // If we're inside a rowgroup, iterate through the rowgroup's children.
+ if let Some(ref mut group) = self.group {
+ if let Some(grandkid) = group.1.next() {
+ return Some((Some(group.0), grandkid.as_table_row()));
+ }
+ }
+ // Otherwise, iterate through the table's children.
+ self.group = None;
+ match self.kids.next() {
+ Some(kid) => {
+ if kid.is_table_rowgroup() {
+ let rowgroup = kid.as_table_rowgroup();
+ let iter = rowgroup.block_flow.base.child_iter();
+ self.group = Some((&rowgroup.block_flow.fragment, iter));
+ self.next()
+ } else if kid.is_table_row() {
+ Some((None, kid.as_table_row()))
+ } else {
+ self.next() // Skip children that are not rows or rowgroups
+ }
+ },
+ None => None,
+ }
+ }
+}
+
+/// Iterator over all the rows of a table, which also
+/// provides the Fragment for rowgroups if any
+struct MutTableRowAndGroupIterator<'a> {
+ kids: MutFlowListIterator<'a>,
+ group: Option<(&'a Fragment, MutFlowListIterator<'a>)>,
+}
+
+impl<'a> MutTableRowAndGroupIterator<'a> {
+ fn new(base: &'a mut BaseFlow) -> Self {
+ MutTableRowAndGroupIterator {
+ kids: base.child_iter_mut(),
+ group: None,
+ }
+ }
+}
+
+impl<'a> Iterator for MutTableRowAndGroupIterator<'a> {
+ type Item = (Option<&'a Fragment>, &'a mut TableRowFlow);
+ #[inline]
+ fn next(&mut self) -> Option<Self::Item> {
+ // If we're inside a rowgroup, iterate through the rowgroup's children.
+ if let Some(ref mut group) = self.group {
+ if let Some(grandkid) = group.1.next() {
+ return Some((Some(group.0), grandkid.as_mut_table_row()));
+ }
+ }
+ // Otherwise, iterate through the table's children.
+ self.group = None;
+ match self.kids.next() {
+ Some(kid) => {
+ if kid.is_table_rowgroup() {
+ let rowgroup = kid.as_mut_table_rowgroup();
+ let iter = rowgroup.block_flow.base.child_iter_mut();
+ self.group = Some((&rowgroup.block_flow.fragment, iter));
+ self.next()
+ } else if kid.is_table_row() {
+ Some((None, kid.as_mut_table_row()))
+ } else {
+ self.next() // Skip children that are not rows or rowgroups
+ }
+ },
+ None => None,
+ }
+ }
+}
+
+/// Iterator over all the rows of a table
+struct TableRowIterator<'a>(MutTableRowAndGroupIterator<'a>);
+
+impl<'a> TableRowIterator<'a> {
+ fn new(base: &'a mut BaseFlow) -> Self {
+ TableRowIterator(MutTableRowAndGroupIterator::new(base))
+ }
+}
+
+impl<'a> Iterator for TableRowIterator<'a> {
+ type Item = &'a mut TableRowFlow;
+ #[inline]
+ fn next(&mut self) -> Option<Self::Item> {
+ self.0.next().map(|n| n.1)
+ }
+}
+
+/// An iterator over table cells, yielding all relevant style objects
+/// for each cell
+///
+/// Used for correctly handling table layers from
+/// https://drafts.csswg.org/css2/tables.html#table-layers
+struct TableCellStyleIterator<'table> {
+ column_styles: Vec<ColumnStyle<'table>>,
+ row_iterator: TableRowAndGroupIterator<'table>,
+ row_info: Option<TableCellStyleIteratorRowInfo<'table>>,
+ column_index: TableCellColumnIndexData,
+}
+
+struct TableCellStyleIteratorRowInfo<'table> {
+ row: &'table TableRowFlow,
+ rowgroup: Option<&'table Fragment>,
+ cell_iterator: FlowListIterator<'table>,
+}
+
+impl<'table> TableCellStyleIterator<'table> {
+ fn new(table: &'table TableFlow) -> Self {
+ let column_styles = table.column_styles();
+ let mut row_iterator = TableRowAndGroupIterator::new(&table.block_flow.base);
+ let row_info = if let Some((group, row)) = row_iterator.next() {
+ Some(TableCellStyleIteratorRowInfo {
+ row: &row,
+ rowgroup: group,
+ cell_iterator: row.block_flow.base.child_iter(),
+ })
+ } else {
+ None
+ };
+ TableCellStyleIterator {
+ column_styles,
+ row_iterator,
+ row_info,
+ column_index: Default::default(),
+ }
+ }
+}
+
+struct TableCellStyleInfo<'table> {
+ cell: &'table TableCellFlow,
+ colgroup_style: Option<&'table ComputedValues>,
+ col_style: Option<&'table ComputedValues>,
+ rowgroup_style: Option<&'table ComputedValues>,
+ row_style: &'table ComputedValues,
+}
+
+struct TableCellColumnIndexData {
+ /// Which column this is in the table
+ pub absolute: u32,
+ /// The index of the current column in column_styles
+ /// (i.e. which <col> element it is)
+ pub relative: u32,
+ /// In case of multispan <col>s, where we are in the
+ /// span of the current <col> element
+ pub relative_offset: u32,
+}
+
+impl Default for TableCellColumnIndexData {
+ fn default() -> Self {
+ TableCellColumnIndexData {
+ absolute: 0,
+ relative: 0,
+ relative_offset: 0,
+ }
+ }
+}
+
+impl TableCellColumnIndexData {
+ /// Moves forward by `amount` columns, updating the various indices used
+ ///
+ /// This totally ignores rowspan -- if colspan and rowspan clash,
+ /// they just overlap, so we ignore it.
+ fn advance(&mut self, amount: u32, column_styles: &[ColumnStyle]) {
+ self.absolute += amount;
+ self.relative_offset += amount;
+ if let Some(mut current_col) = column_styles.get(self.relative as usize) {
+ while self.relative_offset >= current_col.span {
+ // move to the next column
+ self.relative += 1;
+ self.relative_offset -= current_col.span;
+ if let Some(column_style) = column_styles.get(self.relative as usize) {
+ current_col = column_style;
+ } else {
+ // we ran out of column_styles,
+ // so we don't need to update the indices
+ break;
+ }
+ }
+ }
+ }
+}
+
+impl<'table> Iterator for TableCellStyleIterator<'table> {
+ type Item = TableCellStyleInfo<'table>;
+ #[inline]
+ fn next(&mut self) -> Option<Self::Item> {
+ // FIXME We do this awkward .take() followed by shoving it back in
+ // because without NLL the row_info borrow lasts too long
+ if let Some(mut row_info) = self.row_info.take() {
+ if let Some(rowspan) = row_info
+ .row
+ .incoming_rowspan
+ .get(self.column_index.absolute as usize)
+ {
+ // we are not allowed to use this column as a starting point. Try the next one.
+ if *rowspan > 1 {
+ self.column_index.advance(1, &self.column_styles);
+ // put row_info back in
+ self.row_info = Some(row_info);
+ // try again
+ return self.next();
+ }
+ }
+ if let Some(cell) = row_info.cell_iterator.next() {
+ let rowgroup_style = row_info.rowgroup.map(|r| r.style());
+ let row_style = row_info.row.block_flow.fragment.style();
+ let cell = cell.as_table_cell();
+ let (col_style, colgroup_style) = if let Some(column_style) =
+ self.column_styles.get(self.column_index.relative as usize)
+ {
+ let styles = (
+ column_style.col_style.clone(),
+ column_style.colgroup_style.clone(),
+ );
+ self.column_index
+ .advance(cell.column_span, &self.column_styles);
+
+ styles
+ } else {
+ (None, None)
+ };
+ // put row_info back in
+ self.row_info = Some(row_info);
+ return Some(TableCellStyleInfo {
+ cell,
+ colgroup_style,
+ col_style,
+ rowgroup_style,
+ row_style,
+ });
+ } else {
+ // next row
+ if let Some((group, row)) = self.row_iterator.next() {
+ self.row_info = Some(TableCellStyleIteratorRowInfo {
+ row: &row,
+ rowgroup: group,
+ cell_iterator: row.block_flow.base.child_iter(),
+ });
+ self.column_index = Default::default();
+ self.next()
+ } else {
+ // out of rows
+ // row_info stays None
+ None
+ }
+ }
+ } else {
+ // empty table
+ None
+ }
+ }
+}
+
+impl<'table> TableCellStyleInfo<'table> {
+ fn build_display_list(&self, mut state: &mut DisplayListBuildState) {
+ use style::computed_values::visibility::T as Visibility;
+
+ if !self.cell.visible ||
+ self.cell
+ .block_flow
+ .fragment
+ .style()
+ .get_inherited_box()
+ .visibility !=
+ Visibility::Visible
+ {
+ return;
+ }
+ let border_painting_mode = match self
+ .cell
+ .block_flow
+ .fragment
+ .style
+ .get_inherited_table()
+ .border_collapse
+ {
+ border_collapse::T::Separate => BorderPaintingMode::Separate,
+ border_collapse::T::Collapse => {
+ BorderPaintingMode::Collapse(&self.cell.collapsed_borders)
+ },
+ };
+ {
+ let cell_flow = &self.cell.block_flow;
+ let initial = ComputedValues::initial_values();
+
+ let build_dl = |sty: &ComputedValues, state: &mut &mut DisplayListBuildState| {
+ let background = sty.get_background();
+ // Don't redraw backgrounds that we've already drawn
+ if background as *const Background == initial.get_background() as *const _ {
+ return;
+ }
+ let background_color = sty.resolve_color(background.background_color);
+ cell_flow.build_display_list_for_background_if_applicable_with_background(
+ state,
+ background,
+ background_color,
+ );
+ };
+
+ if let Some(ref sty) = self.colgroup_style {
+ build_dl(&sty, &mut state);
+ }
+ if let Some(ref sty) = self.col_style {
+ build_dl(&sty, &mut state);
+ }
+ if let Some(ref sty) = self.rowgroup_style {
+ build_dl(sty, &mut state);
+ }
+ build_dl(self.row_style, &mut state);
+ }
+ // the restyle damage will be set in TableCellFlow::build_display_list()
+ self.cell
+ .block_flow
+ .build_display_list_for_block_no_damage(state, border_painting_mode)
+ }
+}
diff --git a/components/layout_2020/table_caption.rs b/components/layout_2020/table_caption.rs
new file mode 100644
index 00000000000..3d44190f986
--- /dev/null
+++ b/components/layout_2020/table_caption.rs
@@ -0,0 +1,139 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+//! CSS table formatting contexts.
+
+use crate::block::BlockFlow;
+use crate::context::LayoutContext;
+use crate::display_list::{
+ DisplayListBuildState, StackingContextCollectionFlags, StackingContextCollectionState,
+};
+use crate::flow::{Flow, FlowClass, OpaqueFlow};
+use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
+use app_units::Au;
+use euclid::Point2D;
+use gfx_traits::print_tree::PrintTree;
+use std::fmt;
+use style::logical_geometry::LogicalSize;
+use style::properties::ComputedValues;
+
+#[allow(unsafe_code)]
+unsafe impl crate::flow::HasBaseFlow for TableCaptionFlow {}
+
+/// A table formatting context.
+#[repr(C)]
+pub struct TableCaptionFlow {
+ pub block_flow: BlockFlow,
+}
+
+impl TableCaptionFlow {
+ pub fn from_fragment(fragment: Fragment) -> TableCaptionFlow {
+ TableCaptionFlow {
+ block_flow: BlockFlow::from_fragment(fragment),
+ }
+ }
+}
+
+impl Flow for TableCaptionFlow {
+ fn class(&self) -> FlowClass {
+ FlowClass::TableCaption
+ }
+
+ fn as_mut_block(&mut self) -> &mut BlockFlow {
+ &mut self.block_flow
+ }
+
+ fn as_block(&self) -> &BlockFlow {
+ &self.block_flow
+ }
+
+ fn bubble_inline_sizes(&mut self) {
+ self.block_flow.bubble_inline_sizes();
+ }
+
+ fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
+ debug!(
+ "assign_inline_sizes({}): assigning inline_size for flow",
+ "table_caption"
+ );
+ self.block_flow.assign_inline_sizes(layout_context);
+ }
+
+ fn assign_block_size(&mut self, layout_context: &LayoutContext) {
+ debug!("assign_block_size: assigning block_size for table_caption");
+ self.block_flow.assign_block_size(layout_context);
+ }
+
+ fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
+ self.block_flow
+ .compute_stacking_relative_position(layout_context)
+ }
+
+ fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
+ self.block_flow
+ .update_late_computed_inline_position_if_necessary(inline_position)
+ }
+
+ fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
+ self.block_flow
+ .update_late_computed_block_position_if_necessary(block_position)
+ }
+
+ fn build_display_list(&mut self, state: &mut DisplayListBuildState) {
+ debug!("build_display_list_table_caption: same process as block flow");
+ self.block_flow.build_display_list(state);
+ }
+
+ fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
+ self.block_flow
+ .collect_stacking_contexts_for_block(state, StackingContextCollectionFlags::empty());
+ }
+
+ fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>) {
+ self.block_flow.repair_style(new_style)
+ }
+
+ fn compute_overflow(&self) -> Overflow {
+ self.block_flow.compute_overflow()
+ }
+
+ fn contains_roots_of_absolute_flow_tree(&self) -> bool {
+ self.block_flow.contains_roots_of_absolute_flow_tree()
+ }
+
+ fn is_absolute_containing_block(&self) -> bool {
+ self.block_flow.is_absolute_containing_block()
+ }
+
+ fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
+ self.block_flow.generated_containing_block_size(flow)
+ }
+
+ fn iterate_through_fragment_border_boxes(
+ &self,
+ iterator: &mut dyn FragmentBorderBoxIterator,
+ level: i32,
+ stacking_context_position: &Point2D<Au>,
+ ) {
+ self.block_flow.iterate_through_fragment_border_boxes(
+ iterator,
+ level,
+ stacking_context_position,
+ )
+ }
+
+ fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) {
+ self.block_flow.mutate_fragments(mutator)
+ }
+
+ fn print_extra_flow_children(&self, print_tree: &mut PrintTree) {
+ self.block_flow.print_extra_flow_children(print_tree);
+ }
+}
+
+impl fmt::Debug for TableCaptionFlow {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "TableCaptionFlow: {:?}", self.block_flow)
+ }
+}
diff --git a/components/layout_2020/table_cell.rs b/components/layout_2020/table_cell.rs
new file mode 100644
index 00000000000..ec79f9f4540
--- /dev/null
+++ b/components/layout_2020/table_cell.rs
@@ -0,0 +1,506 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+//! CSS table formatting contexts.
+
+use crate::block::{BlockFlow, ISizeAndMarginsComputer, MarginsMayCollapseFlag};
+use crate::context::LayoutContext;
+use crate::display_list::{
+ DisplayListBuildState, StackingContextCollectionFlags, StackingContextCollectionState,
+};
+use crate::flow::{Flow, FlowClass, FlowFlags, GetBaseFlow, OpaqueFlow};
+use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
+use crate::layout_debug;
+use crate::table::InternalTable;
+use crate::table_row::{CollapsedBorder, CollapsedBorderProvenance};
+use app_units::Au;
+use euclid::{Point2D, Rect, SideOffsets2D, Size2D};
+use gfx_traits::print_tree::PrintTree;
+use script_layout_interface::wrapper_traits::ThreadSafeLayoutNode;
+use std::fmt;
+use style::logical_geometry::{LogicalMargin, LogicalRect, LogicalSize, WritingMode};
+use style::properties::ComputedValues;
+use style::values::computed::length::Size;
+use style::values::computed::Color;
+use style::values::generics::box_::{VerticalAlign, VerticalAlignKeyword};
+use style::values::specified::BorderStyle;
+
+#[allow(unsafe_code)]
+unsafe impl crate::flow::HasBaseFlow for TableCellFlow {}
+
+/// A table formatting context.
+#[derive(Serialize)]
+#[repr(C)]
+pub struct TableCellFlow {
+ /// Data common to all block flows.
+ pub block_flow: BlockFlow,
+
+ /// Border collapse information for the cell.
+ pub collapsed_borders: CollapsedBordersForCell,
+
+ /// The column span of this cell.
+ pub column_span: u32,
+
+ /// The rows spanned by this cell.
+ pub row_span: u32,
+
+ /// Whether this cell is visible. If false, the value of `empty-cells` means that we must not
+ /// display this cell.
+ pub visible: bool,
+}
+
+impl TableCellFlow {
+ pub fn from_fragment(fragment: Fragment) -> TableCellFlow {
+ TableCellFlow {
+ block_flow: BlockFlow::from_fragment(fragment),
+ collapsed_borders: CollapsedBordersForCell::new(),
+ column_span: 1,
+ row_span: 1,
+ visible: true,
+ }
+ }
+
+ pub fn from_node_fragment_and_visibility_flag<N: ThreadSafeLayoutNode>(
+ node: &N,
+ fragment: Fragment,
+ visible: bool,
+ ) -> TableCellFlow {
+ TableCellFlow {
+ block_flow: BlockFlow::from_fragment(fragment),
+ collapsed_borders: CollapsedBordersForCell::new(),
+ column_span: node.get_colspan(),
+ row_span: node.get_rowspan(),
+ visible: visible,
+ }
+ }
+
+ pub fn fragment(&mut self) -> &Fragment {
+ &self.block_flow.fragment
+ }
+
+ pub fn mut_fragment(&mut self) -> &mut Fragment {
+ &mut self.block_flow.fragment
+ }
+
+ /// Assign block-size for table-cell 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_cell_base(&mut self, layout_context: &LayoutContext) {
+ let remaining = self.block_flow.assign_block_size_block_base(
+ layout_context,
+ None,
+ MarginsMayCollapseFlag::MarginsMayNotCollapse,
+ );
+ debug_assert!(remaining.is_none());
+ }
+
+ /// Position this cell's children according to vertical-align.
+ pub fn valign_children(&mut self) {
+ // Note to the reader: this code has been tested with negative margins.
+ // We end up with a "end" that's before the "start," but the math still works out.
+ let mut extents = None;
+ for kid in self.base().children.iter() {
+ let kid_base = kid.base();
+ if kid_base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) {
+ continue;
+ }
+ let start = kid_base.position.start.b -
+ kid_base
+ .collapsible_margins
+ .block_start_margin_for_noncollapsible_context();
+ let end = kid_base.position.start.b +
+ kid_base.position.size.block +
+ kid_base
+ .collapsible_margins
+ .block_end_margin_for_noncollapsible_context();
+ match extents {
+ Some((ref mut first_start, ref mut last_end)) => {
+ if start < *first_start {
+ *first_start = start
+ }
+ if end > *last_end {
+ *last_end = end
+ }
+ },
+ None => extents = Some((start, end)),
+ }
+ }
+ let (first_start, last_end) = match extents {
+ Some(extents) => extents,
+ None => return,
+ };
+
+ let kids_size = last_end - first_start;
+ let self_size = self.base().position.size.block -
+ self.block_flow.fragment.border_padding.block_start_end();
+ let kids_self_gap = self_size - kids_size;
+
+ // This offset should also account for VerticalAlign::baseline.
+ // Need max cell ascent from the first row of this cell.
+ let offset = match self.block_flow.fragment.style().get_box().vertical_align {
+ VerticalAlign::Keyword(VerticalAlignKeyword::Middle) => kids_self_gap / 2,
+ VerticalAlign::Keyword(VerticalAlignKeyword::Bottom) => kids_self_gap,
+ _ => Au(0),
+ };
+ if offset == Au(0) {
+ return;
+ }
+
+ for kid in self.mut_base().children.iter_mut() {
+ let kid_base = kid.mut_base();
+ if !kid_base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) {
+ kid_base.position.start.b += offset
+ }
+ }
+ }
+
+ // Total block size of child
+ //
+ // Call after block size calculation
+ pub fn total_block_size(&mut self) -> Au {
+ // TODO: Percentage block-size
+ let specified = self
+ .fragment()
+ .style()
+ .content_block_size()
+ .to_used_value(Au(0))
+ .unwrap_or(Au(0));
+ specified + self.fragment().border_padding.block_start_end()
+ }
+}
+
+impl Flow for TableCellFlow {
+ fn class(&self) -> FlowClass {
+ FlowClass::TableCell
+ }
+
+ fn as_mut_table_cell(&mut self) -> &mut TableCellFlow {
+ self
+ }
+
+ fn as_table_cell(&self) -> &TableCellFlow {
+ self
+ }
+
+ fn as_mut_block(&mut self) -> &mut BlockFlow {
+ &mut self.block_flow
+ }
+
+ fn as_block(&self) -> &BlockFlow {
+ &self.block_flow
+ }
+
+ /// Minimum/preferred inline-sizes set by this function are used in automatic table layout
+ /// calculation.
+ fn bubble_inline_sizes(&mut self) {
+ let _scope = layout_debug_scope!(
+ "table_cell::bubble_inline_sizes {:x}",
+ self.block_flow.base.debug_id()
+ );
+
+ self.block_flow.bubble_inline_sizes_for_block(true);
+ let specified_inline_size = match self.block_flow.fragment.style().content_inline_size() {
+ Size::Auto => Au(0),
+ Size::LengthPercentage(ref lp) => lp.to_used_value(Au(0)),
+ };
+
+ 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, layout_context: &LayoutContext) {
+ let _scope = layout_debug_scope!(
+ "table_cell::assign_inline_sizes {:x}",
+ self.block_flow.base.debug_id()
+ );
+ debug!(
+ "assign_inline_sizes({}): assigning inline_size for flow",
+ "table_cell"
+ );
+
+ let shared_context = layout_context.shared_context();
+ // 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.block_container_inline_size;
+
+ let inline_size_computer = InternalTable;
+ inline_size_computer.compute_used_inline_size(
+ &mut self.block_flow,
+ shared_context,
+ 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 inline_end_content_edge = self.block_flow.base.block_container_inline_size -
+ self.block_flow.fragment.border_padding.inline_start_end() -
+ self.block_flow.fragment.border_box.size.inline;
+ 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(
+ shared_context,
+ inline_start_content_edge,
+ inline_end_content_edge,
+ content_inline_size,
+ |_, _, _, _, _, _| {},
+ );
+ }
+
+ fn assign_block_size(&mut self, layout_context: &LayoutContext) {
+ debug!("assign_block_size: assigning block_size for table_cell");
+ self.assign_block_size_table_cell_base(layout_context);
+ }
+
+ fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
+ self.block_flow
+ .compute_stacking_relative_position(layout_context)
+ }
+
+ fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
+ self.block_flow
+ .update_late_computed_inline_position_if_necessary(inline_position)
+ }
+
+ fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
+ self.block_flow
+ .update_late_computed_block_position_if_necessary(block_position)
+ }
+
+ fn build_display_list(&mut self, _: &mut DisplayListBuildState) {
+ use style::servo::restyle_damage::ServoRestyleDamage;
+ // This is handled by TableCellStyleInfo::build_display_list()
+ // when the containing table builds its display list
+
+ // we skip setting the damage in TableCellStyleInfo::build_display_list()
+ // because we only have immutable access
+ self.block_flow
+ .fragment
+ .restyle_damage
+ .remove(ServoRestyleDamage::REPAINT);
+ }
+
+ fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
+ self.block_flow
+ .collect_stacking_contexts_for_block(state, StackingContextCollectionFlags::empty());
+ }
+
+ fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>) {
+ self.block_flow.repair_style(new_style)
+ }
+
+ fn compute_overflow(&self) -> Overflow {
+ self.block_flow.compute_overflow()
+ }
+
+ fn contains_roots_of_absolute_flow_tree(&self) -> bool {
+ self.block_flow.contains_roots_of_absolute_flow_tree()
+ }
+
+ fn is_absolute_containing_block(&self) -> bool {
+ self.block_flow.is_absolute_containing_block()
+ }
+
+ fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
+ self.block_flow.generated_containing_block_size(flow)
+ }
+
+ fn iterate_through_fragment_border_boxes(
+ &self,
+ iterator: &mut dyn FragmentBorderBoxIterator,
+ level: i32,
+ stacking_context_position: &Point2D<Au>,
+ ) {
+ self.block_flow.iterate_through_fragment_border_boxes(
+ iterator,
+ level,
+ stacking_context_position,
+ )
+ }
+
+ fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) {
+ self.block_flow.mutate_fragments(mutator)
+ }
+
+ fn print_extra_flow_children(&self, print_tree: &mut PrintTree) {
+ self.block_flow.print_extra_flow_children(print_tree);
+ }
+}
+
+impl fmt::Debug for TableCellFlow {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "TableCellFlow: {:?}", self.block_flow)
+ }
+}
+
+#[derive(Clone, Copy, Debug, Serialize)]
+pub struct CollapsedBordersForCell {
+ pub inline_start_border: CollapsedBorder,
+ pub inline_end_border: CollapsedBorder,
+ pub block_start_border: CollapsedBorder,
+ pub block_end_border: CollapsedBorder,
+ pub inline_start_width: Au,
+ pub inline_end_width: Au,
+ pub block_start_width: Au,
+ pub block_end_width: Au,
+}
+
+impl CollapsedBordersForCell {
+ fn new() -> CollapsedBordersForCell {
+ CollapsedBordersForCell {
+ inline_start_border: CollapsedBorder::new(),
+ inline_end_border: CollapsedBorder::new(),
+ block_start_border: CollapsedBorder::new(),
+ block_end_border: CollapsedBorder::new(),
+ inline_start_width: Au(0),
+ inline_end_width: Au(0),
+ block_start_width: Au(0),
+ block_end_width: Au(0),
+ }
+ }
+
+ fn should_paint_inline_start_border(&self) -> bool {
+ self.inline_start_border.provenance != CollapsedBorderProvenance::FromPreviousTableCell
+ }
+
+ fn should_paint_inline_end_border(&self) -> bool {
+ self.inline_end_border.provenance != CollapsedBorderProvenance::FromNextTableCell
+ }
+
+ fn should_paint_block_start_border(&self) -> bool {
+ self.block_start_border.provenance != CollapsedBorderProvenance::FromPreviousTableCell
+ }
+
+ fn should_paint_block_end_border(&self) -> bool {
+ self.block_end_border.provenance != CollapsedBorderProvenance::FromNextTableCell
+ }
+
+ pub fn adjust_border_widths_for_painting(&self, border_widths: &mut LogicalMargin<Au>) {
+ border_widths.inline_start = if !self.should_paint_inline_start_border() {
+ Au(0)
+ } else {
+ self.inline_start_border.width
+ };
+ border_widths.inline_end = if !self.should_paint_inline_end_border() {
+ Au(0)
+ } else {
+ self.inline_end_border.width
+ };
+ border_widths.block_start = if !self.should_paint_block_start_border() {
+ Au(0)
+ } else {
+ self.block_start_border.width
+ };
+ border_widths.block_end = if !self.should_paint_block_end_border() {
+ Au(0)
+ } else {
+ self.block_end_border.width
+ }
+ }
+
+ pub fn adjust_border_bounds_for_painting(
+ &self,
+ border_bounds: &mut Rect<Au>,
+ writing_mode: WritingMode,
+ ) {
+ let inline_start_divisor = if self.should_paint_inline_start_border() {
+ 2
+ } else {
+ -2
+ };
+ let inline_start_offset =
+ self.inline_start_width / 2 + self.inline_start_border.width / inline_start_divisor;
+ let inline_end_divisor = if self.should_paint_inline_end_border() {
+ 2
+ } else {
+ -2
+ };
+ let inline_end_offset =
+ self.inline_end_width / 2 + self.inline_end_border.width / inline_end_divisor;
+ let block_start_divisor = if self.should_paint_block_start_border() {
+ 2
+ } else {
+ -2
+ };
+ let block_start_offset =
+ self.block_start_width / 2 + self.block_start_border.width / block_start_divisor;
+ let block_end_divisor = if self.should_paint_block_end_border() {
+ 2
+ } else {
+ -2
+ };
+ let block_end_offset =
+ self.block_end_width / 2 + self.block_end_border.width / block_end_divisor;
+
+ // FIXME(pcwalton): Get the real container size.
+ let mut logical_bounds =
+ LogicalRect::from_physical(writing_mode, *border_bounds, Size2D::new(Au(0), Au(0)));
+ logical_bounds.start.i = logical_bounds.start.i - inline_start_offset;
+ logical_bounds.start.b = logical_bounds.start.b - block_start_offset;
+ logical_bounds.size.inline =
+ logical_bounds.size.inline + inline_start_offset + inline_end_offset;
+ logical_bounds.size.block =
+ logical_bounds.size.block + block_start_offset + block_end_offset;
+ *border_bounds = logical_bounds.to_physical(writing_mode, Size2D::new(Au(0), Au(0)))
+ }
+
+ pub fn adjust_border_colors_and_styles_for_painting(
+ &self,
+ border_colors: &mut SideOffsets2D<Color>,
+ border_styles: &mut SideOffsets2D<BorderStyle>,
+ writing_mode: WritingMode,
+ ) {
+ let logical_border_colors = LogicalMargin::new(
+ writing_mode,
+ self.block_start_border.color,
+ self.inline_end_border.color,
+ self.block_end_border.color,
+ self.inline_start_border.color,
+ );
+ *border_colors = logical_border_colors.to_physical(writing_mode);
+
+ let logical_border_styles = LogicalMargin::new(
+ writing_mode,
+ self.block_start_border.style,
+ self.inline_end_border.style,
+ self.block_end_border.style,
+ self.inline_start_border.style,
+ );
+ *border_styles = logical_border_styles.to_physical(writing_mode);
+ }
+}
diff --git a/components/layout_2020/table_colgroup.rs b/components/layout_2020/table_colgroup.rs
new file mode 100644
index 00000000000..c07ad56f95a
--- /dev/null
+++ b/components/layout_2020/table_colgroup.rs
@@ -0,0 +1,131 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+//! CSS table formatting contexts.
+
+use crate::context::LayoutContext;
+use crate::display_list::{DisplayListBuildState, StackingContextCollectionState};
+use crate::flow::{BaseFlow, Flow, FlowClass, ForceNonfloatedFlag, OpaqueFlow};
+use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
+use crate::layout_debug;
+use app_units::Au;
+use euclid::Point2D;
+use std::fmt;
+use style::logical_geometry::LogicalSize;
+use style::properties::ComputedValues;
+use style::values::computed::Size;
+
+#[allow(unsafe_code)]
+unsafe impl crate::flow::HasBaseFlow for TableColGroupFlow {}
+
+/// A table formatting context.
+#[repr(C)]
+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. (We use `LengthPercentageOrAuto` here in
+ /// lieu of `ColumnInlineSize` because column groups do not establish minimum or preferred
+ /// inline sizes.)
+ pub inline_sizes: Vec<Size>,
+}
+
+impl TableColGroupFlow {
+ pub fn from_fragments(fragment: Fragment, fragments: Vec<Fragment>) -> TableColGroupFlow {
+ let writing_mode = fragment.style().writing_mode;
+ TableColGroupFlow {
+ base: BaseFlow::new(
+ Some(fragment.style()),
+ writing_mode,
+ ForceNonfloatedFlag::ForceNonfloated,
+ ),
+ fragment: Some(fragment),
+ cols: fragments,
+ inline_sizes: vec![],
+ }
+ }
+}
+
+impl Flow for TableColGroupFlow {
+ fn class(&self) -> FlowClass {
+ FlowClass::TableColGroup
+ }
+
+ fn as_mut_table_colgroup(&mut self) -> &mut TableColGroupFlow {
+ self
+ }
+
+ fn as_table_colgroup(&self) -> &TableColGroupFlow {
+ self
+ }
+
+ fn bubble_inline_sizes(&mut self) {
+ let _scope = layout_debug_scope!(
+ "table_colgroup::bubble_inline_sizes {:x}",
+ self.base.debug_id()
+ );
+
+ for fragment in &self.cols {
+ // Retrieve the specified value from the appropriate CSS property.
+ let inline_size = fragment.style().content_inline_size();
+ for _ in 0..fragment.column_span() {
+ self.inline_sizes.push(inline_size)
+ }
+ }
+ }
+
+ /// Table column inline-sizes are assigned in the table flow and propagated to table row flows
+ /// and/or rowgroup flows. Therefore, table colgroup flows do not need to assign inline-sizes.
+ fn assign_inline_sizes(&mut self, _: &LayoutContext) {}
+
+ /// Table columns do not have block-size.
+ fn assign_block_size(&mut self, _: &LayoutContext) {}
+
+ fn update_late_computed_inline_position_if_necessary(&mut self, _: Au) {}
+
+ fn update_late_computed_block_position_if_necessary(&mut self, _: Au) {}
+
+ // Table columns are invisible.
+ fn build_display_list(&mut self, _: &mut DisplayListBuildState) {}
+
+ fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
+ self.base.stacking_context_id = state.current_stacking_context_id;
+ self.base.clipping_and_scrolling = Some(state.current_clipping_and_scrolling);
+ }
+
+ fn repair_style(&mut self, _: &crate::ServoArc<ComputedValues>) {}
+
+ fn compute_overflow(&self) -> Overflow {
+ Overflow::new()
+ }
+
+ fn generated_containing_block_size(&self, _: OpaqueFlow) -> LogicalSize<Au> {
+ panic!("Table column groups can't be containing blocks!")
+ }
+
+ fn iterate_through_fragment_border_boxes(
+ &self,
+ _: &mut dyn FragmentBorderBoxIterator,
+ _: i32,
+ _: &Point2D<Au>,
+ ) {
+ }
+
+ fn mutate_fragments(&mut self, _: &mut dyn FnMut(&mut Fragment)) {}
+}
+
+impl fmt::Debug 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_2020/table_row.rs b/components/layout_2020/table_row.rs
new file mode 100644
index 00000000000..824da31edad
--- /dev/null
+++ b/components/layout_2020/table_row.rs
@@ -0,0 +1,1158 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+//! CSS table formatting contexts.
+
+use crate::block::{BlockFlow, ISizeAndMarginsComputer};
+use crate::context::LayoutContext;
+use crate::display_list::{
+ DisplayListBuildState, StackingContextCollectionFlags, StackingContextCollectionState,
+};
+use crate::flow::{
+ EarlyAbsolutePositionInfo, Flow, FlowClass, GetBaseFlow, ImmutableFlowUtils, OpaqueFlow,
+};
+use crate::flow_list::MutFlowListIterator;
+use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
+use crate::layout_debug;
+use crate::table::{ColumnComputedInlineSize, ColumnIntrinsicInlineSize, InternalTable, VecExt};
+use crate::table_cell::{CollapsedBordersForCell, TableCellFlow};
+use app_units::Au;
+use euclid::Point2D;
+use gfx_traits::print_tree::PrintTree;
+use serde::{Serialize, Serializer};
+use std::cmp::max;
+use std::fmt;
+use std::iter::{Enumerate, IntoIterator, Peekable};
+use style::computed_values::border_collapse::T as BorderCollapse;
+use style::computed_values::border_spacing::T as BorderSpacing;
+use style::computed_values::border_top_style::T as BorderStyle;
+use style::logical_geometry::{LogicalSize, PhysicalSide, WritingMode};
+use style::properties::ComputedValues;
+use style::values::computed::{Color, Size};
+
+#[allow(unsafe_code)]
+unsafe impl crate::flow::HasBaseFlow for TableRowFlow {}
+
+/// A single row of a table.
+#[repr(C)]
+pub struct TableRowFlow {
+ /// Fields common to all block flows.
+ pub block_flow: BlockFlow,
+
+ /// Information about the intrinsic inline-sizes of each cell.
+ pub cell_intrinsic_inline_sizes: Vec<CellIntrinsicInlineSize>,
+
+ /// Information about the computed inline-sizes of each column.
+ pub column_computed_inline_sizes: Vec<ColumnComputedInlineSize>,
+
+ /// The number of remaining rows spanned by cells in previous rows, indexed by column.
+ ///
+ /// Columns that are not included in this vector have the default rowspan of "1". If there are
+ /// no cells with rowspan != 1 in previous rows, this vector may be empty.
+ pub incoming_rowspan: Vec<u32>,
+
+ /// The spacing for this row, propagated down from the table during the inline-size assignment
+ /// phase.
+ pub spacing: BorderSpacing,
+
+ /// The direction of the columns, propagated down from the table during the inline-size
+ /// assignment phase.
+ pub table_writing_mode: WritingMode,
+
+ /// Information about the borders for each cell that we bubble up to our parent. This is only
+ /// computed if `border-collapse` is `collapse`.
+ pub preliminary_collapsed_borders: CollapsedBordersForRow,
+
+ /// Information about the borders for each cell, post-collapse. This is only computed if
+ /// `border-collapse` is `collapse`.
+ pub final_collapsed_borders: CollapsedBordersForRow,
+
+ /// The computed cell spacing widths post-collapse.
+ pub collapsed_border_spacing: CollapsedBorderSpacingForRow,
+}
+
+impl Serialize for TableRowFlow {
+ fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+ self.block_flow.serialize(serializer)
+ }
+}
+
+/// Information about the column inline size and span for each cell.
+#[derive(Clone, Copy, Serialize)]
+pub struct CellIntrinsicInlineSize {
+ /// Inline sizes that this cell contributes to the column.
+ pub column_size: ColumnIntrinsicInlineSize,
+ /// The column span of this cell.
+ pub column_span: u32,
+ /// The row span of this cell.
+ pub row_span: u32,
+}
+
+impl TableRowFlow {
+ pub fn from_fragment(fragment: Fragment) -> TableRowFlow {
+ let writing_mode = fragment.style().writing_mode;
+ TableRowFlow {
+ block_flow: BlockFlow::from_fragment(fragment),
+ cell_intrinsic_inline_sizes: Vec::new(),
+ column_computed_inline_sizes: Vec::new(),
+ incoming_rowspan: Vec::new(),
+ spacing: BorderSpacing::zero(),
+ table_writing_mode: writing_mode,
+ preliminary_collapsed_borders: CollapsedBordersForRow::new(),
+ final_collapsed_borders: CollapsedBordersForRow::new(),
+ collapsed_border_spacing: CollapsedBorderSpacingForRow::new(),
+ }
+ }
+
+ /// Compute block-size for table-row flow.
+ ///
+ /// TODO(pcwalton): This doesn't handle floats and positioned elements right.
+ ///
+ /// Returns the block size
+ pub fn compute_block_size_table_row_base<'a>(
+ &'a mut self,
+ layout_context: &LayoutContext,
+ incoming_rowspan_data: &mut Vec<Au>,
+ border_info: &[TableRowSizeData],
+ row_index: usize,
+ ) -> Au {
+ fn include_sizes_from_previous_rows(
+ col: &mut usize,
+ incoming_rowspan: &[u32],
+ incoming_rowspan_data: &mut Vec<Au>,
+ max_block_size: &mut Au,
+ ) {
+ while let Some(span) = incoming_rowspan.get(*col) {
+ if *span == 1 {
+ break;
+ }
+ let incoming = if let Some(incoming) = incoming_rowspan_data.get(*col) {
+ *incoming
+ } else {
+ // This happens when we have a cell with both rowspan and colspan
+ // incoming_rowspan_data only records the data for the first column,
+ // but that's ok because we only need to account for each spanning cell
+ // once. So we skip ahead.
+ *col += 1;
+ continue;
+ };
+ *max_block_size = max(*max_block_size, incoming);
+ *col += 1;
+ }
+ }
+ // Per CSS 2.1 § 17.5.3, find max_y = max(computed `block-size`, minimum block-size of
+ // all cells).
+ let mut max_block_size = Au(0);
+ let thread_id = self.block_flow.base.thread_id;
+ let content_box = self.block_flow.base.position -
+ self.block_flow.fragment.border_padding -
+ self.block_flow.fragment.margin;
+
+ let mut col = 0;
+ for kid in self.block_flow.base.child_iter_mut() {
+ include_sizes_from_previous_rows(
+ &mut col,
+ &self.incoming_rowspan,
+ incoming_rowspan_data,
+ &mut max_block_size,
+ );
+ kid.place_float_if_applicable();
+ debug_assert!(
+ !kid.base().flags.is_float(),
+ "table cells should never float"
+ );
+ kid.assign_block_size_for_inorder_child_if_necessary(
+ layout_context,
+ thread_id,
+ content_box,
+ );
+
+ let mut row_span;
+ let column_span;
+ let cell_total;
+ {
+ let cell = kid.as_mut_table_cell();
+ row_span = cell.row_span;
+ column_span = cell.column_span as usize;
+ cell_total = cell.total_block_size();
+ }
+ let child_node = kid.mut_base();
+ child_node.position.start.b = Au(0);
+ let mut cell_block_size_pressure = max(cell_total, child_node.position.size.block);
+
+ if row_span != 1 {
+ if incoming_rowspan_data.len() <= col {
+ incoming_rowspan_data.resize(col + 1, Au(0));
+ }
+ let border_sizes_spanned =
+ get_spanned_border_size(border_info, row_index, &mut row_span);
+
+ cell_block_size_pressure -= border_sizes_spanned;
+
+ // XXXManishearth in case this row covers more than cell_block_size_pressure / row_span
+ // anyway, we should use that to reduce the pressure on future rows. This will
+ // require an extra slow-path loop, sadly.
+ cell_block_size_pressure /= row_span as i32;
+ incoming_rowspan_data[col] = cell_block_size_pressure;
+ }
+
+ max_block_size = max(max_block_size, cell_block_size_pressure);
+ col += column_span;
+ }
+ include_sizes_from_previous_rows(
+ &mut col,
+ &self.incoming_rowspan,
+ incoming_rowspan_data,
+ &mut max_block_size,
+ );
+
+ // TODO: Percentage block-size
+ let block_size = self
+ .block_flow
+ .fragment
+ .style()
+ .content_block_size()
+ .to_used_value(Au(0))
+ .unwrap_or(max_block_size);
+ max(block_size, max_block_size)
+ }
+
+ pub fn assign_block_size_to_self_and_children(
+ &mut self,
+ sizes: &[TableRowSizeData],
+ index: usize,
+ ) {
+ // Assign the block-size of kid fragments, which is the same value as own block-size.
+ let block_size = sizes[index].size;
+ for kid in self.block_flow.base.child_iter_mut() {
+ let child_table_cell = kid.as_mut_table_cell();
+ let block_size = if child_table_cell.row_span != 1 {
+ let mut row_span = child_table_cell.row_span;
+ let border_sizes_spanned = get_spanned_border_size(sizes, index, &mut row_span);
+ let row_sizes = sizes[index..]
+ .iter()
+ .take(row_span as usize)
+ .fold(Au(0), |accum, r| accum + r.size);
+ row_sizes + border_sizes_spanned
+ } else {
+ block_size
+ };
+ {
+ let kid_fragment = child_table_cell.mut_fragment();
+ let mut position = kid_fragment.border_box;
+ position.size.block = block_size;
+ kid_fragment.border_box = position;
+ }
+
+ // Assign the child's block size.
+ child_table_cell.block_flow.base.position.size.block = block_size;
+
+ // Now we know the cell height, vertical align the cell's children.
+ child_table_cell.valign_children();
+
+ // Write in the size of the relative containing block for children. (This
+ // information is also needed to handle RTL.)
+ child_table_cell
+ .block_flow
+ .base
+ .early_absolute_position_info = EarlyAbsolutePositionInfo {
+ relative_containing_block_size: self.block_flow.fragment.content_box().size,
+ relative_containing_block_mode: self.block_flow.fragment.style().writing_mode,
+ };
+ }
+
+ // Assign the block-size of own fragment
+ 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 populate_collapsed_border_spacing<'a, I>(
+ &mut self,
+ collapsed_inline_direction_border_widths_for_table: &[Au],
+ collapsed_block_direction_border_widths_for_table: &mut Peekable<I>,
+ ) where
+ I: Iterator<Item = &'a Au>,
+ {
+ self.collapsed_border_spacing.inline.clear();
+ self.collapsed_border_spacing.inline.extend(
+ collapsed_inline_direction_border_widths_for_table
+ .into_iter()
+ .map(|x| *x),
+ );
+
+ if let Some(collapsed_block_direction_border_width_for_table) =
+ collapsed_block_direction_border_widths_for_table.next()
+ {
+ self.collapsed_border_spacing.block_start =
+ *collapsed_block_direction_border_width_for_table
+ }
+ if let Some(collapsed_block_direction_border_width_for_table) =
+ collapsed_block_direction_border_widths_for_table.peek()
+ {
+ self.collapsed_border_spacing.block_end =
+ **collapsed_block_direction_border_width_for_table
+ }
+ }
+}
+
+#[derive(Debug, Default)]
+pub struct TableRowSizeData {
+ /// The block-size of the row.
+ pub size: Au,
+ /// Border spacing up to this row (not including spacing below the row)
+ pub cumulative_border_spacing: Au,
+ /// The "segment" of the table it is in. Tables containing
+ /// both row groups and rows have the bare rows grouped in
+ /// segments separated by row groups. It's helpful to look
+ /// at these as if they are rowgroups themselves.
+ ///
+ /// This is enough information for us to be able to check whether we
+ /// are in a case where we are overflowing a rowgroup with rowspan,
+ /// however calculating the amount of overflow requires lookahead.
+ pub rowgroup_id: u32,
+}
+
+/// Given an array of (_, cumulative_border_size), the index of the
+/// current row, and the >1 row_span of the cell, calculate the amount of
+/// border-spacing spanned by the row. In case the rowspan was larger
+/// than required, this will fix it up.
+fn get_spanned_border_size(sizes: &[TableRowSizeData], row_index: usize, row_span: &mut u32) -> Au {
+ // A zero rowspan is functionally equivalent to rowspan=infinity
+ if *row_span == 0 || row_index + *row_span as usize > sizes.len() {
+ *row_span = (sizes.len() - row_index) as u32;
+ }
+ let mut last_row_idx = row_index + *row_span as usize - 1;
+ // This is a slow path and should be rare -- this should only get triggered
+ // when you use `rowspan=0` or an overlarge rowspan in a table with
+ // mixed rows + rowgroups
+ if sizes[last_row_idx].rowgroup_id != sizes[row_index].rowgroup_id {
+ // XXXManishearth this loop can be avoided by also storing
+ // a "last_rowgroup_at" index so we can leapfrog back quickly
+ *row_span = sizes[row_index..last_row_idx + 1]
+ .iter()
+ .position(|s| s.rowgroup_id != sizes[row_index].rowgroup_id)
+ .unwrap() as u32;
+ last_row_idx = row_index + *row_span as usize - 1;
+ }
+ sizes[last_row_idx].cumulative_border_spacing - sizes[row_index].cumulative_border_spacing
+}
+
+impl Flow for TableRowFlow {
+ fn class(&self) -> FlowClass {
+ FlowClass::TableRow
+ }
+
+ fn as_mut_table_row(&mut self) -> &mut TableRowFlow {
+ self
+ }
+
+ fn as_table_row(&self) -> &TableRowFlow {
+ self
+ }
+
+ fn as_mut_block(&mut self) -> &mut BlockFlow {
+ &mut self.block_flow
+ }
+
+ fn as_block(&self) -> &BlockFlow {
+ &self.block_flow
+ }
+
+ /// 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) {
+ let _scope = layout_debug_scope!(
+ "table_row::bubble_inline_sizes {:x}",
+ self.block_flow.base.debug_id()
+ );
+
+ // Bubble up the specified inline-sizes from child table cells.
+ let (mut min_inline_size, mut pref_inline_size) = (Au(0), Au(0));
+ let collapsing_borders = self
+ .block_flow
+ .fragment
+ .style()
+ .get_inherited_table()
+ .border_collapse ==
+ BorderCollapse::Collapse;
+ let row_style = &*self.block_flow.fragment.style;
+ self.preliminary_collapsed_borders
+ .reset(CollapsedBorder::inline_start(
+ &row_style,
+ CollapsedBorderProvenance::FromTableRow,
+ ));
+
+ {
+ let children_count = self.block_flow.base.children.len();
+ let mut iterator = self.block_flow.base.child_iter_mut().enumerate().peekable();
+ while let Some((i, kid)) = iterator.next() {
+ assert!(kid.is_table_cell());
+
+ // Collect the specified column inline-size of the cell. This is used in both
+ // fixed and automatic table layout calculation.
+ let child_specified_inline_size;
+ let child_column_span;
+ let child_row_span;
+ {
+ let child_table_cell = kid.as_mut_table_cell();
+ child_specified_inline_size = child_table_cell
+ .block_flow
+ .fragment
+ .style
+ .content_inline_size();
+ child_column_span = child_table_cell.column_span;
+ child_row_span = child_table_cell.row_span;
+
+ // Perform border collapse if necessary.
+ if collapsing_borders {
+ perform_inline_direction_border_collapse_for_row(
+ row_style,
+ children_count,
+ i,
+ child_table_cell,
+ &mut iterator,
+ &mut self.preliminary_collapsed_borders,
+ )
+ }
+ }
+
+ // Collect minimum and preferred inline-sizes of the cell for automatic table layout
+ // calculation.
+ let child_base = kid.mut_base();
+ let child_column_inline_size = ColumnIntrinsicInlineSize {
+ minimum_length: match child_specified_inline_size {
+ Size::Auto => None,
+ Size::LengthPercentage(ref lp) => lp.0.maybe_to_used_value(None),
+ }
+ .unwrap_or(child_base.intrinsic_inline_sizes.minimum_inline_size),
+ percentage: match child_specified_inline_size {
+ Size::Auto => 0.0,
+ Size::LengthPercentage(ref lp) => lp.0.as_percentage().map_or(0.0, |p| p.0),
+ },
+ preferred: child_base.intrinsic_inline_sizes.preferred_inline_size,
+ constrained: match child_specified_inline_size {
+ Size::Auto => false,
+ Size::LengthPercentage(ref lp) => lp.0.maybe_to_used_value(None).is_some(),
+ },
+ };
+ min_inline_size = min_inline_size + child_column_inline_size.minimum_length;
+ pref_inline_size = pref_inline_size + child_column_inline_size.preferred;
+ self.cell_intrinsic_inline_sizes
+ .push(CellIntrinsicInlineSize {
+ column_size: child_column_inline_size,
+ column_span: child_column_span,
+ row_span: child_row_span,
+ });
+ }
+ }
+
+ self.block_flow
+ .base
+ .intrinsic_inline_sizes
+ .minimum_inline_size = min_inline_size;
+ self.block_flow
+ .base
+ .intrinsic_inline_sizes
+ .preferred_inline_size = max(min_inline_size, pref_inline_size);
+ }
+
+ fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
+ let _scope = layout_debug_scope!(
+ "table_row::assign_inline_sizes {:x}",
+ self.block_flow.base.debug_id()
+ );
+ debug!(
+ "assign_inline_sizes({}): assigning inline_size for flow",
+ "table_row"
+ );
+
+ let shared_context = layout_context.shared_context();
+ // The position was set to the containing block by the flow's parent.
+ let containing_block_inline_size = self.block_flow.base.block_container_inline_size;
+ // FIXME: In case of border-collapse: collapse, inline_start_content_edge should be
+ // border_inline_start.
+ let inline_start_content_edge = Au(0);
+ let inline_end_content_edge = Au(0);
+
+ let inline_size_computer = InternalTable;
+ inline_size_computer.compute_used_inline_size(
+ &mut self.block_flow,
+ shared_context,
+ containing_block_inline_size,
+ );
+
+ // Spread out the completed inline sizes among columns with spans > 1.
+ let num_columns = self.column_computed_inline_sizes.len();
+ let mut computed_inline_size_for_cells = Vec::with_capacity(num_columns);
+ let mut col = 0;
+
+ for cell_intrinsic_inline_size in &self.cell_intrinsic_inline_sizes {
+ // Skip any column occupied by a cell from a previous row.
+ while col < self.incoming_rowspan.len() && self.incoming_rowspan[col] != 1 {
+ let size = match self.column_computed_inline_sizes.get(col) {
+ Some(column_computed_inline_size) => *column_computed_inline_size,
+ None => ColumnComputedInlineSize { size: Au(0) }, // See FIXME below.
+ };
+ computed_inline_size_for_cells.push(size);
+ col += 1;
+ }
+ // Start with the computed inline size for the first column in the span.
+ let mut column_computed_inline_size = match self.column_computed_inline_sizes.get(col) {
+ Some(column_computed_inline_size) => *column_computed_inline_size,
+ None => {
+ // We're in fixed layout mode and there are more cells in this row than
+ // columns we know about. According to CSS 2.1 § 17.5.2.1, the behavior is
+ // now undefined. So just use zero.
+ //
+ // FIXME(pcwalton): $10 says this isn't Web compatible.
+ ColumnComputedInlineSize { size: Au(0) }
+ },
+ };
+ col += 1;
+
+ // Add in computed inline sizes for any extra columns in the span.
+ for _ in 1..cell_intrinsic_inline_size.column_span {
+ let extra_column_computed_inline_size =
+ match self.column_computed_inline_sizes.get(col) {
+ Some(column_computed_inline_size) => column_computed_inline_size,
+ None => break,
+ };
+ column_computed_inline_size.size = column_computed_inline_size.size +
+ extra_column_computed_inline_size.size +
+ self.spacing.horizontal();
+ col += 1;
+ }
+
+ computed_inline_size_for_cells.push(column_computed_inline_size)
+ }
+
+ // Set up border collapse info.
+ let border_collapse_info = match self
+ .block_flow
+ .fragment
+ .style()
+ .get_inherited_table()
+ .border_collapse
+ {
+ BorderCollapse::Collapse => Some(BorderCollapseInfoForChildTableCell {
+ collapsed_borders_for_row: &self.final_collapsed_borders,
+ collapsed_border_spacing_for_row: &self.collapsed_border_spacing,
+ }),
+ BorderCollapse::Separate => None,
+ };
+
+ // Push those inline sizes down to the cells.
+ let spacing = self.spacing;
+ let row_writing_mode = self.block_flow.base.writing_mode;
+ let table_writing_mode = self.table_writing_mode;
+ let incoming_rowspan = &self.incoming_rowspan;
+ let mut column_index = 0;
+
+ self.block_flow.propagate_assigned_inline_size_to_children(
+ shared_context,
+ inline_start_content_edge,
+ inline_end_content_edge,
+ containing_block_inline_size,
+ |child_flow,
+ child_index,
+ content_inline_size,
+ _writing_mode,
+ inline_start_margin_edge,
+ inline_end_margin_edge| {
+ set_inline_position_of_child_flow(
+ child_flow,
+ child_index,
+ &mut column_index,
+ incoming_rowspan,
+ row_writing_mode,
+ table_writing_mode,
+ &computed_inline_size_for_cells,
+ &spacing,
+ &border_collapse_info,
+ content_inline_size,
+ inline_start_margin_edge,
+ inline_end_margin_edge,
+ );
+ },
+ )
+ }
+
+ fn assign_block_size(&mut self, _: &LayoutContext) {
+ // the surrounding table or rowgroup does this
+ }
+
+ fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
+ self.block_flow
+ .compute_stacking_relative_position(layout_context)
+ }
+
+ fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
+ self.block_flow
+ .update_late_computed_inline_position_if_necessary(inline_position)
+ }
+
+ fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
+ self.block_flow
+ .update_late_computed_block_position_if_necessary(block_position)
+ }
+
+ fn build_display_list(&mut self, _: &mut DisplayListBuildState) {
+ use style::servo::restyle_damage::ServoRestyleDamage;
+ // handled in TableCellStyleInfo::build_display_list
+ // we skip setting the damage in TableCellStyleInfo::build_display_list()
+ // because we only have immutable access
+ self.block_flow
+ .fragment
+ .restyle_damage
+ .remove(ServoRestyleDamage::REPAINT);
+ }
+
+ fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
+ self.block_flow
+ .collect_stacking_contexts_for_block(state, StackingContextCollectionFlags::empty());
+ }
+
+ fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>) {
+ self.block_flow.repair_style(new_style)
+ }
+
+ fn compute_overflow(&self) -> Overflow {
+ self.block_flow.compute_overflow()
+ }
+
+ fn contains_roots_of_absolute_flow_tree(&self) -> bool {
+ self.block_flow.contains_roots_of_absolute_flow_tree()
+ }
+
+ fn is_absolute_containing_block(&self) -> bool {
+ self.block_flow.is_absolute_containing_block()
+ }
+
+ fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
+ self.block_flow.generated_containing_block_size(flow)
+ }
+
+ fn iterate_through_fragment_border_boxes(
+ &self,
+ iterator: &mut dyn FragmentBorderBoxIterator,
+ level: i32,
+ stacking_context_position: &Point2D<Au>,
+ ) {
+ self.block_flow.iterate_through_fragment_border_boxes(
+ iterator,
+ level,
+ stacking_context_position,
+ )
+ }
+
+ fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) {
+ self.block_flow.mutate_fragments(mutator)
+ }
+
+ fn print_extra_flow_children(&self, print_tree: &mut PrintTree) {
+ self.block_flow.print_extra_flow_children(print_tree);
+ }
+}
+
+impl fmt::Debug for TableRowFlow {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "TableRowFlow: {:?}", self.block_flow)
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct CollapsedBordersForRow {
+ /// The size of this vector should be equal to the number of cells plus one.
+ pub inline: Vec<CollapsedBorder>,
+ /// The size of this vector should be equal to the number of cells.
+ pub block_start: Vec<CollapsedBorder>,
+ /// The size of this vector should be equal to the number of cells.
+ pub block_end: Vec<CollapsedBorder>,
+}
+
+impl CollapsedBordersForRow {
+ pub fn new() -> CollapsedBordersForRow {
+ CollapsedBordersForRow {
+ inline: Vec::new(),
+ block_start: Vec::new(),
+ block_end: Vec::new(),
+ }
+ }
+
+ pub fn reset(&mut self, first_inline_border: CollapsedBorder) {
+ self.inline.clear();
+ self.inline.push(first_inline_border);
+ self.block_start.clear();
+ self.block_end.clear()
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct CollapsedBorderSpacingForRow {
+ /// The spacing in between each column.
+ inline: Vec<Au>,
+ /// The spacing above this row.
+ pub block_start: Au,
+ /// The spacing below this row.
+ block_end: Au,
+}
+
+impl CollapsedBorderSpacingForRow {
+ fn new() -> CollapsedBorderSpacingForRow {
+ CollapsedBorderSpacingForRow {
+ inline: Vec::new(),
+ block_start: Au(0),
+ block_end: Au(0),
+ }
+ }
+}
+
+/// All aspects of a border that can collapse with adjacent borders. See CSS 2.1 § 17.6.2.1.
+#[derive(Clone, Copy, Debug)]
+pub struct CollapsedBorder {
+ /// The style of the border.
+ pub style: BorderStyle,
+ /// The width of the border.
+ pub width: Au,
+ /// The color of the border.
+ pub color: Color,
+ /// The type of item that this border comes from.
+ pub provenance: CollapsedBorderProvenance,
+}
+
+impl Serialize for CollapsedBorder {
+ fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+ serializer.serialize_unit()
+ }
+}
+
+/// Where a border style comes from.
+///
+/// The integer values here correspond to the border conflict resolution rules in CSS 2.1 §
+/// 17.6.2.1. Higher values override lower values.
+// FIXME(#8586): FromTableRow, FromTableRowGroup, FromTableColumn,
+// FromTableColumnGroup are unused
+#[allow(dead_code)]
+#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
+pub enum CollapsedBorderProvenance {
+ FromPreviousTableCell = 6,
+ FromNextTableCell = 5,
+ FromTableRow = 4,
+ FromTableRowGroup = 3,
+ FromTableColumn = 2,
+ FromTableColumnGroup = 1,
+ FromTable = 0,
+}
+
+impl CollapsedBorder {
+ /// Creates a collapsible border style for no border.
+ pub fn new() -> CollapsedBorder {
+ CollapsedBorder {
+ style: BorderStyle::None,
+ width: Au(0),
+ color: Color::transparent(),
+ provenance: CollapsedBorderProvenance::FromTable,
+ }
+ }
+
+ /// Creates a collapsed border from the block-start border described in the given CSS style
+ /// object.
+ fn top(css_style: &ComputedValues, provenance: CollapsedBorderProvenance) -> CollapsedBorder {
+ CollapsedBorder {
+ style: css_style.get_border().border_top_style,
+ width: Au::from(css_style.get_border().border_top_width),
+ color: css_style.get_border().border_top_color,
+ provenance: provenance,
+ }
+ }
+
+ /// Creates a collapsed border style from the right border described in the given CSS style
+ /// object.
+ fn right(css_style: &ComputedValues, provenance: CollapsedBorderProvenance) -> CollapsedBorder {
+ CollapsedBorder {
+ style: css_style.get_border().border_right_style,
+ width: Au::from(css_style.get_border().border_right_width),
+ color: css_style.get_border().border_right_color,
+ provenance: provenance,
+ }
+ }
+
+ /// Creates a collapsed border style from the bottom border described in the given CSS style
+ /// object.
+ fn bottom(
+ css_style: &ComputedValues,
+ provenance: CollapsedBorderProvenance,
+ ) -> CollapsedBorder {
+ CollapsedBorder {
+ style: css_style.get_border().border_bottom_style,
+ width: Au::from(css_style.get_border().border_bottom_width),
+ color: css_style.get_border().border_bottom_color,
+ provenance: provenance,
+ }
+ }
+
+ /// Creates a collapsed border style from the left border described in the given CSS style
+ /// object.
+ fn left(css_style: &ComputedValues, provenance: CollapsedBorderProvenance) -> CollapsedBorder {
+ CollapsedBorder {
+ style: css_style.get_border().border_left_style,
+ width: Au::from(css_style.get_border().border_left_width),
+ color: css_style.get_border().border_left_color,
+ provenance: provenance,
+ }
+ }
+
+ /// Creates a collapsed border style from the given physical side.
+ fn from_side(
+ side: PhysicalSide,
+ css_style: &ComputedValues,
+ provenance: CollapsedBorderProvenance,
+ ) -> CollapsedBorder {
+ match side {
+ PhysicalSide::Top => CollapsedBorder::top(css_style, provenance),
+ PhysicalSide::Right => CollapsedBorder::right(css_style, provenance),
+ PhysicalSide::Bottom => CollapsedBorder::bottom(css_style, provenance),
+ PhysicalSide::Left => CollapsedBorder::left(css_style, provenance),
+ }
+ }
+
+ /// Creates a collapsed border style from the inline-start border described in the given CSS
+ /// style object.
+ pub fn inline_start(
+ css_style: &ComputedValues,
+ provenance: CollapsedBorderProvenance,
+ ) -> CollapsedBorder {
+ CollapsedBorder::from_side(
+ css_style.writing_mode.inline_start_physical_side(),
+ css_style,
+ provenance,
+ )
+ }
+
+ /// Creates a collapsed border style from the inline-start border described in the given CSS
+ /// style object.
+ pub fn inline_end(
+ css_style: &ComputedValues,
+ provenance: CollapsedBorderProvenance,
+ ) -> CollapsedBorder {
+ CollapsedBorder::from_side(
+ css_style.writing_mode.inline_end_physical_side(),
+ css_style,
+ provenance,
+ )
+ }
+
+ /// Creates a collapsed border style from the block-start border described in the given CSS
+ /// style object.
+ pub fn block_start(
+ css_style: &ComputedValues,
+ provenance: CollapsedBorderProvenance,
+ ) -> CollapsedBorder {
+ CollapsedBorder::from_side(
+ css_style.writing_mode.block_start_physical_side(),
+ css_style,
+ provenance,
+ )
+ }
+
+ /// Creates a collapsed border style from the block-end border described in the given CSS style
+ /// object.
+ pub fn block_end(
+ css_style: &ComputedValues,
+ provenance: CollapsedBorderProvenance,
+ ) -> CollapsedBorder {
+ CollapsedBorder::from_side(
+ css_style.writing_mode.block_end_physical_side(),
+ css_style,
+ provenance,
+ )
+ }
+
+ /// If `other` has a higher priority per CSS 2.1 § 17.6.2.1, replaces `self` with it.
+ pub fn combine(&mut self, other: &CollapsedBorder) {
+ match (self.style, other.style) {
+ // Step 1.
+ (BorderStyle::Hidden, _) => {},
+ (_, BorderStyle::Hidden) => *self = *other,
+ // Step 2.
+ (BorderStyle::None, _) => *self = *other,
+ (_, BorderStyle::None) => {},
+ // Step 3.
+ _ if self.width > other.width => {},
+ _ if self.width < other.width => *self = *other,
+ (this_style, other_style) if this_style > other_style => {},
+ (this_style, other_style) if this_style < other_style => *self = *other,
+ // Step 4.
+ _ if (self.provenance as i8) >= other.provenance as i8 => {},
+ _ => *self = *other,
+ }
+ }
+}
+
+/// Pushes column inline size, incoming rowspan, and border collapse info down to a child.
+pub fn propagate_column_inline_sizes_to_child(
+ child_flow: &mut dyn Flow,
+ table_writing_mode: WritingMode,
+ column_computed_inline_sizes: &[ColumnComputedInlineSize],
+ border_spacing: &BorderSpacing,
+ incoming_rowspan: &mut Vec<u32>,
+) {
+ // If the child is a row group or a row, the column inline-size and rowspan info should be copied from its
+ // parent.
+ //
+ // FIXME(pcwalton): This seems inefficient. Reference count it instead?
+ match child_flow.class() {
+ FlowClass::TableRowGroup => {
+ incoming_rowspan.clear();
+ let child_table_rowgroup_flow = child_flow.as_mut_table_rowgroup();
+ child_table_rowgroup_flow.spacing = *border_spacing;
+ for kid in child_table_rowgroup_flow.block_flow.base.child_iter_mut() {
+ propagate_column_inline_sizes_to_child(
+ kid,
+ table_writing_mode,
+ column_computed_inline_sizes,
+ border_spacing,
+ incoming_rowspan,
+ );
+ }
+ },
+ FlowClass::TableRow => {
+ let child_table_row_flow = child_flow.as_mut_table_row();
+ child_table_row_flow.column_computed_inline_sizes =
+ column_computed_inline_sizes.to_vec();
+ child_table_row_flow.spacing = *border_spacing;
+ child_table_row_flow.table_writing_mode = table_writing_mode;
+ child_table_row_flow.incoming_rowspan = incoming_rowspan.clone();
+
+ // Update the incoming rowspan for the next row.
+ let mut col = 0;
+ for cell in &child_table_row_flow.cell_intrinsic_inline_sizes {
+ // Skip any column occupied by a cell from a previous row.
+ while col < incoming_rowspan.len() && incoming_rowspan[col] != 1 {
+ if incoming_rowspan[col] > 1 {
+ incoming_rowspan[col] -= 1;
+ }
+ col += 1;
+ }
+ for _ in 0..cell.column_span {
+ if col < incoming_rowspan.len() && incoming_rowspan[col] > 1 {
+ incoming_rowspan[col] -= 1;
+ }
+ // If this cell spans later rows, record its rowspan.
+ if cell.row_span != 1 {
+ if incoming_rowspan.len() < col + 1 {
+ incoming_rowspan.resize(col + 1, 1);
+ }
+ // HTML § 4.9.11: For rowspan, the value 0 means the cell is to span all
+ // the remaining rows in the rowgroup.
+ if cell.row_span > incoming_rowspan[col] || cell.row_span == 0 {
+ incoming_rowspan[col] = cell.row_span;
+ }
+ }
+ col += 1;
+ }
+ }
+ },
+ c => warn!("unexpected flow in table {:?}", c),
+ }
+}
+
+/// Lay out table cells inline according to the computer column sizes.
+fn set_inline_position_of_child_flow(
+ child_flow: &mut dyn Flow,
+ child_index: usize,
+ column_index: &mut usize,
+ incoming_rowspan: &[u32],
+ row_writing_mode: WritingMode,
+ table_writing_mode: WritingMode,
+ column_computed_inline_sizes: &[ColumnComputedInlineSize],
+ border_spacing: &BorderSpacing,
+ border_collapse_info: &Option<BorderCollapseInfoForChildTableCell>,
+ parent_content_inline_size: Au,
+ inline_start_margin_edge: &mut Au,
+ inline_end_margin_edge: &mut Au,
+) {
+ if !child_flow.is_table_cell() {
+ return;
+ }
+
+ let reverse_column_order = table_writing_mode.is_bidi_ltr() != row_writing_mode.is_bidi_ltr();
+
+ // Advance past any column occupied by a cell from a previous row.
+ while *column_index < incoming_rowspan.len() && incoming_rowspan[*column_index] != 1 {
+ let column_inline_size = column_computed_inline_sizes[*column_index].size;
+ let border_inline_size = match *border_collapse_info {
+ Some(_) => Au(0), // FIXME: Make collapsed borders account for colspan/rowspan.
+ None => border_spacing.horizontal(),
+ };
+ if reverse_column_order {
+ *inline_end_margin_edge += column_inline_size + border_inline_size;
+ } else {
+ *inline_start_margin_edge += column_inline_size + border_inline_size;
+ }
+ *column_index += 1;
+ }
+
+ // Handle border collapsing, if necessary.
+ let child_table_cell = child_flow.as_mut_table_cell();
+ match *border_collapse_info {
+ Some(ref border_collapse_info) => {
+ // Write in the child's border collapse state.
+ child_table_cell.collapsed_borders = CollapsedBordersForCell {
+ inline_start_border: border_collapse_info
+ .collapsed_borders_for_row
+ .inline
+ .get(child_index)
+ .map_or(CollapsedBorder::new(), |x| *x),
+ inline_end_border: border_collapse_info
+ .collapsed_borders_for_row
+ .inline
+ .get(child_index + 1)
+ .map_or(CollapsedBorder::new(), |x| *x),
+ block_start_border: border_collapse_info
+ .collapsed_borders_for_row
+ .block_start
+ .get(child_index)
+ .map_or(CollapsedBorder::new(), |x| *x),
+ block_end_border: border_collapse_info
+ .collapsed_borders_for_row
+ .block_end
+ .get(child_index)
+ .map_or(CollapsedBorder::new(), |x| *x),
+ inline_start_width: border_collapse_info
+ .collapsed_border_spacing_for_row
+ .inline
+ .get(child_index)
+ .map_or(Au(0), |x| *x),
+ inline_end_width: border_collapse_info
+ .collapsed_border_spacing_for_row
+ .inline
+ .get(child_index + 1)
+ .map_or(Au(0), |x| *x),
+ block_start_width: border_collapse_info
+ .collapsed_border_spacing_for_row
+ .block_start,
+ block_end_width: border_collapse_info
+ .collapsed_border_spacing_for_row
+ .block_end,
+ };
+
+ // Move over past the collapsed border.
+ if reverse_column_order {
+ *inline_end_margin_edge += child_table_cell.collapsed_borders.inline_start_width;
+ } else {
+ *inline_start_margin_edge += child_table_cell.collapsed_borders.inline_start_width;
+ }
+ },
+ None => {
+ // Take spacing into account.
+ if reverse_column_order {
+ *inline_end_margin_edge += border_spacing.horizontal();
+ } else {
+ *inline_start_margin_edge += border_spacing.horizontal();
+ }
+ },
+ }
+
+ let column_inline_size = column_computed_inline_sizes[*column_index].size;
+ *column_index += 1;
+
+ let kid_base = &mut child_table_cell.block_flow.base;
+ kid_base.block_container_inline_size = column_inline_size;
+
+ if reverse_column_order {
+ // Columns begin from the inline-end edge.
+ kid_base.position.start.i =
+ parent_content_inline_size - *inline_end_margin_edge - column_inline_size;
+ *inline_end_margin_edge += column_inline_size;
+ } else {
+ // Columns begin from the inline-start edge.
+ kid_base.position.start.i = *inline_start_margin_edge;
+ *inline_start_margin_edge += column_inline_size;
+ }
+}
+
+#[derive(Clone, Copy)]
+pub struct BorderCollapseInfoForChildTableCell<'a> {
+ collapsed_borders_for_row: &'a CollapsedBordersForRow,
+ collapsed_border_spacing_for_row: &'a CollapsedBorderSpacingForRow,
+}
+
+/// Performs border-collapse in the inline direction for all the cells' inside borders in the
+/// inline-direction cells and propagates the outside borders (the far left and right) up to the
+/// table row. This is done eagerly here so that at least the inline inside border collapse
+/// computations can be parallelized across all the rows of the table.
+fn perform_inline_direction_border_collapse_for_row(
+ row_style: &ComputedValues,
+ children_count: usize,
+ child_index: usize,
+ child_table_cell: &mut TableCellFlow,
+ iterator: &mut Peekable<Enumerate<MutFlowListIterator>>,
+ preliminary_collapsed_borders: &mut CollapsedBordersForRow,
+) {
+ // In the first cell, combine its border with the one coming from the row.
+ if child_index == 0 {
+ let first_inline_border = &mut preliminary_collapsed_borders.inline[0];
+ first_inline_border.combine(&CollapsedBorder::inline_start(
+ &*child_table_cell.block_flow.fragment.style,
+ CollapsedBorderProvenance::FromNextTableCell,
+ ));
+ }
+
+ let inline_collapsed_border = preliminary_collapsed_borders.inline.push_or_set(
+ child_index + 1,
+ CollapsedBorder::inline_end(
+ &*child_table_cell.block_flow.fragment.style,
+ CollapsedBorderProvenance::FromPreviousTableCell,
+ ),
+ );
+
+ if let Some(&(_, ref next_child_flow)) = iterator.peek() {
+ let next_child_flow = next_child_flow.as_block();
+ inline_collapsed_border.combine(&CollapsedBorder::inline_start(
+ &*next_child_flow.fragment.style,
+ CollapsedBorderProvenance::FromNextTableCell,
+ ))
+ };
+
+ // In the last cell, also take into account the border that may
+ // come from the row.
+ if child_index + 1 == children_count {
+ inline_collapsed_border.combine(&CollapsedBorder::inline_end(
+ &row_style,
+ CollapsedBorderProvenance::FromTableRow,
+ ));
+ }
+
+ let mut block_start_border = CollapsedBorder::block_start(
+ &*child_table_cell.block_flow.fragment.style,
+ CollapsedBorderProvenance::FromNextTableCell,
+ );
+ block_start_border.combine(&CollapsedBorder::block_start(
+ row_style,
+ CollapsedBorderProvenance::FromTableRow,
+ ));
+ preliminary_collapsed_borders
+ .block_start
+ .push_or_set(child_index, block_start_border);
+ let mut block_end_border = CollapsedBorder::block_end(
+ &*child_table_cell.block_flow.fragment.style,
+ CollapsedBorderProvenance::FromPreviousTableCell,
+ );
+ block_end_border.combine(&CollapsedBorder::block_end(
+ row_style,
+ CollapsedBorderProvenance::FromTableRow,
+ ));
+
+ preliminary_collapsed_borders
+ .block_end
+ .push_or_set(child_index, block_end_border);
+}
diff --git a/components/layout_2020/table_rowgroup.rs b/components/layout_2020/table_rowgroup.rs
new file mode 100644
index 00000000000..814cbe0d2d2
--- /dev/null
+++ b/components/layout_2020/table_rowgroup.rs
@@ -0,0 +1,274 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+//! CSS table formatting contexts.
+
+use crate::block::{BlockFlow, ISizeAndMarginsComputer};
+use crate::context::LayoutContext;
+use crate::display_list::{
+ DisplayListBuildState, StackingContextCollectionFlags, StackingContextCollectionState,
+};
+use crate::flow::{Flow, FlowClass, OpaqueFlow};
+use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
+use crate::layout_debug;
+use crate::table::{ColumnIntrinsicInlineSize, InternalTable, TableLikeFlow};
+use app_units::Au;
+use euclid::Point2D;
+use gfx_traits::print_tree::PrintTree;
+use serde::{Serialize, Serializer};
+use std::fmt;
+use std::iter::{IntoIterator, Iterator, Peekable};
+use style::computed_values::{border_collapse, border_spacing};
+use style::logical_geometry::LogicalSize;
+use style::properties::ComputedValues;
+
+#[allow(unsafe_code)]
+unsafe impl crate::flow::HasBaseFlow for TableRowGroupFlow {}
+
+/// A table formatting context.
+#[repr(C)]
+pub struct TableRowGroupFlow {
+ /// Fields common to all block flows.
+ pub block_flow: BlockFlow,
+
+ /// Information about the intrinsic inline-sizes of each column.
+ pub column_intrinsic_inline_sizes: Vec<ColumnIntrinsicInlineSize>,
+
+ /// The spacing for this rowgroup.
+ pub spacing: border_spacing::T,
+
+ /// The final width of the borders in the inline direction for each cell, computed by the
+ /// entire table and pushed down into each row during inline size computation.
+ pub collapsed_inline_direction_border_widths_for_table: Vec<Au>,
+
+ /// The final width of the borders in the block direction for each cell, computed by the
+ /// entire table and pushed down into each row during inline size computation.
+ pub collapsed_block_direction_border_widths_for_table: Vec<Au>,
+}
+
+impl Serialize for TableRowGroupFlow {
+ fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+ self.block_flow.serialize(serializer)
+ }
+}
+
+impl TableRowGroupFlow {
+ pub fn from_fragment(fragment: Fragment) -> TableRowGroupFlow {
+ TableRowGroupFlow {
+ block_flow: BlockFlow::from_fragment(fragment),
+ column_intrinsic_inline_sizes: Vec::new(),
+ spacing: border_spacing::T::zero(),
+ collapsed_inline_direction_border_widths_for_table: Vec::new(),
+ collapsed_block_direction_border_widths_for_table: Vec::new(),
+ }
+ }
+
+ pub fn populate_collapsed_border_spacing<'a, I>(
+ &mut self,
+ collapsed_inline_direction_border_widths_for_table: &[Au],
+ collapsed_block_direction_border_widths_for_table: &mut Peekable<I>,
+ ) where
+ I: Iterator<Item = &'a Au>,
+ {
+ self.collapsed_inline_direction_border_widths_for_table
+ .clear();
+ self.collapsed_inline_direction_border_widths_for_table
+ .extend(
+ collapsed_inline_direction_border_widths_for_table
+ .into_iter()
+ .map(|x| *x),
+ );
+
+ for _ in 0..self.block_flow.base.children.len() {
+ if let Some(collapsed_block_direction_border_width_for_table) =
+ collapsed_block_direction_border_widths_for_table.next()
+ {
+ self.collapsed_block_direction_border_widths_for_table
+ .push(*collapsed_block_direction_border_width_for_table)
+ }
+ }
+ if let Some(collapsed_block_direction_border_width_for_table) =
+ collapsed_block_direction_border_widths_for_table.peek()
+ {
+ self.collapsed_block_direction_border_widths_for_table
+ .push(**collapsed_block_direction_border_width_for_table)
+ }
+ }
+}
+
+impl Flow for TableRowGroupFlow {
+ fn class(&self) -> FlowClass {
+ FlowClass::TableRowGroup
+ }
+
+ fn as_mut_table_rowgroup(&mut self) -> &mut TableRowGroupFlow {
+ self
+ }
+
+ fn as_table_rowgroup(&self) -> &TableRowGroupFlow {
+ self
+ }
+
+ fn as_mut_block(&mut self) -> &mut BlockFlow {
+ &mut self.block_flow
+ }
+
+ fn as_block(&self) -> &BlockFlow {
+ &self.block_flow
+ }
+
+ fn bubble_inline_sizes(&mut self) {
+ let _scope = layout_debug_scope!(
+ "table_rowgroup::bubble_inline_sizes {:x}",
+ self.block_flow.base.debug_id()
+ );
+ // Proper calculation of intrinsic sizes in table layout requires access to the entire
+ // table, which we don't have yet. Defer to our parent.
+ }
+
+ /// 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, layout_context: &LayoutContext) {
+ let _scope = layout_debug_scope!(
+ "table_rowgroup::assign_inline_sizes {:x}",
+ self.block_flow.base.debug_id()
+ );
+ debug!(
+ "assign_inline_sizes({}): assigning inline_size for flow",
+ "table_rowgroup"
+ );
+
+ let shared_context = layout_context.shared_context();
+ // The position was set to the containing block by the flow's parent.
+ let containing_block_inline_size = self.block_flow.base.block_container_inline_size;
+ let (inline_start_content_edge, inline_end_content_edge) = (Au(0), Au(0));
+ let content_inline_size = containing_block_inline_size;
+
+ let border_collapse = self
+ .block_flow
+ .fragment
+ .style
+ .get_inherited_table()
+ .border_collapse;
+ let inline_size_computer = InternalTable;
+ inline_size_computer.compute_used_inline_size(
+ &mut self.block_flow,
+ shared_context,
+ containing_block_inline_size,
+ );
+
+ let collapsed_inline_direction_border_widths_for_table =
+ &self.collapsed_inline_direction_border_widths_for_table;
+ let mut collapsed_block_direction_border_widths_for_table = self
+ .collapsed_block_direction_border_widths_for_table
+ .iter()
+ .peekable();
+ self.block_flow.propagate_assigned_inline_size_to_children(
+ shared_context,
+ inline_start_content_edge,
+ inline_end_content_edge,
+ content_inline_size,
+ |child_flow,
+ _child_index,
+ _content_inline_size,
+ _writing_mode,
+ _inline_start_margin_edge,
+ _inline_end_margin_edge| {
+ if border_collapse == border_collapse::T::Collapse {
+ let child_table_row = child_flow.as_mut_table_row();
+ child_table_row.populate_collapsed_border_spacing(
+ collapsed_inline_direction_border_widths_for_table,
+ &mut collapsed_block_direction_border_widths_for_table,
+ );
+ }
+ },
+ );
+ }
+
+ fn assign_block_size(&mut self, lc: &LayoutContext) {
+ debug!("assign_block_size: assigning block_size for table_rowgroup");
+ self.block_flow
+ .assign_block_size_for_table_like_flow(self.spacing.vertical(), lc);
+ }
+
+ fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
+ self.block_flow
+ .compute_stacking_relative_position(layout_context)
+ }
+
+ fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
+ self.block_flow
+ .update_late_computed_inline_position_if_necessary(inline_position)
+ }
+
+ fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
+ self.block_flow
+ .update_late_computed_block_position_if_necessary(block_position)
+ }
+
+ fn build_display_list(&mut self, _: &mut DisplayListBuildState) {
+ use style::servo::restyle_damage::ServoRestyleDamage;
+
+ // we skip setting the damage in TableCellStyleInfo::build_display_list()
+ // because we only have immutable access
+ self.block_flow
+ .fragment
+ .restyle_damage
+ .remove(ServoRestyleDamage::REPAINT);
+ }
+
+ fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
+ self.block_flow.collect_stacking_contexts_for_block(
+ state,
+ StackingContextCollectionFlags::POSITION_NEVER_CREATES_CONTAINING_BLOCK,
+ );
+ }
+
+ fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>) {
+ self.block_flow.repair_style(new_style)
+ }
+
+ fn compute_overflow(&self) -> Overflow {
+ self.block_flow.compute_overflow()
+ }
+
+ fn contains_roots_of_absolute_flow_tree(&self) -> bool {
+ self.block_flow.contains_roots_of_absolute_flow_tree()
+ }
+
+ fn is_absolute_containing_block(&self) -> bool {
+ self.block_flow.is_absolute_containing_block()
+ }
+
+ fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
+ self.block_flow.generated_containing_block_size(flow)
+ }
+
+ fn iterate_through_fragment_border_boxes(
+ &self,
+ iterator: &mut dyn FragmentBorderBoxIterator,
+ level: i32,
+ stacking_context_position: &Point2D<Au>,
+ ) {
+ self.block_flow.iterate_through_fragment_border_boxes(
+ iterator,
+ level,
+ stacking_context_position,
+ )
+ }
+
+ fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) {
+ self.block_flow.mutate_fragments(mutator)
+ }
+
+ fn print_extra_flow_children(&self, print_tree: &mut PrintTree) {
+ self.block_flow.print_extra_flow_children(print_tree);
+ }
+}
+
+impl fmt::Debug for TableRowGroupFlow {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "TableRowGroupFlow: {:?}", self.block_flow)
+ }
+}
diff --git a/components/layout_2020/table_wrapper.rs b/components/layout_2020/table_wrapper.rs
new file mode 100644
index 00000000000..65ecc07d3ab
--- /dev/null
+++ b/components/layout_2020/table_wrapper.rs
@@ -0,0 +1,996 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+//! CSS tables.
+//!
+//! This follows the "More Precise Definitions of Inline Layout and Table Layout" proposal written
+//! by L. David Baron (Mozilla) here:
+//!
+//! http://dbaron.org/css/intrinsic/
+//!
+//! Hereafter this document is referred to as INTRINSIC.
+
+use crate::block::{
+ AbsoluteNonReplaced, BlockFlow, FloatNonReplaced, ISizeAndMarginsComputer, ISizeConstraintInput,
+};
+use crate::block::{ISizeConstraintSolution, MarginsMayCollapseFlag};
+use crate::context::LayoutContext;
+use crate::display_list::StackingContextCollectionState;
+use crate::display_list::{DisplayListBuildState, StackingContextCollectionFlags};
+use crate::floats::FloatKind;
+use crate::flow::{Flow, FlowClass, FlowFlags, ImmutableFlowUtils, OpaqueFlow};
+use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
+use crate::model::MaybeAuto;
+use crate::table::{ColumnComputedInlineSize, ColumnIntrinsicInlineSize};
+use app_units::Au;
+use euclid::Point2D;
+use gfx_traits::print_tree::PrintTree;
+use std::cmp::{max, min};
+use std::fmt;
+use std::ops::Add;
+use style::computed_values::{position, table_layout};
+use style::context::SharedStyleContext;
+use style::logical_geometry::{LogicalRect, LogicalSize};
+use style::properties::ComputedValues;
+use style::values::computed::Size;
+use style::values::CSSFloat;
+
+#[derive(Clone, Copy, Debug, Serialize)]
+pub enum TableLayout {
+ Fixed,
+ Auto,
+}
+
+#[allow(unsafe_code)]
+unsafe impl crate::flow::HasBaseFlow for TableWrapperFlow {}
+
+/// A table wrapper flow based on a block formatting context.
+#[derive(Serialize)]
+#[repr(C)]
+pub struct TableWrapperFlow {
+ pub block_flow: BlockFlow,
+
+ /// Intrinsic column inline sizes according to INTRINSIC § 4.1
+ pub column_intrinsic_inline_sizes: Vec<ColumnIntrinsicInlineSize>,
+
+ /// Table-layout property
+ pub table_layout: TableLayout,
+}
+
+impl TableWrapperFlow {
+ pub fn from_fragment(fragment: Fragment) -> TableWrapperFlow {
+ TableWrapperFlow::from_fragment_and_float_kind(fragment, None)
+ }
+
+ pub fn from_fragment_and_float_kind(
+ fragment: Fragment,
+ float_kind: Option<FloatKind>,
+ ) -> TableWrapperFlow {
+ let mut block_flow = BlockFlow::from_fragment_and_float_kind(fragment, float_kind);
+ let table_layout =
+ if block_flow.fragment().style().get_table().table_layout == table_layout::T::Fixed {
+ TableLayout::Fixed
+ } else {
+ TableLayout::Auto
+ };
+ TableWrapperFlow {
+ block_flow: block_flow,
+ column_intrinsic_inline_sizes: vec![],
+ table_layout: table_layout,
+ }
+ }
+
+ fn border_padding_and_spacing(&mut self) -> (Au, Au) {
+ let (mut table_border_padding, mut spacing) = (Au(0), Au(0));
+ for kid in self.block_flow.base.child_iter_mut() {
+ if kid.is_table() {
+ let kid_table = kid.as_table();
+ spacing = kid_table.total_horizontal_spacing();
+ table_border_padding = kid_table
+ .block_flow
+ .fragment
+ .border_padding
+ .inline_start_end();
+ break;
+ }
+ }
+ (table_border_padding, spacing)
+ }
+
+ // Instructs our first child, which is the table itself, to compute its border and padding.
+ //
+ // This is a little weird because we're computing border/padding/margins for our child,
+ // when normally the child computes it itself. But it has to be this way because the
+ // padding will affect where we place the child. This is an odd artifact of the way that
+ // tables are separated into table flows and table wrapper flows.
+ fn compute_border_and_padding_of_table(&mut self) {
+ let available_inline_size = self.block_flow.base.block_container_inline_size;
+ for kid in self.block_flow.base.child_iter_mut() {
+ if !kid.is_table() {
+ continue;
+ }
+
+ let kid_table = kid.as_mut_table();
+ let kid_block_flow = &mut kid_table.block_flow;
+ kid_block_flow
+ .fragment
+ .compute_border_and_padding(available_inline_size);
+ kid_block_flow
+ .fragment
+ .compute_block_direction_margins(available_inline_size);
+ kid_block_flow
+ .fragment
+ .compute_inline_direction_margins(available_inline_size);
+ return;
+ }
+ }
+
+ /// Calculates table column sizes for automatic layout per INTRINSIC § 4.3.
+ fn calculate_table_column_sizes_for_automatic_layout(
+ &mut self,
+ intermediate_column_inline_sizes: &mut [IntermediateColumnInlineSize],
+ ) {
+ let available_inline_size = self.available_inline_size();
+
+ // Compute all the guesses for the column sizes, and sum them.
+ let mut total_guess = AutoLayoutCandidateGuess::new();
+ let guesses: Vec<AutoLayoutCandidateGuess> = self
+ .column_intrinsic_inline_sizes
+ .iter()
+ .map(|column_intrinsic_inline_size| {
+ let guess = AutoLayoutCandidateGuess::from_column_intrinsic_inline_size(
+ column_intrinsic_inline_size,
+ available_inline_size,
+ );
+ total_guess = &total_guess + &guess;
+ guess
+ })
+ .collect();
+
+ // Assign inline sizes.
+ let selection =
+ SelectedAutoLayoutCandidateGuess::select(&total_guess, available_inline_size);
+ let mut total_used_inline_size = Au(0);
+ for (intermediate_column_inline_size, guess) in intermediate_column_inline_sizes
+ .iter_mut()
+ .zip(guesses.iter())
+ {
+ intermediate_column_inline_size.size = guess.calculate(selection);
+ intermediate_column_inline_size.percentage = 0.0;
+ total_used_inline_size = total_used_inline_size + intermediate_column_inline_size.size
+ }
+
+ // Distribute excess inline-size if necessary per INTRINSIC § 4.4.
+ //
+ // FIXME(pcwalton, spec): How do I deal with fractional excess?
+ let excess_inline_size = available_inline_size - total_used_inline_size;
+ if excess_inline_size > Au(0) &&
+ selection ==
+ SelectedAutoLayoutCandidateGuess::UsePreferredGuessAndDistributeExcessInlineSize
+ {
+ let mut info = ExcessInlineSizeDistributionInfo::new();
+ for column_intrinsic_inline_size in &self.column_intrinsic_inline_sizes {
+ info.update(column_intrinsic_inline_size)
+ }
+
+ let mut total_distributed_excess_size = Au(0);
+ for (intermediate_column_inline_size, column_intrinsic_inline_size) in
+ intermediate_column_inline_sizes
+ .iter_mut()
+ .zip(self.column_intrinsic_inline_sizes.iter())
+ {
+ info.distribute_excess_inline_size_to_column(
+ intermediate_column_inline_size,
+ column_intrinsic_inline_size,
+ excess_inline_size,
+ &mut total_distributed_excess_size,
+ )
+ }
+ total_used_inline_size = available_inline_size
+ }
+
+ self.set_inline_size(total_used_inline_size)
+ }
+
+ fn available_inline_size(&mut self) -> Au {
+ let available_inline_size = self.block_flow.fragment.border_box.size.inline;
+ let (table_border_padding, spacing) = self.border_padding_and_spacing();
+
+ // FIXME(pcwalton, spec): INTRINSIC § 8 does not properly define how to compute this, but
+ // says "the basic idea is the same as the shrink-to-fit width that CSS2.1 defines". So we
+ // just use the shrink-to-fit inline size.
+ let available_inline_size = match self.block_flow.fragment.style().content_inline_size() {
+ Size::Auto => {
+ self.block_flow
+ .get_shrink_to_fit_inline_size(available_inline_size) -
+ table_border_padding
+ },
+ // FIXME(mttr): This fixes #4421 without breaking our current reftests, but I'm not
+ // completely sure this is "correct".
+ //
+ // That said, `available_inline_size` is, as far as I can tell, equal to the table's
+ // computed width property (W) and is used from this point forward in a way that seems
+ // to correspond with CSS 2.1 § 17.5.2.2 under "Column and caption widths influence the
+ // final table width as follows: …"
+ _ => available_inline_size,
+ };
+ available_inline_size - spacing
+ }
+
+ fn set_inline_size(&mut self, total_used_inline_size: Au) {
+ let (table_border_padding, spacing) = self.border_padding_and_spacing();
+ self.block_flow.fragment.border_box.size.inline =
+ total_used_inline_size + table_border_padding + spacing;
+ self.block_flow.base.position.size.inline = total_used_inline_size +
+ table_border_padding +
+ spacing +
+ self.block_flow.fragment.margin.inline_start_end();
+
+ let writing_mode = self.block_flow.base.writing_mode;
+ let container_mode = self.block_flow.base.block_container_writing_mode;
+
+ if writing_mode.is_bidi_ltr() != container_mode.is_bidi_ltr() {
+ // If our "start" direction is different from our parent flow, then `border_box.start.i`
+ // depends on `border_box.size.inline`.
+ self.block_flow.fragment.border_box.start.i =
+ self.block_flow.base.block_container_inline_size -
+ self.block_flow.fragment.margin.inline_end -
+ self.block_flow.fragment.border_box.size.inline;
+ }
+ }
+
+ fn compute_used_inline_size(
+ &mut self,
+ shared_context: &SharedStyleContext,
+ parent_flow_inline_size: Au,
+ intermediate_column_inline_sizes: &[IntermediateColumnInlineSize],
+ ) {
+ let (border_padding, spacing) = self.border_padding_and_spacing();
+ let minimum_width_of_all_columns = intermediate_column_inline_sizes.iter().fold(
+ border_padding + spacing,
+ |accumulator, intermediate_column_inline_sizes| {
+ accumulator + intermediate_column_inline_sizes.size
+ },
+ );
+ let preferred_width_of_all_columns = self.column_intrinsic_inline_sizes.iter().fold(
+ border_padding + spacing,
+ |accumulator, column_intrinsic_inline_sizes| {
+ accumulator + column_intrinsic_inline_sizes.preferred
+ },
+ );
+
+ // Delegate to the appropriate inline size computer to find the constraint inputs and write
+ // the constraint solutions in.
+ if self.block_flow.base.flags.is_float() {
+ let inline_size_computer = FloatedTable {
+ minimum_width_of_all_columns: minimum_width_of_all_columns,
+ preferred_width_of_all_columns: preferred_width_of_all_columns,
+ table_border_padding: border_padding,
+ };
+ let input = inline_size_computer.compute_inline_size_constraint_inputs(
+ &mut self.block_flow,
+ parent_flow_inline_size,
+ shared_context,
+ );
+
+ let solution =
+ inline_size_computer.solve_inline_size_constraints(&mut self.block_flow, &input);
+ inline_size_computer
+ .set_inline_size_constraint_solutions(&mut self.block_flow, solution);
+ inline_size_computer
+ .set_inline_position_of_flow_if_necessary(&mut self.block_flow, solution);
+ return;
+ }
+
+ if !self
+ .block_flow
+ .base
+ .flags
+ .contains(FlowFlags::INLINE_POSITION_IS_STATIC)
+ {
+ let inline_size_computer = AbsoluteTable {
+ minimum_width_of_all_columns: minimum_width_of_all_columns,
+ preferred_width_of_all_columns: preferred_width_of_all_columns,
+ table_border_padding: border_padding,
+ };
+ let input = inline_size_computer.compute_inline_size_constraint_inputs(
+ &mut self.block_flow,
+ parent_flow_inline_size,
+ shared_context,
+ );
+
+ let solution =
+ inline_size_computer.solve_inline_size_constraints(&mut self.block_flow, &input);
+ inline_size_computer
+ .set_inline_size_constraint_solutions(&mut self.block_flow, solution);
+ inline_size_computer
+ .set_inline_position_of_flow_if_necessary(&mut self.block_flow, solution);
+ return;
+ }
+
+ let inline_size_computer = Table {
+ minimum_width_of_all_columns: minimum_width_of_all_columns,
+ preferred_width_of_all_columns: preferred_width_of_all_columns,
+ table_border_padding: border_padding,
+ };
+ let input = inline_size_computer.compute_inline_size_constraint_inputs(
+ &mut self.block_flow,
+ parent_flow_inline_size,
+ shared_context,
+ );
+
+ let solution =
+ inline_size_computer.solve_inline_size_constraints(&mut self.block_flow, &input);
+ inline_size_computer.set_inline_size_constraint_solutions(&mut self.block_flow, solution);
+ inline_size_computer
+ .set_inline_position_of_flow_if_necessary(&mut self.block_flow, solution);
+ }
+}
+
+impl Flow for TableWrapperFlow {
+ fn class(&self) -> FlowClass {
+ FlowClass::TableWrapper
+ }
+
+ fn as_table_wrapper(&self) -> &TableWrapperFlow {
+ self
+ }
+
+ fn as_mut_block(&mut self) -> &mut BlockFlow {
+ &mut self.block_flow
+ }
+
+ fn as_block(&self) -> &BlockFlow {
+ &self.block_flow
+ }
+
+ fn mark_as_root(&mut self) {
+ self.block_flow.mark_as_root();
+ }
+
+ fn bubble_inline_sizes(&mut self) {
+ // Get the intrinsic column inline-sizes info from the table flow.
+ for kid in self.block_flow.base.child_iter_mut() {
+ debug_assert!(kid.is_table_caption() || kid.is_table());
+ if kid.is_table() {
+ let table = kid.as_table();
+ self.column_intrinsic_inline_sizes = table.column_intrinsic_inline_sizes.clone();
+ }
+ }
+
+ self.block_flow.bubble_inline_sizes();
+ }
+
+ fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
+ debug!(
+ "assign_inline_sizes({}): assigning inline_size for flow",
+ if self.block_flow.base.flags.is_float() {
+ "floated table_wrapper"
+ } else {
+ "table_wrapper"
+ }
+ );
+
+ let shared_context = layout_context.shared_context();
+ self.block_flow
+ .initialize_container_size_for_root(shared_context);
+
+ let mut intermediate_column_inline_sizes = self
+ .column_intrinsic_inline_sizes
+ .iter()
+ .map(
+ |column_intrinsic_inline_size| IntermediateColumnInlineSize {
+ size: column_intrinsic_inline_size.minimum_length,
+ percentage: column_intrinsic_inline_size.percentage,
+ },
+ )
+ .collect::<Vec<_>>();
+
+ // 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.block_flow.base.block_container_inline_size;
+ if self.block_flow.base.flags.is_float() {
+ self.block_flow
+ .float
+ .as_mut()
+ .unwrap()
+ .containing_inline_size = containing_block_inline_size;
+ }
+
+ // This has to be done before computing our inline size because `compute_used_inline_size`
+ // internally consults the border and padding of the table.
+ self.compute_border_and_padding_of_table();
+
+ self.compute_used_inline_size(
+ shared_context,
+ containing_block_inline_size,
+ &intermediate_column_inline_sizes,
+ );
+
+ match self.table_layout {
+ TableLayout::Auto => self.calculate_table_column_sizes_for_automatic_layout(
+ &mut intermediate_column_inline_sizes,
+ ),
+ TableLayout::Fixed => {},
+ }
+
+ 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;
+ let inline_end_content_edge = self.block_flow.fragment.border_padding.inline_end +
+ self.block_flow.fragment.margin.inline_end;
+
+ // In case of fixed layout, column inline-sizes are calculated in table flow.
+ let assigned_column_inline_sizes = match self.table_layout {
+ TableLayout::Fixed => None,
+ TableLayout::Auto => Some(
+ intermediate_column_inline_sizes
+ .iter()
+ .map(|sizes| ColumnComputedInlineSize { size: sizes.size })
+ .collect::<Vec<_>>(),
+ ),
+ };
+
+ match assigned_column_inline_sizes {
+ None => self.block_flow.propagate_assigned_inline_size_to_children(
+ shared_context,
+ inline_start_content_edge,
+ inline_end_content_edge,
+ content_inline_size,
+ |_, _, _, _, _, _| {},
+ ),
+ Some(ref assigned_column_inline_sizes) => {
+ self.block_flow.propagate_assigned_inline_size_to_children(
+ shared_context,
+ inline_start_content_edge,
+ inline_end_content_edge,
+ content_inline_size,
+ |child_flow, _, _, _, _, _| {
+ if child_flow.class() == FlowClass::Table {
+ child_flow.as_mut_table().column_computed_inline_sizes =
+ assigned_column_inline_sizes.to_vec();
+ }
+ },
+ )
+ },
+ }
+ }
+
+ fn assign_block_size(&mut self, layout_context: &LayoutContext) {
+ debug!("assign_block_size: assigning block_size for table_wrapper");
+ let remaining = self.block_flow.assign_block_size_block_base(
+ layout_context,
+ None,
+ MarginsMayCollapseFlag::MarginsMayNotCollapse,
+ );
+ debug_assert!(remaining.is_none());
+ }
+
+ fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
+ self.block_flow
+ .compute_stacking_relative_position(layout_context)
+ }
+
+ fn place_float_if_applicable<'a>(&mut self) {
+ self.block_flow.place_float_if_applicable()
+ }
+
+ fn assign_block_size_for_inorder_child_if_necessary(
+ &mut self,
+ layout_context: &LayoutContext,
+ parent_thread_id: u8,
+ content_box: LogicalRect<Au>,
+ ) -> bool {
+ self.block_flow
+ .assign_block_size_for_inorder_child_if_necessary(
+ layout_context,
+ parent_thread_id,
+ content_box,
+ )
+ }
+
+ fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
+ self.block_flow
+ .update_late_computed_inline_position_if_necessary(inline_position)
+ }
+
+ fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
+ self.block_flow
+ .update_late_computed_block_position_if_necessary(block_position)
+ }
+
+ fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
+ self.block_flow.generated_containing_block_size(flow)
+ }
+
+ fn build_display_list(&mut self, state: &mut DisplayListBuildState) {
+ self.block_flow.build_display_list(state);
+ }
+
+ fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
+ self.block_flow.collect_stacking_contexts_for_block(
+ state,
+ StackingContextCollectionFlags::POSITION_NEVER_CREATES_CONTAINING_BLOCK |
+ StackingContextCollectionFlags::NEVER_CREATES_CLIP_SCROLL_NODE,
+ );
+ }
+
+ fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>) {
+ self.block_flow.repair_style(new_style)
+ }
+
+ fn compute_overflow(&self) -> Overflow {
+ self.block_flow.compute_overflow()
+ }
+
+ fn iterate_through_fragment_border_boxes(
+ &self,
+ iterator: &mut dyn FragmentBorderBoxIterator,
+ level: i32,
+ stacking_context_position: &Point2D<Au>,
+ ) {
+ self.block_flow.iterate_through_fragment_border_boxes(
+ iterator,
+ level,
+ stacking_context_position,
+ )
+ }
+
+ fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) {
+ self.block_flow.mutate_fragments(mutator)
+ }
+
+ fn print_extra_flow_children(&self, print_tree: &mut PrintTree) {
+ self.block_flow.print_extra_flow_children(print_tree);
+ }
+
+ fn positioning(&self) -> position::T {
+ self.block_flow.positioning()
+ }
+}
+
+impl fmt::Debug for TableWrapperFlow {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ if self.block_flow.base.flags.is_float() {
+ write!(f, "TableWrapperFlow(Float): {:?}", self.block_flow)
+ } else {
+ write!(f, "TableWrapperFlow: {:?}", self.block_flow)
+ }
+ }
+}
+
+/// The layout "guesses" defined in INTRINSIC § 4.3.
+struct AutoLayoutCandidateGuess {
+ /// The column inline-size assignment where each column is assigned its intrinsic minimum
+ /// inline-size.
+ minimum_guess: Au,
+
+ /// The column inline-size assignment where:
+ /// * A column with an intrinsic percentage inline-size greater than 0% is assigned the
+ /// larger of:
+ /// - Its intrinsic percentage inline-size times the assignable inline-size;
+ /// - Its intrinsic minimum inline-size;
+ /// * Other columns receive their intrinsic minimum inline-size.
+ minimum_percentage_guess: Au,
+
+ /// The column inline-size assignment where:
+ /// * Each column with an intrinsic percentage inline-size greater than 0% is assigned the
+ /// larger of:
+ /// - Its intrinsic percentage inline-size times the assignable inline-size;
+ /// - Its intrinsic minimum inline-size;
+ /// * Any other column that is constrained is assigned its intrinsic preferred inline-size;
+ /// * Other columns are assigned their intrinsic minimum inline-size.
+ minimum_specified_guess: Au,
+
+ /// The column inline-size assignment where:
+ /// * Each column with an intrinsic percentage inline-size greater than 0% is assigned the
+ /// larger of:
+ /// - Its intrinsic percentage inline-size times the assignable inline-size;
+ /// - Its intrinsic minimum inline-size;
+ /// * Other columns are assigned their intrinsic preferred inline-size.
+ preferred_guess: Au,
+}
+
+impl AutoLayoutCandidateGuess {
+ /// Creates a guess with all elements initialized to zero.
+ fn new() -> AutoLayoutCandidateGuess {
+ AutoLayoutCandidateGuess {
+ minimum_guess: Au(0),
+ minimum_percentage_guess: Au(0),
+ minimum_specified_guess: Au(0),
+ preferred_guess: Au(0),
+ }
+ }
+
+ /// Fills in the inline-size guesses for this column per INTRINSIC § 4.3.
+ fn from_column_intrinsic_inline_size(
+ column_intrinsic_inline_size: &ColumnIntrinsicInlineSize,
+ assignable_inline_size: Au,
+ ) -> AutoLayoutCandidateGuess {
+ let minimum_percentage_guess = max(
+ assignable_inline_size.scale_by(column_intrinsic_inline_size.percentage),
+ column_intrinsic_inline_size.minimum_length,
+ );
+ AutoLayoutCandidateGuess {
+ minimum_guess: column_intrinsic_inline_size.minimum_length,
+ minimum_percentage_guess: minimum_percentage_guess,
+ // FIXME(pcwalton): We need the notion of *constrainedness* per INTRINSIC § 4 to
+ // implement this one correctly.
+ minimum_specified_guess: if column_intrinsic_inline_size.percentage > 0.0 {
+ minimum_percentage_guess
+ } else if column_intrinsic_inline_size.constrained {
+ column_intrinsic_inline_size.preferred
+ } else {
+ column_intrinsic_inline_size.minimum_length
+ },
+ preferred_guess: if column_intrinsic_inline_size.percentage > 0.0 {
+ minimum_percentage_guess
+ } else {
+ column_intrinsic_inline_size.preferred
+ },
+ }
+ }
+
+ /// Calculates the inline-size, interpolating appropriately based on the value of `selection`.
+ ///
+ /// This does *not* distribute excess inline-size. That must be done later if necessary.
+ fn calculate(&self, selection: SelectedAutoLayoutCandidateGuess) -> Au {
+ match selection {
+ SelectedAutoLayoutCandidateGuess::UseMinimumGuess => self.minimum_guess,
+ SelectedAutoLayoutCandidateGuess::
+ InterpolateBetweenMinimumGuessAndMinimumPercentageGuess(weight) => {
+ interp(self.minimum_guess, self.minimum_percentage_guess, weight)
+ }
+ SelectedAutoLayoutCandidateGuess::
+ InterpolateBetweenMinimumPercentageGuessAndMinimumSpecifiedGuess(weight) => {
+ interp(self.minimum_percentage_guess, self.minimum_specified_guess, weight)
+ }
+ SelectedAutoLayoutCandidateGuess::
+ InterpolateBetweenMinimumSpecifiedGuessAndPreferredGuess(weight) => {
+ interp(self.minimum_specified_guess, self.preferred_guess, weight)
+ }
+ SelectedAutoLayoutCandidateGuess::UsePreferredGuessAndDistributeExcessInlineSize => {
+ self.preferred_guess
+ }
+ }
+ }
+}
+
+impl<'a> Add for &'a AutoLayoutCandidateGuess {
+ type Output = AutoLayoutCandidateGuess;
+ #[inline]
+ fn add(self, other: &AutoLayoutCandidateGuess) -> AutoLayoutCandidateGuess {
+ AutoLayoutCandidateGuess {
+ minimum_guess: self.minimum_guess + other.minimum_guess,
+ minimum_percentage_guess: self.minimum_percentage_guess +
+ other.minimum_percentage_guess,
+ minimum_specified_guess: self.minimum_specified_guess + other.minimum_specified_guess,
+ preferred_guess: self.preferred_guess + other.preferred_guess,
+ }
+ }
+}
+
+/// The `CSSFloat` member specifies the weight of the smaller of the two guesses, on a scale from
+/// 0.0 to 1.0.
+#[derive(Clone, Copy, Debug, PartialEq)]
+enum SelectedAutoLayoutCandidateGuess {
+ UseMinimumGuess,
+ InterpolateBetweenMinimumGuessAndMinimumPercentageGuess(CSSFloat),
+ InterpolateBetweenMinimumPercentageGuessAndMinimumSpecifiedGuess(CSSFloat),
+ InterpolateBetweenMinimumSpecifiedGuessAndPreferredGuess(CSSFloat),
+ UsePreferredGuessAndDistributeExcessInlineSize,
+}
+
+impl SelectedAutoLayoutCandidateGuess {
+ /// See INTRINSIC § 4.3.
+ ///
+ /// FIXME(pcwalton, INTRINSIC spec): INTRINSIC doesn't specify whether these are exclusive or
+ /// inclusive ranges.
+ fn select(
+ guess: &AutoLayoutCandidateGuess,
+ assignable_inline_size: Au,
+ ) -> SelectedAutoLayoutCandidateGuess {
+ if assignable_inline_size < guess.minimum_guess {
+ SelectedAutoLayoutCandidateGuess::UseMinimumGuess
+ } else if assignable_inline_size < guess.minimum_percentage_guess {
+ let weight = weight(
+ guess.minimum_guess,
+ assignable_inline_size,
+ guess.minimum_percentage_guess,
+ );
+ SelectedAutoLayoutCandidateGuess::InterpolateBetweenMinimumGuessAndMinimumPercentageGuess(weight)
+ } else if assignable_inline_size < guess.minimum_specified_guess {
+ let weight = weight(
+ guess.minimum_percentage_guess,
+ assignable_inline_size,
+ guess.minimum_specified_guess,
+ );
+ SelectedAutoLayoutCandidateGuess::InterpolateBetweenMinimumPercentageGuessAndMinimumSpecifiedGuess(weight)
+ } else if assignable_inline_size < guess.preferred_guess {
+ let weight = weight(
+ guess.minimum_specified_guess,
+ assignable_inline_size,
+ guess.preferred_guess,
+ );
+ SelectedAutoLayoutCandidateGuess::InterpolateBetweenMinimumSpecifiedGuessAndPreferredGuess(weight)
+ } else {
+ SelectedAutoLayoutCandidateGuess::UsePreferredGuessAndDistributeExcessInlineSize
+ }
+ }
+}
+
+/// Computes the weight needed to linearly interpolate `middle` between two guesses `low` and
+/// `high` as specified by INTRINSIC § 4.3.
+fn weight(low: Au, middle: Au, high: Au) -> CSSFloat {
+ (middle - low).to_f32_px() / (high - low).to_f32_px()
+}
+
+/// Linearly interpolates between two guesses, as specified by INTRINSIC § 4.3.
+fn interp(low: Au, high: Au, weight: CSSFloat) -> Au {
+ low + (high - low).scale_by(weight)
+}
+
+struct ExcessInlineSizeDistributionInfo {
+ preferred_inline_size_of_nonconstrained_columns_with_no_percentage: Au,
+ count_of_nonconstrained_columns_with_no_percentage: u32,
+ preferred_inline_size_of_constrained_columns_with_no_percentage: Au,
+ total_percentage: CSSFloat,
+ column_count: u32,
+}
+
+impl ExcessInlineSizeDistributionInfo {
+ fn new() -> ExcessInlineSizeDistributionInfo {
+ ExcessInlineSizeDistributionInfo {
+ preferred_inline_size_of_nonconstrained_columns_with_no_percentage: Au(0),
+ count_of_nonconstrained_columns_with_no_percentage: 0,
+ preferred_inline_size_of_constrained_columns_with_no_percentage: Au(0),
+ total_percentage: 0.0,
+ column_count: 0,
+ }
+ }
+
+ fn update(&mut self, column_intrinsic_inline_size: &ColumnIntrinsicInlineSize) {
+ if !column_intrinsic_inline_size.constrained &&
+ column_intrinsic_inline_size.percentage == 0.0
+ {
+ self.preferred_inline_size_of_nonconstrained_columns_with_no_percentage = self
+ .preferred_inline_size_of_nonconstrained_columns_with_no_percentage +
+ column_intrinsic_inline_size.preferred;
+ self.count_of_nonconstrained_columns_with_no_percentage += 1
+ }
+ if column_intrinsic_inline_size.constrained &&
+ column_intrinsic_inline_size.percentage == 0.0
+ {
+ self.preferred_inline_size_of_constrained_columns_with_no_percentage = self
+ .preferred_inline_size_of_constrained_columns_with_no_percentage +
+ column_intrinsic_inline_size.preferred
+ }
+ self.total_percentage += column_intrinsic_inline_size.percentage;
+ self.column_count += 1
+ }
+
+ /// Based on the information here, distributes excess inline-size to the given column per
+ /// INTRINSIC § 4.4.
+ ///
+ /// `#[inline]` so the compiler will hoist out the branch, which is loop-invariant.
+ #[inline]
+ fn distribute_excess_inline_size_to_column(
+ &self,
+ intermediate_column_inline_size: &mut IntermediateColumnInlineSize,
+ column_intrinsic_inline_size: &ColumnIntrinsicInlineSize,
+ excess_inline_size: Au,
+ total_distributed_excess_size: &mut Au,
+ ) {
+ let proportion =
+ if self.preferred_inline_size_of_nonconstrained_columns_with_no_percentage > Au(0) {
+ // FIXME(spec, pcwalton): Gecko and WebKit do *something* here when there are
+ // nonconstrained columns with no percentage *and* no preferred width. What do they
+ // do?
+ if !column_intrinsic_inline_size.constrained &&
+ column_intrinsic_inline_size.percentage == 0.0
+ {
+ column_intrinsic_inline_size.preferred.to_f32_px() /
+ self.preferred_inline_size_of_nonconstrained_columns_with_no_percentage
+ .to_f32_px()
+ } else {
+ 0.0
+ }
+ } else if self.count_of_nonconstrained_columns_with_no_percentage > 0 {
+ 1.0 / (self.count_of_nonconstrained_columns_with_no_percentage as CSSFloat)
+ } else if self.preferred_inline_size_of_constrained_columns_with_no_percentage > Au(0) {
+ column_intrinsic_inline_size.preferred.to_f32_px() /
+ self.preferred_inline_size_of_constrained_columns_with_no_percentage
+ .to_f32_px()
+ } else if self.total_percentage > 0.0 {
+ column_intrinsic_inline_size.percentage / self.total_percentage
+ } else {
+ 1.0 / (self.column_count as CSSFloat)
+ };
+
+ // The `min` here has the effect of throwing away fractional excess at the end of the
+ // table.
+ let amount_to_distribute = min(
+ excess_inline_size.scale_by(proportion),
+ excess_inline_size - *total_distributed_excess_size,
+ );
+ *total_distributed_excess_size = *total_distributed_excess_size + amount_to_distribute;
+ intermediate_column_inline_size.size =
+ intermediate_column_inline_size.size + amount_to_distribute
+ }
+}
+
+/// An intermediate column size assignment.
+struct IntermediateColumnInlineSize {
+ size: Au,
+ percentage: f32,
+}
+
+/// Returns the computed inline size of the table wrapper represented by `block`.
+///
+/// `table_border_padding` is the sum of the sizes of all border and padding in the inline
+/// direction of the table contained within this table wrapper.
+fn initial_computed_inline_size(
+ block: &mut BlockFlow,
+ containing_block_inline_size: Au,
+ minimum_width_of_all_columns: Au,
+ preferred_width_of_all_columns: Au,
+ table_border_padding: Au,
+) -> MaybeAuto {
+ match block.fragment.style.content_inline_size() {
+ Size::Auto => {
+ if preferred_width_of_all_columns + table_border_padding <= containing_block_inline_size
+ {
+ MaybeAuto::Specified(preferred_width_of_all_columns + table_border_padding)
+ } else if minimum_width_of_all_columns > containing_block_inline_size {
+ MaybeAuto::Specified(minimum_width_of_all_columns)
+ } else {
+ MaybeAuto::Auto
+ }
+ },
+ Size::LengthPercentage(ref lp) => {
+ let used = lp.to_used_value(containing_block_inline_size);
+ MaybeAuto::Specified(max(
+ used - table_border_padding,
+ minimum_width_of_all_columns,
+ ))
+ },
+ }
+}
+
+struct Table {
+ minimum_width_of_all_columns: Au,
+ preferred_width_of_all_columns: Au,
+ table_border_padding: Au,
+}
+
+impl ISizeAndMarginsComputer for Table {
+ fn compute_border_and_padding(&self, block: &mut BlockFlow, containing_block_inline_size: Au) {
+ block
+ .fragment
+ .compute_border_and_padding(containing_block_inline_size)
+ }
+
+ fn initial_computed_inline_size(
+ &self,
+ block: &mut BlockFlow,
+ parent_flow_inline_size: Au,
+ shared_context: &SharedStyleContext,
+ ) -> MaybeAuto {
+ let containing_block_inline_size =
+ self.containing_block_inline_size(block, parent_flow_inline_size, shared_context);
+ initial_computed_inline_size(
+ block,
+ containing_block_inline_size,
+ self.minimum_width_of_all_columns,
+ self.preferred_width_of_all_columns,
+ self.table_border_padding,
+ )
+ }
+
+ fn solve_inline_size_constraints(
+ &self,
+ block: &mut BlockFlow,
+ input: &ISizeConstraintInput,
+ ) -> ISizeConstraintSolution {
+ self.solve_block_inline_size_constraints(block, input)
+ }
+}
+
+struct FloatedTable {
+ minimum_width_of_all_columns: Au,
+ preferred_width_of_all_columns: Au,
+ table_border_padding: Au,
+}
+
+impl ISizeAndMarginsComputer for FloatedTable {
+ fn compute_border_and_padding(&self, block: &mut BlockFlow, containing_block_inline_size: Au) {
+ block
+ .fragment
+ .compute_border_and_padding(containing_block_inline_size)
+ }
+
+ fn initial_computed_inline_size(
+ &self,
+ block: &mut BlockFlow,
+ parent_flow_inline_size: Au,
+ shared_context: &SharedStyleContext,
+ ) -> MaybeAuto {
+ let containing_block_inline_size =
+ self.containing_block_inline_size(block, parent_flow_inline_size, shared_context);
+ initial_computed_inline_size(
+ block,
+ containing_block_inline_size,
+ self.minimum_width_of_all_columns,
+ self.preferred_width_of_all_columns,
+ self.table_border_padding,
+ )
+ }
+
+ fn solve_inline_size_constraints(
+ &self,
+ block: &mut BlockFlow,
+ input: &ISizeConstraintInput,
+ ) -> ISizeConstraintSolution {
+ FloatNonReplaced.solve_inline_size_constraints(block, input)
+ }
+}
+
+struct AbsoluteTable {
+ minimum_width_of_all_columns: Au,
+ preferred_width_of_all_columns: Au,
+ table_border_padding: Au,
+}
+
+impl ISizeAndMarginsComputer for AbsoluteTable {
+ fn compute_border_and_padding(&self, block: &mut BlockFlow, containing_block_inline_size: Au) {
+ block
+ .fragment
+ .compute_border_and_padding(containing_block_inline_size)
+ }
+
+ fn initial_computed_inline_size(
+ &self,
+ block: &mut BlockFlow,
+ parent_flow_inline_size: Au,
+ shared_context: &SharedStyleContext,
+ ) -> MaybeAuto {
+ let containing_block_inline_size =
+ self.containing_block_inline_size(block, parent_flow_inline_size, shared_context);
+ initial_computed_inline_size(
+ block,
+ containing_block_inline_size,
+ self.minimum_width_of_all_columns,
+ self.preferred_width_of_all_columns,
+ self.table_border_padding,
+ )
+ }
+
+ fn containing_block_inline_size(
+ &self,
+ block: &mut BlockFlow,
+ parent_flow_inline_size: Au,
+ shared_context: &SharedStyleContext,
+ ) -> Au {
+ AbsoluteNonReplaced.containing_block_inline_size(
+ block,
+ parent_flow_inline_size,
+ shared_context,
+ )
+ }
+
+ fn solve_inline_size_constraints(
+ &self,
+ block: &mut BlockFlow,
+ input: &ISizeConstraintInput,
+ ) -> ISizeConstraintSolution {
+ AbsoluteNonReplaced.solve_inline_size_constraints(block, input)
+ }
+
+ fn set_inline_position_of_flow_if_necessary(
+ &self,
+ block: &mut BlockFlow,
+ solution: ISizeConstraintSolution,
+ ) {
+ AbsoluteNonReplaced.set_inline_position_of_flow_if_necessary(block, solution);
+ }
+}
diff --git a/components/layout_2020/tests/size_of.rs b/components/layout_2020/tests/size_of.rs
new file mode 100644
index 00000000000..e2cec625075
--- /dev/null
+++ b/components/layout_2020/tests/size_of.rs
@@ -0,0 +1,18 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+#![cfg(target_pointer_width = "64")]
+
+#[macro_use]
+extern crate size_of_test;
+
+use layout::Fragment;
+use layout::SpecificFragmentInfo;
+
+size_of_test!(test_size_of_fragment, Fragment, 176);
+size_of_test!(
+ test_size_of_specific_fragment_info,
+ SpecificFragmentInfo,
+ 24
+);
diff --git a/components/layout_2020/text.rs b/components/layout_2020/text.rs
new file mode 100644
index 00000000000..199bd7e07f2
--- /dev/null
+++ b/components/layout_2020/text.rs
@@ -0,0 +1,798 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+//! Text layout.
+
+use crate::context::LayoutFontContext;
+use crate::fragment::{Fragment, ScannedTextFlags};
+use crate::fragment::{ScannedTextFragmentInfo, SpecificFragmentInfo, UnscannedTextFragmentInfo};
+use crate::inline::{InlineFragmentNodeFlags, InlineFragments};
+use crate::linked_list::split_off_head;
+use app_units::Au;
+use gfx::font::{FontMetrics, FontRef, RunMetrics, ShapingFlags, ShapingOptions};
+use gfx::text::glyph::ByteIndex;
+use gfx::text::text_run::TextRun;
+use gfx::text::util::{self, CompressionMode};
+use range::Range;
+use servo_atoms::Atom;
+use std::borrow::ToOwned;
+use std::collections::LinkedList;
+use std::mem;
+use std::sync::Arc;
+use style::computed_values::text_rendering::T as TextRendering;
+use style::computed_values::white_space::T as WhiteSpace;
+use style::computed_values::word_break::T as WordBreak;
+use style::logical_geometry::{LogicalSize, WritingMode};
+use style::properties::style_structs::Font as FontStyleStruct;
+use style::properties::ComputedValues;
+use style::values::generics::text::LineHeight;
+use style::values::specified::text::{TextTransform, TextTransformCase};
+use unicode_bidi as bidi;
+use unicode_script::{get_script, Script};
+use xi_unicode::LineBreakLeafIter;
+
+/// Returns the concatenated text of a list of unscanned text fragments.
+fn text(fragments: &LinkedList<Fragment>) -> String {
+ // FIXME: Some of this work is later duplicated in split_first_fragment_at_newline_if_necessary
+ // and transform_text. This code should be refactored so that the all the scanning for
+ // newlines is done in a single pass.
+
+ let mut text = String::new();
+
+ for fragment in fragments {
+ if let SpecificFragmentInfo::UnscannedText(ref info) = fragment.specific {
+ if fragment.white_space().preserve_newlines() {
+ text.push_str(&info.text);
+ } else {
+ text.push_str(&info.text.replace("\n", " "));
+ }
+ }
+ }
+ text
+}
+
+/// A stack-allocated object for scanning an inline flow into `TextRun`-containing `TextFragment`s.
+pub struct TextRunScanner {
+ pub clump: LinkedList<Fragment>,
+}
+
+impl TextRunScanner {
+ pub fn new() -> TextRunScanner {
+ TextRunScanner {
+ clump: LinkedList::new(),
+ }
+ }
+
+ pub fn scan_for_runs(
+ &mut self,
+ font_context: &mut LayoutFontContext,
+ mut fragments: LinkedList<Fragment>,
+ ) -> InlineFragments {
+ debug!(
+ "TextRunScanner: scanning {} fragments for text runs...",
+ fragments.len()
+ );
+ debug_assert!(!fragments.is_empty());
+
+ // Calculate bidi embedding levels, so we can split bidirectional fragments for reordering.
+ let text = text(&fragments);
+ let para_level = fragments
+ .front()
+ .unwrap()
+ .style
+ .writing_mode
+ .to_bidi_level();
+ let bidi_info = bidi::BidiInfo::new(&text, Some(para_level));
+
+ // Optimization: If all the text is LTR, don't bother splitting on bidi levels.
+ let bidi_levels = if bidi_info.has_rtl() {
+ Some(&bidi_info.levels[..])
+ } else {
+ None
+ };
+
+ // FIXME(pcwalton): We want to be sure not to allocate multiple times, since this is a
+ // performance-critical spot, but this may overestimate and allocate too much memory.
+ let mut new_fragments = Vec::with_capacity(fragments.len());
+ let mut last_whitespace = false;
+ let mut paragraph_bytes_processed = 0;
+
+ // The first time we process a text run we will set this
+ // linebreaker. There is no way for the linebreaker to start
+ // with an empty state; you must give it its first input immediately.
+ //
+ // This linebreaker is shared across text runs, so we can know if
+ // there is a break at the beginning of a text run or clump, e.g.
+ // in the case of FooBar<span>Baz</span>
+ let mut linebreaker = None;
+
+ while !fragments.is_empty() {
+ // Create a clump.
+ split_first_fragment_at_newline_if_necessary(&mut fragments);
+ self.clump.append(&mut split_off_head(&mut fragments));
+ while !fragments.is_empty() &&
+ self.clump
+ .back()
+ .unwrap()
+ .can_merge_with_fragment(fragments.front().unwrap())
+ {
+ split_first_fragment_at_newline_if_necessary(&mut fragments);
+ self.clump.append(&mut split_off_head(&mut fragments));
+ }
+
+ // Flush that clump to the list of fragments we're building up.
+ last_whitespace = self.flush_clump_to_list(
+ font_context,
+ &mut new_fragments,
+ &mut paragraph_bytes_processed,
+ bidi_levels,
+ last_whitespace,
+ &mut linebreaker,
+ );
+ }
+
+ debug!("TextRunScanner: complete.");
+ InlineFragments {
+ 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.
+ fn flush_clump_to_list(
+ &mut self,
+ mut font_context: &mut LayoutFontContext,
+ out_fragments: &mut Vec<Fragment>,
+ paragraph_bytes_processed: &mut usize,
+ bidi_levels: Option<&[bidi::Level]>,
+ mut last_whitespace: bool,
+ linebreaker: &mut Option<LineBreakLeafIter>,
+ ) -> bool {
+ debug!(
+ "TextRunScanner: flushing {} fragments in range",
+ self.clump.len()
+ );
+
+ debug_assert!(!self.clump.is_empty());
+ match self.clump.front().unwrap().specific {
+ SpecificFragmentInfo::UnscannedText(_) => {},
+ _ => {
+ debug_assert!(
+ self.clump.len() == 1,
+ "WAT: can't coalesce non-text nodes in flush_clump_to_list()!"
+ );
+ out_fragments.push(self.clump.pop_front().unwrap());
+ return false;
+ },
+ }
+
+ // Concatenate all of the transformed strings together, saving the new character indices.
+ let mut mappings: Vec<RunMapping> = Vec::new();
+ let runs = {
+ let font_group;
+ let compression;
+ let text_transform;
+ let letter_spacing;
+ let word_spacing;
+ let text_rendering;
+ let word_break;
+ {
+ let in_fragment = self.clump.front().unwrap();
+ let font_style = in_fragment.style().clone_font();
+ let inherited_text_style = in_fragment.style().get_inherited_text();
+ font_group = font_context.font_group(font_style);
+ compression = match in_fragment.white_space() {
+ WhiteSpace::Normal | WhiteSpace::Nowrap => {
+ CompressionMode::CompressWhitespaceNewline
+ },
+ WhiteSpace::Pre | WhiteSpace::PreWrap => CompressionMode::CompressNone,
+ WhiteSpace::PreLine => CompressionMode::CompressWhitespace,
+ };
+ text_transform = inherited_text_style.text_transform;
+ letter_spacing = inherited_text_style.letter_spacing;
+ word_spacing = inherited_text_style.word_spacing.to_hash_key();
+ text_rendering = inherited_text_style.text_rendering;
+ word_break = inherited_text_style.word_break;
+ }
+
+ // First, transform/compress text of all the nodes.
+ let (mut run_info_list, mut run_info) = (Vec::new(), RunInfo::new());
+ let mut insertion_point = None;
+
+ for (fragment_index, in_fragment) in self.clump.iter().enumerate() {
+ debug!(" flushing {:?}", in_fragment);
+ let mut mapping = RunMapping::new(&run_info_list[..], fragment_index);
+ let text;
+ let selection;
+ match in_fragment.specific {
+ SpecificFragmentInfo::UnscannedText(ref text_fragment_info) => {
+ text = &text_fragment_info.text;
+ selection = text_fragment_info.selection;
+ },
+ _ => panic!("Expected an unscanned text fragment!"),
+ };
+ insertion_point = match selection {
+ Some(range) if range.is_empty() => {
+ // `range` is the range within the current fragment. To get the range
+ // within the text run, offset it by the length of the preceding fragments.
+ Some(range.begin() + ByteIndex(run_info.text.len() as isize))
+ },
+ _ => None,
+ };
+
+ let (mut start_position, mut end_position) = (0, 0);
+ for (byte_index, character) in text.char_indices() {
+ if !character.is_control() {
+ let font = font_group
+ .borrow_mut()
+ .find_by_codepoint(&mut font_context, character);
+
+ let bidi_level = match bidi_levels {
+ Some(levels) => levels[*paragraph_bytes_processed],
+ None => bidi::Level::ltr(),
+ };
+
+ // Break the run if the new character has a different explicit script than the
+ // previous characters.
+ //
+ // TODO: Special handling of paired punctuation characters.
+ // http://www.unicode.org/reports/tr24/#Common
+ let script = get_script(character);
+ let compatible_script = is_compatible(script, run_info.script);
+ if compatible_script && !is_specific(run_info.script) && is_specific(script)
+ {
+ run_info.script = script;
+ }
+
+ let selected = match selection {
+ Some(range) => range.contains(ByteIndex(byte_index as isize)),
+ None => false,
+ };
+
+ // Now, if necessary, flush the mapping we were building up.
+ let flush_run = !run_info.has_font(&font) ||
+ run_info.bidi_level != bidi_level ||
+ !compatible_script;
+ let new_mapping_needed = flush_run || mapping.selected != selected;
+
+ if new_mapping_needed {
+ // We ignore empty mappings at the very start of a fragment.
+ // The run info values are uninitialized at this point so
+ // flushing an empty mapping is pointless.
+ if end_position > 0 {
+ mapping.flush(
+ &mut mappings,
+ &mut run_info,
+ &**text,
+ compression,
+ text_transform,
+ &mut last_whitespace,
+ &mut start_position,
+ end_position,
+ );
+ }
+ if run_info.text.len() > 0 {
+ if flush_run {
+ run_info.flush(&mut run_info_list, &mut insertion_point);
+ run_info = RunInfo::new();
+ }
+ mapping = RunMapping::new(&run_info_list[..], fragment_index);
+ }
+ run_info.font = font;
+ run_info.bidi_level = bidi_level;
+ run_info.script = script;
+ mapping.selected = selected;
+ }
+ }
+
+ // Consume this character.
+ end_position += character.len_utf8();
+ *paragraph_bytes_processed += character.len_utf8();
+ }
+
+ // Flush the last mapping we created for this fragment to the list.
+ mapping.flush(
+ &mut mappings,
+ &mut run_info,
+ &**text,
+ compression,
+ text_transform,
+ &mut last_whitespace,
+ &mut start_position,
+ end_position,
+ );
+ }
+
+ // Push the final run info.
+ run_info.flush(&mut run_info_list, &mut insertion_point);
+
+ // Per CSS 2.1 § 16.4, "when the resultant space between two characters is not the same
+ // as the default space, user agents should not use ligatures." This ensures that, for
+ // example, `finally` with a wide `letter-spacing` renders as `f i n a l l y` and not
+ // `fi n a l l y`.
+ let mut flags = ShapingFlags::empty();
+ if letter_spacing.0.px() != 0. {
+ flags.insert(ShapingFlags::IGNORE_LIGATURES_SHAPING_FLAG);
+ }
+ if text_rendering == TextRendering::Optimizespeed {
+ flags.insert(ShapingFlags::IGNORE_LIGATURES_SHAPING_FLAG);
+ flags.insert(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG)
+ }
+ if word_break == WordBreak::KeepAll {
+ flags.insert(ShapingFlags::KEEP_ALL_FLAG);
+ }
+ let options = ShapingOptions {
+ letter_spacing: if letter_spacing.0.px() == 0. {
+ None
+ } else {
+ Some(Au::from(letter_spacing.0))
+ },
+ word_spacing,
+ script: Script::Common,
+ flags: flags,
+ };
+
+ let mut result = Vec::with_capacity(run_info_list.len());
+ for run_info in run_info_list {
+ let mut options = options;
+ options.script = run_info.script;
+ if run_info.bidi_level.is_rtl() {
+ options.flags.insert(ShapingFlags::RTL_FLAG);
+ }
+
+ // If no font is found (including fallbacks), there's no way we can render.
+ let font = run_info
+ .font
+ .or_else(|| font_group.borrow_mut().first(&mut font_context))
+ .expect("No font found for text run!");
+
+ let (run, break_at_zero) = TextRun::new(
+ &mut *font.borrow_mut(),
+ run_info.text,
+ &options,
+ run_info.bidi_level,
+ linebreaker,
+ );
+ result.push((
+ ScannedTextRun {
+ run: Arc::new(run),
+ insertion_point: run_info.insertion_point,
+ },
+ break_at_zero,
+ ))
+ }
+ result
+ };
+
+ // Make new fragments with the runs and adjusted text indices.
+ debug!("TextRunScanner: pushing {} fragment(s)", self.clump.len());
+ let mut mappings = mappings.into_iter().peekable();
+ let mut prev_fragments_to_meld = Vec::new();
+
+ for (logical_offset, old_fragment) in mem::replace(&mut self.clump, LinkedList::new())
+ .into_iter()
+ .enumerate()
+ {
+ let mut is_first_mapping_of_this_old_fragment = true;
+ loop {
+ match mappings.peek() {
+ Some(mapping) if mapping.old_fragment_index == logical_offset => {},
+ Some(_) | None => {
+ if is_first_mapping_of_this_old_fragment {
+ // There were no mappings for this unscanned fragment. Transfer its
+ // flags to the previous/next sibling elements instead.
+ if let Some(ref mut last_fragment) = out_fragments.last_mut() {
+ last_fragment.meld_with_next_inline_fragment(&old_fragment);
+ }
+ prev_fragments_to_meld.push(old_fragment);
+ }
+ break;
+ },
+ };
+ let mapping = mappings.next().unwrap();
+ let (scanned_run, break_at_zero) = runs[mapping.text_run_index].clone();
+
+ let mut byte_range = Range::new(
+ ByteIndex(mapping.byte_range.begin() as isize),
+ ByteIndex(mapping.byte_range.length() as isize),
+ );
+
+ let mut flags = ScannedTextFlags::empty();
+ if !break_at_zero && mapping.byte_range.begin() == 0 {
+ // If this is the first segment of the text run,
+ // and the text run doesn't break at zero, suppress line breaks
+ flags.insert(ScannedTextFlags::SUPPRESS_LINE_BREAK_BEFORE)
+ }
+ let text_size = old_fragment.border_box.size;
+
+ let requires_line_break_afterward_if_wrapping_on_newlines = scanned_run.run.text
+ [mapping.byte_range.begin()..mapping.byte_range.end()]
+ .ends_with('\n');
+
+ if requires_line_break_afterward_if_wrapping_on_newlines {
+ byte_range.extend_by(ByteIndex(-1)); // Trim the '\n'
+ flags.insert(
+ ScannedTextFlags::REQUIRES_LINE_BREAK_AFTERWARD_IF_WRAPPING_ON_NEWLINES,
+ );
+ }
+
+ if mapping.selected {
+ flags.insert(ScannedTextFlags::SELECTED);
+ }
+
+ let insertion_point =
+ if mapping.contains_insertion_point(scanned_run.insertion_point) {
+ scanned_run.insertion_point
+ } else {
+ None
+ };
+
+ let mut new_text_fragment_info = Box::new(ScannedTextFragmentInfo::new(
+ scanned_run.run,
+ byte_range,
+ text_size,
+ insertion_point,
+ flags,
+ ));
+
+ let new_metrics = new_text_fragment_info.run.metrics_for_range(&byte_range);
+ let writing_mode = old_fragment.style.writing_mode;
+ let bounding_box_size = bounding_box_for_run_metrics(&new_metrics, writing_mode);
+ new_text_fragment_info.content_size = bounding_box_size;
+
+ let mut new_fragment = old_fragment.transform(
+ bounding_box_size,
+ SpecificFragmentInfo::ScannedText(new_text_fragment_info),
+ );
+
+ let is_last_mapping_of_this_old_fragment = match mappings.peek() {
+ Some(mapping) if mapping.old_fragment_index == logical_offset => false,
+ _ => true,
+ };
+
+ if let Some(ref mut context) = new_fragment.inline_context {
+ for node in &mut context.nodes {
+ if !is_last_mapping_of_this_old_fragment {
+ node.flags
+ .remove(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT);
+ }
+ if !is_first_mapping_of_this_old_fragment {
+ node.flags
+ .remove(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT);
+ }
+ }
+ }
+
+ for prev_fragment in prev_fragments_to_meld.drain(..) {
+ new_fragment.meld_with_prev_inline_fragment(&prev_fragment);
+ }
+
+ is_first_mapping_of_this_old_fragment = false;
+ out_fragments.push(new_fragment)
+ }
+ }
+
+ last_whitespace
+ }
+}
+
+#[inline]
+fn bounding_box_for_run_metrics(
+ metrics: &RunMetrics,
+ writing_mode: WritingMode,
+) -> LogicalSize<Au> {
+ // TODO: When the text-orientation property is supported, the block and inline directions may
+ // be swapped for horizontal glyphs in vertical lines.
+ LogicalSize::new(
+ writing_mode,
+ metrics.bounding_box.size.width,
+ metrics.bounding_box.size.height,
+ )
+}
+
+/// Returns the metrics of the font represented by the given `FontStyleStruct`.
+///
+/// `#[inline]` because often the caller only needs a few fields from the font metrics.
+///
+/// # Panics
+///
+/// Panics if no font can be found for the given font style.
+#[inline]
+pub fn font_metrics_for_style(
+ mut font_context: &mut LayoutFontContext,
+ style: crate::ServoArc<FontStyleStruct>,
+) -> FontMetrics {
+ let font_group = font_context.font_group(style);
+ let font = font_group.borrow_mut().first(&mut font_context);
+ let font = font.as_ref().unwrap().borrow();
+
+ font.metrics.clone()
+}
+
+/// 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.size();
+ match style.get_inherited_text().line_height {
+ LineHeight::Normal => Au::from(metrics.line_gap),
+ LineHeight::Number(l) => font_size.scale_by(l.0),
+ LineHeight::Length(l) => Au::from(l),
+ }
+}
+
+fn split_first_fragment_at_newline_if_necessary(fragments: &mut LinkedList<Fragment>) {
+ if fragments.is_empty() {
+ return;
+ }
+
+ let new_fragment = {
+ let first_fragment = fragments.front_mut().unwrap();
+ let string_before;
+ let selection_before;
+ {
+ if !first_fragment.white_space().preserve_newlines() {
+ return;
+ }
+
+ let unscanned_text_fragment_info = match first_fragment.specific {
+ SpecificFragmentInfo::UnscannedText(ref mut unscanned_text_fragment_info) => {
+ unscanned_text_fragment_info
+ },
+ _ => return,
+ };
+
+ let position = match unscanned_text_fragment_info.text.find('\n') {
+ Some(position) if position < unscanned_text_fragment_info.text.len() - 1 => {
+ position
+ },
+ Some(_) | None => return,
+ };
+
+ string_before = unscanned_text_fragment_info.text[..(position + 1)].to_owned();
+ unscanned_text_fragment_info.text = unscanned_text_fragment_info.text[(position + 1)..]
+ .to_owned()
+ .into_boxed_str();
+ let offset = ByteIndex(string_before.len() as isize);
+ match unscanned_text_fragment_info.selection {
+ Some(ref mut selection) if selection.begin() >= offset => {
+ // Selection is entirely in the second fragment.
+ selection_before = None;
+ selection.shift_by(-offset);
+ },
+ Some(ref mut selection) if selection.end() > offset => {
+ // Selection is split across two fragments.
+ selection_before = Some(Range::new(selection.begin(), offset));
+ *selection = Range::new(ByteIndex(0), selection.end() - offset);
+ },
+ _ => {
+ // Selection is entirely in the first fragment.
+ selection_before = unscanned_text_fragment_info.selection;
+ unscanned_text_fragment_info.selection = None;
+ },
+ };
+ }
+ first_fragment.transform(
+ first_fragment.border_box.size,
+ SpecificFragmentInfo::UnscannedText(Box::new(UnscannedTextFragmentInfo::new(
+ string_before.into_boxed_str(),
+ selection_before,
+ ))),
+ )
+ };
+
+ fragments.push_front(new_fragment);
+}
+
+/// Information about a text run that we're about to create. This is used in `scan_for_runs`.
+struct RunInfo {
+ /// The text that will go in this text run.
+ text: String,
+ /// The insertion point in this text run, if applicable.
+ insertion_point: Option<ByteIndex>,
+ /// The font that the text should be rendered with.
+ font: Option<FontRef>,
+ /// The bidirection embedding level of this text run.
+ bidi_level: bidi::Level,
+ /// The Unicode script property of this text run.
+ script: Script,
+}
+
+impl RunInfo {
+ fn new() -> RunInfo {
+ RunInfo {
+ text: String::new(),
+ insertion_point: None,
+ font: None,
+ bidi_level: bidi::Level::ltr(),
+ script: Script::Common,
+ }
+ }
+
+ /// Finish processing this RunInfo and add it to the "done" list.
+ ///
+ /// * `insertion_point`: The position of the insertion point, in characters relative to the start
+ /// of this text run.
+ fn flush(mut self, list: &mut Vec<RunInfo>, insertion_point: &mut Option<ByteIndex>) {
+ if let Some(idx) = *insertion_point {
+ let char_len = ByteIndex(self.text.len() as isize);
+ if idx <= char_len {
+ // The insertion point is in this text run.
+ self.insertion_point = insertion_point.take()
+ } else {
+ // Continue looking for the insertion point in the next text run.
+ *insertion_point = Some(idx - char_len)
+ }
+ }
+ list.push(self);
+ }
+
+ fn has_font(&self, font: &Option<FontRef>) -> bool {
+ fn identifier(font: &Option<FontRef>) -> Option<Atom> {
+ font.as_ref().map(|f| f.borrow().identifier())
+ }
+
+ identifier(&self.font) == identifier(font)
+ }
+}
+
+/// A mapping from a portion of an unscanned text fragment to the text run we're going to create
+/// for it.
+#[derive(Clone, Copy, Debug)]
+struct RunMapping {
+ /// The range of byte indices within the text fragment.
+ byte_range: Range<usize>,
+ /// The index of the unscanned text fragment that this mapping corresponds to.
+ old_fragment_index: usize,
+ /// The index of the text run we're going to create.
+ text_run_index: usize,
+ /// Is the text in this fragment selected?
+ selected: bool,
+}
+
+impl RunMapping {
+ /// Given the current set of text runs, creates a run mapping for the next fragment.
+ /// `run_info_list` describes the set of runs we've seen already.
+ fn new(run_info_list: &[RunInfo], fragment_index: usize) -> RunMapping {
+ RunMapping {
+ byte_range: Range::new(0, 0),
+ old_fragment_index: fragment_index,
+ text_run_index: run_info_list.len(),
+ selected: false,
+ }
+ }
+
+ /// Flushes this run mapping to the list. `run_info` describes the text run that we're
+ /// currently working on. `text` refers to the text of this fragment.
+ fn flush(
+ mut self,
+ mappings: &mut Vec<RunMapping>,
+ run_info: &mut RunInfo,
+ text: &str,
+ compression: CompressionMode,
+ text_transform: TextTransform,
+ last_whitespace: &mut bool,
+ start_position: &mut usize,
+ end_position: usize,
+ ) {
+ let was_empty = *start_position == end_position;
+ let old_byte_length = run_info.text.len();
+ *last_whitespace = util::transform_text(
+ &text[(*start_position)..end_position],
+ compression,
+ *last_whitespace,
+ &mut run_info.text,
+ );
+
+ // Account for `text-transform`. (Confusingly, this is not handled in "text
+ // transformation" above, but we follow Gecko in the naming.)
+ let is_first_run = *start_position == 0;
+ apply_style_transform_if_necessary(
+ &mut run_info.text,
+ old_byte_length,
+ text_transform,
+ *last_whitespace,
+ is_first_run,
+ );
+ *start_position = end_position;
+
+ let new_byte_length = run_info.text.len();
+ let is_empty = new_byte_length == old_byte_length;
+
+ // Don't save mappings that contain only discarded characters.
+ // (But keep ones that contained no characters to begin with, since they might have been
+ // generated by an empty flow to draw its borders/padding/insertion point.)
+ if is_empty && !was_empty {
+ return;
+ }
+
+ self.byte_range = Range::new(old_byte_length, new_byte_length - old_byte_length);
+ mappings.push(self)
+ }
+
+ /// Is the insertion point for this text run within this mapping?
+ ///
+ /// NOTE: We treat the range as inclusive at both ends, since the insertion point can lie
+ /// before the first character *or* after the last character, and should be drawn even if the
+ /// text is empty.
+ fn contains_insertion_point(&self, insertion_point: Option<ByteIndex>) -> bool {
+ match insertion_point.map(ByteIndex::to_usize) {
+ None => false,
+ Some(idx) => self.byte_range.begin() <= idx && idx <= self.byte_range.end(),
+ }
+ }
+}
+
+/// Accounts for `text-transform`.
+///
+/// FIXME(#4311, pcwalton): Title-case mapping can change length of the string;
+/// case mapping should be language-specific; `full-width`;
+/// use graphemes instead of characters.
+fn apply_style_transform_if_necessary(
+ string: &mut String,
+ first_character_position: usize,
+ text_transform: TextTransform,
+ last_whitespace: bool,
+ is_first_run: bool,
+) {
+ match text_transform.case_ {
+ TextTransformCase::None => {},
+ TextTransformCase::Uppercase => {
+ let original = string[first_character_position..].to_owned();
+ string.truncate(first_character_position);
+ for ch in original.chars().flat_map(|ch| ch.to_uppercase()) {
+ string.push(ch);
+ }
+ },
+ TextTransformCase::Lowercase => {
+ let original = string[first_character_position..].to_owned();
+ string.truncate(first_character_position);
+ for ch in original.chars().flat_map(|ch| ch.to_lowercase()) {
+ string.push(ch);
+ }
+ },
+ TextTransformCase::Capitalize => {
+ let original = string[first_character_position..].to_owned();
+ string.truncate(first_character_position);
+
+ let mut capitalize_next_letter = is_first_run || last_whitespace;
+ for character in original.chars() {
+ // FIXME(#4311, pcwalton): Should be the CSS/Unicode notion of a *typographic
+ // letter unit*, not an *alphabetic* character:
+ //
+ // http://dev.w3.org/csswg/css-text/#typographic-letter-unit
+ if capitalize_next_letter && character.is_alphabetic() {
+ string.push(character.to_uppercase().next().unwrap());
+ capitalize_next_letter = false;
+ continue;
+ }
+
+ string.push(character);
+
+ // FIXME(#4311, pcwalton): Try UAX29 instead of just whitespace.
+ if character.is_whitespace() {
+ capitalize_next_letter = true
+ }
+ }
+ },
+ }
+}
+
+#[derive(Clone)]
+struct ScannedTextRun {
+ run: Arc<TextRun>,
+ insertion_point: Option<ByteIndex>,
+}
+
+/// Can a character with script `b` continue a text run with script `a`?
+fn is_compatible(a: Script, b: Script) -> bool {
+ a == b || !is_specific(a) || !is_specific(b)
+}
+
+/// Returns true if the script is not invalid or inherited.
+fn is_specific(script: Script) -> bool {
+ script != Script::Common && script != Script::Inherited
+}
diff --git a/components/layout_2020/traversal.rs b/components/layout_2020/traversal.rs
new file mode 100644
index 00000000000..e968c184341
--- /dev/null
+++ b/components/layout_2020/traversal.rs
@@ -0,0 +1,350 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+//! Traversals over the DOM and flow trees, running the layout computations.
+
+use crate::construct::FlowConstructor;
+use crate::context::LayoutContext;
+use crate::display_list::DisplayListBuildState;
+use crate::flow::{Flow, FlowFlags, GetBaseFlow, ImmutableFlowUtils};
+use crate::wrapper::ThreadSafeLayoutNodeHelpers;
+use crate::wrapper::{GetRawData, LayoutNodeLayoutData};
+use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutNode};
+use servo_config::opts;
+use style::context::{SharedStyleContext, StyleContext};
+use style::data::ElementData;
+use style::dom::{NodeInfo, TElement, TNode};
+use style::selector_parser::RestyleDamage;
+use style::servo::restyle_damage::ServoRestyleDamage;
+use style::traversal::PerLevelTraversalData;
+use style::traversal::{recalc_style_at, DomTraversal};
+
+pub struct RecalcStyleAndConstructFlows<'a> {
+ context: LayoutContext<'a>,
+}
+
+impl<'a> RecalcStyleAndConstructFlows<'a> {
+ /// Creates a traversal context, taking ownership of the shared layout context.
+ pub fn new(context: LayoutContext<'a>) -> Self {
+ RecalcStyleAndConstructFlows { context: context }
+ }
+
+ /// Consumes this traversal context, returning ownership of the shared layout
+ /// context to the caller.
+ pub fn destroy(self) -> LayoutContext<'a> {
+ self.context
+ }
+}
+
+#[allow(unsafe_code)]
+impl<'a, E> DomTraversal<E> for RecalcStyleAndConstructFlows<'a>
+where
+ E: TElement,
+ E::ConcreteNode: LayoutNode,
+ E::FontMetricsProvider: Send,
+{
+ fn process_preorder<F>(
+ &self,
+ traversal_data: &PerLevelTraversalData,
+ context: &mut StyleContext<E>,
+ node: E::ConcreteNode,
+ note_child: F,
+ ) where
+ F: FnMut(E::ConcreteNode),
+ {
+ // FIXME(pcwalton): Stop allocating here. Ideally this should just be
+ // done by the HTML parser.
+ unsafe { node.initialize_data() };
+
+ if !node.is_text_node() {
+ let el = node.as_element().unwrap();
+ let mut data = el.mutate_data().unwrap();
+ recalc_style_at(self, traversal_data, context, el, &mut data, note_child);
+ }
+ }
+
+ fn process_postorder(&self, _style_context: &mut StyleContext<E>, node: E::ConcreteNode) {
+ construct_flows_at(&self.context, node);
+ }
+
+ fn text_node_needs_traversal(node: E::ConcreteNode, parent_data: &ElementData) -> bool {
+ // Text nodes never need styling. However, there are two cases they may need
+ // flow construction:
+ // (1) They child doesn't yet have layout data (preorder traversal initializes it).
+ // (2) The parent element has restyle damage (so the text flow also needs fixup).
+ node.get_raw_data().is_none() || !parent_data.damage.is_empty()
+ }
+
+ fn shared_context(&self) -> &SharedStyleContext {
+ &self.context.style_context
+ }
+}
+
+/// A top-down traversal.
+pub trait PreorderFlowTraversal {
+ /// The operation to perform. Return true to continue or false to stop.
+ fn process(&self, flow: &mut dyn Flow);
+
+ /// Returns true if this node should be processed and false if neither this node nor its
+ /// descendants should be processed.
+ fn should_process_subtree(&self, _flow: &mut dyn Flow) -> bool {
+ true
+ }
+
+ /// Returns true if this node must be processed in-order. If this returns false,
+ /// we skip the operation for this node, but continue processing the descendants.
+ /// This is called *after* parent nodes are visited.
+ fn should_process(&self, _flow: &mut dyn Flow) -> bool {
+ true
+ }
+
+ /// Traverses the tree in preorder.
+ fn traverse(&self, flow: &mut dyn Flow) {
+ if !self.should_process_subtree(flow) {
+ return;
+ }
+ if self.should_process(flow) {
+ self.process(flow);
+ }
+ for kid in flow.mut_base().child_iter_mut() {
+ self.traverse(kid);
+ }
+ }
+
+ /// Traverse the Absolute flow tree in preorder.
+ ///
+ /// Traverse all your direct absolute descendants, who will then traverse
+ /// their direct absolute descendants.
+ ///
+ /// Return true if the traversal is to continue or false to stop.
+ fn traverse_absolute_flows(&self, flow: &mut dyn Flow) {
+ if self.should_process(flow) {
+ self.process(flow);
+ }
+ for descendant_link in flow.mut_base().abs_descendants.iter() {
+ self.traverse_absolute_flows(descendant_link)
+ }
+ }
+}
+
+/// 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(&self, flow: &mut dyn Flow);
+
+ /// 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(&self, _flow: &mut dyn Flow) -> bool {
+ true
+ }
+
+ /// Traverses the tree in postorder.
+ fn traverse(&self, flow: &mut dyn Flow) {
+ for kid in flow.mut_base().child_iter_mut() {
+ self.traverse(kid);
+ }
+ if self.should_process(flow) {
+ self.process(flow);
+ }
+ }
+}
+
+/// An in-order (sequential only) traversal.
+pub trait InorderFlowTraversal {
+ /// The operation to perform. Returns the level of the tree we're at.
+ fn process(&mut self, flow: &mut dyn Flow, level: u32);
+
+ /// Returns true if this node should be processed and false if neither this node nor its
+ /// descendants should be processed.
+ fn should_process_subtree(&mut self, _flow: &mut dyn Flow) -> bool {
+ true
+ }
+
+ /// Traverses the tree in-order.
+ fn traverse(&mut self, flow: &mut dyn Flow, level: u32) {
+ if !self.should_process_subtree(flow) {
+ return;
+ }
+ self.process(flow, level);
+ for kid in flow.mut_base().child_iter_mut() {
+ self.traverse(kid, level + 1);
+ }
+ }
+}
+
+/// A bottom-up, parallelizable traversal.
+pub trait PostorderNodeMutTraversal<ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode> {
+ /// The operation to perform. Return true to continue or false to stop.
+ fn process(&mut self, node: &ConcreteThreadSafeLayoutNode);
+}
+
+/// The flow construction traversal, which builds flows for styled nodes.
+#[inline]
+#[allow(unsafe_code)]
+fn construct_flows_at<N>(context: &LayoutContext, node: N)
+where
+ N: LayoutNode,
+{
+ debug!("construct_flows_at: {:?}", node);
+
+ // Construct flows for this node.
+ {
+ let tnode = node.to_threadsafe();
+
+ // Always reconstruct if incremental layout is turned off.
+ let nonincremental_layout = opts::get().nonincremental_layout;
+ if nonincremental_layout ||
+ tnode.restyle_damage() != RestyleDamage::empty() ||
+ node.as_element()
+ .map_or(false, |el| el.has_dirty_descendants())
+ {
+ let mut flow_constructor = FlowConstructor::new(context);
+ if nonincremental_layout || !flow_constructor.repair_if_possible(&tnode) {
+ flow_constructor.process(&tnode);
+ debug!(
+ "Constructed flow for {:?}: {:x}",
+ tnode,
+ tnode.flow_debug_id()
+ );
+ }
+ }
+
+ tnode
+ .mutate_layout_data()
+ .unwrap()
+ .flags
+ .insert(crate::data::LayoutDataFlags::HAS_BEEN_TRAVERSED);
+ }
+
+ if let Some(el) = node.as_element() {
+ unsafe {
+ el.unset_dirty_descendants();
+ }
+ }
+}
+
+/// 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 BubbleISizes<'a> {
+ pub layout_context: &'a LayoutContext<'a>,
+}
+
+impl<'a> PostorderFlowTraversal for BubbleISizes<'a> {
+ #[inline]
+ fn process(&self, flow: &mut dyn Flow) {
+ flow.bubble_inline_sizes();
+ flow.mut_base()
+ .restyle_damage
+ .remove(ServoRestyleDamage::BUBBLE_ISIZES);
+ }
+
+ #[inline]
+ fn should_process(&self, flow: &mut dyn Flow) -> bool {
+ flow.base()
+ .restyle_damage
+ .contains(ServoRestyleDamage::BUBBLE_ISIZES)
+ }
+}
+
+/// The assign-inline-sizes traversal. In Gecko this corresponds to `Reflow`.
+#[derive(Clone, Copy)]
+pub struct AssignISizes<'a> {
+ pub layout_context: &'a LayoutContext<'a>,
+}
+
+impl<'a> PreorderFlowTraversal for AssignISizes<'a> {
+ #[inline]
+ fn process(&self, flow: &mut dyn Flow) {
+ flow.assign_inline_sizes(self.layout_context);
+ }
+
+ #[inline]
+ fn should_process(&self, flow: &mut dyn Flow) -> bool {
+ flow.base()
+ .restyle_damage
+ .intersects(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW)
+ }
+}
+
+/// 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 and computes
+/// positions. In Gecko this corresponds to `Reflow`.
+#[derive(Clone, Copy)]
+pub struct AssignBSizes<'a> {
+ pub layout_context: &'a LayoutContext<'a>,
+}
+
+impl<'a> PostorderFlowTraversal for AssignBSizes<'a> {
+ #[inline]
+ fn process(&self, flow: &mut dyn Flow) {
+ // Can't do anything with anything that floats might flow through until we reach their
+ // inorder parent.
+ //
+ // NB: We must return without resetting the restyle bits for these, as we haven't actually
+ // reflowed anything!
+ if flow.floats_might_flow_through() {
+ return;
+ }
+
+ flow.assign_block_size(self.layout_context);
+ }
+
+ #[inline]
+ fn should_process(&self, flow: &mut dyn Flow) -> bool {
+ let base = flow.base();
+ base.restyle_damage.intersects(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW) &&
+ // The fragmentation countainer is responsible for calling
+ // Flow::fragment recursively
+ !base.flags.contains(FlowFlags::CAN_BE_FRAGMENTED)
+ }
+}
+
+pub struct ComputeStackingRelativePositions<'a> {
+ pub layout_context: &'a LayoutContext<'a>,
+}
+
+impl<'a> PreorderFlowTraversal for ComputeStackingRelativePositions<'a> {
+ #[inline]
+ fn should_process_subtree(&self, flow: &mut dyn Flow) -> bool {
+ flow.base()
+ .restyle_damage
+ .contains(ServoRestyleDamage::REPOSITION)
+ }
+
+ #[inline]
+ fn process(&self, flow: &mut dyn Flow) {
+ flow.compute_stacking_relative_position(self.layout_context);
+ flow.mut_base()
+ .restyle_damage
+ .remove(ServoRestyleDamage::REPOSITION)
+ }
+}
+
+pub struct BuildDisplayList<'a> {
+ pub state: DisplayListBuildState<'a>,
+}
+
+impl<'a> BuildDisplayList<'a> {
+ #[inline]
+ pub fn traverse(&mut self, flow: &mut dyn Flow) {
+ let parent_stacking_context_id = self.state.current_stacking_context_id;
+ self.state.current_stacking_context_id = flow.base().stacking_context_id;
+
+ let parent_clipping_and_scrolling = self.state.current_clipping_and_scrolling;
+ self.state.current_clipping_and_scrolling = flow.clipping_and_scrolling();
+
+ flow.build_display_list(&mut self.state);
+ flow.mut_base()
+ .restyle_damage
+ .remove(ServoRestyleDamage::REPAINT);
+
+ for kid in flow.mut_base().child_iter_mut() {
+ self.traverse(kid);
+ }
+
+ self.state.current_stacking_context_id = parent_stacking_context_id;
+ self.state.current_clipping_and_scrolling = parent_clipping_and_scrolling;
+ }
+}
diff --git a/components/layout_2020/wrapper.rs b/components/layout_2020/wrapper.rs
new file mode 100644
index 00000000000..063d7f3d25d
--- /dev/null
+++ b/components/layout_2020/wrapper.rs
@@ -0,0 +1,174 @@
+/* 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 https://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 `LayoutDom` 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 thread 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:
+//!
+//! * 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()`.
+
+#![allow(unsafe_code)]
+
+use crate::data::{LayoutData, LayoutDataFlags, StyleAndLayoutData};
+use atomic_refcell::{AtomicRef, AtomicRefMut};
+use script_layout_interface::wrapper_traits::GetLayoutData;
+use script_layout_interface::wrapper_traits::{ThreadSafeLayoutElement, ThreadSafeLayoutNode};
+use style::dom::{NodeInfo, TNode};
+use style::selector_parser::RestyleDamage;
+use style::values::computed::counters::ContentItem;
+use style::values::generics::counters::Content;
+
+pub trait LayoutNodeLayoutData {
+ /// Similar to borrow_data*, but returns the full PersistentLayoutData rather
+ /// than only the style::data::ElementData.
+ fn borrow_layout_data(&self) -> Option<AtomicRef<LayoutData>>;
+ fn mutate_layout_data(&self) -> Option<AtomicRefMut<LayoutData>>;
+ fn flow_debug_id(self) -> usize;
+}
+
+impl<T: GetLayoutData> LayoutNodeLayoutData for T {
+ fn borrow_layout_data(&self) -> Option<AtomicRef<LayoutData>> {
+ self.get_raw_data().map(|d| d.layout_data.borrow())
+ }
+
+ fn mutate_layout_data(&self) -> Option<AtomicRefMut<LayoutData>> {
+ self.get_raw_data().map(|d| d.layout_data.borrow_mut())
+ }
+
+ fn flow_debug_id(self) -> usize {
+ self.borrow_layout_data()
+ .map_or(0, |d| d.flow_construction_result.debug_id())
+ }
+}
+
+pub trait GetRawData {
+ fn get_raw_data(&self) -> Option<&StyleAndLayoutData>;
+}
+
+impl<T: GetLayoutData> GetRawData for T {
+ fn get_raw_data(&self) -> Option<&StyleAndLayoutData> {
+ self.get_style_and_layout_data().map(|opaque| {
+ let container = opaque.ptr.as_ptr() as *mut StyleAndLayoutData;
+ unsafe { &*container }
+ })
+ }
+}
+
+pub trait ThreadSafeLayoutNodeHelpers {
+ /// Returns the layout data flags for this node.
+ fn flags(self) -> LayoutDataFlags;
+
+ /// Adds the given flags to this node.
+ fn insert_flags(self, new_flags: LayoutDataFlags);
+
+ /// Removes the given flags from this node.
+ fn remove_flags(self, flags: LayoutDataFlags);
+
+ /// If this is a text node, generated content, or a form element, copies out
+ /// its content. Otherwise, panics.
+ ///
+ /// FIXME(pcwalton): This might have too much copying and/or allocation. Profile this.
+ fn text_content(&self) -> TextContent;
+
+ /// The RestyleDamage from any restyling, or RestyleDamage::rebuild_and_reflow() if this
+ /// is the first time layout is visiting this node. We implement this here, rather than
+ /// with the rest of the wrapper layer, because we need layout code to determine whether
+ /// layout has visited the node.
+ fn restyle_damage(self) -> RestyleDamage;
+}
+
+impl<T: ThreadSafeLayoutNode> ThreadSafeLayoutNodeHelpers for T {
+ fn flags(self) -> LayoutDataFlags {
+ self.borrow_layout_data().as_ref().unwrap().flags
+ }
+
+ fn insert_flags(self, new_flags: LayoutDataFlags) {
+ self.mutate_layout_data().unwrap().flags.insert(new_flags);
+ }
+
+ fn remove_flags(self, flags: LayoutDataFlags) {
+ self.mutate_layout_data().unwrap().flags.remove(flags);
+ }
+
+ fn text_content(&self) -> TextContent {
+ if self.get_pseudo_element_type().is_replaced_content() {
+ let style = self.as_element().unwrap().resolved_style();
+
+ return TextContent::GeneratedContent(match style.as_ref().get_counters().content {
+ Content::Items(ref value) => value.to_vec(),
+ _ => vec![],
+ });
+ }
+
+ TextContent::Text(self.node_text_content().into_boxed_str())
+ }
+
+ fn restyle_damage(self) -> RestyleDamage {
+ // We need the underlying node to potentially access the parent in the
+ // case of text nodes. This is safe as long as we don't let the parent
+ // escape and never access its descendants.
+ let mut node = unsafe { self.unsafe_get() };
+
+ // If this is a text node, use the parent element, since that's what
+ // controls our style.
+ if node.is_text_node() {
+ node = node.parent_node().unwrap();
+ debug_assert!(node.is_element());
+ }
+
+ let damage = {
+ let data = node.get_raw_data().unwrap();
+
+ if !data
+ .layout_data
+ .borrow()
+ .flags
+ .contains(crate::data::LayoutDataFlags::HAS_BEEN_TRAVERSED)
+ {
+ // We're reflowing a node that was styled for the first time and
+ // has never been visited by layout. Return rebuild_and_reflow,
+ // because that's what the code expects.
+ RestyleDamage::rebuild_and_reflow()
+ } else {
+ data.style_data.element_data.borrow().damage
+ }
+ };
+
+ damage
+ }
+}
+
+pub enum TextContent {
+ Text(Box<str>),
+ GeneratedContent(Vec<ContentItem>),
+}
+
+impl TextContent {
+ pub fn is_empty(&self) -> bool {
+ match *self {
+ TextContent::Text(_) => false,
+ TextContent::GeneratedContent(ref content) => content.is_empty(),
+ }
+ }
+}
diff --git a/components/layout_thread_2020/Cargo.toml b/components/layout_thread_2020/Cargo.toml
index 8711cec2638..3cc6f57136e 100644
--- a/components/layout_thread_2020/Cargo.toml
+++ b/components/layout_thread_2020/Cargo.toml
@@ -11,18 +11,43 @@ name = "layout_thread"
path = "lib.rs"
[dependencies]
+app_units = "0.7"
+atomic_refcell = "0.1"
crossbeam-channel = "0.3"
+embedder_traits = {path = "../embedder_traits"}
euclid = "0.20"
+fnv = "1.0"
+fxhash = "0.2"
gfx = {path = "../gfx"}
+gfx_traits = {path = "../gfx_traits"}
+histogram = "0.6.8"
+html5ever = "0.23"
ipc-channel = "0.11"
layout = {path = "../layout_2020", package = "layout_2020"}
layout_traits = {path = "../layout_traits"}
+lazy_static = "1"
+libc = "0.2"
+log = "0.4"
+time = "0.1.17"
+malloc_size_of = { path = "../malloc_size_of" }
metrics = {path = "../metrics"}
msg = {path = "../msg"}
net_traits = {path = "../net_traits"}
+parking_lot = {version = "0.8", features = ["nightly"]}
profile_traits = {path = "../profile_traits"}
+range = {path = "../range"}
+rayon = "1"
+script = {path = "../script"}
script_layout_interface = {path = "../script_layout_interface"}
script_traits = {path = "../script_traits"}
-servo_url = {path = "../url"}
+selectors = { path = "../selectors" }
+serde_json = "1.0"
+servo_allocator = {path = "../allocator"}
+servo_arc = {path = "../servo_arc"}
+servo_atoms = {path = "../atoms"}
+servo_config = {path = "../config"}
servo_geometry = {path = "../geometry"}
+servo_url = {path = "../url"}
+style = {path = "../style"}
+style_traits = {path = "../style_traits"}
webrender_api = {git = "https://github.com/servo/webrender", features = ["ipc"]}
diff --git a/components/layout_thread_2020/dom_wrapper.rs b/components/layout_thread_2020/dom_wrapper.rs
new file mode 100644
index 00000000000..5d815517532
--- /dev/null
+++ b/components/layout_thread_2020/dom_wrapper.rs
@@ -0,0 +1,1550 @@
+/* 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 https://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 `LayoutDom` 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 thread 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:
+//!
+//! * 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()`.
+
+#![allow(unsafe_code)]
+
+use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
+use gfx_traits::ByteIndex;
+use html5ever::{LocalName, Namespace};
+use layout::data::StyleAndLayoutData;
+use layout::wrapper::GetRawData;
+use msg::constellation_msg::{BrowsingContextId, PipelineId};
+use net_traits::image::base::{Image, ImageMetadata};
+use range::Range;
+use script::layout_exports::NodeFlags;
+use script::layout_exports::PendingRestyle;
+use script::layout_exports::ShadowRoot;
+use script::layout_exports::{
+ CharacterDataTypeId, DocumentFragmentTypeId, ElementTypeId, HTMLElementTypeId, NodeTypeId,
+ TextTypeId,
+};
+use script::layout_exports::{Document, Element, Node, Text};
+use script::layout_exports::{LayoutCharacterDataHelpers, LayoutDocumentHelpers};
+use script::layout_exports::{
+ LayoutDom, LayoutElementHelpers, LayoutNodeHelpers, LayoutShadowRootHelpers,
+ RawLayoutElementHelpers,
+};
+use script_layout_interface::wrapper_traits::{
+ DangerousThreadSafeLayoutNode, GetLayoutData, LayoutNode,
+};
+use script_layout_interface::wrapper_traits::{
+ PseudoElementType, ThreadSafeLayoutElement, ThreadSafeLayoutNode,
+};
+use script_layout_interface::{
+ HTMLCanvasData, HTMLMediaData, LayoutNodeType, OpaqueStyleAndLayoutData,
+};
+use script_layout_interface::{SVGSVGData, StyleData, TrustedNodeAddress};
+use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint};
+use selectors::matching::VisitedHandlingMode;
+use selectors::matching::{ElementSelectorFlags, MatchingContext, QuirksMode};
+use selectors::sink::Push;
+use servo_arc::{Arc, ArcBorrow};
+use servo_atoms::Atom;
+use servo_url::ServoUrl;
+use std::fmt;
+use std::fmt::Debug;
+use std::hash::{Hash, Hasher};
+use std::marker::PhantomData;
+use std::ptr::NonNull;
+use std::sync::atomic::Ordering;
+use std::sync::Arc as StdArc;
+use style::applicable_declarations::ApplicableDeclarationBlock;
+use style::attr::AttrValue;
+use style::context::SharedStyleContext;
+use style::data::ElementData;
+use style::dom::{DomChildren, LayoutIterator, NodeInfo, OpaqueNode};
+use style::dom::{TDocument, TElement, TNode, TShadowRoot};
+use style::element_state::*;
+use style::font_metrics::ServoMetricsProvider;
+use style::media_queries::Device;
+use style::properties::{ComputedValues, PropertyDeclarationBlock};
+use style::selector_parser::{extended_filtering, PseudoElement, SelectorImpl};
+use style::selector_parser::{AttrValue as SelectorAttrValue, Lang, NonTSPseudoClass};
+use style::shared_lock::{
+ Locked as StyleLocked, SharedRwLock as StyleSharedRwLock, SharedRwLockReadGuard,
+};
+use style::str::is_whitespace;
+use style::stylist::CascadeData;
+use style::CaseSensitivityExt;
+
+pub unsafe fn drop_style_and_layout_data(data: OpaqueStyleAndLayoutData) {
+ let ptr = data.ptr.as_ptr() as *mut StyleData;
+ let non_opaque: *mut StyleAndLayoutData = ptr as *mut _;
+ let _ = Box::from_raw(non_opaque);
+}
+
+#[derive(Clone, Copy)]
+pub struct ServoLayoutNode<'a> {
+ /// The wrapped node.
+ node: LayoutDom<Node>,
+
+ /// Being chained to a PhantomData prevents `LayoutNode`s from escaping.
+ chain: PhantomData<&'a ()>,
+}
+
+impl<'ln> Debug for ServoLayoutNode<'ln> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ if let Some(el) = self.as_element() {
+ el.fmt(f)
+ } else {
+ if self.is_text_node() {
+ write!(f, "<text node> ({:#x})", self.opaque().0)
+ } else {
+ write!(f, "<non-text node> ({:#x})", self.opaque().0)
+ }
+ }
+ }
+}
+
+impl<'a> PartialEq for ServoLayoutNode<'a> {
+ #[inline]
+ fn eq(&self, other: &ServoLayoutNode) -> bool {
+ self.node == other.node
+ }
+}
+
+impl<'ln> ServoLayoutNode<'ln> {
+ fn from_layout_js(n: LayoutDom<Node>) -> ServoLayoutNode<'ln> {
+ ServoLayoutNode {
+ node: n,
+ chain: PhantomData,
+ }
+ }
+
+ pub unsafe fn new(address: &TrustedNodeAddress) -> ServoLayoutNode {
+ ServoLayoutNode::from_layout_js(LayoutDom::from_trusted_node_address(*address))
+ }
+
+ /// Creates a new layout node with the same lifetime as this layout node.
+ pub unsafe fn new_with_this_lifetime(&self, node: &LayoutDom<Node>) -> ServoLayoutNode<'ln> {
+ ServoLayoutNode {
+ node: *node,
+ chain: self.chain,
+ }
+ }
+
+ fn script_type_id(&self) -> NodeTypeId {
+ unsafe { self.node.type_id_for_layout() }
+ }
+}
+
+impl<'ln> NodeInfo for ServoLayoutNode<'ln> {
+ fn is_element(&self) -> bool {
+ unsafe { self.node.is_element_for_layout() }
+ }
+
+ fn is_text_node(&self) -> bool {
+ self.script_type_id() ==
+ NodeTypeId::CharacterData(CharacterDataTypeId::Text(TextTypeId::Text))
+ }
+}
+
+#[derive(Clone, Copy, PartialEq)]
+pub struct ServoShadowRoot<'a> {
+ /// The wrapped shadow root.
+ shadow_root: LayoutDom<ShadowRoot>,
+
+ /// Being chained to a PhantomData prevents `ShadowRoot`s from escaping.
+ chain: PhantomData<&'a ()>,
+}
+
+impl<'lr> Debug for ServoShadowRoot<'lr> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.as_node().fmt(f)
+ }
+}
+
+impl<'lr> TShadowRoot for ServoShadowRoot<'lr> {
+ type ConcreteNode = ServoLayoutNode<'lr>;
+
+ fn as_node(&self) -> Self::ConcreteNode {
+ ServoLayoutNode::from_layout_js(self.shadow_root.upcast())
+ }
+
+ fn host(&self) -> ServoLayoutElement<'lr> {
+ ServoLayoutElement::from_layout_js(unsafe { self.shadow_root.get_host_for_layout() })
+ }
+
+ fn style_data<'a>(&self) -> Option<&'a CascadeData>
+ where
+ Self: 'a,
+ {
+ Some(unsafe {
+ &self
+ .shadow_root
+ .get_style_data_for_layout::<ServoLayoutElement>()
+ .data
+ })
+ }
+}
+
+impl<'lr> ServoShadowRoot<'lr> {
+ fn from_layout_js(shadow_root: LayoutDom<ShadowRoot>) -> ServoShadowRoot<'lr> {
+ ServoShadowRoot {
+ shadow_root,
+ chain: PhantomData,
+ }
+ }
+
+ pub unsafe fn flush_stylesheets(
+ &self,
+ device: &Device,
+ quirks_mode: QuirksMode,
+ guard: &SharedRwLockReadGuard,
+ ) {
+ self.shadow_root
+ .flush_stylesheets::<ServoLayoutElement>(device, quirks_mode, guard)
+ }
+}
+
+impl<'ln> TNode for ServoLayoutNode<'ln> {
+ type ConcreteDocument = ServoLayoutDocument<'ln>;
+ type ConcreteElement = ServoLayoutElement<'ln>;
+ type ConcreteShadowRoot = ServoShadowRoot<'ln>;
+
+ fn parent_node(&self) -> Option<Self> {
+ unsafe {
+ self.node
+ .composed_parent_node_ref()
+ .map(|node| self.new_with_this_lifetime(&node))
+ }
+ }
+
+ fn first_child(&self) -> Option<Self> {
+ unsafe {
+ self.node
+ .first_child_ref()
+ .map(|node| self.new_with_this_lifetime(&node))
+ }
+ }
+
+ fn last_child(&self) -> Option<Self> {
+ unsafe {
+ self.node
+ .last_child_ref()
+ .map(|node| self.new_with_this_lifetime(&node))
+ }
+ }
+
+ fn prev_sibling(&self) -> Option<Self> {
+ unsafe {
+ self.node
+ .prev_sibling_ref()
+ .map(|node| self.new_with_this_lifetime(&node))
+ }
+ }
+
+ fn next_sibling(&self) -> Option<Self> {
+ unsafe {
+ self.node
+ .next_sibling_ref()
+ .map(|node| self.new_with_this_lifetime(&node))
+ }
+ }
+
+ fn owner_doc(&self) -> Self::ConcreteDocument {
+ ServoLayoutDocument::from_layout_js(unsafe { self.node.owner_doc_for_layout() })
+ }
+
+ fn traversal_parent(&self) -> Option<ServoLayoutElement<'ln>> {
+ let parent = self.parent_node()?;
+ if let Some(shadow) = parent.as_shadow_root() {
+ return Some(shadow.host());
+ };
+ parent.as_element()
+ }
+
+ fn opaque(&self) -> OpaqueNode {
+ unsafe { self.get_jsmanaged().opaque() }
+ }
+
+ fn debug_id(self) -> usize {
+ self.opaque().0
+ }
+
+ fn as_element(&self) -> Option<ServoLayoutElement<'ln>> {
+ as_element(self.node)
+ }
+
+ fn as_document(&self) -> Option<ServoLayoutDocument<'ln>> {
+ self.node
+ .downcast()
+ .map(ServoLayoutDocument::from_layout_js)
+ }
+
+ fn as_shadow_root(&self) -> Option<ServoShadowRoot<'ln>> {
+ self.node.downcast().map(ServoShadowRoot::from_layout_js)
+ }
+
+ fn is_in_document(&self) -> bool {
+ unsafe { self.node.get_flag(NodeFlags::IS_IN_DOC) }
+ }
+}
+
+impl<'ln> LayoutNode for ServoLayoutNode<'ln> {
+ type ConcreteThreadSafeLayoutNode = ServoThreadSafeLayoutNode<'ln>;
+
+ fn to_threadsafe(&self) -> Self::ConcreteThreadSafeLayoutNode {
+ ServoThreadSafeLayoutNode::new(self)
+ }
+
+ fn type_id(&self) -> LayoutNodeType {
+ self.script_type_id().into()
+ }
+
+ unsafe fn initialize_data(&self) {
+ if self.get_raw_data().is_none() {
+ let ptr: *mut StyleAndLayoutData = Box::into_raw(Box::new(StyleAndLayoutData::new()));
+ let opaque = OpaqueStyleAndLayoutData {
+ ptr: NonNull::new_unchecked(ptr as *mut StyleData),
+ };
+ self.init_style_and_layout_data(opaque);
+ };
+ }
+
+ unsafe fn init_style_and_layout_data(&self, data: OpaqueStyleAndLayoutData) {
+ self.get_jsmanaged().init_style_and_layout_data(data);
+ }
+
+ unsafe fn take_style_and_layout_data(&self) -> OpaqueStyleAndLayoutData {
+ self.get_jsmanaged().take_style_and_layout_data()
+ }
+
+ fn is_connected(&self) -> bool {
+ unsafe { self.node.get_flag(NodeFlags::IS_CONNECTED) }
+ }
+}
+
+impl<'ln> GetLayoutData for ServoLayoutNode<'ln> {
+ fn get_style_and_layout_data(&self) -> Option<OpaqueStyleAndLayoutData> {
+ unsafe { self.get_jsmanaged().get_style_and_layout_data() }
+ }
+}
+
+impl<'le> GetLayoutData for ServoLayoutElement<'le> {
+ fn get_style_and_layout_data(&self) -> Option<OpaqueStyleAndLayoutData> {
+ self.as_node().get_style_and_layout_data()
+ }
+}
+
+impl<'ln> GetLayoutData for ServoThreadSafeLayoutNode<'ln> {
+ fn get_style_and_layout_data(&self) -> Option<OpaqueStyleAndLayoutData> {
+ self.node.get_style_and_layout_data()
+ }
+}
+
+impl<'le> GetLayoutData for ServoThreadSafeLayoutElement<'le> {
+ fn get_style_and_layout_data(&self) -> Option<OpaqueStyleAndLayoutData> {
+ self.element.as_node().get_style_and_layout_data()
+ }
+}
+
+impl<'ln> ServoLayoutNode<'ln> {
+ /// Returns the interior of this node as a `LayoutDom`. This is highly unsafe for layout to
+ /// call and as such is marked `unsafe`.
+ pub unsafe fn get_jsmanaged(&self) -> &LayoutDom<Node> {
+ &self.node
+ }
+}
+
+// A wrapper around documents that ensures ayout can only ever access safe properties.
+#[derive(Clone, Copy)]
+pub struct ServoLayoutDocument<'ld> {
+ document: LayoutDom<Document>,
+ chain: PhantomData<&'ld ()>,
+}
+
+impl<'ld> TDocument for ServoLayoutDocument<'ld> {
+ type ConcreteNode = ServoLayoutNode<'ld>;
+
+ fn as_node(&self) -> Self::ConcreteNode {
+ ServoLayoutNode::from_layout_js(self.document.upcast())
+ }
+
+ fn quirks_mode(&self) -> QuirksMode {
+ unsafe { self.document.quirks_mode() }
+ }
+
+ fn is_html_document(&self) -> bool {
+ unsafe { self.document.is_html_document_for_layout() }
+ }
+}
+
+impl<'ld> ServoLayoutDocument<'ld> {
+ pub fn root_element(&self) -> Option<ServoLayoutElement<'ld>> {
+ self.as_node()
+ .dom_children()
+ .flat_map(|n| n.as_element())
+ .next()
+ }
+
+ pub fn drain_pending_restyles(&self) -> Vec<(ServoLayoutElement<'ld>, PendingRestyle)> {
+ let elements = unsafe { self.document.drain_pending_restyles() };
+ elements
+ .into_iter()
+ .map(|(el, snapshot)| (ServoLayoutElement::from_layout_js(el), snapshot))
+ .collect()
+ }
+
+ pub fn needs_paint_from_layout(&self) {
+ unsafe { self.document.needs_paint_from_layout() }
+ }
+
+ pub fn will_paint(&self) {
+ unsafe { self.document.will_paint() }
+ }
+
+ pub fn style_shared_lock(&self) -> &StyleSharedRwLock {
+ unsafe { self.document.style_shared_lock() }
+ }
+
+ pub fn shadow_roots(&self) -> Vec<ServoShadowRoot> {
+ unsafe {
+ self.document
+ .shadow_roots()
+ .iter()
+ .map(|sr| {
+ debug_assert!(sr.upcast::<Node>().get_flag(NodeFlags::IS_CONNECTED));
+ ServoShadowRoot::from_layout_js(*sr)
+ })
+ .collect()
+ }
+ }
+
+ pub fn flush_shadow_roots_stylesheets(
+ &self,
+ device: &Device,
+ quirks_mode: QuirksMode,
+ guard: &SharedRwLockReadGuard,
+ ) {
+ unsafe {
+ if !self.document.shadow_roots_styles_changed() {
+ return;
+ }
+ self.document.flush_shadow_roots_stylesheets();
+ for shadow_root in self.shadow_roots() {
+ shadow_root.flush_stylesheets(device, quirks_mode, guard);
+ }
+ }
+ }
+
+ pub fn from_layout_js(doc: LayoutDom<Document>) -> ServoLayoutDocument<'ld> {
+ ServoLayoutDocument {
+ document: doc,
+ chain: PhantomData,
+ }
+ }
+}
+
+/// A wrapper around elements that ensures layout can only ever access safe properties.
+#[derive(Clone, Copy)]
+pub struct ServoLayoutElement<'le> {
+ element: LayoutDom<Element>,
+ chain: PhantomData<&'le ()>,
+}
+
+impl<'le> fmt::Debug for ServoLayoutElement<'le> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "<{}", self.element.local_name())?;
+ if let Some(id) = self.id() {
+ write!(f, " id={}", id)?;
+ }
+ write!(f, "> ({:#x})", self.as_node().opaque().0)
+ }
+}
+
+impl<'le> TElement for ServoLayoutElement<'le> {
+ type ConcreteNode = ServoLayoutNode<'le>;
+ type TraversalChildrenIterator = DomChildren<Self::ConcreteNode>;
+
+ type FontMetricsProvider = ServoMetricsProvider;
+
+ fn as_node(&self) -> ServoLayoutNode<'le> {
+ ServoLayoutNode::from_layout_js(self.element.upcast())
+ }
+
+ fn traversal_children(&self) -> LayoutIterator<Self::TraversalChildrenIterator> {
+ LayoutIterator(if let Some(shadow) = self.shadow_root() {
+ shadow.as_node().dom_children()
+ } else {
+ self.as_node().dom_children()
+ })
+ }
+
+ fn is_html_element(&self) -> bool {
+ unsafe { self.element.is_html_element() }
+ }
+
+ fn is_mathml_element(&self) -> bool {
+ *self.element.namespace() == ns!(mathml)
+ }
+
+ fn is_svg_element(&self) -> bool {
+ *self.element.namespace() == ns!(svg)
+ }
+
+ fn has_part_attr(&self) -> bool {
+ false
+ }
+
+ fn style_attribute(&self) -> Option<ArcBorrow<StyleLocked<PropertyDeclarationBlock>>> {
+ unsafe {
+ (*self.element.style_attribute())
+ .as_ref()
+ .map(|x| x.borrow_arc())
+ }
+ }
+
+ fn state(&self) -> ElementState {
+ self.element.get_state_for_layout()
+ }
+
+ #[inline]
+ fn has_attr(&self, namespace: &Namespace, attr: &LocalName) -> bool {
+ self.get_attr(namespace, attr).is_some()
+ }
+
+ #[inline]
+ fn id(&self) -> Option<&Atom> {
+ unsafe { (*self.element.id_attribute()).as_ref() }
+ }
+
+ #[inline(always)]
+ fn each_class<F>(&self, mut callback: F)
+ where
+ F: FnMut(&Atom),
+ {
+ unsafe {
+ if let Some(ref classes) = self.element.get_classes_for_layout() {
+ for class in *classes {
+ callback(class)
+ }
+ }
+ }
+ }
+
+ fn has_dirty_descendants(&self) -> bool {
+ unsafe {
+ self.as_node()
+ .node
+ .get_flag(NodeFlags::HAS_DIRTY_DESCENDANTS)
+ }
+ }
+
+ fn has_snapshot(&self) -> bool {
+ unsafe { self.as_node().node.get_flag(NodeFlags::HAS_SNAPSHOT) }
+ }
+
+ fn handled_snapshot(&self) -> bool {
+ unsafe { self.as_node().node.get_flag(NodeFlags::HANDLED_SNAPSHOT) }
+ }
+
+ unsafe fn set_handled_snapshot(&self) {
+ self.as_node()
+ .node
+ .set_flag(NodeFlags::HANDLED_SNAPSHOT, true);
+ }
+
+ unsafe fn set_dirty_descendants(&self) {
+ debug_assert!(self.as_node().is_connected());
+ self.as_node()
+ .node
+ .set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true)
+ }
+
+ unsafe fn unset_dirty_descendants(&self) {
+ self.as_node()
+ .node
+ .set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, false)
+ }
+
+ fn store_children_to_process(&self, n: isize) {
+ let data = self.get_style_data().unwrap();
+ data.parallel
+ .children_to_process
+ .store(n, Ordering::Relaxed);
+ }
+
+ fn did_process_child(&self) -> isize {
+ let data = self.get_style_data().unwrap();
+ let old_value = data
+ .parallel
+ .children_to_process
+ .fetch_sub(1, Ordering::Relaxed);
+ debug_assert!(old_value >= 1);
+ old_value - 1
+ }
+
+ unsafe fn clear_data(&self) {
+ if self.get_raw_data().is_some() {
+ drop_style_and_layout_data(self.as_node().take_style_and_layout_data());
+ }
+ }
+
+ unsafe fn ensure_data(&self) -> AtomicRefMut<ElementData> {
+ self.as_node().initialize_data();
+ self.mutate_data().unwrap()
+ }
+
+ fn get_data(&self) -> Option<&AtomicRefCell<ElementData>> {
+ unsafe {
+ self.get_style_and_layout_data()
+ .map(|d| &(*(d.ptr.as_ptr() as *mut StyleData)).element_data)
+ }
+ }
+
+ fn skip_item_display_fixup(&self) -> bool {
+ false
+ }
+
+ unsafe fn set_selector_flags(&self, flags: ElementSelectorFlags) {
+ self.element.insert_selector_flags(flags);
+ }
+
+ fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool {
+ self.element.has_selector_flags(flags)
+ }
+
+ fn has_animations(&self) -> bool {
+ // We use this function not only for Gecko but also for Servo to know if this element has
+ // animations, so we maybe try to get the important rules of this element. This is used for
+ // off-main thread animations, but we don't support it on Servo, so return false directly.
+ false
+ }
+
+ fn has_css_animations(&self) -> bool {
+ unreachable!("this should be only called on gecko");
+ }
+
+ fn has_css_transitions(&self) -> bool {
+ unreachable!("this should be only called on gecko");
+ }
+
+ #[inline]
+ fn lang_attr(&self) -> Option<SelectorAttrValue> {
+ self.get_attr(&ns!(xml), &local_name!("lang"))
+ .or_else(|| self.get_attr(&ns!(), &local_name!("lang")))
+ .map(|v| String::from(v as &str))
+ }
+
+ fn match_element_lang(
+ &self,
+ override_lang: Option<Option<SelectorAttrValue>>,
+ value: &Lang,
+ ) -> bool {
+ // Servo supports :lang() from CSS Selectors 4, which can take a comma-
+ // separated list of language tags in the pseudo-class, and which
+ // performs RFC 4647 extended filtering matching on them.
+ //
+ // FIXME(heycam): This is wrong, since extended_filtering accepts
+ // a string containing commas (separating each language tag in
+ // a list) but the pseudo-class instead should be parsing and
+ // storing separate <ident> or <string>s for each language tag.
+ //
+ // FIXME(heycam): Look at `element`'s document's Content-Language
+ // HTTP header for language tags to match `value` against. To
+ // do this, we should make `get_lang_for_layout` return an Option,
+ // so we can decide when to fall back to the Content-Language check.
+ let element_lang = match override_lang {
+ Some(Some(lang)) => lang,
+ Some(None) => String::new(),
+ None => self.element.get_lang_for_layout(),
+ };
+ extended_filtering(&element_lang, &*value)
+ }
+
+ fn is_html_document_body_element(&self) -> bool {
+ // This is only used for the "tables inherit from body" quirk, which we
+ // don't implement.
+ //
+ // FIXME(emilio): We should be able to give the right answer though!
+ false
+ }
+
+ fn synthesize_presentational_hints_for_legacy_attributes<V>(
+ &self,
+ _visited_handling: VisitedHandlingMode,
+ hints: &mut V,
+ ) where
+ V: Push<ApplicableDeclarationBlock>,
+ {
+ unsafe {
+ self.element
+ .synthesize_presentational_hints_for_legacy_attributes(hints);
+ }
+ }
+
+ /// The shadow root this element is a host of.
+ fn shadow_root(&self) -> Option<ServoShadowRoot<'le>> {
+ unsafe {
+ self.element
+ .get_shadow_root_for_layout()
+ .map(ServoShadowRoot::from_layout_js)
+ }
+ }
+
+ /// The shadow root which roots the subtree this element is contained in.
+ fn containing_shadow(&self) -> Option<ServoShadowRoot<'le>> {
+ unsafe {
+ self.element
+ .upcast()
+ .containing_shadow_root_for_layout()
+ .map(ServoShadowRoot::from_layout_js)
+ }
+ }
+
+ fn local_name(&self) -> &LocalName {
+ self.element.local_name()
+ }
+
+ fn namespace(&self) -> &Namespace {
+ self.element.namespace()
+ }
+}
+
+impl<'le> PartialEq for ServoLayoutElement<'le> {
+ fn eq(&self, other: &Self) -> bool {
+ self.as_node() == other.as_node()
+ }
+}
+
+impl<'le> Hash for ServoLayoutElement<'le> {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.element.hash(state);
+ }
+}
+
+impl<'le> Eq for ServoLayoutElement<'le> {}
+
+impl<'le> ServoLayoutElement<'le> {
+ fn from_layout_js(el: LayoutDom<Element>) -> ServoLayoutElement<'le> {
+ ServoLayoutElement {
+ element: el,
+ chain: PhantomData,
+ }
+ }
+
+ #[inline]
+ fn get_attr_enum(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue> {
+ unsafe { (*self.element.unsafe_get()).get_attr_for_layout(namespace, name) }
+ }
+
+ #[inline]
+ fn get_attr(&self, namespace: &Namespace, name: &LocalName) -> Option<&str> {
+ unsafe { (*self.element.unsafe_get()).get_attr_val_for_layout(namespace, name) }
+ }
+
+ fn get_style_data(&self) -> Option<&StyleData> {
+ unsafe {
+ self.get_style_and_layout_data()
+ .map(|d| &*(d.ptr.as_ptr() as *mut StyleData))
+ }
+ }
+
+ pub unsafe fn unset_snapshot_flags(&self) {
+ self.as_node()
+ .node
+ .set_flag(NodeFlags::HAS_SNAPSHOT | NodeFlags::HANDLED_SNAPSHOT, false);
+ }
+
+ pub unsafe fn set_has_snapshot(&self) {
+ self.as_node().node.set_flag(NodeFlags::HAS_SNAPSHOT, true);
+ }
+
+ pub unsafe fn note_dirty_descendant(&self) {
+ use selectors::Element;
+
+ let mut current = Some(*self);
+ while let Some(el) = current {
+ // FIXME(bholley): Ideally we'd have the invariant that any element
+ // with has_dirty_descendants also has the bit set on all its
+ // ancestors. However, there are currently some corner-cases where
+ // we get that wrong. I have in-flight patches to fix all this
+ // stuff up, so we just always propagate this bit for now.
+ el.set_dirty_descendants();
+ current = el.parent_element();
+ }
+ }
+}
+
+fn as_element<'le>(node: LayoutDom<Node>) -> Option<ServoLayoutElement<'le>> {
+ node.downcast().map(ServoLayoutElement::from_layout_js)
+}
+
+impl<'le> ::selectors::Element for ServoLayoutElement<'le> {
+ type Impl = SelectorImpl;
+
+ fn opaque(&self) -> ::selectors::OpaqueElement {
+ ::selectors::OpaqueElement::new(unsafe { &*(self.as_node().opaque().0 as *const ()) })
+ }
+
+ fn parent_element(&self) -> Option<ServoLayoutElement<'le>> {
+ unsafe {
+ self.element
+ .upcast()
+ .composed_parent_node_ref()
+ .and_then(as_element)
+ }
+ }
+
+ fn parent_node_is_shadow_root(&self) -> bool {
+ match self.as_node().parent_node() {
+ None => false,
+ Some(node) => {
+ node.script_type_id() ==
+ NodeTypeId::DocumentFragment(DocumentFragmentTypeId::ShadowRoot)
+ },
+ }
+ }
+
+ fn containing_shadow_host(&self) -> Option<Self> {
+ self.containing_shadow().map(|s| s.host())
+ }
+
+ fn prev_sibling_element(&self) -> Option<ServoLayoutElement<'le>> {
+ let mut node = self.as_node();
+ while let Some(sibling) = node.prev_sibling() {
+ if let Some(element) = sibling.as_element() {
+ return Some(element);
+ }
+ node = sibling;
+ }
+ None
+ }
+
+ fn next_sibling_element(&self) -> Option<ServoLayoutElement<'le>> {
+ let mut node = self.as_node();
+ while let Some(sibling) = node.next_sibling() {
+ if let Some(element) = sibling.as_element() {
+ return Some(element);
+ }
+ node = sibling;
+ }
+ None
+ }
+
+ fn attr_matches(
+ &self,
+ ns: &NamespaceConstraint<&Namespace>,
+ local_name: &LocalName,
+ operation: &AttrSelectorOperation<&String>,
+ ) -> bool {
+ match *ns {
+ NamespaceConstraint::Specific(ref ns) => self
+ .get_attr_enum(ns, local_name)
+ .map_or(false, |value| value.eval_selector(operation)),
+ NamespaceConstraint::Any => {
+ let values =
+ unsafe { (*self.element.unsafe_get()).get_attr_vals_for_layout(local_name) };
+ values.iter().any(|value| value.eval_selector(operation))
+ },
+ }
+ }
+
+ fn is_root(&self) -> bool {
+ match self.as_node().parent_node() {
+ None => false,
+ Some(node) => match node.script_type_id() {
+ NodeTypeId::Document(_) => true,
+ _ => false,
+ },
+ }
+ }
+
+ fn is_empty(&self) -> bool {
+ self.as_node()
+ .dom_children()
+ .all(|node| match node.script_type_id() {
+ NodeTypeId::Element(..) => false,
+ NodeTypeId::CharacterData(CharacterDataTypeId::Text(TextTypeId::Text)) => unsafe {
+ node.node.downcast().unwrap().data_for_layout().is_empty()
+ },
+ _ => true,
+ })
+ }
+
+ #[inline]
+ fn has_local_name(&self, name: &LocalName) -> bool {
+ self.element.local_name() == name
+ }
+
+ #[inline]
+ fn has_namespace(&self, ns: &Namespace) -> bool {
+ self.element.namespace() == ns
+ }
+
+ #[inline]
+ fn is_same_type(&self, other: &Self) -> bool {
+ self.element.local_name() == other.element.local_name() &&
+ self.element.namespace() == other.element.namespace()
+ }
+
+ fn is_pseudo_element(&self) -> bool {
+ false
+ }
+
+ fn match_pseudo_element(
+ &self,
+ _pseudo: &PseudoElement,
+ _context: &mut MatchingContext<Self::Impl>,
+ ) -> bool {
+ false
+ }
+
+ fn match_non_ts_pseudo_class<F>(
+ &self,
+ pseudo_class: &NonTSPseudoClass,
+ _: &mut MatchingContext<Self::Impl>,
+ _: &mut F,
+ ) -> bool
+ where
+ F: FnMut(&Self, ElementSelectorFlags),
+ {
+ match *pseudo_class {
+ // https://github.com/servo/servo/issues/8718
+ NonTSPseudoClass::Link | NonTSPseudoClass::AnyLink => self.is_link(),
+ NonTSPseudoClass::Visited => false,
+
+ NonTSPseudoClass::Lang(ref lang) => self.match_element_lang(None, &*lang),
+
+ NonTSPseudoClass::ServoNonZeroBorder => unsafe {
+ match (*self.element.unsafe_get())
+ .get_attr_for_layout(&ns!(), &local_name!("border"))
+ {
+ None | Some(&AttrValue::UInt(_, 0)) => false,
+ _ => true,
+ }
+ },
+ NonTSPseudoClass::ReadOnly => !self
+ .element
+ .get_state_for_layout()
+ .contains(pseudo_class.state_flag()),
+
+ NonTSPseudoClass::Active |
+ NonTSPseudoClass::Focus |
+ NonTSPseudoClass::Fullscreen |
+ NonTSPseudoClass::Hover |
+ NonTSPseudoClass::Enabled |
+ NonTSPseudoClass::Disabled |
+ NonTSPseudoClass::Checked |
+ NonTSPseudoClass::Indeterminate |
+ NonTSPseudoClass::ReadWrite |
+ NonTSPseudoClass::PlaceholderShown |
+ NonTSPseudoClass::Target => self
+ .element
+ .get_state_for_layout()
+ .contains(pseudo_class.state_flag()),
+ }
+ }
+
+ #[inline]
+ fn is_link(&self) -> bool {
+ unsafe {
+ match self.as_node().script_type_id() {
+ // https://html.spec.whatwg.org/multipage/#selector-link
+ NodeTypeId::Element(ElementTypeId::HTMLElement(
+ HTMLElementTypeId::HTMLAnchorElement,
+ )) |
+ NodeTypeId::Element(ElementTypeId::HTMLElement(
+ HTMLElementTypeId::HTMLAreaElement,
+ )) |
+ NodeTypeId::Element(ElementTypeId::HTMLElement(
+ HTMLElementTypeId::HTMLLinkElement,
+ )) => (*self.element.unsafe_get())
+ .get_attr_val_for_layout(&ns!(), &local_name!("href"))
+ .is_some(),
+ _ => false,
+ }
+ }
+ }
+
+ #[inline]
+ fn has_id(&self, id: &Atom, case_sensitivity: CaseSensitivity) -> bool {
+ unsafe {
+ (*self.element.id_attribute())
+ .as_ref()
+ .map_or(false, |atom| case_sensitivity.eq_atom(atom, id))
+ }
+ }
+
+ #[inline]
+ fn is_part(&self, _name: &Atom) -> bool {
+ false
+ }
+
+ #[inline]
+ fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
+ unsafe { self.element.has_class_for_layout(name, case_sensitivity) }
+ }
+
+ fn is_html_slot_element(&self) -> bool {
+ unsafe { self.element.is_html_element() && self.local_name() == &local_name!("slot") }
+ }
+
+ fn is_html_element_in_html_document(&self) -> bool {
+ unsafe {
+ if !self.element.is_html_element() {
+ return false;
+ }
+ }
+
+ self.as_node().owner_doc().is_html_document()
+ }
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct ServoThreadSafeLayoutNode<'ln> {
+ /// The wrapped node.
+ node: ServoLayoutNode<'ln>,
+
+ /// The pseudo-element type, with (optionally)
+ /// a specified display value to override the stylesheet.
+ pseudo: PseudoElementType,
+}
+
+impl<'a> PartialEq for ServoThreadSafeLayoutNode<'a> {
+ #[inline]
+ fn eq(&self, other: &ServoThreadSafeLayoutNode<'a>) -> bool {
+ self.node == other.node
+ }
+}
+
+impl<'ln> DangerousThreadSafeLayoutNode for ServoThreadSafeLayoutNode<'ln> {
+ unsafe fn dangerous_first_child(&self) -> Option<Self> {
+ self.get_jsmanaged()
+ .first_child_ref()
+ .map(|node| self.new_with_this_lifetime(&node))
+ }
+ unsafe fn dangerous_next_sibling(&self) -> Option<Self> {
+ self.get_jsmanaged()
+ .next_sibling_ref()
+ .map(|node| self.new_with_this_lifetime(&node))
+ }
+}
+
+impl<'ln> ServoThreadSafeLayoutNode<'ln> {
+ /// Creates a new layout node with the same lifetime as this layout node.
+ pub unsafe fn new_with_this_lifetime(
+ &self,
+ node: &LayoutDom<Node>,
+ ) -> ServoThreadSafeLayoutNode<'ln> {
+ ServoThreadSafeLayoutNode {
+ node: self.node.new_with_this_lifetime(node),
+ pseudo: PseudoElementType::Normal,
+ }
+ }
+
+ /// Creates a new `ServoThreadSafeLayoutNode` from the given `ServoLayoutNode`.
+ pub fn new<'a>(node: &ServoLayoutNode<'a>) -> ServoThreadSafeLayoutNode<'a> {
+ ServoThreadSafeLayoutNode {
+ node: node.clone(),
+ pseudo: PseudoElementType::Normal,
+ }
+ }
+
+ /// Returns the interior of this node as a `LayoutDom`. This is highly unsafe for layout to
+ /// call and as such is marked `unsafe`.
+ unsafe fn get_jsmanaged(&self) -> &LayoutDom<Node> {
+ self.node.get_jsmanaged()
+ }
+}
+
+impl<'ln> NodeInfo for ServoThreadSafeLayoutNode<'ln> {
+ fn is_element(&self) -> bool {
+ self.node.is_element()
+ }
+
+ fn is_text_node(&self) -> bool {
+ self.node.is_text_node()
+ }
+}
+
+impl<'ln> ThreadSafeLayoutNode for ServoThreadSafeLayoutNode<'ln> {
+ type ConcreteNode = ServoLayoutNode<'ln>;
+ type ConcreteThreadSafeLayoutElement = ServoThreadSafeLayoutElement<'ln>;
+ type ConcreteElement = ServoLayoutElement<'ln>;
+ type ChildrenIterator = ThreadSafeLayoutNodeChildrenIterator<Self>;
+
+ fn opaque(&self) -> OpaqueNode {
+ unsafe { self.get_jsmanaged().opaque() }
+ }
+
+ fn type_id(&self) -> Option<LayoutNodeType> {
+ if self.pseudo == PseudoElementType::Normal {
+ Some(self.node.type_id())
+ } else {
+ None
+ }
+ }
+
+ fn parent_style(&self) -> Arc<ComputedValues> {
+ let parent = self.node.parent_node().unwrap().as_element().unwrap();
+ let parent_data = parent.get_data().unwrap().borrow();
+ parent_data.styles.primary().clone()
+ }
+
+ fn debug_id(self) -> usize {
+ self.node.debug_id()
+ }
+
+ fn children(&self) -> LayoutIterator<Self::ChildrenIterator> {
+ if let Some(shadow) = self.node.as_element().and_then(|e| e.shadow_root()) {
+ return LayoutIterator(ThreadSafeLayoutNodeChildrenIterator::new(
+ shadow.as_node().to_threadsafe(),
+ ));
+ }
+ LayoutIterator(ThreadSafeLayoutNodeChildrenIterator::new(*self))
+ }
+
+ fn as_element(&self) -> Option<ServoThreadSafeLayoutElement<'ln>> {
+ self.node
+ .as_element()
+ .map(|el| ServoThreadSafeLayoutElement {
+ element: el,
+ pseudo: self.pseudo,
+ })
+ }
+
+ fn get_style_and_layout_data(&self) -> Option<OpaqueStyleAndLayoutData> {
+ self.node.get_style_and_layout_data()
+ }
+
+ fn is_ignorable_whitespace(&self, context: &SharedStyleContext) -> bool {
+ unsafe {
+ let text: LayoutDom<Text> = match self.get_jsmanaged().downcast() {
+ Some(text) => text,
+ None => return false,
+ };
+
+ if !is_whitespace(text.upcast().data_for_layout()) {
+ 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.
+ !self
+ .style(context)
+ .get_inherited_text()
+ .white_space
+ .preserve_newlines()
+ }
+ }
+
+ unsafe fn unsafe_get(self) -> Self::ConcreteNode {
+ self.node
+ }
+
+ fn node_text_content(&self) -> String {
+ let this = unsafe { self.get_jsmanaged() };
+ return this.text_content();
+ }
+
+ fn selection(&self) -> Option<Range<ByteIndex>> {
+ let this = unsafe { self.get_jsmanaged() };
+
+ this.selection().map(|range| {
+ Range::new(
+ ByteIndex(range.start as isize),
+ ByteIndex(range.len() as isize),
+ )
+ })
+ }
+
+ fn image_url(&self) -> Option<ServoUrl> {
+ let this = unsafe { self.get_jsmanaged() };
+ this.image_url()
+ }
+
+ fn image_density(&self) -> Option<f64> {
+ let this = unsafe { self.get_jsmanaged() };
+ this.image_density()
+ }
+
+ fn image_data(&self) -> Option<(Option<StdArc<Image>>, Option<ImageMetadata>)> {
+ let this = unsafe { self.get_jsmanaged() };
+ this.image_data()
+ }
+
+ fn canvas_data(&self) -> Option<HTMLCanvasData> {
+ let this = unsafe { self.get_jsmanaged() };
+ this.canvas_data()
+ }
+
+ fn media_data(&self) -> Option<HTMLMediaData> {
+ let this = unsafe { self.get_jsmanaged() };
+ this.media_data()
+ }
+
+ fn svg_data(&self) -> Option<SVGSVGData> {
+ let this = unsafe { self.get_jsmanaged() };
+ this.svg_data()
+ }
+
+ // Can return None if the iframe has no nested browsing context
+ fn iframe_browsing_context_id(&self) -> Option<BrowsingContextId> {
+ let this = unsafe { self.get_jsmanaged() };
+ this.iframe_browsing_context_id()
+ }
+
+ // Can return None if the iframe has no nested browsing context
+ fn iframe_pipeline_id(&self) -> Option<PipelineId> {
+ let this = unsafe { self.get_jsmanaged() };
+ this.iframe_pipeline_id()
+ }
+
+ fn get_colspan(&self) -> u32 {
+ unsafe {
+ self.get_jsmanaged()
+ .downcast::<Element>()
+ .unwrap()
+ .get_colspan()
+ }
+ }
+
+ fn get_rowspan(&self) -> u32 {
+ unsafe {
+ self.get_jsmanaged()
+ .downcast::<Element>()
+ .unwrap()
+ .get_rowspan()
+ }
+ }
+}
+
+pub struct ThreadSafeLayoutNodeChildrenIterator<ConcreteNode: ThreadSafeLayoutNode> {
+ current_node: Option<ConcreteNode>,
+ parent_node: ConcreteNode,
+}
+
+impl<ConcreteNode> ThreadSafeLayoutNodeChildrenIterator<ConcreteNode>
+where
+ ConcreteNode: DangerousThreadSafeLayoutNode,
+{
+ pub fn new(parent: ConcreteNode) -> Self {
+ let first_child: Option<ConcreteNode> = match parent.get_pseudo_element_type() {
+ PseudoElementType::Normal => parent
+ .get_before_pseudo()
+ .or_else(|| parent.get_details_summary_pseudo())
+ .or_else(|| unsafe { parent.dangerous_first_child() }),
+ PseudoElementType::DetailsContent | PseudoElementType::DetailsSummary => unsafe {
+ parent.dangerous_first_child()
+ },
+ _ => None,
+ };
+ ThreadSafeLayoutNodeChildrenIterator {
+ current_node: first_child,
+ parent_node: parent,
+ }
+ }
+}
+
+impl<ConcreteNode> Iterator for ThreadSafeLayoutNodeChildrenIterator<ConcreteNode>
+where
+ ConcreteNode: DangerousThreadSafeLayoutNode,
+{
+ type Item = ConcreteNode;
+ fn next(&mut self) -> Option<ConcreteNode> {
+ use selectors::Element;
+ match self.parent_node.get_pseudo_element_type() {
+ PseudoElementType::Before | PseudoElementType::After => None,
+
+ PseudoElementType::DetailsSummary => {
+ let mut current_node = self.current_node.clone();
+ loop {
+ let next_node = if let Some(ref node) = current_node {
+ if let Some(element) = node.as_element() {
+ if element.has_local_name(&local_name!("summary")) &&
+ element.has_namespace(&ns!(html))
+ {
+ self.current_node = None;
+ return Some(node.clone());
+ }
+ }
+ unsafe { node.dangerous_next_sibling() }
+ } else {
+ self.current_node = None;
+ return None;
+ };
+ current_node = next_node;
+ }
+ },
+
+ PseudoElementType::DetailsContent => {
+ let node = self.current_node.clone();
+ let node = node.and_then(|node| {
+ if node.is_element() &&
+ node.as_element()
+ .unwrap()
+ .has_local_name(&local_name!("summary")) &&
+ node.as_element().unwrap().has_namespace(&ns!(html))
+ {
+ unsafe { node.dangerous_next_sibling() }
+ } else {
+ Some(node)
+ }
+ });
+ self.current_node = node.and_then(|node| unsafe { node.dangerous_next_sibling() });
+ node
+ },
+
+ PseudoElementType::Normal => {
+ let node = self.current_node.clone();
+ if let Some(ref node) = node {
+ self.current_node = match node.get_pseudo_element_type() {
+ PseudoElementType::Before => self
+ .parent_node
+ .get_details_summary_pseudo()
+ .or_else(|| unsafe { self.parent_node.dangerous_first_child() })
+ .or_else(|| self.parent_node.get_after_pseudo()),
+ PseudoElementType::Normal => unsafe { node.dangerous_next_sibling() }
+ .or_else(|| self.parent_node.get_after_pseudo()),
+ PseudoElementType::DetailsSummary => {
+ self.parent_node.get_details_content_pseudo()
+ },
+ PseudoElementType::DetailsContent => self.parent_node.get_after_pseudo(),
+ PseudoElementType::After => None,
+ };
+ }
+ node
+ },
+ }
+ }
+}
+
+/// A wrapper around elements that ensures layout can only
+/// ever access safe properties and cannot race on elements.
+#[derive(Clone, Copy, Debug)]
+pub struct ServoThreadSafeLayoutElement<'le> {
+ element: ServoLayoutElement<'le>,
+
+ /// The pseudo-element type, with (optionally)
+ /// a specified display value to override the stylesheet.
+ pseudo: PseudoElementType,
+}
+
+impl<'le> ThreadSafeLayoutElement for ServoThreadSafeLayoutElement<'le> {
+ type ConcreteThreadSafeLayoutNode = ServoThreadSafeLayoutNode<'le>;
+ type ConcreteElement = ServoLayoutElement<'le>;
+
+ fn as_node(&self) -> ServoThreadSafeLayoutNode<'le> {
+ ServoThreadSafeLayoutNode {
+ node: self.element.as_node(),
+ pseudo: self.pseudo.clone(),
+ }
+ }
+
+ fn get_pseudo_element_type(&self) -> PseudoElementType {
+ self.pseudo
+ }
+
+ fn with_pseudo(&self, pseudo: PseudoElementType) -> Self {
+ ServoThreadSafeLayoutElement {
+ element: self.element.clone(),
+ pseudo,
+ }
+ }
+
+ fn type_id(&self) -> Option<LayoutNodeType> {
+ self.as_node().type_id()
+ }
+
+ unsafe fn unsafe_get(self) -> ServoLayoutElement<'le> {
+ self.element
+ }
+
+ fn get_attr_enum(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue> {
+ self.element.get_attr_enum(namespace, name)
+ }
+
+ fn get_attr<'a>(&'a self, namespace: &Namespace, name: &LocalName) -> Option<&'a str> {
+ self.element.get_attr(namespace, name)
+ }
+
+ fn style_data(&self) -> AtomicRef<ElementData> {
+ self.element
+ .get_data()
+ .expect("Unstyled layout node?")
+ .borrow()
+ }
+
+ fn is_shadow_host(&self) -> bool {
+ self.element.shadow_root().is_some()
+ }
+}
+
+/// This implementation of `::selectors::Element` is used for implementing lazy
+/// pseudo-elements.
+///
+/// Lazy pseudo-elements in Servo only allows selectors using safe properties,
+/// i.e., local_name, attributes, so they can only be used for **private**
+/// pseudo-elements (like `::-servo-details-content`).
+///
+/// Probably a few more of this functions can be implemented (like `has_class`, etc.),
+/// but they have no use right now.
+///
+/// Note that the element implementation is needed only for selector matching,
+/// not for inheritance (styles are inherited appropiately).
+impl<'le> ::selectors::Element for ServoThreadSafeLayoutElement<'le> {
+ type Impl = SelectorImpl;
+
+ fn opaque(&self) -> ::selectors::OpaqueElement {
+ ::selectors::OpaqueElement::new(unsafe { &*(self.as_node().opaque().0 as *const ()) })
+ }
+
+ fn is_pseudo_element(&self) -> bool {
+ false
+ }
+
+ fn parent_element(&self) -> Option<Self> {
+ warn!("ServoThreadSafeLayoutElement::parent_element called");
+ None
+ }
+
+ fn parent_node_is_shadow_root(&self) -> bool {
+ false
+ }
+
+ fn containing_shadow_host(&self) -> Option<Self> {
+ None
+ }
+
+ // Skips non-element nodes
+ fn prev_sibling_element(&self) -> Option<Self> {
+ warn!("ServoThreadSafeLayoutElement::prev_sibling_element called");
+ None
+ }
+
+ // Skips non-element nodes
+ fn next_sibling_element(&self) -> Option<Self> {
+ warn!("ServoThreadSafeLayoutElement::next_sibling_element called");
+ None
+ }
+
+ fn is_html_slot_element(&self) -> bool {
+ self.element.is_html_slot_element()
+ }
+
+ fn is_html_element_in_html_document(&self) -> bool {
+ debug!("ServoThreadSafeLayoutElement::is_html_element_in_html_document called");
+ true
+ }
+
+ #[inline]
+ fn has_local_name(&self, name: &LocalName) -> bool {
+ self.element.local_name() == name
+ }
+
+ #[inline]
+ fn has_namespace(&self, ns: &Namespace) -> bool {
+ self.element.namespace() == ns
+ }
+
+ #[inline]
+ fn is_same_type(&self, other: &Self) -> bool {
+ self.element.local_name() == other.element.local_name() &&
+ self.element.namespace() == other.element.namespace()
+ }
+
+ fn match_pseudo_element(
+ &self,
+ _pseudo: &PseudoElement,
+ _context: &mut MatchingContext<Self::Impl>,
+ ) -> bool {
+ false
+ }
+
+ fn attr_matches(
+ &self,
+ ns: &NamespaceConstraint<&Namespace>,
+ local_name: &LocalName,
+ operation: &AttrSelectorOperation<&String>,
+ ) -> bool {
+ match *ns {
+ NamespaceConstraint::Specific(ref ns) => self
+ .get_attr_enum(ns, local_name)
+ .map_or(false, |value| value.eval_selector(operation)),
+ NamespaceConstraint::Any => {
+ let values = unsafe {
+ (*self.element.element.unsafe_get()).get_attr_vals_for_layout(local_name)
+ };
+ values.iter().any(|v| v.eval_selector(operation))
+ },
+ }
+ }
+
+ fn match_non_ts_pseudo_class<F>(
+ &self,
+ _: &NonTSPseudoClass,
+ _: &mut MatchingContext<Self::Impl>,
+ _: &mut F,
+ ) -> bool
+ where
+ F: FnMut(&Self, ElementSelectorFlags),
+ {
+ // NB: This could maybe be implemented
+ warn!("ServoThreadSafeLayoutElement::match_non_ts_pseudo_class called");
+ false
+ }
+
+ fn is_link(&self) -> bool {
+ warn!("ServoThreadSafeLayoutElement::is_link called");
+ false
+ }
+
+ fn has_id(&self, _id: &Atom, _case_sensitivity: CaseSensitivity) -> bool {
+ debug!("ServoThreadSafeLayoutElement::has_id called");
+ false
+ }
+
+ #[inline]
+ fn is_part(&self, _name: &Atom) -> bool {
+ debug!("ServoThreadSafeLayoutElement::is_part called");
+ false
+ }
+
+ fn has_class(&self, _name: &Atom, _case_sensitivity: CaseSensitivity) -> bool {
+ debug!("ServoThreadSafeLayoutElement::has_class called");
+ false
+ }
+
+ fn is_empty(&self) -> bool {
+ warn!("ServoThreadSafeLayoutElement::is_empty called");
+ false
+ }
+
+ fn is_root(&self) -> bool {
+ warn!("ServoThreadSafeLayoutElement::is_root called");
+ false
+ }
+}
diff --git a/components/layout_thread_2020/lib.rs b/components/layout_thread_2020/lib.rs
index b5bbe316dcc..4f5018e0a98 100644
--- a/components/layout_thread_2020/lib.rs
+++ b/components/layout_thread_2020/lib.rs
@@ -2,42 +2,310 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
-use crossbeam_channel::{Receiver, Sender};
-use euclid::Size2D;
+// Work around https://github.com/rust-lang/rust/issues/62132
+#![recursion_limit = "128"]
+
+//! The layout thread. Performs layout on the DOM, builds display lists and sends them to be
+//! painted.
+
+#[macro_use]
+extern crate crossbeam_channel;
+#[macro_use]
+extern crate html5ever;
+#[macro_use]
+extern crate layout;
+#[macro_use]
+extern crate lazy_static;
+#[macro_use]
+extern crate log;
+#[macro_use]
+extern crate profile_traits;
+
+mod dom_wrapper;
+
+use crate::dom_wrapper::drop_style_and_layout_data;
+use crate::dom_wrapper::{ServoLayoutDocument, ServoLayoutElement, ServoLayoutNode};
+use app_units::Au;
+use crossbeam_channel::{unbounded, Receiver, Sender};
+use embedder_traits::resources::{self, Resource};
+use euclid::{Point2D, Rect, Size2D, TypedScale, TypedSize2D};
+use fnv::FnvHashMap;
+use fxhash::{FxHashMap, FxHashSet};
+use gfx::font;
use gfx::font_cache_thread::FontCacheThread;
-use ipc_channel::ipc::{IpcReceiver, IpcSender};
-use metrics::PaintTimeMetrics;
-use msg::constellation_msg::TopLevelBrowsingContextId;
-use msg::constellation_msg::{BackgroundHangMonitorRegister, PipelineId};
-use net_traits::image_cache::ImageCache;
-use profile_traits::{mem, time};
-use script_traits::LayoutMsg as ConstellationMsg;
-use script_traits::{ConstellationControlMsg, LayoutControlMsg};
-use servo_geometry::DeviceIndependentPixel;
+use gfx::font_context;
+use gfx_traits::{node_id_from_scroll_id, Epoch};
+use histogram::Histogram;
+use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
+use ipc_channel::router::ROUTER;
+use layout::animation;
+use layout::construct::ConstructionResult;
+use layout::context::malloc_size_of_persistent_local_context;
+use layout::context::LayoutContext;
+use layout::context::RegisteredPainter;
+use layout::context::RegisteredPainters;
+use layout::display_list::items::{OpaqueNode, WebRenderImageInfo};
+use layout::display_list::{IndexableText, ToLayout, WebRenderDisplayListConverter};
+use layout::flow::{Flow, GetBaseFlow, ImmutableFlowUtils, MutableOwnedFlowUtils};
+use layout::flow_ref::FlowRef;
+use layout::incremental::{RelayoutMode, SpecialRestyleDamage};
+use layout::layout_debug;
+use layout::parallel;
+use layout::query::{
+ process_content_box_request, process_content_boxes_request, LayoutRPCImpl, LayoutThreadData,
+};
+use layout::query::{process_element_inner_text_query, process_node_geometry_request};
+use layout::query::{process_node_scroll_area_request, process_node_scroll_id_request};
+use layout::query::{
+ process_offset_parent_query, process_resolved_style_request, process_style_query,
+};
+use layout::sequential;
+use layout::traversal::{
+ ComputeStackingRelativePositions, PreorderFlowTraversal, RecalcStyleAndConstructFlows,
+};
+use layout::wrapper::LayoutNodeLayoutData;
+use layout_traits::LayoutThreadFactory;
+use libc::c_void;
+use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
+use metrics::{PaintTimeMetrics, ProfilerMetadataFactory, ProgressiveWebMetric};
+use msg::constellation_msg::{
+ BackgroundHangMonitor, BackgroundHangMonitorRegister, HangAnnotation,
+};
+use msg::constellation_msg::{BrowsingContextId, MonitoredComponentId, TopLevelBrowsingContextId};
+use msg::constellation_msg::{LayoutHangAnnotation, MonitoredComponentType, PipelineId};
+use net_traits::image_cache::{ImageCache, UsePlaceholder};
+use parking_lot::RwLock;
+use profile_traits::mem::{self as profile_mem, Report, ReportKind, ReportsChan};
+use profile_traits::time::{self as profile_time, profile, TimerMetadata};
+use profile_traits::time::{TimerMetadataFrameType, TimerMetadataReflowType};
+use script_layout_interface::message::{LayoutThreadInit, Msg, NodesFromPointQueryType, Reflow};
+use script_layout_interface::message::{QueryMsg, ReflowComplete, ReflowGoal, ScriptReflow};
+use script_layout_interface::rpc::TextIndexResponse;
+use script_layout_interface::rpc::{LayoutRPC, OffsetParentResponse, StyleResponse};
+use script_layout_interface::wrapper_traits::LayoutNode;
+use script_traits::Painter;
+use script_traits::{ConstellationControlMsg, LayoutControlMsg, LayoutMsg as ConstellationMsg};
+use script_traits::{DrawAPaintImageResult, IFrameSizeMsg, PaintWorkletError, WindowSizeType};
+use script_traits::{ScrollState, UntrustedNodeAddress};
+use selectors::Element;
+use servo_arc::Arc as ServoArc;
+use servo_atoms::Atom;
+use servo_config::opts;
+use servo_config::pref;
+use servo_geometry::{DeviceIndependentPixel, MaxRect};
use servo_url::ServoUrl;
-use std::sync::atomic::AtomicBool;
-use std::sync::Arc;
+use std::borrow::ToOwned;
+use std::cell::{Cell, RefCell};
+use std::collections::HashMap;
+use std::ops::{Deref, DerefMut};
+use std::process;
+use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
+use std::sync::{Arc, Mutex, MutexGuard};
+use std::thread;
+use std::time::Duration;
+use style::animation::Animation;
+use style::context::{QuirksMode, RegisteredSpeculativePainter, RegisteredSpeculativePainters};
+use style::context::{SharedStyleContext, ThreadLocalStyleContextCreationInfo};
+use style::dom::{ShowSubtree, ShowSubtreeDataAndPrimaryValues, TDocument, TElement, TNode};
+use style::driver;
+use style::error_reporting::RustLogReporter;
+use style::global_style_data::{GLOBAL_STYLE_DATA, STYLE_THREAD_POOL};
+use style::invalidation::element::restyle_hints::RestyleHint;
+use style::logical_geometry::LogicalPoint;
+use style::media_queries::{Device, MediaList, MediaType};
+use style::properties::PropertyId;
+use style::selector_parser::SnapshotMap;
+use style::servo::restyle_damage::ServoRestyleDamage;
+use style::shared_lock::{SharedRwLock, SharedRwLockReadGuard, StylesheetGuards};
+use style::stylesheets::{
+ DocumentStyleSheet, Origin, Stylesheet, StylesheetInDocument, UserAgentStylesheets,
+};
+use style::stylist::Stylist;
+use style::thread_state::{self, ThreadState};
+use style::timer::Timer;
+use style::traversal::DomTraversal;
+use style::traversal_flags::TraversalFlags;
+use style_traits::CSSPixel;
+use style_traits::DevicePixel;
+use style_traits::SpeculativePainter;
+
+/// Information needed by the layout thread.
+pub struct LayoutThread {
+ /// The ID of the pipeline that we belong to.
+ id: PipelineId,
+
+ /// The ID of the top-level browsing context that we belong to.
+ top_level_browsing_context_id: TopLevelBrowsingContextId,
+
+ /// The URL of the pipeline that we belong to.
+ url: ServoUrl,
+
+ /// Performs CSS selector matching and style resolution.
+ stylist: Stylist,
+
+ /// Is the current reflow of an iframe, as opposed to a root window?
+ is_iframe: bool,
+
+ /// The port on which we receive messages from the script thread.
+ port: Receiver<Msg>,
+
+ /// The port on which we receive messages from the constellation.
+ pipeline_port: Receiver<LayoutControlMsg>,
+
+ /// The port on which we receive messages from the font cache thread.
+ font_cache_receiver: Receiver<()>,
+
+ /// The channel on which the font cache can send messages to us.
+ font_cache_sender: IpcSender<()>,
+
+ /// A means of communication with the background hang monitor.
+ background_hang_monitor: Box<dyn BackgroundHangMonitor>,
+
+ /// The channel on which messages can be sent to the constellation.
+ constellation_chan: IpcSender<ConstellationMsg>,
+
+ /// The channel on which messages can be sent to the script thread.
+ script_chan: IpcSender<ConstellationControlMsg>,
+
+ /// The channel on which messages can be sent to the time profiler.
+ time_profiler_chan: profile_time::ProfilerChan,
+
+ /// The channel on which messages can be sent to the memory profiler.
+ mem_profiler_chan: profile_mem::ProfilerChan,
+
+ /// Reference to the script thread image cache.
+ image_cache: Arc<dyn ImageCache>,
+
+ /// Public interface to the font cache thread.
+ font_cache_thread: FontCacheThread,
+
+ /// Is this the first reflow in this LayoutThread?
+ first_reflow: Cell<bool>,
+
+ /// Flag to indicate whether to use parallel operations
+ parallel_flag: bool,
+
+ /// Starts at zero, and increased by one every time a layout completes.
+ /// This can be used to easily check for invalid stale data.
+ generation: Cell<u32>,
+
+ /// A channel on which new animations that have been triggered by style recalculation can be
+ /// sent.
+ new_animations_sender: Sender<Animation>,
+
+ /// Receives newly-discovered animations.
+ new_animations_receiver: Receiver<Animation>,
+
+ /// The number of Web fonts that have been requested but not yet loaded.
+ outstanding_web_fonts: Arc<AtomicUsize>,
+
+ /// The root of the flow tree.
+ root_flow: RefCell<Option<FlowRef>>,
+
+ /// The document-specific shared lock used for author-origin stylesheets
+ document_shared_lock: Option<SharedRwLock>,
+
+ /// The list of currently-running animations.
+ running_animations: ServoArc<RwLock<FxHashMap<OpaqueNode, Vec<Animation>>>>,
+
+ /// The list of animations that have expired since the last style recalculation.
+ expired_animations: ServoArc<RwLock<FxHashMap<OpaqueNode, Vec<Animation>>>>,
+
+ /// A counter for epoch messages
+ epoch: Cell<Epoch>,
+
+ /// The size of the viewport. This may be different from the size of the screen due to viewport
+ /// constraints.
+ viewport_size: Size2D<Au>,
+
+ /// A mutex to allow for fast, read-only RPC of layout's internal data
+ /// structures, while still letting the LayoutThread modify them.
+ ///
+ /// All the other elements of this struct are read-only.
+ rw_data: Arc<Mutex<LayoutThreadData>>,
+
+ webrender_image_cache: Arc<RwLock<FnvHashMap<(ServoUrl, UsePlaceholder), WebRenderImageInfo>>>,
+
+ /// The executors for paint worklets.
+ registered_painters: RegisteredPaintersImpl,
+
+ /// Webrender interface.
+ webrender_api: webrender_api::RenderApi,
+
+ /// Webrender document.
+ webrender_document: webrender_api::DocumentId,
+
+ /// The timer object to control the timing of the animations. This should
+ /// only be a test-mode timer during testing for animations.
+ timer: Timer,
+
+ /// Paint time metrics.
+ paint_time_metrics: PaintTimeMetrics,
+
+ /// The time a layout query has waited before serviced by layout thread.
+ layout_query_waiting_time: Histogram,
-pub struct LayoutThread;
+ /// The sizes of all iframes encountered during the last layout operation.
+ last_iframe_sizes: RefCell<HashMap<BrowsingContextId, TypedSize2D<f32, CSSPixel>>>,
-impl layout_traits::LayoutThreadFactory for LayoutThread {
- type Message = script_layout_interface::message::Msg;
+ /// Flag that indicates if LayoutThread is busy handling a request.
+ busy: Arc<AtomicBool>,
- #[allow(unused)]
+ /// Load web fonts synchronously to avoid non-deterministic network-driven reflows.
+ load_webfonts_synchronously: bool,
+
+ /// The initial request size of the window
+ initial_window_size: TypedSize2D<u32, DeviceIndependentPixel>,
+
+ /// The ratio of device pixels per px at the default scale.
+ /// If unspecified, will use the platform default setting.
+ device_pixels_per_px: Option<f32>,
+
+ /// Dumps the display list form after a layout.
+ dump_display_list: bool,
+
+ /// Dumps the display list in JSON form after a layout.
+ dump_display_list_json: bool,
+
+ /// Dumps the DOM after restyle.
+ dump_style_tree: bool,
+
+ /// Dumps the flow tree after a layout.
+ dump_rule_tree: bool,
+
+ /// Emits notifications when there is a relayout.
+ relayout_event: bool,
+
+ /// True to turn off incremental layout.
+ nonincremental_layout: bool,
+
+ /// True if each step of layout is traced to an external JSON file
+ /// for debugging purposes. Setting this implies sequential layout
+ /// and paint.
+ trace_layout: bool,
+
+ /// Dumps the flow tree after a layout.
+ dump_flow_tree: bool,
+}
+
+impl LayoutThreadFactory for LayoutThread {
+ type Message = Msg;
+
+ /// Spawns a new layout thread.
fn create(
id: PipelineId,
top_level_browsing_context_id: TopLevelBrowsingContextId,
url: ServoUrl,
is_iframe: bool,
- chan: (Sender<Self::Message>, Receiver<Self::Message>),
+ chan: (Sender<Msg>, Receiver<Msg>),
pipeline_port: IpcReceiver<LayoutControlMsg>,
- background_hang_monitor: Box<dyn BackgroundHangMonitorRegister>,
+ background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>,
constellation_chan: IpcSender<ConstellationMsg>,
script_chan: IpcSender<ConstellationControlMsg>,
image_cache: Arc<dyn ImageCache>,
font_cache_thread: FontCacheThread,
- time_profiler_chan: time::ProfilerChan,
- mem_profiler_chan: mem::ProfilerChan,
+ time_profiler_chan: profile_time::ProfilerChan,
+ mem_profiler_chan: profile_mem::ProfilerChan,
content_process_shutdown_chan: Option<IpcSender<()>>,
webrender_api_sender: webrender_api::RenderApiSender,
webrender_document: webrender_api::DocumentId,
@@ -55,5 +323,1843 @@ impl layout_traits::LayoutThreadFactory for LayoutThread {
trace_layout: bool,
dump_flow_tree: bool,
) {
+ thread::Builder::new()
+ .name(format!("LayoutThread {:?}", id))
+ .spawn(move || {
+ thread_state::initialize(ThreadState::LAYOUT);
+
+ // In order to get accurate crash reports, we install the top-level bc id.
+ TopLevelBrowsingContextId::install(top_level_browsing_context_id);
+
+ {
+ // Ensures layout thread is destroyed before we send shutdown message
+ let sender = chan.0;
+
+ let background_hang_monitor = background_hang_monitor_register
+ .register_component(
+ MonitoredComponentId(id, MonitoredComponentType::Layout),
+ Duration::from_millis(1000),
+ Duration::from_millis(5000),
+ );
+
+ let layout = LayoutThread::new(
+ id,
+ top_level_browsing_context_id,
+ url,
+ is_iframe,
+ chan.1,
+ pipeline_port,
+ background_hang_monitor,
+ constellation_chan,
+ script_chan,
+ image_cache.clone(),
+ font_cache_thread,
+ time_profiler_chan,
+ mem_profiler_chan.clone(),
+ webrender_api_sender,
+ webrender_document,
+ paint_time_metrics,
+ busy,
+ load_webfonts_synchronously,
+ initial_window_size,
+ device_pixels_per_px,
+ dump_display_list,
+ dump_display_list_json,
+ dump_style_tree,
+ dump_rule_tree,
+ relayout_event,
+ nonincremental_layout,
+ trace_layout,
+ dump_flow_tree,
+ );
+
+ let reporter_name = format!("layout-reporter-{}", id);
+ mem_profiler_chan.run_with_memory_reporting(
+ || {
+ layout.start();
+ },
+ reporter_name,
+ sender,
+ Msg::CollectReports,
+ );
+ }
+ if let Some(content_process_shutdown_chan) = content_process_shutdown_chan {
+ let _ = content_process_shutdown_chan.send(());
+ }
+ })
+ .expect("Thread spawning failed");
+ }
+}
+
+struct ScriptReflowResult {
+ script_reflow: ScriptReflow,
+ result: RefCell<Option<ReflowComplete>>,
+}
+
+impl Deref for ScriptReflowResult {
+ type Target = ScriptReflow;
+ fn deref(&self) -> &ScriptReflow {
+ &self.script_reflow
+ }
+}
+
+impl DerefMut for ScriptReflowResult {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.script_reflow
+ }
+}
+
+impl ScriptReflowResult {
+ fn new(script_reflow: ScriptReflow) -> ScriptReflowResult {
+ ScriptReflowResult {
+ script_reflow: script_reflow,
+ result: RefCell::new(Some(Default::default())),
+ }
+ }
+}
+
+impl Drop for ScriptReflowResult {
+ fn drop(&mut self) {
+ self.script_reflow
+ .script_join_chan
+ .send(self.result.borrow_mut().take().unwrap())
+ .unwrap();
+ }
+}
+
+/// The `LayoutThread` `rw_data` lock must remain locked until the first reflow,
+/// as RPC calls don't make sense until then. Use this in combination with
+/// `LayoutThread::lock_rw_data` and `LayoutThread::return_rw_data`.
+pub enum RWGuard<'a> {
+ /// If the lock was previously held, from when the thread started.
+ Held(MutexGuard<'a, LayoutThreadData>),
+ /// If the lock was just used, and has been returned since there has been
+ /// a reflow already.
+ Used(MutexGuard<'a, LayoutThreadData>),
+}
+
+impl<'a> Deref for RWGuard<'a> {
+ type Target = LayoutThreadData;
+ fn deref(&self) -> &LayoutThreadData {
+ match *self {
+ RWGuard::Held(ref x) => &**x,
+ RWGuard::Used(ref x) => &**x,
+ }
+ }
+}
+
+impl<'a> DerefMut for RWGuard<'a> {
+ fn deref_mut(&mut self) -> &mut LayoutThreadData {
+ match *self {
+ RWGuard::Held(ref mut x) => &mut **x,
+ RWGuard::Used(ref mut x) => &mut **x,
+ }
+ }
+}
+
+struct RwData<'a, 'b: 'a> {
+ rw_data: &'b Arc<Mutex<LayoutThreadData>>,
+ possibly_locked_rw_data: &'a mut Option<MutexGuard<'b, LayoutThreadData>>,
+}
+
+impl<'a, 'b: 'a> RwData<'a, 'b> {
+ /// If no reflow has happened yet, this will just return the lock in
+ /// `possibly_locked_rw_data`. Otherwise, it will acquire the `rw_data` lock.
+ ///
+ /// If you do not wish RPCs to remain blocked, just drop the `RWGuard`
+ /// returned from this function. If you _do_ wish for them to remain blocked,
+ /// use `block`.
+ fn lock(&mut self) -> RWGuard<'b> {
+ match self.possibly_locked_rw_data.take() {
+ None => RWGuard::Used(self.rw_data.lock().unwrap()),
+ Some(x) => RWGuard::Held(x),
+ }
+ }
+}
+
+fn add_font_face_rules(
+ stylesheet: &Stylesheet,
+ guard: &SharedRwLockReadGuard,
+ device: &Device,
+ font_cache_thread: &FontCacheThread,
+ font_cache_sender: &IpcSender<()>,
+ outstanding_web_fonts_counter: &Arc<AtomicUsize>,
+ load_webfonts_synchronously: bool,
+) {
+ if load_webfonts_synchronously {
+ let (sender, receiver) = ipc::channel().unwrap();
+ stylesheet.effective_font_face_rules(&device, guard, |rule| {
+ if let Some(font_face) = rule.font_face() {
+ let effective_sources = font_face.effective_sources();
+ font_cache_thread.add_web_font(
+ font_face.family().clone(),
+ effective_sources,
+ sender.clone(),
+ );
+ receiver.recv().unwrap();
+ }
+ })
+ } else {
+ stylesheet.effective_font_face_rules(&device, guard, |rule| {
+ if let Some(font_face) = rule.font_face() {
+ let effective_sources = font_face.effective_sources();
+ outstanding_web_fonts_counter.fetch_add(1, Ordering::SeqCst);
+ font_cache_thread.add_web_font(
+ font_face.family().clone(),
+ effective_sources,
+ (*font_cache_sender).clone(),
+ );
+ }
+ })
+ }
+}
+
+impl LayoutThread {
+ /// Creates a new `LayoutThread` structure.
+ fn new(
+ id: PipelineId,
+ top_level_browsing_context_id: TopLevelBrowsingContextId,
+ url: ServoUrl,
+ is_iframe: bool,
+ port: Receiver<Msg>,
+ pipeline_port: IpcReceiver<LayoutControlMsg>,
+ background_hang_monitor: Box<dyn BackgroundHangMonitor>,
+ constellation_chan: IpcSender<ConstellationMsg>,
+ script_chan: IpcSender<ConstellationControlMsg>,
+ image_cache: Arc<dyn ImageCache>,
+ font_cache_thread: FontCacheThread,
+ time_profiler_chan: profile_time::ProfilerChan,
+ mem_profiler_chan: profile_mem::ProfilerChan,
+ webrender_api_sender: webrender_api::RenderApiSender,
+ webrender_document: webrender_api::DocumentId,
+ paint_time_metrics: PaintTimeMetrics,
+ busy: Arc<AtomicBool>,
+ load_webfonts_synchronously: bool,
+ initial_window_size: TypedSize2D<u32, DeviceIndependentPixel>,
+ device_pixels_per_px: Option<f32>,
+ dump_display_list: bool,
+ dump_display_list_json: bool,
+ dump_style_tree: bool,
+ dump_rule_tree: bool,
+ relayout_event: bool,
+ nonincremental_layout: bool,
+ trace_layout: bool,
+ dump_flow_tree: bool,
+ ) -> LayoutThread {
+ // The device pixel ratio is incorrect (it does not have the hidpi value),
+ // but it will be set correctly when the initial reflow takes place.
+ let device = Device::new(
+ MediaType::screen(),
+ initial_window_size.to_f32() * TypedScale::new(1.0),
+ TypedScale::new(device_pixels_per_px.unwrap_or(1.0)),
+ );
+
+ // Create the channel on which new animations can be sent.
+ let (new_animations_sender, new_animations_receiver) = unbounded();
+
+ // Proxy IPC messages from the pipeline to the layout thread.
+ let pipeline_receiver = ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(pipeline_port);
+
+ // Ask the router to proxy IPC messages from the font cache thread to the layout thread.
+ let (ipc_font_cache_sender, ipc_font_cache_receiver) = ipc::channel().unwrap();
+ let font_cache_receiver =
+ ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(ipc_font_cache_receiver);
+
+ LayoutThread {
+ id: id,
+ top_level_browsing_context_id: top_level_browsing_context_id,
+ url: url,
+ is_iframe: is_iframe,
+ port: port,
+ pipeline_port: pipeline_receiver,
+ script_chan: script_chan.clone(),
+ background_hang_monitor,
+ constellation_chan: constellation_chan.clone(),
+ time_profiler_chan: time_profiler_chan,
+ mem_profiler_chan: mem_profiler_chan,
+ registered_painters: RegisteredPaintersImpl(Default::default()),
+ image_cache: image_cache.clone(),
+ font_cache_thread: font_cache_thread,
+ first_reflow: Cell::new(true),
+ font_cache_receiver: font_cache_receiver,
+ font_cache_sender: ipc_font_cache_sender,
+ parallel_flag: true,
+ generation: Cell::new(0),
+ new_animations_sender: new_animations_sender,
+ new_animations_receiver: new_animations_receiver,
+ outstanding_web_fonts: Arc::new(AtomicUsize::new(0)),
+ root_flow: RefCell::new(None),
+ document_shared_lock: None,
+ running_animations: ServoArc::new(RwLock::new(Default::default())),
+ expired_animations: ServoArc::new(RwLock::new(Default::default())),
+ epoch: Cell::new(Epoch(0)),
+ viewport_size: Size2D::new(Au(0), Au(0)),
+ webrender_api: webrender_api_sender.create_api(),
+ webrender_document,
+ stylist: Stylist::new(device, QuirksMode::NoQuirks),
+ rw_data: Arc::new(Mutex::new(LayoutThreadData {
+ constellation_chan: constellation_chan,
+ display_list: None,
+ indexable_text: IndexableText::default(),
+ content_box_response: None,
+ content_boxes_response: Vec::new(),
+ client_rect_response: Rect::zero(),
+ scroll_id_response: None,
+ scroll_area_response: Rect::zero(),
+ resolved_style_response: String::new(),
+ offset_parent_response: OffsetParentResponse::empty(),
+ style_response: StyleResponse(None),
+ scroll_offsets: HashMap::new(),
+ text_index_response: TextIndexResponse(None),
+ nodes_from_point_response: vec![],
+ element_inner_text_response: String::new(),
+ })),
+ webrender_image_cache: Arc::new(RwLock::new(FnvHashMap::default())),
+ timer: if pref!(layout.animations.test.enabled) {
+ Timer::test_mode()
+ } else {
+ Timer::new()
+ },
+ paint_time_metrics: paint_time_metrics,
+ layout_query_waiting_time: Histogram::new(),
+ last_iframe_sizes: Default::default(),
+ busy,
+ load_webfonts_synchronously,
+ initial_window_size,
+ device_pixels_per_px,
+ dump_display_list,
+ dump_display_list_json,
+ dump_style_tree,
+ dump_rule_tree,
+ relayout_event,
+ nonincremental_layout,
+ trace_layout,
+ dump_flow_tree,
+ }
+ }
+
+ /// Starts listening on the port.
+ fn start(mut self) {
+ let rw_data = self.rw_data.clone();
+ let mut possibly_locked_rw_data = Some(rw_data.lock().unwrap());
+ let mut rw_data = RwData {
+ rw_data: &rw_data,
+ possibly_locked_rw_data: &mut possibly_locked_rw_data,
+ };
+ while self.handle_request(&mut rw_data) {
+ // Loop indefinitely.
+ }
+ }
+
+ // Create a layout context for use in building display lists, hit testing, &c.
+ fn build_layout_context<'a>(
+ &'a self,
+ guards: StylesheetGuards<'a>,
+ script_initiated_layout: bool,
+ snapshot_map: &'a SnapshotMap,
+ ) -> LayoutContext<'a> {
+ let thread_local_style_context_creation_data =
+ ThreadLocalStyleContextCreationInfo::new(self.new_animations_sender.clone());
+
+ LayoutContext {
+ id: self.id,
+ style_context: SharedStyleContext {
+ stylist: &self.stylist,
+ options: GLOBAL_STYLE_DATA.options.clone(),
+ guards,
+ visited_styles_enabled: false,
+ running_animations: self.running_animations.clone(),
+ expired_animations: self.expired_animations.clone(),
+ registered_speculative_painters: &self.registered_painters,
+ local_context_creation_data: Mutex::new(thread_local_style_context_creation_data),
+ timer: self.timer.clone(),
+ traversal_flags: TraversalFlags::empty(),
+ snapshot_map: snapshot_map,
+ },
+ image_cache: self.image_cache.clone(),
+ font_cache_thread: Mutex::new(self.font_cache_thread.clone()),
+ webrender_image_cache: self.webrender_image_cache.clone(),
+ pending_images: if script_initiated_layout {
+ Some(Mutex::new(vec![]))
+ } else {
+ None
+ },
+ newly_transitioning_nodes: if script_initiated_layout {
+ Some(Mutex::new(vec![]))
+ } else {
+ None
+ },
+ registered_painters: &self.registered_painters,
+ }
+ }
+
+ fn notify_activity_to_hang_monitor(&self, request: &Msg) {
+ let hang_annotation = match request {
+ Msg::AddStylesheet(..) => LayoutHangAnnotation::AddStylesheet,
+ Msg::RemoveStylesheet(..) => LayoutHangAnnotation::RemoveStylesheet,
+ Msg::SetQuirksMode(..) => LayoutHangAnnotation::SetQuirksMode,
+ Msg::Reflow(..) => LayoutHangAnnotation::Reflow,
+ Msg::GetRPC(..) => LayoutHangAnnotation::GetRPC,
+ Msg::TickAnimations => LayoutHangAnnotation::TickAnimations,
+ Msg::AdvanceClockMs(..) => LayoutHangAnnotation::AdvanceClockMs,
+ Msg::ReapStyleAndLayoutData(..) => LayoutHangAnnotation::ReapStyleAndLayoutData,
+ Msg::CollectReports(..) => LayoutHangAnnotation::CollectReports,
+ Msg::PrepareToExit(..) => LayoutHangAnnotation::PrepareToExit,
+ Msg::ExitNow => LayoutHangAnnotation::ExitNow,
+ Msg::GetCurrentEpoch(..) => LayoutHangAnnotation::GetCurrentEpoch,
+ Msg::GetWebFontLoadState(..) => LayoutHangAnnotation::GetWebFontLoadState,
+ Msg::CreateLayoutThread(..) => LayoutHangAnnotation::CreateLayoutThread,
+ Msg::SetFinalUrl(..) => LayoutHangAnnotation::SetFinalUrl,
+ Msg::SetScrollStates(..) => LayoutHangAnnotation::SetScrollStates,
+ Msg::UpdateScrollStateFromScript(..) => {
+ LayoutHangAnnotation::UpdateScrollStateFromScript
+ },
+ Msg::RegisterPaint(..) => LayoutHangAnnotation::RegisterPaint,
+ Msg::SetNavigationStart(..) => LayoutHangAnnotation::SetNavigationStart,
+ Msg::GetRunningAnimations(..) => LayoutHangAnnotation::GetRunningAnimations,
+ };
+ self.background_hang_monitor
+ .notify_activity(HangAnnotation::Layout(hang_annotation));
+ }
+
+ /// Receives and dispatches messages from the script and constellation threads
+ fn handle_request<'a, 'b>(&mut self, possibly_locked_rw_data: &mut RwData<'a, 'b>) -> bool {
+ enum Request {
+ FromPipeline(LayoutControlMsg),
+ FromScript(Msg),
+ FromFontCache,
+ }
+
+ // Notify the background-hang-monitor we are waiting for an event.
+ self.background_hang_monitor.notify_wait();
+
+ let request = select! {
+ recv(self.pipeline_port) -> msg => Request::FromPipeline(msg.unwrap()),
+ recv(self.port) -> msg => Request::FromScript(msg.unwrap()),
+ recv(self.font_cache_receiver) -> msg => { msg.unwrap(); Request::FromFontCache }
+ };
+
+ self.busy.store(true, Ordering::Relaxed);
+ let result = match request {
+ Request::FromPipeline(LayoutControlMsg::SetScrollStates(new_scroll_states)) => self
+ .handle_request_helper(
+ Msg::SetScrollStates(new_scroll_states),
+ possibly_locked_rw_data,
+ ),
+ Request::FromPipeline(LayoutControlMsg::TickAnimations) => {
+ self.handle_request_helper(Msg::TickAnimations, possibly_locked_rw_data)
+ },
+ Request::FromPipeline(LayoutControlMsg::GetCurrentEpoch(sender)) => {
+ self.handle_request_helper(Msg::GetCurrentEpoch(sender), possibly_locked_rw_data)
+ },
+ Request::FromPipeline(LayoutControlMsg::GetWebFontLoadState(sender)) => self
+ .handle_request_helper(Msg::GetWebFontLoadState(sender), possibly_locked_rw_data),
+ Request::FromPipeline(LayoutControlMsg::ExitNow) => {
+ self.handle_request_helper(Msg::ExitNow, possibly_locked_rw_data)
+ },
+ Request::FromPipeline(LayoutControlMsg::PaintMetric(epoch, paint_time)) => {
+ self.paint_time_metrics.maybe_set_metric(epoch, paint_time);
+ true
+ },
+ Request::FromScript(msg) => self.handle_request_helper(msg, possibly_locked_rw_data),
+ Request::FromFontCache => {
+ let _rw_data = possibly_locked_rw_data.lock();
+ self.outstanding_web_fonts.fetch_sub(1, Ordering::SeqCst);
+ font_context::invalidate_font_caches();
+ self.script_chan
+ .send(ConstellationControlMsg::WebFontLoaded(self.id))
+ .unwrap();
+ true
+ },
+ };
+ self.busy.store(false, Ordering::Relaxed);
+ result
+ }
+
+ /// Receives and dispatches messages from other threads.
+ fn handle_request_helper<'a, 'b>(
+ &mut self,
+ request: Msg,
+ possibly_locked_rw_data: &mut RwData<'a, 'b>,
+ ) -> bool {
+ self.notify_activity_to_hang_monitor(&request);
+
+ match request {
+ Msg::AddStylesheet(stylesheet, before_stylesheet) => {
+ let guard = stylesheet.shared_lock.read();
+ self.handle_add_stylesheet(&stylesheet, &guard);
+
+ match before_stylesheet {
+ Some(insertion_point) => self.stylist.insert_stylesheet_before(
+ DocumentStyleSheet(stylesheet.clone()),
+ DocumentStyleSheet(insertion_point),
+ &guard,
+ ),
+ None => self
+ .stylist
+ .append_stylesheet(DocumentStyleSheet(stylesheet.clone()), &guard),
+ }
+ },
+ Msg::RemoveStylesheet(stylesheet) => {
+ let guard = stylesheet.shared_lock.read();
+ self.stylist
+ .remove_stylesheet(DocumentStyleSheet(stylesheet.clone()), &guard);
+ },
+ Msg::SetQuirksMode(mode) => self.handle_set_quirks_mode(mode),
+ Msg::GetRPC(response_chan) => {
+ response_chan
+ .send(Box::new(LayoutRPCImpl(self.rw_data.clone())) as Box<dyn LayoutRPC + Send>)
+ .unwrap();
+ },
+ Msg::Reflow(data) => {
+ let mut data = ScriptReflowResult::new(data);
+ profile(
+ profile_time::ProfilerCategory::LayoutPerform,
+ self.profiler_metadata(),
+ self.time_profiler_chan.clone(),
+ || self.handle_reflow(&mut data, possibly_locked_rw_data),
+ );
+ },
+ Msg::TickAnimations => self.tick_all_animations(possibly_locked_rw_data),
+ Msg::SetScrollStates(new_scroll_states) => {
+ self.set_scroll_states(new_scroll_states, possibly_locked_rw_data);
+ },
+ Msg::UpdateScrollStateFromScript(state) => {
+ let mut rw_data = possibly_locked_rw_data.lock();
+ rw_data
+ .scroll_offsets
+ .insert(state.scroll_id, state.scroll_offset);
+
+ let point = Point2D::new(-state.scroll_offset.x, -state.scroll_offset.y);
+ let mut txn = webrender_api::Transaction::new();
+ txn.scroll_node_with_id(
+ webrender_api::units::LayoutPoint::from_untyped(&point),
+ state.scroll_id,
+ webrender_api::ScrollClamping::ToContentBounds,
+ );
+ self.webrender_api
+ .send_transaction(self.webrender_document, txn);
+ },
+ Msg::ReapStyleAndLayoutData(dead_data) => unsafe {
+ drop_style_and_layout_data(dead_data)
+ },
+ Msg::CollectReports(reports_chan) => {
+ self.collect_reports(reports_chan, possibly_locked_rw_data);
+ },
+ Msg::GetCurrentEpoch(sender) => {
+ let _rw_data = possibly_locked_rw_data.lock();
+ sender.send(self.epoch.get()).unwrap();
+ },
+ Msg::AdvanceClockMs(how_many, do_tick) => {
+ self.handle_advance_clock_ms(how_many, possibly_locked_rw_data, do_tick);
+ },
+ Msg::GetWebFontLoadState(sender) => {
+ let _rw_data = possibly_locked_rw_data.lock();
+ let outstanding_web_fonts = self.outstanding_web_fonts.load(Ordering::SeqCst);
+ sender.send(outstanding_web_fonts != 0).unwrap();
+ },
+ Msg::CreateLayoutThread(info) => self.create_layout_thread(info),
+ Msg::SetFinalUrl(final_url) => {
+ self.url = final_url;
+ },
+ Msg::RegisterPaint(name, mut properties, painter) => {
+ debug!("Registering the painter");
+ let properties = properties
+ .drain(..)
+ .filter_map(|name| {
+ let id = PropertyId::parse_enabled_for_all_content(&*name).ok()?;
+ Some((name.clone(), id))
+ })
+ .filter(|&(_, ref id)| !id.is_shorthand())
+ .collect();
+ let registered_painter = RegisteredPainterImpl {
+ name: name.clone(),
+ properties,
+ painter,
+ };
+ self.registered_painters.0.insert(name, registered_painter);
+ },
+ Msg::PrepareToExit(response_chan) => {
+ self.prepare_to_exit(response_chan);
+ return false;
+ },
+ // Receiving the Exit message at this stage only happens when layout is undergoing a "force exit".
+ Msg::ExitNow => {
+ debug!("layout: ExitNow received");
+ self.exit_now();
+ return false;
+ },
+ Msg::SetNavigationStart(time) => {
+ self.paint_time_metrics.set_navigation_start(time);
+ },
+ Msg::GetRunningAnimations(sender) => {
+ let _ = sender.send(self.running_animations.read().len());
+ },
+ }
+
+ true
+ }
+
+ fn collect_reports<'a, 'b>(
+ &self,
+ reports_chan: ReportsChan,
+ possibly_locked_rw_data: &mut RwData<'a, 'b>,
+ ) {
+ let mut reports = vec![];
+ // Servo uses vanilla jemalloc, which doesn't have a
+ // malloc_enclosing_size_of function.
+ let mut ops = MallocSizeOfOps::new(servo_allocator::usable_size, None, None);
+
+ // FIXME(njn): Just measuring the display tree for now.
+ let rw_data = possibly_locked_rw_data.lock();
+ let display_list = rw_data.display_list.as_ref();
+ let formatted_url = &format!("url({})", self.url);
+ reports.push(Report {
+ path: path![formatted_url, "layout-thread", "display-list"],
+ kind: ReportKind::ExplicitJemallocHeapSize,
+ size: display_list.map_or(0, |sc| sc.size_of(&mut ops)),
+ });
+
+ reports.push(Report {
+ path: path![formatted_url, "layout-thread", "stylist"],
+ kind: ReportKind::ExplicitJemallocHeapSize,
+ size: self.stylist.size_of(&mut ops),
+ });
+
+ // The LayoutThread has data in Persistent TLS...
+ reports.push(Report {
+ path: path![formatted_url, "layout-thread", "local-context"],
+ kind: ReportKind::ExplicitJemallocHeapSize,
+ size: malloc_size_of_persistent_local_context(&mut ops),
+ });
+
+ reports_chan.send(reports);
+ }
+
+ fn create_layout_thread(&self, info: LayoutThreadInit) {
+ LayoutThread::create(
+ info.id,
+ self.top_level_browsing_context_id,
+ info.url.clone(),
+ info.is_parent,
+ info.layout_pair,
+ info.pipeline_port,
+ info.background_hang_monitor_register,
+ info.constellation_chan,
+ info.script_chan.clone(),
+ info.image_cache.clone(),
+ self.font_cache_thread.clone(),
+ self.time_profiler_chan.clone(),
+ self.mem_profiler_chan.clone(),
+ info.content_process_shutdown_chan,
+ self.webrender_api.clone_sender(),
+ self.webrender_document,
+ info.paint_time_metrics,
+ info.layout_is_busy,
+ self.load_webfonts_synchronously,
+ self.initial_window_size,
+ self.device_pixels_per_px,
+ self.dump_display_list,
+ self.dump_display_list_json,
+ self.dump_style_tree,
+ self.dump_rule_tree,
+ self.relayout_event,
+ self.nonincremental_layout,
+ self.trace_layout,
+ self.dump_flow_tree,
+ );
+ }
+
+ /// Enters a quiescent state in which no new messages will be processed until an `ExitNow` is
+ /// received. A pong is immediately sent on the given response channel.
+ fn prepare_to_exit(&mut self, response_chan: Sender<()>) {
+ response_chan.send(()).unwrap();
+ loop {
+ match self.port.recv().unwrap() {
+ Msg::ReapStyleAndLayoutData(dead_data) => unsafe {
+ drop_style_and_layout_data(dead_data)
+ },
+ Msg::ExitNow => {
+ debug!("layout thread is exiting...");
+ self.exit_now();
+ break;
+ },
+ Msg::CollectReports(_) => {
+ // Just ignore these messages at this point.
+ },
+ _ => panic!("layout: unexpected message received after `PrepareToExitMsg`"),
+ }
+ }
+ }
+
+ /// Shuts down the layout thread now. If there are any DOM nodes left, layout will now (safely)
+ /// crash.
+ fn exit_now(&mut self) {
+ // Drop the root flow explicitly to avoid holding style data, such as
+ // rule nodes. The `Stylist` checks when it is dropped that all rule
+ // nodes have been GCed, so we want drop anyone who holds them first.
+ let waiting_time_min = self.layout_query_waiting_time.minimum().unwrap_or(0);
+ let waiting_time_max = self.layout_query_waiting_time.maximum().unwrap_or(0);
+ let waiting_time_mean = self.layout_query_waiting_time.mean().unwrap_or(0);
+ let waiting_time_stddev = self.layout_query_waiting_time.stddev().unwrap_or(0);
+ debug!(
+ "layout: query waiting time: min: {}, max: {}, mean: {}, standard_deviation: {}",
+ waiting_time_min, waiting_time_max, waiting_time_mean, waiting_time_stddev
+ );
+
+ self.root_flow.borrow_mut().take();
+ self.background_hang_monitor.unregister();
+ }
+
+ fn handle_add_stylesheet(&self, stylesheet: &Stylesheet, guard: &SharedRwLockReadGuard) {
+ // Find all font-face rules and notify the font cache of them.
+ // GWTODO: Need to handle unloading web fonts.
+ if stylesheet.is_effective_for_device(self.stylist.device(), &guard) {
+ add_font_face_rules(
+ &*stylesheet,
+ &guard,
+ self.stylist.device(),
+ &self.font_cache_thread,
+ &self.font_cache_sender,
+ &self.outstanding_web_fonts,
+ self.load_webfonts_synchronously,
+ );
+ }
+ }
+
+ /// Advances the animation clock of the document.
+ fn handle_advance_clock_ms<'a, 'b>(
+ &mut self,
+ how_many_ms: i32,
+ possibly_locked_rw_data: &mut RwData<'a, 'b>,
+ tick_animations: bool,
+ ) {
+ self.timer.increment(how_many_ms as f64 / 1000.0);
+ if tick_animations {
+ self.tick_all_animations(possibly_locked_rw_data);
+ }
+ }
+
+ /// Sets quirks mode for the document, causing the quirks mode stylesheet to be used.
+ fn handle_set_quirks_mode<'a, 'b>(&mut self, quirks_mode: QuirksMode) {
+ self.stylist.set_quirks_mode(quirks_mode);
+ }
+
+ fn try_get_layout_root<N: LayoutNode>(&self, node: N) -> Option<FlowRef> {
+ let result = node.mutate_layout_data()?.flow_construction_result.get();
+
+ let mut flow = match result {
+ ConstructionResult::Flow(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_absolute_descendants(abs_descendants);
+ flow
+ },
+ _ => return None,
+ };
+
+ FlowRef::deref_mut(&mut flow).mark_as_root();
+
+ Some(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(layout_root: &mut dyn Flow, layout_context: &LayoutContext) {
+ let _scope = layout_debug_scope!("solve_constraints");
+ sequential::reflow(layout_root, layout_context, RelayoutMode::Incremental);
+ }
+
+ /// 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(
+ traversal: &rayon::ThreadPool,
+ layout_root: &mut dyn Flow,
+ profiler_metadata: Option<TimerMetadata>,
+ time_profiler_chan: profile_time::ProfilerChan,
+ layout_context: &LayoutContext,
+ ) {
+ let _scope = layout_debug_scope!("solve_constraints_parallel");
+
+ // NOTE: this currently computes borders, so any pruning should separate that
+ // operation out.
+ parallel::reflow(
+ layout_root,
+ profiler_metadata,
+ time_profiler_chan,
+ layout_context,
+ traversal,
+ );
+ }
+
+ /// Computes the stacking-relative positions of all flows and, if the painting is dirty and the
+ /// reflow type need it, builds the display list.
+ fn compute_abs_pos_and_build_display_list(
+ &self,
+ data: &Reflow,
+ reflow_goal: &ReflowGoal,
+ document: Option<&ServoLayoutDocument>,
+ layout_root: &mut dyn Flow,
+ layout_context: &mut LayoutContext,
+ rw_data: &mut LayoutThreadData,
+ ) {
+ let writing_mode = layout_root.base().writing_mode;
+ let (metadata, sender) = (self.profiler_metadata(), self.time_profiler_chan.clone());
+ profile(
+ profile_time::ProfilerCategory::LayoutDispListBuild,
+ metadata.clone(),
+ sender.clone(),
+ || {
+ layout_root.mut_base().stacking_relative_position =
+ LogicalPoint::zero(writing_mode)
+ .to_physical(writing_mode, self.viewport_size)
+ .to_vector();
+
+ layout_root.mut_base().clip = data.page_clip_rect;
+
+ let traversal = ComputeStackingRelativePositions {
+ layout_context: layout_context,
+ };
+ traversal.traverse(layout_root);
+
+ if layout_root
+ .base()
+ .restyle_damage
+ .contains(ServoRestyleDamage::REPAINT) ||
+ rw_data.display_list.is_none()
+ {
+ if reflow_goal.needs_display_list() {
+ let background_color = get_root_flow_background_color(layout_root);
+ let mut build_state = sequential::build_display_list_for_subtree(
+ layout_root,
+ layout_context,
+ background_color,
+ data.page_clip_rect.size,
+ );
+
+ debug!("Done building display list.");
+
+ let root_size = {
+ let root_flow = layout_root.base();
+ if self.stylist.viewport_constraints().is_some() {
+ root_flow.position.size.to_physical(root_flow.writing_mode)
+ } else {
+ root_flow.overflow.scroll.size
+ }
+ };
+
+ let origin = Rect::new(Point2D::new(Au(0), Au(0)), root_size).to_layout();
+ build_state.root_stacking_context.bounds = origin;
+ build_state.root_stacking_context.overflow = origin;
+
+ if !build_state.iframe_sizes.is_empty() {
+ // build_state.iframe_sizes is only used here, so its okay to replace
+ // it with an empty vector
+ let iframe_sizes =
+ std::mem::replace(&mut build_state.iframe_sizes, vec![]);
+ // Collect the last frame's iframe sizes to compute any differences.
+ // Every frame starts with a fresh collection so that any removed
+ // iframes do not linger.
+ let last_iframe_sizes = std::mem::replace(
+ &mut *self.last_iframe_sizes.borrow_mut(),
+ HashMap::default(),
+ );
+ let mut size_messages = vec![];
+ for new_size in iframe_sizes {
+ // Only notify the constellation about existing iframes
+ // that have a new size, or iframes that did not previously
+ // exist.
+ if let Some(old_size) = last_iframe_sizes.get(&new_size.id) {
+ if *old_size != new_size.size {
+ size_messages.push(IFrameSizeMsg {
+ data: new_size,
+ type_: WindowSizeType::Resize,
+ });
+ }
+ } else {
+ size_messages.push(IFrameSizeMsg {
+ data: new_size,
+ type_: WindowSizeType::Initial,
+ });
+ }
+ self.last_iframe_sizes
+ .borrow_mut()
+ .insert(new_size.id, new_size.size);
+ }
+
+ if !size_messages.is_empty() {
+ let msg = ConstellationMsg::IFrameSizes(size_messages);
+ if let Err(e) = self.constellation_chan.send(msg) {
+ warn!("Layout resize to constellation failed ({}).", e);
+ }
+ }
+ }
+
+ rw_data.indexable_text = std::mem::replace(
+ &mut build_state.indexable_text,
+ IndexableText::default(),
+ );
+ rw_data.display_list = Some(build_state.to_display_list());
+ }
+ }
+
+ if !reflow_goal.needs_display() {
+ // Defer the paint step until the next ForDisplay.
+ //
+ // We need to tell the document about this so it doesn't
+ // incorrectly suppress reflows. See #13131.
+ document
+ .expect("No document in a non-display reflow?")
+ .needs_paint_from_layout();
+ return;
+ }
+ if let Some(document) = document {
+ document.will_paint();
+ }
+
+ let display_list = rw_data.display_list.as_mut().unwrap();
+
+ if self.dump_display_list {
+ display_list.print();
+ }
+ if self.dump_display_list_json {
+ println!("{}", serde_json::to_string_pretty(&display_list).unwrap());
+ }
+
+ debug!("Layout done!");
+
+ // TODO: Avoid the temporary conversion and build webrender sc/dl directly!
+ let builder = display_list.convert_to_webrender(self.id);
+
+ let viewport_size = Size2D::new(
+ self.viewport_size.width.to_f32_px(),
+ self.viewport_size.height.to_f32_px(),
+ );
+
+ let mut epoch = self.epoch.get();
+ epoch.next();
+ self.epoch.set(epoch);
+
+ let viewport_size = webrender_api::units::LayoutSize::from_untyped(&viewport_size);
+
+ // Observe notifications about rendered frames if needed right before
+ // sending the display list to WebRender in order to set time related
+ // Progressive Web Metrics.
+ self.paint_time_metrics
+ .maybe_observe_paint_time(self, epoch, &*display_list);
+
+ let mut txn = webrender_api::Transaction::new();
+ txn.set_display_list(
+ webrender_api::Epoch(epoch.0),
+ None,
+ viewport_size,
+ builder.finalize(),
+ true,
+ );
+ txn.generate_frame();
+ self.webrender_api
+ .send_transaction(self.webrender_document, txn);
+ },
+ );
+ }
+
+ /// The high-level routine that performs layout threads.
+ fn handle_reflow<'a, 'b>(
+ &mut self,
+ data: &mut ScriptReflowResult,
+ possibly_locked_rw_data: &mut RwData<'a, 'b>,
+ ) {
+ let document = unsafe { ServoLayoutNode::new(&data.document) };
+ let document = document.as_document().unwrap();
+
+ // Parallelize if there's more than 750 objects based on rzambre's suggestion
+ // https://github.com/servo/servo/issues/10110
+ self.parallel_flag = data.dom_count > 750;
+ debug!("layout: received layout request for: {}", self.url);
+ debug!("Number of objects in DOM: {}", data.dom_count);
+ debug!("layout: parallel? {}", self.parallel_flag);
+
+ let mut rw_data = possibly_locked_rw_data.lock();
+
+ // Record the time that layout query has been waited.
+ let now = time::precise_time_ns();
+ if let ReflowGoal::LayoutQuery(_, timestamp) = data.reflow_goal {
+ self.layout_query_waiting_time
+ .increment(now - timestamp)
+ .expect("layout: wrong layout query timestamp");
+ };
+
+ let element = match document.root_element() {
+ None => {
+ // Since we cannot compute anything, give spec-required placeholders.
+ debug!("layout: No root node: bailing");
+ match data.reflow_goal {
+ ReflowGoal::LayoutQuery(ref query_msg, _) => match query_msg {
+ &QueryMsg::ContentBoxQuery(_) => {
+ rw_data.content_box_response = None;
+ },
+ &QueryMsg::ContentBoxesQuery(_) => {
+ rw_data.content_boxes_response = Vec::new();
+ },
+ &QueryMsg::NodesFromPointQuery(..) => {
+ rw_data.nodes_from_point_response = Vec::new();
+ },
+ &QueryMsg::NodeGeometryQuery(_) => {
+ rw_data.client_rect_response = Rect::zero();
+ },
+ &QueryMsg::NodeScrollGeometryQuery(_) => {
+ rw_data.scroll_area_response = Rect::zero();
+ },
+ &QueryMsg::NodeScrollIdQuery(_) => {
+ rw_data.scroll_id_response = None;
+ },
+ &QueryMsg::ResolvedStyleQuery(_, _, _) => {
+ rw_data.resolved_style_response = String::new();
+ },
+ &QueryMsg::OffsetParentQuery(_) => {
+ rw_data.offset_parent_response = OffsetParentResponse::empty();
+ },
+ &QueryMsg::StyleQuery(_) => {
+ rw_data.style_response = StyleResponse(None);
+ },
+ &QueryMsg::TextIndexQuery(..) => {
+ rw_data.text_index_response = TextIndexResponse(None);
+ },
+ &QueryMsg::ElementInnerTextQuery(_) => {
+ rw_data.element_inner_text_response = String::new();
+ },
+ },
+ ReflowGoal::Full | ReflowGoal::TickAnimations => {},
+ }
+ return;
+ },
+ Some(x) => x,
+ };
+
+ debug!(
+ "layout: processing reflow request for: {:?} ({}) (query={:?})",
+ element, self.url, data.reflow_goal
+ );
+ trace!("{:?}", ShowSubtree(element.as_node()));
+
+ let initial_viewport = data.window_size.initial_viewport;
+ let device_pixel_ratio = data.window_size.device_pixel_ratio;
+ let old_viewport_size = self.viewport_size;
+ let current_screen_size = Size2D::new(
+ Au::from_f32_px(initial_viewport.width),
+ Au::from_f32_px(initial_viewport.height),
+ );
+
+ // Calculate the actual viewport as per DEVICE-ADAPT § 6
+ // If the entire flow tree is invalid, then it will be reflowed anyhow.
+ let document_shared_lock = document.style_shared_lock();
+ self.document_shared_lock = Some(document_shared_lock.clone());
+ let author_guard = document_shared_lock.read();
+
+ let ua_stylesheets = &*UA_STYLESHEETS;
+ let ua_or_user_guard = ua_stylesheets.shared_lock.read();
+ let guards = StylesheetGuards {
+ author: &author_guard,
+ ua_or_user: &ua_or_user_guard,
+ };
+
+ let had_used_viewport_units = self.stylist.device().used_viewport_units();
+ let device = Device::new(MediaType::screen(), initial_viewport, device_pixel_ratio);
+ let sheet_origins_affected_by_device_change = self.stylist.set_device(device, &guards);
+
+ self.stylist
+ .force_stylesheet_origins_dirty(sheet_origins_affected_by_device_change);
+ self.viewport_size =
+ self.stylist
+ .viewport_constraints()
+ .map_or(current_screen_size, |constraints| {
+ debug!("Viewport constraints: {:?}", constraints);
+
+ // other rules are evaluated against the actual viewport
+ Size2D::new(
+ Au::from_f32_px(constraints.size.width),
+ Au::from_f32_px(constraints.size.height),
+ )
+ });
+
+ let viewport_size_changed = self.viewport_size != old_viewport_size;
+ if viewport_size_changed {
+ if let Some(constraints) = self.stylist.viewport_constraints() {
+ // let the constellation know about the viewport constraints
+ rw_data
+ .constellation_chan
+ .send(ConstellationMsg::ViewportConstrained(
+ self.id,
+ constraints.clone(),
+ ))
+ .unwrap();
+ }
+ if had_used_viewport_units {
+ if let Some(mut data) = element.mutate_data() {
+ data.hint.insert(RestyleHint::recascade_subtree());
+ }
+ }
+ }
+
+ {
+ if self.first_reflow.get() {
+ debug!("First reflow, rebuilding user and UA rules");
+ for stylesheet in &ua_stylesheets.user_or_user_agent_stylesheets {
+ self.stylist
+ .append_stylesheet(stylesheet.clone(), &ua_or_user_guard);
+ self.handle_add_stylesheet(&stylesheet.0, &ua_or_user_guard);
+ }
+
+ if self.stylist.quirks_mode() != QuirksMode::NoQuirks {
+ self.stylist.append_stylesheet(
+ ua_stylesheets.quirks_mode_stylesheet.clone(),
+ &ua_or_user_guard,
+ );
+ self.handle_add_stylesheet(
+ &ua_stylesheets.quirks_mode_stylesheet.0,
+ &ua_or_user_guard,
+ );
+ }
+ }
+
+ if data.stylesheets_changed {
+ debug!("Doc sheets changed, flushing author sheets too");
+ self.stylist
+ .force_stylesheet_origins_dirty(Origin::Author.into());
+ }
+ }
+
+ if viewport_size_changed {
+ if let Some(mut flow) = self.try_get_layout_root(element.as_node()) {
+ LayoutThread::reflow_all_nodes(FlowRef::deref_mut(&mut flow));
+ }
+ }
+
+ debug!(
+ "Shadow roots in document {:?}",
+ document.shadow_roots().len()
+ );
+
+ // Flush shadow roots stylesheets if dirty.
+ document.flush_shadow_roots_stylesheets(
+ &self.stylist.device(),
+ document.quirks_mode(),
+ guards.author.clone(),
+ );
+
+ let restyles = document.drain_pending_restyles();
+ debug!("Draining restyles: {}", restyles.len());
+
+ let mut map = SnapshotMap::new();
+ let elements_with_snapshot: Vec<_> = restyles
+ .iter()
+ .filter(|r| r.1.snapshot.is_some())
+ .map(|r| r.0)
+ .collect();
+
+ for (el, restyle) in restyles {
+ // Propagate the descendant bit up the ancestors. Do this before
+ // the restyle calculation so that we can also do it for new
+ // unstyled nodes, which the descendants bit helps us find.
+ if let Some(parent) = el.parent_element() {
+ unsafe { parent.note_dirty_descendant() };
+ }
+
+ // If we haven't styled this node yet, we don't need to track a
+ // restyle.
+ let style_data = match el.get_data() {
+ Some(d) => d,
+ None => {
+ unsafe { el.unset_snapshot_flags() };
+ continue;
+ },
+ };
+
+ if let Some(s) = restyle.snapshot {
+ unsafe { el.set_has_snapshot() };
+ map.insert(el.as_node().opaque(), s);
+ }
+
+ let mut style_data = style_data.borrow_mut();
+
+ // Stash the data on the element for processing by the style system.
+ style_data.hint.insert(restyle.hint.into());
+ style_data.damage = restyle.damage;
+ debug!("Noting restyle for {:?}: {:?}", el, style_data);
+ }
+
+ self.stylist.flush(&guards, Some(element), Some(&map));
+
+ // Create a layout context for use throughout the following passes.
+ let mut layout_context = self.build_layout_context(guards.clone(), true, &map);
+
+ let (thread_pool, num_threads) = if self.parallel_flag {
+ (
+ STYLE_THREAD_POOL.style_thread_pool.as_ref(),
+ STYLE_THREAD_POOL.num_threads,
+ )
+ } else {
+ (None, 1)
+ };
+
+ let traversal = RecalcStyleAndConstructFlows::new(layout_context);
+ let token = {
+ let shared =
+ <RecalcStyleAndConstructFlows as DomTraversal<ServoLayoutElement>>::shared_context(
+ &traversal,
+ );
+ RecalcStyleAndConstructFlows::pre_traverse(element, shared)
+ };
+
+ if token.should_traverse() {
+ // Recalculate CSS styles and rebuild flows and fragments.
+ profile(
+ profile_time::ProfilerCategory::LayoutStyleRecalc,
+ self.profiler_metadata(),
+ self.time_profiler_chan.clone(),
+ || {
+ // Perform CSS selector matching and flow construction.
+ driver::traverse_dom::<ServoLayoutElement, RecalcStyleAndConstructFlows>(
+ &traversal,
+ token,
+ thread_pool,
+ );
+ },
+ );
+ // TODO(pcwalton): Measure energy usage of text shaping, perhaps?
+ let text_shaping_time =
+ font::get_and_reset_text_shaping_performance_counter() / num_threads;
+ profile_time::send_profile_data(
+ profile_time::ProfilerCategory::LayoutTextShaping,
+ self.profiler_metadata(),
+ &self.time_profiler_chan,
+ 0,
+ text_shaping_time as u64,
+ 0,
+ 0,
+ );
+
+ // Retrieve the (possibly rebuilt) root flow.
+ *self.root_flow.borrow_mut() = self.try_get_layout_root(element.as_node());
+ }
+
+ for element in elements_with_snapshot {
+ unsafe { element.unset_snapshot_flags() }
+ }
+
+ layout_context = traversal.destroy();
+
+ if self.dump_style_tree {
+ println!("{:?}", ShowSubtreeDataAndPrimaryValues(element.as_node()));
+ }
+
+ if self.dump_rule_tree {
+ layout_context
+ .style_context
+ .stylist
+ .rule_tree()
+ .dump_stdout(&guards);
+ }
+
+ // GC the rule tree if some heuristics are met.
+ unsafe {
+ layout_context.style_context.stylist.rule_tree().maybe_gc();
+ }
+
+ // Perform post-style recalculation layout passes.
+ if let Some(mut root_flow) = self.root_flow.borrow().clone() {
+ self.perform_post_style_recalc_layout_passes(
+ &mut root_flow,
+ &data.reflow_info,
+ &data.reflow_goal,
+ Some(&document),
+ &mut rw_data,
+ &mut layout_context,
+ FxHashSet::default(),
+ );
+ }
+
+ self.first_reflow.set(false);
+ self.respond_to_query_if_necessary(
+ &data.reflow_goal,
+ &mut *rw_data,
+ &mut layout_context,
+ data.result.borrow_mut().as_mut().unwrap(),
+ );
+ }
+
+ fn respond_to_query_if_necessary(
+ &self,
+ reflow_goal: &ReflowGoal,
+ rw_data: &mut LayoutThreadData,
+ context: &mut LayoutContext,
+ reflow_result: &mut ReflowComplete,
+ ) {
+ let pending_images = match context.pending_images {
+ Some(ref pending) => std::mem::replace(&mut *pending.lock().unwrap(), vec![]),
+ None => vec![],
+ };
+ reflow_result.pending_images = pending_images;
+
+ let newly_transitioning_nodes = match context.newly_transitioning_nodes {
+ Some(ref nodes) => std::mem::replace(&mut *nodes.lock().unwrap(), vec![]),
+ None => vec![],
+ };
+ reflow_result.newly_transitioning_nodes = newly_transitioning_nodes;
+
+ let mut root_flow = match self.root_flow.borrow().clone() {
+ Some(root_flow) => root_flow,
+ None => return,
+ };
+ let root_flow = FlowRef::deref_mut(&mut root_flow);
+ match *reflow_goal {
+ ReflowGoal::LayoutQuery(ref querymsg, _) => match querymsg {
+ &QueryMsg::ContentBoxQuery(node) => {
+ rw_data.content_box_response = process_content_box_request(node, root_flow);
+ },
+ &QueryMsg::ContentBoxesQuery(node) => {
+ rw_data.content_boxes_response = process_content_boxes_request(node, root_flow);
+ },
+ &QueryMsg::TextIndexQuery(node, point_in_node) => {
+ let point_in_node = Point2D::new(
+ Au::from_f32_px(point_in_node.x),
+ Au::from_f32_px(point_in_node.y),
+ );
+ rw_data.text_index_response =
+ TextIndexResponse(rw_data.indexable_text.text_index(node, point_in_node));
+ },
+ &QueryMsg::NodeGeometryQuery(node) => {
+ rw_data.client_rect_response = process_node_geometry_request(node, root_flow);
+ },
+ &QueryMsg::NodeScrollGeometryQuery(node) => {
+ rw_data.scroll_area_response =
+ process_node_scroll_area_request(node, root_flow);
+ },
+ &QueryMsg::NodeScrollIdQuery(node) => {
+ let node = unsafe { ServoLayoutNode::new(&node) };
+ rw_data.scroll_id_response =
+ Some(process_node_scroll_id_request(self.id, node));
+ },
+ &QueryMsg::ResolvedStyleQuery(node, ref pseudo, ref property) => {
+ let node = unsafe { ServoLayoutNode::new(&node) };
+ rw_data.resolved_style_response =
+ process_resolved_style_request(context, node, pseudo, property, root_flow);
+ },
+ &QueryMsg::OffsetParentQuery(node) => {
+ rw_data.offset_parent_response = process_offset_parent_query(node, root_flow);
+ },
+ &QueryMsg::StyleQuery(node) => {
+ let node = unsafe { ServoLayoutNode::new(&node) };
+ rw_data.style_response = process_style_query(node);
+ },
+ &QueryMsg::NodesFromPointQuery(client_point, ref reflow_goal) => {
+ let mut flags = match reflow_goal {
+ &NodesFromPointQueryType::Topmost => webrender_api::HitTestFlags::empty(),
+ &NodesFromPointQueryType::All => webrender_api::HitTestFlags::FIND_ALL,
+ };
+
+ // The point we get is not relative to the entire WebRender scene, but to this
+ // particular pipeline, so we need to tell WebRender about that.
+ flags.insert(webrender_api::HitTestFlags::POINT_RELATIVE_TO_PIPELINE_VIEWPORT);
+
+ let client_point =
+ webrender_api::units::WorldPoint::from_untyped(&client_point);
+ let results = self.webrender_api.hit_test(
+ self.webrender_document,
+ Some(self.id.to_webrender()),
+ client_point,
+ flags,
+ );
+
+ rw_data.nodes_from_point_response = results
+ .items
+ .iter()
+ .map(|item| UntrustedNodeAddress(item.tag.0 as *const c_void))
+ .collect()
+ },
+ &QueryMsg::ElementInnerTextQuery(node) => {
+ let node = unsafe { ServoLayoutNode::new(&node) };
+ rw_data.element_inner_text_response =
+ process_element_inner_text_query(node, &rw_data.indexable_text);
+ },
+ },
+ ReflowGoal::Full | ReflowGoal::TickAnimations => {},
+ }
+ }
+
+ fn set_scroll_states<'a, 'b>(
+ &mut self,
+ new_scroll_states: Vec<ScrollState>,
+ possibly_locked_rw_data: &mut RwData<'a, 'b>,
+ ) {
+ let mut rw_data = possibly_locked_rw_data.lock();
+ let mut script_scroll_states = vec![];
+ let mut layout_scroll_states = HashMap::new();
+ for new_state in &new_scroll_states {
+ let offset = new_state.scroll_offset;
+ layout_scroll_states.insert(new_state.scroll_id, offset);
+
+ if new_state.scroll_id.is_root() {
+ script_scroll_states.push((UntrustedNodeAddress::from_id(0), offset))
+ } else if let Some(node_id) = node_id_from_scroll_id(new_state.scroll_id.0 as usize) {
+ script_scroll_states.push((UntrustedNodeAddress::from_id(node_id), offset))
+ }
+ }
+ let _ = self
+ .script_chan
+ .send(ConstellationControlMsg::SetScrollState(
+ self.id,
+ script_scroll_states,
+ ));
+ rw_data.scroll_offsets = layout_scroll_states
+ }
+
+ fn tick_all_animations<'a, 'b>(&mut self, possibly_locked_rw_data: &mut RwData<'a, 'b>) {
+ let mut rw_data = possibly_locked_rw_data.lock();
+ self.tick_animations(&mut rw_data);
+ }
+
+ fn tick_animations(&mut self, rw_data: &mut LayoutThreadData) {
+ if self.relayout_event {
+ println!(
+ "**** pipeline={}\tForDisplay\tSpecial\tAnimationTick",
+ self.id
+ );
+ }
+
+ if let Some(mut root_flow) = self.root_flow.borrow().clone() {
+ let reflow_info = Reflow {
+ page_clip_rect: Rect::max_rect(),
+ };
+
+ // Unwrap here should not panic since self.root_flow is only ever set to Some(_)
+ // in handle_reflow() where self.document_shared_lock is as well.
+ let author_shared_lock = self.document_shared_lock.clone().unwrap();
+ let author_guard = author_shared_lock.read();
+ let ua_or_user_guard = UA_STYLESHEETS.shared_lock.read();
+ let guards = StylesheetGuards {
+ author: &author_guard,
+ ua_or_user: &ua_or_user_guard,
+ };
+ let snapshots = SnapshotMap::new();
+ let mut layout_context = self.build_layout_context(guards, false, &snapshots);
+
+ let invalid_nodes = {
+ // Perform an abbreviated style recalc that operates without access to the DOM.
+ let animations = self.running_animations.read();
+ profile(
+ profile_time::ProfilerCategory::LayoutStyleRecalc,
+ self.profiler_metadata(),
+ self.time_profiler_chan.clone(),
+ || {
+ animation::recalc_style_for_animations::<ServoLayoutElement>(
+ &layout_context,
+ FlowRef::deref_mut(&mut root_flow),
+ &animations,
+ )
+ },
+ )
+ };
+ self.perform_post_style_recalc_layout_passes(
+ &mut root_flow,
+ &reflow_info,
+ &ReflowGoal::TickAnimations,
+ None,
+ &mut *rw_data,
+ &mut layout_context,
+ invalid_nodes,
+ );
+ assert!(layout_context.pending_images.is_none());
+ assert!(layout_context.newly_transitioning_nodes.is_none());
+ }
+ }
+
+ fn perform_post_style_recalc_layout_passes(
+ &self,
+ root_flow: &mut FlowRef,
+ data: &Reflow,
+ reflow_goal: &ReflowGoal,
+ document: Option<&ServoLayoutDocument>,
+ rw_data: &mut LayoutThreadData,
+ context: &mut LayoutContext,
+ invalid_nodes: FxHashSet<OpaqueNode>,
+ ) {
+ {
+ let mut newly_transitioning_nodes = context
+ .newly_transitioning_nodes
+ .as_ref()
+ .map(|nodes| nodes.lock().unwrap());
+ let newly_transitioning_nodes =
+ newly_transitioning_nodes.as_mut().map(|nodes| &mut **nodes);
+ // Kick off animations if any were triggered, expire completed ones.
+ animation::update_animation_state::<ServoLayoutElement>(
+ &self.constellation_chan,
+ &self.script_chan,
+ &mut *self.running_animations.write(),
+ &mut *self.expired_animations.write(),
+ invalid_nodes,
+ newly_transitioning_nodes,
+ &self.new_animations_receiver,
+ self.id,
+ &self.timer,
+ );
+ }
+
+ profile(
+ profile_time::ProfilerCategory::LayoutRestyleDamagePropagation,
+ self.profiler_metadata(),
+ self.time_profiler_chan.clone(),
+ || {
+ // Call `compute_layout_damage` even in non-incremental mode, because it sets flags
+ // that are needed in both incremental and non-incremental traversals.
+ let damage = FlowRef::deref_mut(root_flow).compute_layout_damage();
+
+ if self.nonincremental_layout ||
+ damage.contains(SpecialRestyleDamage::REFLOW_ENTIRE_DOCUMENT)
+ {
+ FlowRef::deref_mut(root_flow).reflow_entire_document()
+ }
+ },
+ );
+
+ if self.trace_layout {
+ layout_debug::begin_trace(root_flow.clone());
+ }
+
+ // Resolve generated content.
+ profile(
+ profile_time::ProfilerCategory::LayoutGeneratedContent,
+ self.profiler_metadata(),
+ self.time_profiler_chan.clone(),
+ || sequential::resolve_generated_content(FlowRef::deref_mut(root_flow), &context),
+ );
+
+ // Guess float placement.
+ profile(
+ profile_time::ProfilerCategory::LayoutFloatPlacementSpeculation,
+ self.profiler_metadata(),
+ self.time_profiler_chan.clone(),
+ || sequential::guess_float_placement(FlowRef::deref_mut(root_flow)),
+ );
+
+ // Perform the primary layout passes over the flow tree to compute the locations of all
+ // the boxes.
+ if root_flow
+ .base()
+ .restyle_damage
+ .intersects(ServoRestyleDamage::REFLOW | ServoRestyleDamage::REFLOW_OUT_OF_FLOW)
+ {
+ profile(
+ profile_time::ProfilerCategory::LayoutMain,
+ self.profiler_metadata(),
+ self.time_profiler_chan.clone(),
+ || {
+ let profiler_metadata = self.profiler_metadata();
+
+ let thread_pool = if self.parallel_flag {
+ STYLE_THREAD_POOL.style_thread_pool.as_ref()
+ } else {
+ None
+ };
+
+ if let Some(pool) = thread_pool {
+ // Parallel mode.
+ LayoutThread::solve_constraints_parallel(
+ pool,
+ FlowRef::deref_mut(root_flow),
+ profiler_metadata,
+ self.time_profiler_chan.clone(),
+ &*context,
+ );
+ } else {
+ //Sequential mode
+ LayoutThread::solve_constraints(FlowRef::deref_mut(root_flow), &context)
+ }
+ },
+ );
+ }
+
+ profile(
+ profile_time::ProfilerCategory::LayoutStoreOverflow,
+ self.profiler_metadata(),
+ self.time_profiler_chan.clone(),
+ || {
+ sequential::store_overflow(context, FlowRef::deref_mut(root_flow) as &mut dyn Flow);
+ },
+ );
+
+ self.perform_post_main_layout_passes(
+ data,
+ root_flow,
+ reflow_goal,
+ document,
+ rw_data,
+ context,
+ );
+ }
+
+ fn perform_post_main_layout_passes(
+ &self,
+ data: &Reflow,
+ mut root_flow: &mut FlowRef,
+ reflow_goal: &ReflowGoal,
+ document: Option<&ServoLayoutDocument>,
+ rw_data: &mut LayoutThreadData,
+ layout_context: &mut LayoutContext,
+ ) {
+ // Build the display list if necessary, and send it to the painter.
+ self.compute_abs_pos_and_build_display_list(
+ data,
+ reflow_goal,
+ document,
+ FlowRef::deref_mut(&mut root_flow),
+ &mut *layout_context,
+ rw_data,
+ );
+
+ if self.trace_layout {
+ layout_debug::end_trace(self.generation.get());
+ }
+
+ if self.dump_flow_tree {
+ root_flow.print("Post layout flow tree".to_owned());
+ }
+
+ self.generation.set(self.generation.get() + 1);
+ }
+
+ fn reflow_all_nodes(flow: &mut dyn Flow) {
+ debug!("reflowing all nodes!");
+ flow.mut_base().restyle_damage.insert(
+ ServoRestyleDamage::REPAINT |
+ ServoRestyleDamage::STORE_OVERFLOW |
+ ServoRestyleDamage::REFLOW |
+ ServoRestyleDamage::REPOSITION,
+ );
+
+ for child in flow.mut_base().child_iter_mut() {
+ LayoutThread::reflow_all_nodes(child);
+ }
+ }
+
+ /// Returns profiling information which is passed to the time profiler.
+ fn profiler_metadata(&self) -> Option<TimerMetadata> {
+ Some(TimerMetadata {
+ url: self.url.to_string(),
+ iframe: if self.is_iframe {
+ TimerMetadataFrameType::IFrame
+ } else {
+ TimerMetadataFrameType::RootWindow
+ },
+ incremental: if self.first_reflow.get() {
+ TimerMetadataReflowType::FirstReflow
+ } else {
+ TimerMetadataReflowType::Incremental
+ },
+ })
+ }
+}
+
+impl ProfilerMetadataFactory for LayoutThread {
+ fn new_metadata(&self) -> Option<TimerMetadata> {
+ self.profiler_metadata()
+ }
+}
+
+// The default computed value for background-color is transparent (see
+// http://dev.w3.org/csswg/css-backgrounds/#background-color). However, we
+// need to propagate the background color from the root HTML/Body
+// element (http://dev.w3.org/csswg/css-backgrounds/#special-backgrounds) if
+// it is non-transparent. The phrase in the spec "If the canvas background
+// is not opaque, what shows through is UA-dependent." is handled by rust-layers
+// clearing the frame buffer to white. This ensures that setting a background
+// color on an iframe element, while the iframe content itself has a default
+// transparent background color is handled correctly.
+fn get_root_flow_background_color(flow: &mut dyn Flow) -> webrender_api::ColorF {
+ let transparent = webrender_api::ColorF {
+ r: 0.0,
+ g: 0.0,
+ b: 0.0,
+ a: 0.0,
+ };
+ if !flow.is_block_like() {
+ return transparent;
+ }
+
+ let block_flow = flow.as_mut_block();
+ let kid = match block_flow.base.children.iter_mut().next() {
+ None => return transparent,
+ Some(kid) => kid,
+ };
+ if !kid.is_block_like() {
+ return transparent;
+ }
+
+ let kid_block_flow = kid.as_block();
+ let color = kid_block_flow.fragment.style.resolve_color(
+ kid_block_flow
+ .fragment
+ .style
+ .get_background()
+ .background_color,
+ );
+ webrender_api::ColorF::new(
+ color.red_f32(),
+ color.green_f32(),
+ color.blue_f32(),
+ color.alpha_f32(),
+ )
+}
+
+fn get_ua_stylesheets() -> Result<UserAgentStylesheets, &'static str> {
+ fn parse_ua_stylesheet(
+ shared_lock: &SharedRwLock,
+ filename: &str,
+ content: &[u8],
+ ) -> Result<DocumentStyleSheet, &'static str> {
+ Ok(DocumentStyleSheet(ServoArc::new(Stylesheet::from_bytes(
+ content,
+ ServoUrl::parse(&format!("chrome://resources/{:?}", filename)).unwrap(),
+ None,
+ None,
+ Origin::UserAgent,
+ MediaList::empty(),
+ shared_lock.clone(),
+ None,
+ None,
+ QuirksMode::NoQuirks,
+ ))))
+ }
+
+ let shared_lock = &GLOBAL_STYLE_DATA.shared_lock;
+
+ // FIXME: presentational-hints.css should be at author origin with zero specificity.
+ // (Does it make a difference?)
+ let mut user_or_user_agent_stylesheets = vec![
+ parse_ua_stylesheet(
+ &shared_lock,
+ "user-agent.css",
+ &resources::read_bytes(Resource::UserAgentCSS),
+ )?,
+ parse_ua_stylesheet(
+ &shared_lock,
+ "servo.css",
+ &resources::read_bytes(Resource::ServoCSS),
+ )?,
+ parse_ua_stylesheet(
+ &shared_lock,
+ "presentational-hints.css",
+ &resources::read_bytes(Resource::PresentationalHintsCSS),
+ )?,
+ ];
+
+ for &(ref contents, ref url) in &opts::get().user_stylesheets {
+ user_or_user_agent_stylesheets.push(DocumentStyleSheet(ServoArc::new(
+ Stylesheet::from_bytes(
+ &contents,
+ url.clone(),
+ None,
+ None,
+ Origin::User,
+ MediaList::empty(),
+ shared_lock.clone(),
+ None,
+ Some(&RustLogReporter),
+ QuirksMode::NoQuirks,
+ ),
+ )));
+ }
+
+ let quirks_mode_stylesheet = parse_ua_stylesheet(
+ &shared_lock,
+ "quirks-mode.css",
+ &resources::read_bytes(Resource::QuirksModeCSS),
+ )?;
+
+ Ok(UserAgentStylesheets {
+ shared_lock: shared_lock.clone(),
+ user_or_user_agent_stylesheets: user_or_user_agent_stylesheets,
+ quirks_mode_stylesheet: quirks_mode_stylesheet,
+ })
+}
+
+lazy_static! {
+ static ref UA_STYLESHEETS: UserAgentStylesheets = {
+ match get_ua_stylesheets() {
+ Ok(stylesheets) => stylesheets,
+ Err(filename) => {
+ error!("Failed to load UA stylesheet {}!", filename);
+ process::exit(1);
+ },
+ }
+ };
+}
+
+struct RegisteredPainterImpl {
+ painter: Box<dyn Painter>,
+ name: Atom,
+ // FIXME: Should be a PrecomputedHashMap.
+ properties: FxHashMap<Atom, PropertyId>,
+}
+
+impl SpeculativePainter for RegisteredPainterImpl {
+ fn speculatively_draw_a_paint_image(
+ &self,
+ properties: Vec<(Atom, String)>,
+ arguments: Vec<String>,
+ ) {
+ self.painter
+ .speculatively_draw_a_paint_image(properties, arguments);
+ }
+}
+
+impl RegisteredSpeculativePainter for RegisteredPainterImpl {
+ fn properties(&self) -> &FxHashMap<Atom, PropertyId> {
+ &self.properties
+ }
+ fn name(&self) -> Atom {
+ self.name.clone()
+ }
+}
+
+impl Painter for RegisteredPainterImpl {
+ fn draw_a_paint_image(
+ &self,
+ size: TypedSize2D<f32, CSSPixel>,
+ device_pixel_ratio: TypedScale<f32, CSSPixel, DevicePixel>,
+ properties: Vec<(Atom, String)>,
+ arguments: Vec<String>,
+ ) -> Result<DrawAPaintImageResult, PaintWorkletError> {
+ self.painter
+ .draw_a_paint_image(size, device_pixel_ratio, properties, arguments)
+ }
+}
+
+impl RegisteredPainter for RegisteredPainterImpl {}
+
+struct RegisteredPaintersImpl(FnvHashMap<Atom, RegisteredPainterImpl>);
+
+impl RegisteredSpeculativePainters for RegisteredPaintersImpl {
+ fn get(&self, name: &Atom) -> Option<&dyn RegisteredSpeculativePainter> {
+ self.0
+ .get(&name)
+ .map(|painter| painter as &dyn RegisteredSpeculativePainter)
+ }
+}
+
+impl RegisteredPainters for RegisteredPaintersImpl {
+ fn get(&self, name: &Atom) -> Option<&dyn RegisteredPainter> {
+ self.0
+ .get(&name)
+ .map(|painter| painter as &dyn RegisteredPainter)
}
}