diff --git a/Cargo.lock b/Cargo.lock index 3cbc5cc30cc..80ba227042e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2332,47 +2332,24 @@ 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]] diff --git a/components/layout_2020/Cargo.toml b/components/layout_2020/Cargo.toml index daa5170b23a..ece58df9a0d 100644 --- a/components/layout_2020/Cargo.toml +++ b/components/layout_2020/Cargo.toml @@ -14,46 +14,21 @@ 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 deleted file mode 100644 index 96e4801fa4e..00000000000 --- a/components/layout_2020/animation.rs +++ /dev/null @@ -1,211 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at 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 deleted file mode 100644 index 24128ca2ae7..00000000000 --- a/components/layout_2020/block.rs +++ /dev/null @@ -1,3697 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at 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 deleted file mode 100644 index 072413995b2..00000000000 --- a/components/layout_2020/construct.rs +++ /dev/null @@ -1,2443 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at 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 `