diff --git a/Cargo.lock b/Cargo.lock index 914e577b4a26..3cbc5cc30cc7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2331,7 +2331,48 @@ dependencies = [ name = "layout_2020" version = "0.0.1" dependencies = [ + "app_units 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "canvas_traits 0.0.1", + "crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "embedder_traits 0.0.1", + "euclid 0.19.8 (registry+https://github.com/rust-lang/crates.io-index)", + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "gfx 0.0.1", + "gfx_traits 0.0.1", + "html5ever 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ipc-channel 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "malloc_size_of 0.0.1", + "msg 0.0.1", + "net_traits 0.0.1", + "num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "ordered-float 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "profile_traits 0.0.1", + "range 0.0.1", + "rayon 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "script_layout_interface 0.0.1", + "script_traits 0.0.1", + "selectors 0.21.0", + "serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", + "servo_arc 0.1.1", + "servo_atoms 0.0.1", + "servo_config 0.0.1", + "servo_geometry 0.0.1", + "servo_url 0.0.1", + "size_of_test 0.0.1", + "smallvec 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", "style 0.0.1", + "style_traits 0.0.1", + "unicode-bidi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-script 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "webrender_api 0.60.0 (git+https://github.com/servo/webrender)", + "xi-unicode 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2384,20 +2425,45 @@ dependencies = [ name = "layout_thread_2020" version = "0.0.1" dependencies = [ + "app_units 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "embedder_traits 0.0.1", "euclid 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "gfx 0.0.1", + "gfx_traits 0.0.1", + "histogram 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", + "html5ever 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)", "ipc-channel 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", "layout_2020 0.0.1", "layout_traits 0.0.1", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "malloc_size_of 0.0.1", "metrics 0.0.1", "msg 0.0.1", "net_traits 0.0.1", + "parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "profile_traits 0.0.1", + "range 0.0.1", + "rayon 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "script 0.0.1", "script_layout_interface 0.0.1", "script_traits 0.0.1", + "selectors 0.21.0", + "serde_json 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", + "servo_allocator 0.0.1", + "servo_arc 0.1.1", + "servo_atoms 0.0.1", + "servo_config 0.0.1", "servo_geometry 0.0.1", "servo_url 0.0.1", + "style 0.0.1", + "style_traits 0.0.1", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "webrender_api 0.60.0 (git+https://github.com/servo/webrender)", ] diff --git a/components/layout_2020/Cargo.toml b/components/layout_2020/Cargo.toml index bff14135888c..daa5170b23a7 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 000000000000..96e4801fa4e2 --- /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( + constellation_chan: &IpcSender, + script_chan: &IpcSender, + running_animations: &mut FxHashMap>, + expired_animations: &mut FxHashMap>, + mut keys_to_remove: FxHashSet, + mut newly_transitioning_nodes: Option<&mut Vec>, + new_animations_receiver: &Receiver, + 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( + context: &LayoutContext, + flow: &mut dyn Flow, + animations: &FxHashMap>, +) -> FxHashSet +where + E: TElement, +{ + let mut invalid_nodes = animations.keys().cloned().collect(); + do_recalc_style_for_animations::(context, flow, animations, &mut invalid_nodes); + invalid_nodes +} + +fn do_recalc_style_for_animations( + context: &LayoutContext, + flow: &mut dyn Flow, + animations: &FxHashMap>, + invalid_nodes: &mut FxHashSet, +) 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::( + &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::(context, kid, animations, invalid_nodes) + } +} diff --git a/components/layout_2020/block.rs b/components/layout_2020/block.rs new file mode 100644 index 000000000000..24128ca2ae74 --- /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, + 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, + ) -> 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 { + 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>, + + /// 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(&self, serializer: S) -> Result { + 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, + ) -> 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 { + 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, + descendant: OpaqueFlow, + ) -> LogicalSize { + 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, + margins_may_collapse: MarginsMayCollapseFlag, + ) -> Option> { + let _scope = layout_debug_scope!("assign_block_size_block_base {:x}", self.base.debug_id()); + + let mut break_at = None; + 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) + } + }) + } + + /// 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 { + 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) -> Option { + 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( + &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, + ) { + 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 { + 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, + ) -> 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, + ) -> Option> { + 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 { + (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) { + 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, + ) { + 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 000000000000..072413995b2f --- /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, + 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, + + /// 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 +/// +/// A +///
B
+/// C +///
+/// ``` +/// +/// 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( + 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::(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, + + /// 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, + + /// 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( + 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( + 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::( + &enclosing_node, + context, + start, + restyle_damage, + )); + fragments + .fragments + .push_back(control_chars_to_fragment::( + &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, +} + +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::(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: + // + //
+ // + // + // + //
+ // + // 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::( + 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::( + 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::( + 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::::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. + /// * `` and `