diff options
author | Anthony Ramine <n.oxyde@gmail.com> | 2019-07-24 11:19:23 +0200 |
---|---|---|
committer | Anthony Ramine <n.oxyde@gmail.com> | 2019-07-31 17:09:17 +0200 |
commit | 317d700f5d977c80ec5404243586449203561a0b (patch) | |
tree | a073af20601d09dd97731db954bbe60c7eef1736 | |
parent | 4846d76e82e2d60875472fb8ea375e22d40a0800 (diff) | |
download | servo-317d700f5d977c80ec5404243586449203561a0b.tar.gz servo-317d700f5d977c80ec5404243586449203561a0b.zip |
Remove most of the things in layout 2020
We keep mostly the query system. There is probably more to delete but
that's a good start I think.
47 files changed, 75 insertions, 29854 deletions
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<E>( - constellation_chan: &IpcSender<ConstellationMsg>, - script_chan: &IpcSender<ConstellationControlMsg>, - running_animations: &mut FxHashMap<OpaqueNode, Vec<Animation>>, - expired_animations: &mut FxHashMap<OpaqueNode, Vec<Animation>>, - mut keys_to_remove: FxHashSet<OpaqueNode>, - mut newly_transitioning_nodes: Option<&mut Vec<UntrustedNodeAddress>>, - new_animations_receiver: &Receiver<Animation>, - pipeline_id: PipelineId, - timer: &Timer, -) where - E: TElement, -{ - let mut new_running_animations = vec![]; - while let Ok(animation) = new_animations_receiver.try_recv() { - let mut should_push = true; - if let Animation::Keyframes(ref node, _, ref name, ref state) = animation { - // If the animation was already present in the list for the - // node, just update its state, else push the new animation to - // run. - if let Some(ref mut animations) = running_animations.get_mut(node) { - // TODO: This being linear is probably not optimal. - for anim in animations.iter_mut() { - if let Animation::Keyframes(_, _, ref anim_name, ref mut anim_state) = *anim { - if *name == *anim_name { - debug!("update_animation_state: Found other animation {}", name); - anim_state.update_from_other(&state, timer); - should_push = false; - break; - } - } - } - } - } - - if should_push { - new_running_animations.push(animation); - } - } - - if running_animations.is_empty() && new_running_animations.is_empty() { - // Nothing to do. Return early so we don't flood the compositor with - // `ChangeRunningAnimationsState` messages. - return; - } - - let now = timer.seconds(); - // Expire old running animations. - // - // TODO: Do not expunge Keyframes animations, since we need that state if - // the animation gets re-triggered. Probably worth splitting in two - // different maps, or at least using a linked list? - for (key, running_animations) in running_animations.iter_mut() { - let mut animations_still_running = vec![]; - for mut running_animation in running_animations.drain(..) { - let still_running = !running_animation.is_expired() && - match running_animation { - Animation::Transition(_, started_at, ref frame) => { - now < started_at + frame.duration - }, - Animation::Keyframes(_, _, _, ref mut state) => { - // This animation is still running, or we need to keep - // iterating. - now < state.started_at + state.duration || state.tick() - }, - }; - - debug!( - "update_animation_state({:?}): {:?}", - still_running, running_animation - ); - - if still_running { - animations_still_running.push(running_animation); - continue; - } - - if let Animation::Transition(node, _, ref frame) = running_animation { - script_chan - .send(ConstellationControlMsg::TransitionEnd( - node.to_untrusted_node_address(), - frame.property_animation.property_name().into(), - frame.duration, - )) - .unwrap(); - } - - expired_animations - .entry(*key) - .or_insert_with(Vec::new) - .push(running_animation); - } - - if animations_still_running.is_empty() { - keys_to_remove.insert(*key); - } else { - *running_animations = animations_still_running - } - } - - for key in keys_to_remove { - running_animations.remove(&key).unwrap(); - } - - // Add new running animations. - for new_running_animation in new_running_animations { - if new_running_animation.is_transition() { - match newly_transitioning_nodes { - Some(ref mut nodes) => { - nodes.push(new_running_animation.node().to_untrusted_node_address()); - }, - None => { - warn!("New transition encountered from compositor-initiated layout."); - }, - } - } - - running_animations - .entry(*new_running_animation.node()) - .or_insert_with(Vec::new) - .push(new_running_animation) - } - - let animation_state = if running_animations.is_empty() { - AnimationState::NoAnimationsPresent - } else { - AnimationState::AnimationsPresent - }; - - constellation_chan - .send(ConstellationMsg::ChangeRunningAnimationsState( - pipeline_id, - animation_state, - )) - .unwrap(); -} - -/// Recalculates style for a set of animations. This does *not* run with the DOM -/// lock held. Returns a set of nodes associated with animations that are no longer -/// valid. -pub fn recalc_style_for_animations<E>( - context: &LayoutContext, - flow: &mut dyn Flow, - animations: &FxHashMap<OpaqueNode, Vec<Animation>>, -) -> FxHashSet<OpaqueNode> -where - E: TElement, -{ - let mut invalid_nodes = animations.keys().cloned().collect(); - do_recalc_style_for_animations::<E>(context, flow, animations, &mut invalid_nodes); - invalid_nodes -} - -fn do_recalc_style_for_animations<E>( - context: &LayoutContext, - flow: &mut dyn Flow, - animations: &FxHashMap<OpaqueNode, Vec<Animation>>, - invalid_nodes: &mut FxHashSet<OpaqueNode>, -) where - E: TElement, -{ - let mut damage = RestyleDamage::empty(); - flow.mutate_fragments(&mut |fragment| { - if let Some(ref animations) = animations.get(&fragment.node) { - invalid_nodes.remove(&fragment.node); - for animation in animations.iter() { - let old_style = fragment.style.clone(); - update_style_for_animation::<E>( - &context.style_context, - animation, - &mut fragment.style, - &ServoMetricsProvider, - ); - let difference = - RestyleDamage::compute_style_difference(&old_style, &fragment.style); - damage |= difference.damage; - } - } - }); - - let base = flow.mut_base(); - base.restyle_damage.insert(damage); - for kid in base.children.iter_mut() { - do_recalc_style_for_animations::<E>(context, kid, animations, invalid_nodes) - } -} diff --git a/components/layout_2020/block.rs b/components/layout_2020/block.rs 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<Au>, - min_block_size: Au, - pub candidate_value: Au, - status: CandidateBSizeIteratorStatus, -} - -impl CandidateBSizeIterator { - /// Creates a new candidate block-size iterator. `block_container_block-size` is `None` if the block-size - /// of the block container has not been determined yet. It will always be `Some` in the case of - /// absolutely-positioned containing blocks. - pub fn new( - fragment: &Fragment, - block_container_block_size: Option<Au>, - ) -> CandidateBSizeIterator { - // Per CSS 2.1 § 10.7, (assuming an horizontal writing mode,) - // percentages in `min-height` and `max-height` refer to the height of - // the containing block. - // If that is not determined yet by the time we need to resolve - // `min-height` and `max-height`, percentage values are ignored. - - let block_size = match fragment.style.content_block_size() { - Size::Auto => MaybeAuto::Auto, - Size::LengthPercentage(ref lp) => { - MaybeAuto::from_option(lp.maybe_to_used_value(block_container_block_size)) - }, - }; - - let max_block_size = match fragment.style.max_block_size() { - MaxSize::None => None, - MaxSize::LengthPercentage(ref lp) => lp.maybe_to_used_value(block_container_block_size), - }; - - let min_block_size = match fragment.style.min_block_size() { - Size::Auto => MaybeAuto::Auto, - Size::LengthPercentage(ref lp) => { - MaybeAuto::from_option(lp.maybe_to_used_value(block_container_block_size)) - }, - } - .specified_or_zero(); - - // If the style includes `box-sizing: border-box`, subtract the border and padding. - let adjustment_for_box_sizing = match fragment.style.get_position().box_sizing { - BoxSizing::BorderBox => fragment.border_padding.block_start_end(), - BoxSizing::ContentBox => Au(0), - }; - - return CandidateBSizeIterator { - block_size: block_size.map(|size| adjust(size, adjustment_for_box_sizing)), - max_block_size: max_block_size.map(|size| adjust(size, adjustment_for_box_sizing)), - min_block_size: adjust(min_block_size, adjustment_for_box_sizing), - candidate_value: Au(0), - status: CandidateBSizeIteratorStatus::Initial, - }; - - fn adjust(size: Au, delta: Au) -> Au { - max(size - delta, Au(0)) - } - } -} - -impl Iterator for CandidateBSizeIterator { - type Item = MaybeAuto; - fn next(&mut self) -> Option<MaybeAuto> { - self.status = match self.status { - CandidateBSizeIteratorStatus::Initial => CandidateBSizeIteratorStatus::Trying, - CandidateBSizeIteratorStatus::Trying => match self.max_block_size { - Some(max_block_size) if self.candidate_value > max_block_size => { - CandidateBSizeIteratorStatus::TryingMax - }, - _ if self.candidate_value < self.min_block_size => { - CandidateBSizeIteratorStatus::TryingMin - }, - _ => CandidateBSizeIteratorStatus::Found, - }, - CandidateBSizeIteratorStatus::TryingMax => { - if self.candidate_value < self.min_block_size { - CandidateBSizeIteratorStatus::TryingMin - } else { - CandidateBSizeIteratorStatus::Found - } - }, - CandidateBSizeIteratorStatus::TryingMin | CandidateBSizeIteratorStatus::Found => { - CandidateBSizeIteratorStatus::Found - }, - }; - - match self.status { - CandidateBSizeIteratorStatus::Trying => Some(self.block_size), - CandidateBSizeIteratorStatus::TryingMax => { - Some(MaybeAuto::Specified(self.max_block_size.unwrap())) - }, - CandidateBSizeIteratorStatus::TryingMin => { - Some(MaybeAuto::Specified(self.min_block_size)) - }, - CandidateBSizeIteratorStatus::Found => None, - CandidateBSizeIteratorStatus::Initial => panic!(), - } - } -} - -enum CandidateBSizeIteratorStatus { - Initial, - Trying, - TryingMax, - TryingMin, - Found, -} - -// A helper function used in block-size calculation. -fn translate_including_floats(cur_b: &mut Au, delta: Au, floats: &mut Floats) { - *cur_b = *cur_b + delta; - let writing_mode = floats.writing_mode; - floats.translate(LogicalSize::new(writing_mode, Au(0), -delta)); -} - -/// The real assign-block-sizes traversal for flows with position 'absolute'. -/// -/// This is a traversal of an Absolute Flow tree. -/// - Relatively positioned flows and the Root flow start new Absolute flow trees. -/// - The kids of a flow in this tree will be the flows for which it is the -/// absolute Containing Block. -/// - Thus, leaf nodes and inner non-root nodes are all Absolute Flows. -/// -/// A Flow tree can have several Absolute Flow trees (depending on the number -/// of relatively positioned flows it has). -/// -/// Note that flows with position 'fixed' just form a flat list as they all -/// have the Root flow as their CB. -pub struct AbsoluteAssignBSizesTraversal<'a>(pub &'a SharedStyleContext<'a>); - -impl<'a> PreorderFlowTraversal for AbsoluteAssignBSizesTraversal<'a> { - #[inline] - fn process(&self, flow: &mut dyn Flow) { - if !flow.is_block_like() { - return; - } - - // This flow might not be an absolutely positioned flow if it is the root of the tree. - let block = flow.as_mut_block(); - if !block - .base - .flags - .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) - { - return; - } - - if !block - .base - .restyle_damage - .intersects(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW) - { - return; - } - - block.calculate_absolute_block_size_and_margins(self.0); - } -} - -pub enum BlockType { - Replaced, - NonReplaced, - AbsoluteReplaced, - AbsoluteNonReplaced, - FloatReplaced, - FloatNonReplaced, - InlineBlockReplaced, - InlineBlockNonReplaced, - InlineFlexItem, -} - -#[derive(Clone, PartialEq)] -pub enum MarginsMayCollapseFlag { - MarginsMayCollapse, - MarginsMayNotCollapse, -} - -#[derive(Debug, PartialEq)] -pub enum FormattingContextType { - None, - Block, - Other, -} - -#[allow(unsafe_code)] -unsafe impl crate::flow::HasBaseFlow for BlockFlow {} - -// A block formatting context. -#[derive(Serialize)] -#[repr(C)] -pub struct BlockFlow { - /// Data common to all flows. - pub base: BaseFlow, - - /// The associated fragment. - pub fragment: Fragment, - - /// Additional floating flow members. - pub float: Option<Box<FloatedBlockInfo>>, - - /// Various flags. - flags: BlockFlowFlags, -} - -bitflags! { - struct BlockFlowFlags: u8 { - #[doc = "If this is set, then this block flow is the root flow."] - const IS_ROOT = 0b0000_0001; - #[doc = "If this is set, then this block flow has overflow and it will scroll."] - const HAS_SCROLLING_OVERFLOW = 0b0000_0010; - } -} - -impl Serialize for BlockFlowFlags { - fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { - self.bits().serialize(serializer) - } -} - -impl BlockFlow { - pub fn from_fragment(fragment: Fragment) -> BlockFlow { - BlockFlow::from_fragment_and_float_kind(fragment, None) - } - - pub fn from_fragment_and_float_kind( - fragment: Fragment, - float_kind: Option<FloatKind>, - ) -> BlockFlow { - let writing_mode = fragment.style().writing_mode; - BlockFlow { - base: BaseFlow::new( - Some(fragment.style()), - writing_mode, - match float_kind { - Some(_) => ForceNonfloatedFlag::FloatIfNecessary, - None => ForceNonfloatedFlag::ForceNonfloated, - }, - ), - fragment: fragment, - float: float_kind.map(|kind| Box::new(FloatedBlockInfo::new(kind))), - flags: BlockFlowFlags::empty(), - } - } - - /// Return the type of this block. - /// - /// This determines the algorithm used to calculate inline-size, block-size, and the - /// relevant margins for this Block. - pub fn block_type(&self) -> BlockType { - if self - .base - .flags - .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) - { - if self.fragment.is_replaced() { - BlockType::AbsoluteReplaced - } else { - BlockType::AbsoluteNonReplaced - } - } else if self.is_inline_flex_item() { - BlockType::InlineFlexItem - } else if self.base.flags.is_float() { - if self.fragment.is_replaced() { - BlockType::FloatReplaced - } else { - BlockType::FloatNonReplaced - } - } else if self.is_inline_block_or_inline_flex() { - if self.fragment.is_replaced() { - BlockType::InlineBlockReplaced - } else { - BlockType::InlineBlockNonReplaced - } - } else { - if self.fragment.is_replaced() { - BlockType::Replaced - } else { - BlockType::NonReplaced - } - } - } - - /// Compute the actual inline size and position for this block. - pub fn compute_used_inline_size( - &mut self, - shared_context: &SharedStyleContext, - containing_block_inline_size: Au, - ) { - let block_type = self.block_type(); - match block_type { - BlockType::AbsoluteReplaced => { - let inline_size_computer = AbsoluteReplaced; - inline_size_computer.compute_used_inline_size( - self, - shared_context, - containing_block_inline_size, - ); - }, - BlockType::AbsoluteNonReplaced => { - let inline_size_computer = AbsoluteNonReplaced; - inline_size_computer.compute_used_inline_size( - self, - shared_context, - containing_block_inline_size, - ); - }, - BlockType::FloatReplaced => { - let inline_size_computer = FloatReplaced; - inline_size_computer.compute_used_inline_size( - self, - shared_context, - containing_block_inline_size, - ); - }, - BlockType::FloatNonReplaced => { - let inline_size_computer = FloatNonReplaced; - inline_size_computer.compute_used_inline_size( - self, - shared_context, - containing_block_inline_size, - ); - }, - BlockType::InlineBlockReplaced => { - let inline_size_computer = InlineBlockReplaced; - inline_size_computer.compute_used_inline_size( - self, - shared_context, - containing_block_inline_size, - ); - }, - BlockType::InlineBlockNonReplaced => { - let inline_size_computer = InlineBlockNonReplaced; - inline_size_computer.compute_used_inline_size( - self, - shared_context, - containing_block_inline_size, - ); - }, - BlockType::Replaced => { - let inline_size_computer = BlockReplaced; - inline_size_computer.compute_used_inline_size( - self, - shared_context, - containing_block_inline_size, - ); - }, - BlockType::NonReplaced => { - let inline_size_computer = BlockNonReplaced; - inline_size_computer.compute_used_inline_size( - self, - shared_context, - containing_block_inline_size, - ); - }, - BlockType::InlineFlexItem => { - let inline_size_computer = InlineFlexItem; - inline_size_computer.compute_used_inline_size( - self, - shared_context, - containing_block_inline_size, - ); - }, - } - } - - /// Return this flow's fragment. - pub fn fragment(&mut self) -> &mut Fragment { - &mut self.fragment - } - - pub fn stacking_relative_border_box(&self, coor: CoordinateSystem) -> Rect<Au> { - return self.fragment.stacking_relative_border_box( - &self.base.stacking_relative_position, - &self - .base - .early_absolute_position_info - .relative_containing_block_size, - self.base - .early_absolute_position_info - .relative_containing_block_mode, - coor, - ); - } - - /// Return the size of the containing block for the given immediate absolute descendant of this - /// flow. - /// - /// Right now, this only gets the containing block size for absolutely positioned elements. - /// Note: We assume this is called in a top-down traversal, so it is ok to reference the CB. - #[inline] - pub fn containing_block_size( - &self, - viewport_size: &Size2D<Au>, - descendant: OpaqueFlow, - ) -> LogicalSize<Au> { - debug_assert!(self - .base - .flags - .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED)); - if self.is_fixed() || self.is_root() { - // Initial containing block is the CB for the root - LogicalSize::from_physical(self.base.writing_mode, *viewport_size) - } else { - self.base - .absolute_cb - .generated_containing_block_size(descendant) - } - } - - /// Return shrink-to-fit inline-size. - /// - /// This is where we use the preferred inline-sizes and minimum inline-sizes - /// calculated in the bubble-inline-sizes traversal. - pub fn get_shrink_to_fit_inline_size(&self, available_inline_size: Au) -> Au { - let content_intrinsic_inline_sizes = self.content_intrinsic_inline_sizes(); - min( - content_intrinsic_inline_sizes.preferred_inline_size, - max( - content_intrinsic_inline_sizes.minimum_inline_size, - available_inline_size, - ), - ) - } - - /// If this is the root flow, shifts all kids down and adjusts our size to account for - /// root flow margins, which should never be collapsed according to CSS § 8.3.1. - /// - /// TODO(#2017, pcwalton): This is somewhat inefficient (traverses kids twice); can we do - /// better? - fn adjust_fragments_for_collapsed_margins_if_root( - &mut self, - shared_context: &SharedStyleContext, - ) { - if !self.is_root() { - return; - } - - let (block_start_margin_value, block_end_margin_value) = match self.base.collapsible_margins - { - CollapsibleMargins::CollapseThrough(_) => { - panic!("Margins unexpectedly collapsed through root flow.") - }, - CollapsibleMargins::Collapse(block_start_margin, block_end_margin) => { - (block_start_margin.collapse(), block_end_margin.collapse()) - }, - CollapsibleMargins::None(block_start, block_end) => (block_start, block_end), - }; - - // Shift all kids down (or up, if margins are negative) if necessary. - if block_start_margin_value != Au(0) { - for kid in self.base.child_iter_mut() { - let kid_base = kid.mut_base(); - kid_base.position.start.b = kid_base.position.start.b + block_start_margin_value - } - } - - // FIXME(#2003, pcwalton): The max is taken here so that you can scroll the page, but this - // is not correct behavior according to CSS 2.1 § 10.5. Instead I think we should treat the - // root element as having `overflow: scroll` and use the layers-based scrolling - // infrastructure to make it scrollable. - let viewport_size = LogicalSize::from_physical( - self.fragment.style.writing_mode, - shared_context.viewport_size(), - ); - let block_size = max( - viewport_size.block, - self.fragment.border_box.size.block + block_start_margin_value + block_end_margin_value, - ); - - self.base.position.size.block = block_size; - self.fragment.border_box.size.block = block_size; - } - - // FIXME: Record enough info to deal with fragmented decorations. - // See https://drafts.csswg.org/css-break/#break-decoration - // For borders, this might be `enum FragmentPosition { First, Middle, Last }` - fn clone_with_children(&self, new_children: FlowList) -> BlockFlow { - BlockFlow { - base: self.base.clone_with_children(new_children), - fragment: self.fragment.clone(), - float: self.float.clone(), - ..*self - } - } - - /// Writes in the size of the relative containing block for children. (This information - /// is also needed to handle RTL.) - fn propagate_early_absolute_position_info_to_children(&mut self) { - for kid in self.base.child_iter_mut() { - kid.mut_base().early_absolute_position_info = EarlyAbsolutePositionInfo { - relative_containing_block_size: self.fragment.content_box().size, - relative_containing_block_mode: self.fragment.style().writing_mode, - } - } - } - - /// Assign block-size for current flow. - /// - /// * Collapse margins for flow's children and set in-flow child flows' block offsets now that - /// we know their block-sizes. - /// * Calculate and set the block-size of the current flow. - /// * Calculate block-size, vertical margins, and block offset for the flow's box using CSS § - /// 10.6.7. - /// - /// For absolute flows, we store the calculated content block-size for the flow. We defer the - /// calculation of the other values until a later traversal. - /// - /// When `fragmentation_context` is given (not `None`), this should fit as much of the content - /// as possible within the available block size. - /// If there is more content (that doesn’t fit), this flow is *fragmented* - /// with the extra content moved to another fragment (a flow like this one) which is returned. - /// See `Flow::fragment`. - /// - /// The return value is always `None` when `fragmentation_context` is `None`. - /// - /// `inline(always)` because this is only ever called by in-order or non-in-order top-level - /// methods. - #[inline(always)] - pub fn assign_block_size_block_base( - &mut self, - layout_context: &LayoutContext, - mut fragmentation_context: Option<FragmentationContext>, - margins_may_collapse: MarginsMayCollapseFlag, - ) -> Option<Arc<dyn Flow>> { - let _scope = layout_debug_scope!("assign_block_size_block_base {:x}", self.base.debug_id()); - - let mut break_at = None; - let content_box = self.fragment.content_box(); - if self - .base - .restyle_damage - .contains(ServoRestyleDamage::REFLOW) - { - // Our current border-box position. - let mut cur_b = Au(0); - - // Absolute positioning establishes a block formatting context. Don't propagate floats - // in or out. (But do propagate them between kids.) - if self - .base - .flags - .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) || - margins_may_collapse != MarginsMayCollapseFlag::MarginsMayCollapse - { - self.base.floats = Floats::new(self.fragment.style.writing_mode); - } - - let writing_mode = self.base.floats.writing_mode; - self.base.floats.translate(LogicalSize::new( - writing_mode, - -self.fragment.inline_start_offset(), - Au(0), - )); - - // The sum of our block-start border and block-start padding. - let block_start_offset = self.fragment.border_padding.block_start; - translate_including_floats(&mut cur_b, block_start_offset, &mut self.base.floats); - - let can_collapse_block_start_margin_with_kids = margins_may_collapse == - MarginsMayCollapseFlag::MarginsMayCollapse && - !self - .base - .flags - .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) && - self.fragment.border_padding.block_start == Au(0); - let mut margin_collapse_info = MarginCollapseInfo::initialize_block_start_margin( - &self.fragment, - can_collapse_block_start_margin_with_kids, - ); - - // At this point, `cur_b` is at the content edge of our box. Now iterate over children. - let mut floats = self.base.floats.clone(); - let thread_id = self.base.thread_id; - let (mut had_floated_children, mut had_children_with_clearance) = (false, false); - for (child_index, kid) in self.base.child_iter_mut().enumerate() { - if kid - .base() - .flags - .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) - { - // Assume that the *hypothetical box* for an absolute flow starts immediately - // after the margin-end border edge of the previous flow. - if kid - .base() - .flags - .contains(FlowFlags::BLOCK_POSITION_IS_STATIC) - { - let previous_bottom_margin = margin_collapse_info.current_float_ceiling(); - - kid.mut_base().position.start.b = cur_b + - kid.base() - .collapsible_margins - .block_start_margin_for_noncollapsible_context() + - previous_bottom_margin - } - kid.place_float_if_applicable(); - if !kid.base().flags.is_float() { - kid.assign_block_size_for_inorder_child_if_necessary( - layout_context, - thread_id, - content_box, - ); - } - - // Skip the collapsing and float processing for absolute flow kids and continue - // with the next flow. - continue; - } - - let previous_b = cur_b; - if let Some(ctx) = fragmentation_context { - let child_ctx = FragmentationContext { - available_block_size: ctx.available_block_size - cur_b, - this_fragment_is_empty: ctx.this_fragment_is_empty, - }; - if let Some(remaining) = kid.fragment(layout_context, Some(child_ctx)) { - break_at = Some((child_index + 1, Some(remaining))); - } - } - - // Assign block-size now for the child if it might have floats in and we couldn't - // before. - kid.mut_base().floats = floats.clone(); - if kid.base().flags.is_float() { - had_floated_children = true; - kid.mut_base().position.start.b = cur_b; - { - let kid_block = kid.as_mut_block(); - let float_ceiling = margin_collapse_info.current_float_ceiling(); - kid_block.float.as_mut().unwrap().float_ceiling = float_ceiling - } - kid.place_float_if_applicable(); - - let kid_base = kid.mut_base(); - floats = kid_base.floats.clone(); - continue; - } - - // If we have clearance, assume there are no floats in. - // - // FIXME(#2008, pcwalton): This could be wrong if we have `clear: left` or `clear: - // right` and there are still floats to impact, of course. But this gets - // complicated with margin collapse. Possibly the right thing to do is to lay out - // the block again in this rare case. (Note that WebKit can lay blocks out twice; - // this may be related, although I haven't looked into it closely.) - if kid.base().flags.clears_floats() { - kid.mut_base().floats = Floats::new(self.fragment.style.writing_mode) - } - - // Lay the child out if this was an in-order traversal. - let need_to_process_child_floats = kid - .assign_block_size_for_inorder_child_if_necessary( - layout_context, - thread_id, - content_box, - ); - - if !had_children_with_clearance && - floats.is_present() && - (kid.base().flags.contains(FlowFlags::CLEARS_LEFT) || - kid.base().flags.contains(FlowFlags::CLEARS_RIGHT)) - { - had_children_with_clearance = true - } - - // Handle any (possibly collapsed) top margin. - let delta = margin_collapse_info.advance_block_start_margin( - &kid.base().collapsible_margins, - !had_children_with_clearance, - ); - translate_including_floats(&mut cur_b, delta, &mut floats); - - // Collapse-through margins should be placed at the top edge, - // so we'll handle the delta after the bottom margin is processed - if let CollapsibleMargins::CollapseThrough(_) = kid.base().collapsible_margins { - cur_b = cur_b - delta; - } - - // Clear past the floats that came in, if necessary. - let clearance = match ( - kid.base().flags.contains(FlowFlags::CLEARS_LEFT), - kid.base().flags.contains(FlowFlags::CLEARS_RIGHT), - ) { - (false, false) => Au(0), - (true, false) => floats.clearance(ClearType::Left), - (false, true) => floats.clearance(ClearType::Right), - (true, true) => floats.clearance(ClearType::Both), - }; - translate_including_floats(&mut cur_b, clearance, &mut floats); - - // At this point, `cur_b` is at the border edge of the child. - kid.mut_base().position.start.b = cur_b; - - // Now pull out the child's outgoing floats. We didn't do this immediately after - // the `assign_block_size_for_inorder_child_if_necessary` call because clearance on - // a block operates on the floats that come *in*, not the floats that go *out*. - if need_to_process_child_floats { - floats = kid.mut_base().floats.clone() - } - - // Move past the child's border box. Do not use the `translate_including_floats` - // function here because the child has already translated floats past its border - // box. - let kid_base = kid.mut_base(); - cur_b = cur_b + kid_base.position.size.block; - - // Handle any (possibly collapsed) block-end margin. - let delta = - margin_collapse_info.advance_block_end_margin(&kid_base.collapsible_margins); - translate_including_floats(&mut cur_b, delta, &mut floats); - - // Collapse-through margin should be placed at the top edge of the flow. - let collapse_delta = match kid_base.collapsible_margins { - CollapsibleMargins::CollapseThrough(_) => { - let delta = margin_collapse_info.current_float_ceiling(); - cur_b = cur_b + delta; - kid_base.position.start.b = kid_base.position.start.b + delta; - delta - }, - _ => Au(0), - }; - - if break_at.is_some() { - break; - } - - if let Some(ref mut ctx) = fragmentation_context { - if cur_b > ctx.available_block_size && !ctx.this_fragment_is_empty { - break_at = Some((child_index, None)); - cur_b = previous_b; - break; - } - ctx.this_fragment_is_empty = false - } - - // For consecutive collapse-through flows, their top margin should be calculated - // from the same baseline. - cur_b = cur_b - collapse_delta; - } - - // Add in our block-end margin and compute our collapsible margins. - let can_collapse_block_end_margin_with_kids = margins_may_collapse == - MarginsMayCollapseFlag::MarginsMayCollapse && - !self - .base - .flags - .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) && - self.fragment.border_padding.block_end == Au(0); - let (collapsible_margins, delta) = margin_collapse_info - .finish_and_compute_collapsible_margins( - &self.fragment, - self.base.block_container_explicit_block_size, - can_collapse_block_end_margin_with_kids, - !had_floated_children, - ); - self.base.collapsible_margins = collapsible_margins; - translate_including_floats(&mut cur_b, delta, &mut floats); - - let mut block_size = cur_b - block_start_offset; - let is_root = self.is_root(); - - if is_root || - self.formatting_context_type() != FormattingContextType::None || - self.base - .flags - .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) - { - // The content block-size includes all the floats per CSS 2.1 § 10.6.7. The easiest - // way to handle this is to just treat it as clearance. - block_size = block_size + floats.clearance(ClearType::Both); - } - - if self - .base - .flags - .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) - { - // FIXME(#2003, pcwalton): The max is taken here so that you can scroll the page, - // but this is not correct behavior according to CSS 2.1 § 10.5. Instead I think we - // should treat the root element as having `overflow: scroll` and use the layers- - // based scrolling infrastructure to make it scrollable. - if is_root { - let viewport_size = LogicalSize::from_physical( - self.fragment.style.writing_mode, - layout_context.shared_context().viewport_size(), - ); - block_size = max(viewport_size.block, block_size) - } - - // Store the content block-size for use in calculating the absolute flow's - // dimensions later. - // - // FIXME(pcwalton): This looks not idempotent. Is it? - self.fragment.border_box.size.block = block_size; - } - - if self - .base - .flags - .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) - { - self.propagate_early_absolute_position_info_to_children(); - return None; - } - - // Compute any explicitly-specified block size. - // Can't use `for` because we assign to `candidate_block_size_iterator.candidate_value`. - let mut candidate_block_size_iterator = CandidateBSizeIterator::new( - &self.fragment, - self.base.block_container_explicit_block_size, - ); - while let Some(candidate_block_size) = candidate_block_size_iterator.next() { - candidate_block_size_iterator.candidate_value = match candidate_block_size { - MaybeAuto::Auto => block_size, - MaybeAuto::Specified(value) => value, - } - } - - // Adjust `cur_b` as necessary to account for the explicitly-specified block-size. - block_size = candidate_block_size_iterator.candidate_value; - let delta = block_size - (cur_b - block_start_offset); - translate_including_floats(&mut cur_b, delta, &mut floats); - - // Take border and padding into account. - let block_end_offset = self.fragment.border_padding.block_end; - translate_including_floats(&mut cur_b, block_end_offset, &mut floats); - - // Now that `cur_b` is at the block-end of the border box, compute the final border box - // position. - self.fragment.border_box.size.block = cur_b; - self.fragment.border_box.start.b = Au(0); - self.base.position.size.block = cur_b; - - self.propagate_early_absolute_position_info_to_children(); - - // Translate the current set of floats back into the parent coordinate system in the - // inline direction, and store them in the flow so that flows that come later in the - // document can access them. - floats.translate(LogicalSize::new( - writing_mode, - self.fragment.inline_start_offset(), - Au(0), - )); - self.base.floats = floats; - self.adjust_fragments_for_collapsed_margins_if_root(layout_context.shared_context()); - } else { - // We don't need to reflow, but we still need to perform in-order traversals if - // necessary. - let thread_id = self.base.thread_id; - for kid in self.base.child_iter_mut() { - kid.assign_block_size_for_inorder_child_if_necessary( - layout_context, - thread_id, - content_box, - ); - } - } - - if (&*self as &dyn Flow).contains_roots_of_absolute_flow_tree() { - // Assign block-sizes for all flows in this absolute flow tree. - // This is preorder because the block-size of an absolute flow may depend on - // the block-size of its containing block, which may also be an absolute flow. - let assign_abs_b_sizes = AbsoluteAssignBSizesTraversal(layout_context.shared_context()); - assign_abs_b_sizes.traverse_absolute_flows(&mut *self); - } - - // Don't remove the dirty bits yet if we're absolutely-positioned, since our final size - // has not been calculated yet. (See `calculate_absolute_block_size_and_margins` for that.) - // Also don't remove the dirty bits if we're a block formatting context since our inline - // size has not yet been computed. (See `assign_inline_position_for_formatting_context()`.) - if (self.base.flags.is_float() || - self.formatting_context_type() == FormattingContextType::None) && - !self - .base - .flags - .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) - { - self.base - .restyle_damage - .remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW); - self.fragment - .restyle_damage - .remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW); - } - - break_at.and_then(|(i, child_remaining)| { - if i == self.base.children.len() && child_remaining.is_none() { - None - } else { - let mut children = self.base.children.split_off(i); - if let Some(child) = child_remaining { - children.push_front_arc(child); - } - Some(Arc::new(self.clone_with_children(children)) as Arc<dyn Flow>) - } - }) - } - - /// Add placement information about current float flow for use by the parent. - /// - /// Also, use information given by parent about other floats to find out our relative position. - /// - /// This does not give any information about any float descendants because they do not affect - /// elements outside of the subtree rooted at this float. - /// - /// This function is called on a kid flow by a parent. Therefore, `assign_block_size_float` was - /// already called on this kid flow by the traversal function. So, the values used are - /// well-defined. - pub fn place_float(&mut self) { - let block_size = self.fragment.border_box.size.block; - let clearance = match self.fragment.clear() { - None => Au(0), - Some(clear) => self.base.floats.clearance(clear), - }; - - let float_info: FloatedBlockInfo = (**self.float.as_ref().unwrap()).clone(); - - // Our `position` field accounts for positive margins, but not negative margins. (See - // calculation of `extra_inline_size_from_margin` below.) Negative margins must be taken - // into account for float placement, however. So we add them in here. - let inline_size_for_float_placement = - self.base.position.size.inline + min(Au(0), self.fragment.margin.inline_start_end()); - - let info = PlacementInfo { - size: LogicalSize::new( - self.fragment.style.writing_mode, - inline_size_for_float_placement, - block_size + self.fragment.margin.block_start_end(), - ) - .convert( - self.fragment.style.writing_mode, - self.base.floats.writing_mode, - ), - ceiling: clearance + float_info.float_ceiling, - max_inline_size: float_info.containing_inline_size, - kind: float_info.float_kind, - }; - - // Place the float and return the `Floats` back to the parent flow. - // After, grab the position and use that to set our position. - self.base.floats.add_float(&info); - - // FIXME (mbrubeck) Get the correct container size for self.base.floats; - let container_size = Size2D::new(self.base.block_container_inline_size, Au(0)); - - // Move in from the margin edge, as per CSS 2.1 § 9.5, floats may not overlap anything on - // their margin edges. - let float_offset = self - .base - .floats - .last_float_pos() - .unwrap() - .convert( - self.base.floats.writing_mode, - self.base.writing_mode, - container_size, - ) - .start; - let margin_offset = LogicalPoint::new( - self.base.writing_mode, - Au(0), - self.fragment.margin.block_start, - ); - - let mut origin = LogicalPoint::new( - self.base.writing_mode, - self.base.position.start.i, - self.base.position.start.b, - ); - origin = origin.add_point(&float_offset).add_point(&margin_offset); - self.base.position = - LogicalRect::from_point_size(self.base.writing_mode, origin, self.base.position.size); - } - - pub fn explicit_block_containing_size( - &self, - shared_context: &SharedStyleContext, - ) -> Option<Au> { - if self.is_root() || self.is_fixed() { - let viewport_size = LogicalSize::from_physical( - self.fragment.style.writing_mode, - shared_context.viewport_size(), - ); - Some(viewport_size.block) - } else if self - .base - .flags - .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) && - self.base.block_container_explicit_block_size.is_none() - { - self.base - .absolute_cb - .explicit_block_containing_size(shared_context) - } else { - self.base.block_container_explicit_block_size - } - } - - pub fn explicit_block_size(&self, containing_block_size: Option<Au>) -> Option<Au> { - let content_block_size = self.fragment.style().content_block_size(); - - match content_block_size { - Size::Auto => { - let container_size = containing_block_size?; - let (block_start, block_end) = { - let position = self.fragment.style().logical_position(); - ( - MaybeAuto::from_style(position.block_start, container_size), - MaybeAuto::from_style(position.block_end, container_size), - ) - }; - - match (block_start, block_end) { - (MaybeAuto::Specified(block_start), MaybeAuto::Specified(block_end)) => { - let available_block_size = - container_size - self.fragment.border_padding.block_start_end(); - - // Non-auto margin-block-start and margin-block-end values have already been - // calculated during assign-inline-size. - let margin = self.fragment.style().logical_margin(); - let margin_block_start = match margin.block_start { - LengthPercentageOrAuto::Auto => MaybeAuto::Auto, - _ => MaybeAuto::Specified(self.fragment.margin.block_start), - }; - let margin_block_end = match margin.block_end { - LengthPercentageOrAuto::Auto => MaybeAuto::Auto, - _ => MaybeAuto::Specified(self.fragment.margin.block_end), - }; - - let margin_block_start = margin_block_start.specified_or_zero(); - let margin_block_end = margin_block_end.specified_or_zero(); - let sum = block_start + block_end + margin_block_start + margin_block_end; - Some(available_block_size - sum) - }, - (_, _) => None, - } - }, - Size::LengthPercentage(ref lp) => lp.maybe_to_used_value(containing_block_size), - } - } - - fn calculate_absolute_block_size_and_margins(&mut self, shared_context: &SharedStyleContext) { - let opaque_self = OpaqueFlow::from_flow(self); - let containing_block_block_size = self - .containing_block_size(&shared_context.viewport_size(), opaque_self) - .block; - - // This is the stored content block-size value from assign-block-size - let content_block_size = self.fragment.border_box.size.block; - - let mut solution = None; - { - // Non-auto margin-block-start and margin-block-end values have already been - // calculated during assign-inline-size. - let margin = self.fragment.style().logical_margin(); - let margin_block_start = match margin.block_start { - LengthPercentageOrAuto::Auto => MaybeAuto::Auto, - _ => MaybeAuto::Specified(self.fragment.margin.block_start), - }; - let margin_block_end = match margin.block_end { - LengthPercentageOrAuto::Auto => MaybeAuto::Auto, - _ => MaybeAuto::Specified(self.fragment.margin.block_end), - }; - - let block_start; - let block_end; - { - let position = self.fragment.style().logical_position(); - block_start = - MaybeAuto::from_style(position.block_start, containing_block_block_size); - block_end = MaybeAuto::from_style(position.block_end, containing_block_block_size); - } - - let available_block_size = - containing_block_block_size - self.fragment.border_padding.block_start_end(); - if self.fragment.is_replaced() { - // Calculate used value of block-size just like we do for inline replaced elements. - // TODO: Pass in the containing block block-size when Fragment's - // assign-block-size can handle it correctly. - self.fragment.assign_replaced_block_size_if_necessary(); - // TODO: Right now, this content block-size value includes the - // margin because of erroneous block-size calculation in fragment. - // Check this when that has been fixed. - let block_size_used_val = self.fragment.border_box.size.block - - self.fragment.border_padding.block_start_end(); - solution = Some( - BSizeConstraintSolution::solve_vertical_constraints_abs_replaced( - block_size_used_val, - margin_block_start, - margin_block_end, - block_start, - block_end, - content_block_size, - available_block_size, - ), - ) - } else { - let mut candidate_block_size_iterator = - CandidateBSizeIterator::new(&self.fragment, Some(containing_block_block_size)); - - // Can't use `for` because we assign to - // `candidate_block_size_iterator.candidate_value`. - while let Some(block_size_used_val) = candidate_block_size_iterator.next() { - solution = Some( - BSizeConstraintSolution::solve_vertical_constraints_abs_nonreplaced( - block_size_used_val, - margin_block_start, - margin_block_end, - block_start, - block_end, - content_block_size, - available_block_size, - ), - ); - - candidate_block_size_iterator.candidate_value = solution.unwrap().block_size; - } - } - } - - let solution = solution.unwrap(); - self.fragment.margin.block_start = solution.margin_block_start; - self.fragment.margin.block_end = solution.margin_block_end; - self.fragment.border_box.start.b = Au(0); - - if !self - .base - .flags - .contains(FlowFlags::BLOCK_POSITION_IS_STATIC) - { - self.base.position.start.b = solution.block_start + self.fragment.margin.block_start - } - - let block_size = solution.block_size + self.fragment.border_padding.block_start_end(); - - self.fragment.border_box.size.block = block_size; - self.base.position.size.block = block_size; - - self.base - .restyle_damage - .remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW); - self.fragment - .restyle_damage - .remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW); - } - - /// Compute inline size based using the `block_container_inline_size` set by the parent flow. - /// - /// This is run in the `AssignISizes` traversal. - fn propagate_and_compute_used_inline_size(&mut self, shared_context: &SharedStyleContext) { - let containing_block_inline_size = self.base.block_container_inline_size; - self.compute_used_inline_size(shared_context, containing_block_inline_size); - if self.base.flags.is_float() { - self.float.as_mut().unwrap().containing_inline_size = containing_block_inline_size - } - } - - /// Assigns the computed inline-start content edge and inline-size to all the children of this - /// block flow. The given `callback`, if supplied, will be called once per child; it is - /// currently used to push down column sizes for tables. - /// - /// `#[inline(always)]` because this is called only from block or table inline-size assignment - /// and the code for block layout is significantly simpler. - #[inline(always)] - pub fn propagate_assigned_inline_size_to_children<F>( - &mut self, - shared_context: &SharedStyleContext, - inline_start_content_edge: Au, - inline_end_content_edge: Au, - content_inline_size: Au, - mut callback: F, - ) where - F: FnMut(&mut dyn Flow, usize, Au, WritingMode, &mut Au, &mut Au), - { - let flags = self.base.flags.clone(); - - let opaque_self = OpaqueFlow::from_flow(self); - - // Calculate non-auto block size to pass to children. - let box_border = match self.fragment.style().get_position().box_sizing { - BoxSizing::BorderBox => self.fragment.border_padding.block_start_end(), - BoxSizing::ContentBox => Au(0), - }; - let parent_container_size = self.explicit_block_containing_size(shared_context); - // https://drafts.csswg.org/css-ui-3/#box-sizing - let mut explicit_content_size = self.explicit_block_size(parent_container_size).map(|x| { - if x < box_border { - Au(0) - } else { - x - box_border - } - }); - if self.is_root() { - explicit_content_size = max(parent_container_size, explicit_content_size); - } - // Calculate containing block inline size. - let containing_block_size = if flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) { - self.containing_block_size(&shared_context.viewport_size(), opaque_self) - .inline - } else { - content_inline_size - }; - // FIXME (mbrubeck): Get correct mode for absolute containing block - let containing_block_mode = self.base.writing_mode; - - let mut inline_start_margin_edge = inline_start_content_edge; - let mut inline_end_margin_edge = inline_end_content_edge; - - let mut iterator = self.base.child_iter_mut().enumerate().peekable(); - while let Some((i, kid)) = iterator.next() { - kid.mut_base().block_container_explicit_block_size = explicit_content_size; - - // The inline-start margin edge of the child flow is at our inline-start content edge, - // and its inline-size is our content inline-size. - let kid_mode = kid.base().writing_mode; - { - // Don't assign positions to children unless they're going to be reflowed. - // Otherwise, the position we assign might be incorrect and never fixed up. (Issue - // #13704.) - // - // For instance, floats have their true inline position calculated in - // `assign_block_size()`, which won't do anything unless `REFLOW` is set. So, if a - // float child does not have `REFLOW` set, we must be careful to avoid touching its - // inline position, as no logic will run afterward to set its true value. - let kid_base = kid.mut_base(); - let reflow_damage = if kid_base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) - { - ServoRestyleDamage::REFLOW_OUT_OF_FLOW - } else { - ServoRestyleDamage::REFLOW - }; - if kid_base - .flags - .contains(FlowFlags::INLINE_POSITION_IS_STATIC) && - kid_base.restyle_damage.contains(reflow_damage) - { - kid_base.position.start.i = - if kid_mode.is_bidi_ltr() == containing_block_mode.is_bidi_ltr() { - inline_start_content_edge - } else { - // The kid's inline 'start' is at the parent's 'end' - inline_end_content_edge - }; - } - kid_base.block_container_inline_size = content_inline_size; - kid_base.block_container_writing_mode = containing_block_mode; - } - - // Call the callback to propagate extra inline size information down to the child. This - // is currently used for tables. - callback( - kid, - i, - content_inline_size, - containing_block_mode, - &mut inline_start_margin_edge, - &mut inline_end_margin_edge, - ); - - // Per CSS 2.1 § 16.3.1, text alignment propagates to all children in flow. - // - // TODO(#2265, pcwalton): Do this in the cascade instead. - let containing_block_text_align = self.fragment.style().get_inherited_text().text_align; - kid.mut_base() - .flags - .set_text_align(containing_block_text_align); - - // Handle `text-indent` on behalf of any inline children that we have. This is - // necessary because any percentages are relative to the containing block, which only - // we know. - if kid.is_inline_flow() { - kid.as_mut_inline().first_line_indentation = self - .fragment - .style() - .get_inherited_text() - .text_indent - .to_used_value(containing_block_size); - } - } - } - - /// Determines the type of formatting context this is. See the definition of - /// `FormattingContextType`. - pub fn formatting_context_type(&self) -> FormattingContextType { - if self.is_inline_flex_item() || self.is_block_flex_item() { - return FormattingContextType::Other; - } - let style = self.fragment.style(); - if style.get_box().float != Float::None { - return FormattingContextType::Other; - } - match style.get_box().display { - Display::TableCell | - Display::TableCaption | - Display::TableRowGroup | - Display::Table | - Display::InlineBlock | - Display::Flex => FormattingContextType::Other, - _ if style.get_box().overflow_x != StyleOverflow::Visible || - style.get_box().overflow_y != StyleOverflow::Visible || - style.is_multicol() => - { - FormattingContextType::Block - }, - _ => FormattingContextType::None, - } - } - - /// Per CSS 2.1 § 9.5, block formatting contexts' inline widths and positions are affected by - /// the presence of floats. This is the part of the assign-heights traversal that computes - /// the final inline position and width for such flows. - /// - /// Note that this is part of the assign-block-sizes traversal, not the assign-inline-sizes - /// traversal as one might expect. That is because, in general, float placement cannot occur - /// until heights are assigned. To work around this unfortunate circular dependency, by the - /// time we get here we have already estimated the width of the block formatting context based - /// on the floats we could see at the time of inline-size assignment. The job of this function, - /// therefore, is not only to assign the final size but also to perform the layout again for - /// this block formatting context if our speculation was wrong. - fn assign_inline_position_for_formatting_context( - &mut self, - layout_context: &LayoutContext, - content_box: LogicalRect<Au>, - ) { - debug_assert_ne!(self.formatting_context_type(), FormattingContextType::None); - - if !self - .base - .restyle_damage - .intersects(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW) - { - return; - } - - // We do this first to avoid recomputing our inline size when we propagate it. - self.base - .restyle_damage - .remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW); - self.fragment - .restyle_damage - .remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW); - - // The code below would completely wreck the layout if run on a flex item, however: - // * Flex items are always the children of flex containers. - // * Flex containers only contain flex items. - // * Floats cannot intrude into flex containers. - // * Floats cannot escape flex items. - // * Flex items cannot also be floats. - // Therefore, a flex item cannot be impacted by a float. - // See also: https://www.w3.org/TR/css-flexbox-1/#flex-containers - if !self.base.might_have_floats_in() { - return; - } - - // If you remove the might_have_floats_in conditional, this will go off. - debug_assert!(!self.is_inline_flex_item()); - - // Compute the available space for us, based on the actual floats. - let rect = self.base.floats.available_rect( - Au(0), - self.fragment.border_box.size.block, - content_box.size.inline, - ); - let available_inline_size = if let Some(rect) = rect { - // Offset our position by whatever displacement is needed to not impact the floats. - // Also, account for margins sliding behind floats. - let inline_offset = if self.fragment.margin.inline_start < rect.start.i { - // Do not do anything for negative margins; those are handled separately. - rect.start.i - max(Au(0), self.fragment.margin.inline_start) - } else { - Au(0) - }; - self.base.position.start.i = content_box.start.i + inline_offset; - // Handle the end margin sliding behind the float. - let end = content_box.size.inline - rect.start.i - rect.size.inline; - let inline_end_offset = if self.fragment.margin.inline_end < end { - end - max(Au(0), self.fragment.margin.inline_end) - } else { - Au(0) - }; - content_box.size.inline - inline_offset - inline_end_offset - } else { - content_box.size.inline - } - self.fragment.margin.inline_start_end(); - let max_inline_size = self - .fragment - .style() - .max_inline_size() - .to_used_value(self.base.block_container_inline_size) - .unwrap_or(MAX_AU); - let min_inline_size = self - .fragment - .style() - .min_inline_size() - .to_used_value(self.base.block_container_inline_size) - .unwrap_or(Au(0)); - let specified_inline_size = self.fragment.style().content_inline_size(); - let container_size = self.base.block_container_inline_size; - let inline_size = match specified_inline_size.to_used_value(container_size) { - Some(size) => match self.fragment.style().get_position().box_sizing { - BoxSizing::BorderBox => size, - BoxSizing::ContentBox => size + self.fragment.border_padding.inline_start_end(), - }, - None => max(min_inline_size, min(available_inline_size, max_inline_size)), - }; - self.base.position.size.inline = inline_size + self.fragment.margin.inline_start_end(); - - // If float speculation failed, fixup our layout, and re-layout all the children. - if self.fragment.margin_box_inline_size() != self.base.position.size.inline { - debug!("assign_inline_position_for_formatting_context: float speculation failed"); - // Fix-up our own layout. - // We can't just traverse_flow_tree_preorder ourself, because that would re-run - // float speculation, instead of acting on the actual results. - self.fragment.border_box.size.inline = inline_size; - // Assign final-final inline sizes on all our children. - self.assign_inline_sizes(layout_context); - // Re-run layout on our children. - for child in self.base.child_iter_mut() { - sequential::reflow(child, layout_context, RelayoutMode::Force); - } - // Assign our final-final block size. - self.assign_block_size(layout_context); - } - - debug_assert_eq!( - self.fragment.margin_box_inline_size(), - self.base.position.size.inline - ); - } - - fn is_inline_block_or_inline_flex(&self) -> bool { - self.fragment.style().get_box().display == Display::InlineBlock || - self.fragment.style().get_box().display == Display::InlineFlex - } - - /// Computes the content portion (only) of the intrinsic inline sizes of this flow. This is - /// used for calculating shrink-to-fit width. Assumes that intrinsic sizes have already been - /// computed for this flow. - fn content_intrinsic_inline_sizes(&self) -> IntrinsicISizes { - let (border_padding, margin) = self.fragment.surrounding_intrinsic_inline_size(); - IntrinsicISizes { - minimum_inline_size: self.base.intrinsic_inline_sizes.minimum_inline_size - - border_padding - - margin, - preferred_inline_size: self.base.intrinsic_inline_sizes.preferred_inline_size - - border_padding - - margin, - } - } - - /// Computes intrinsic inline sizes for a block. - pub fn bubble_inline_sizes_for_block(&mut self, consult_children: bool) { - let _scope = layout_debug_scope!("block::bubble_inline_sizes {:x}", self.base.debug_id()); - - let mut flags = self.base.flags; - if self.definitely_has_zero_block_size() { - // This is kind of a hack for Acid2. But it's a harmless one, because (a) this behavior - // is unspecified; (b) it matches the behavior one would intuitively expect, since - // floats don't flow around blocks that take up no space in the block direction. - flags.remove(FlowFlags::CONTAINS_TEXT_OR_REPLACED_FRAGMENTS); - } else if self.fragment.is_text_or_replaced() { - flags.insert(FlowFlags::CONTAINS_TEXT_OR_REPLACED_FRAGMENTS); - } else { - flags.remove(FlowFlags::CONTAINS_TEXT_OR_REPLACED_FRAGMENTS); - for kid in self.base.children.iter() { - if kid - .base() - .flags - .contains(FlowFlags::CONTAINS_TEXT_OR_REPLACED_FRAGMENTS) - { - flags.insert(FlowFlags::CONTAINS_TEXT_OR_REPLACED_FRAGMENTS); - break; - } - } - } - - // Find the maximum inline-size from children. - // - // See: https://lists.w3.org/Archives/Public/www-style/2014Nov/0085.html - // - // FIXME(pcwalton): This doesn't exactly follow that algorithm at the moment. - // FIXME(pcwalton): This should consider all float descendants, not just children. - let mut computation = self.fragment.compute_intrinsic_inline_sizes(); - let (mut left_float_width, mut right_float_width) = (Au(0), Au(0)); - let (mut left_float_width_accumulator, mut right_float_width_accumulator) = (Au(0), Au(0)); - let mut preferred_inline_size_of_children_without_text_or_replaced_fragments = Au(0); - for kid in self.base.child_iter_mut() { - if kid - .base() - .flags - .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) || - !consult_children - { - continue; - } - - let child_base = kid.mut_base(); - let float_kind = child_base.flags.float_kind(); - computation.content_intrinsic_sizes.minimum_inline_size = max( - computation.content_intrinsic_sizes.minimum_inline_size, - child_base.intrinsic_inline_sizes.minimum_inline_size, - ); - - if child_base.flags.contains(FlowFlags::CLEARS_LEFT) { - left_float_width = max(left_float_width, left_float_width_accumulator); - left_float_width_accumulator = Au(0) - } - if child_base.flags.contains(FlowFlags::CLEARS_RIGHT) { - right_float_width = max(right_float_width, right_float_width_accumulator); - right_float_width_accumulator = Au(0) - } - - match ( - float_kind, - child_base - .flags - .contains(FlowFlags::CONTAINS_TEXT_OR_REPLACED_FRAGMENTS), - ) { - (Float::None, true) => { - computation.content_intrinsic_sizes.preferred_inline_size = max( - computation.content_intrinsic_sizes.preferred_inline_size, - child_base.intrinsic_inline_sizes.preferred_inline_size, - ); - }, - (Float::None, false) => { - preferred_inline_size_of_children_without_text_or_replaced_fragments = max( - preferred_inline_size_of_children_without_text_or_replaced_fragments, - child_base.intrinsic_inline_sizes.preferred_inline_size, - ) - }, - (Float::Left, _) => { - left_float_width_accumulator = left_float_width_accumulator + - child_base.intrinsic_inline_sizes.preferred_inline_size; - }, - (Float::Right, _) => { - right_float_width_accumulator = right_float_width_accumulator + - child_base.intrinsic_inline_sizes.preferred_inline_size; - }, - } - } - - left_float_width = max(left_float_width, left_float_width_accumulator); - right_float_width = max(right_float_width, right_float_width_accumulator); - - computation.content_intrinsic_sizes.preferred_inline_size = - computation.content_intrinsic_sizes.preferred_inline_size + - left_float_width + - right_float_width; - computation.content_intrinsic_sizes.preferred_inline_size = max( - computation.content_intrinsic_sizes.preferred_inline_size, - preferred_inline_size_of_children_without_text_or_replaced_fragments, - ); - - self.base.intrinsic_inline_sizes = computation.finish(); - self.base.flags = flags - } - - pub fn overflow_style_may_require_clip_scroll_node(&self) -> bool { - match ( - self.fragment.style().get_box().overflow_x, - self.fragment.style().get_box().overflow_y, - ) { - (StyleOverflow::Auto, _) | - (StyleOverflow::Scroll, _) | - (StyleOverflow::Hidden, _) | - (_, StyleOverflow::Auto) | - (_, StyleOverflow::Scroll) | - (_, StyleOverflow::Hidden) => true, - (_, _) => false, - } - } - - pub fn compute_inline_sizes(&mut self, shared_context: &SharedStyleContext) { - if !self - .base - .restyle_damage - .intersects(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW) - { - return; - } - - debug!( - "assign_inline_sizes({}): assigning inline_size for flow", - if self.base.flags.is_float() { - "float" - } else { - "block" - } - ); - - self.base.floats = Floats::new(self.base.writing_mode); - - self.initialize_container_size_for_root(shared_context); - - // Our inline-size was set to the inline-size of the containing block by the flow's parent. - // Now compute the real value. - self.propagate_and_compute_used_inline_size(shared_context); - - self.guess_inline_size_for_block_formatting_context_if_necessary() - } - - /// If this is the root flow, initialize values that would normally be set by the parent. - /// - /// Should be called during `assign_inline_sizes` for flows that may be the root. - pub fn initialize_container_size_for_root(&mut self, shared_context: &SharedStyleContext) { - if self.is_root() { - debug!("Setting root position"); - self.base.position.start = LogicalPoint::zero(self.base.writing_mode); - self.base.block_container_inline_size = - LogicalSize::from_physical(self.base.writing_mode, shared_context.viewport_size()) - .inline; - self.base.block_container_writing_mode = self.base.writing_mode; - } - } - - fn guess_inline_size_for_block_formatting_context_if_necessary(&mut self) { - // We don't need to guess anything unless this is a block formatting context. - if self.formatting_context_type() != FormattingContextType::Block { - return; - } - - // If `max-width` is set, then don't perform this speculation. We guess that the - // page set `max-width` in order to avoid hitting floats. The search box on Google - // SERPs falls into this category. - if self.fragment.style.max_inline_size() != MaxSize::None { - return; - } - - // At this point, we know we can't precisely compute the inline-size of this block now, - // because floats might affect it. Speculate that its inline-size is equal to the - // inline-size computed above minus the inline-size of the previous left and/or right - // floats. - let speculated_left_float_size = if self.fragment.margin.inline_start >= Au(0) && - self.base.speculated_float_placement_in.left > self.fragment.margin.inline_start - { - self.base.speculated_float_placement_in.left - self.fragment.margin.inline_start - } else { - Au(0) - }; - let speculated_right_float_size = if self.fragment.margin.inline_end >= Au(0) && - self.base.speculated_float_placement_in.right > self.fragment.margin.inline_end - { - self.base.speculated_float_placement_in.right - self.fragment.margin.inline_end - } else { - Au(0) - }; - self.fragment.border_box.size.inline = self.fragment.border_box.size.inline - - speculated_left_float_size - - speculated_right_float_size - } - - fn definitely_has_zero_block_size(&self) -> bool { - if !self - .fragment - .style - .content_block_size() - .is_definitely_zero() - { - return false; - } - let border_width = self.fragment.border_width(); - if border_width.block_start != Au(0) || border_width.block_end != Au(0) { - return false; - } - let padding = self.fragment.style.logical_padding(); - padding.block_start.is_definitely_zero() && padding.block_end.is_definitely_zero() - } - - pub fn is_inline_flex_item(&self) -> bool { - self.fragment - .flags - .contains(FragmentFlags::IS_INLINE_FLEX_ITEM) - } - - pub fn is_block_flex_item(&self) -> bool { - self.fragment - .flags - .contains(FragmentFlags::IS_BLOCK_FLEX_ITEM) - } - - pub fn mark_scrolling_overflow(&mut self, has_scrolling_overflow: bool) { - if has_scrolling_overflow { - self.flags.insert(BlockFlowFlags::HAS_SCROLLING_OVERFLOW); - } else { - self.flags.remove(BlockFlowFlags::HAS_SCROLLING_OVERFLOW); - } - } - - pub fn has_scrolling_overflow(&self) -> bool { - self.flags.contains(BlockFlowFlags::HAS_SCROLLING_OVERFLOW) - } - - // Return offset from original position because of `position: sticky`. - pub fn sticky_position(&self) -> SideOffsets2D<MaybeAuto> { - let containing_block_size = &self - .base - .early_absolute_position_info - .relative_containing_block_size; - let writing_mode = self - .base - .early_absolute_position_info - .relative_containing_block_mode; - let offsets = self.fragment.style().logical_position(); - let as_margins = LogicalMargin::new( - writing_mode, - MaybeAuto::from_style(offsets.block_start, containing_block_size.inline), - MaybeAuto::from_style(offsets.inline_end, containing_block_size.inline), - MaybeAuto::from_style(offsets.block_end, containing_block_size.inline), - MaybeAuto::from_style(offsets.inline_start, containing_block_size.inline), - ); - as_margins.to_physical(writing_mode) - } - - pub fn background_border_section(&self) -> DisplayListSection { - if self.base.flags.is_float() { - DisplayListSection::BackgroundAndBorders - } else if self - .base - .flags - .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) - { - if self.fragment.establishes_stacking_context() { - DisplayListSection::BackgroundAndBorders - } else { - DisplayListSection::BlockBackgroundsAndBorders - } - } else { - DisplayListSection::BlockBackgroundsAndBorders - } - } -} - -impl Flow for BlockFlow { - fn class(&self) -> FlowClass { - FlowClass::Block - } - - fn as_mut_block(&mut self) -> &mut BlockFlow { - self - } - - fn as_block(&self) -> &BlockFlow { - self - } - - /// Pass 1 of reflow: computes minimum and preferred inline-sizes. - /// - /// Recursively (bottom-up) determine the flow's minimum and preferred inline-sizes. When - /// called on this flow, all child flows have had their minimum and preferred inline-sizes set. - /// This function must decide minimum/preferred inline-sizes based on its children's - /// inline-sizes and the dimensions of any fragments it is responsible for flowing. - fn bubble_inline_sizes(&mut self) { - // If this block has a fixed width, just use that for the minimum and preferred width, - // rather than bubbling up children inline width. - // FIXME(emilio): This should probably be writing-mode-aware. - let consult_children = match self.fragment.style().get_position().width { - Size::Auto => true, - Size::LengthPercentage(ref lp) => lp.maybe_to_used_value(None).is_none(), - }; - self.bubble_inline_sizes_for_block(consult_children); - self.fragment - .restyle_damage - .remove(ServoRestyleDamage::BUBBLE_ISIZES); - } - - /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. - /// When called on this context, the context has had its inline-size set by the parent context. - /// - /// Dual fragments consume some inline-size first, and the remainder is assigned to all child - /// (block) contexts. - fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) { - let _scope = layout_debug_scope!("block::assign_inline_sizes {:x}", self.base.debug_id()); - - let shared_context = layout_context.shared_context(); - self.compute_inline_sizes(shared_context); - - // Move in from the inline-start border edge. - let inline_start_content_edge = - self.fragment.border_box.start.i + self.fragment.border_padding.inline_start; - - let padding_and_borders = self.fragment.border_padding.inline_start_end(); - - // Distance from the inline-end margin edge to the inline-end content edge. - let inline_end_content_edge = - self.fragment.margin.inline_end + self.fragment.border_padding.inline_end; - - let content_inline_size = self.fragment.border_box.size.inline - padding_and_borders; - - self.propagate_assigned_inline_size_to_children( - shared_context, - inline_start_content_edge, - inline_end_content_edge, - content_inline_size, - |_, _, _, _, _, _| {}, - ); - } - - fn place_float_if_applicable<'a>(&mut self) { - if self.base.flags.is_float() { - self.place_float(); - } - } - - fn assign_block_size_for_inorder_child_if_necessary( - &mut self, - layout_context: &LayoutContext, - parent_thread_id: u8, - content_box: LogicalRect<Au>, - ) -> bool { - if self.base.flags.is_float() { - return false; - } - - let is_formatting_context = self.formatting_context_type() != FormattingContextType::None; - if !self - .base - .flags - .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) && - is_formatting_context - { - self.assign_inline_position_for_formatting_context(layout_context, content_box); - } - - if (self as &dyn Flow).floats_might_flow_through() { - self.base.thread_id = parent_thread_id; - if self - .base - .restyle_damage - .intersects(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW) - { - self.assign_block_size(layout_context); - // Don't remove the restyle damage; `assign_block_size` decides whether that is - // appropriate (which in the case of e.g. absolutely-positioned flows, it is not). - } - return true; - } - - if is_formatting_context { - // If this is a formatting context and definitely did not have floats in, then we must - // translate the floats past us. - let writing_mode = self.base.floats.writing_mode; - let delta = self.base.position.size.block; - self.base - .floats - .translate(LogicalSize::new(writing_mode, Au(0), -delta)); - return true; - } - - false - } - - fn assign_block_size(&mut self, ctx: &LayoutContext) { - let remaining = Flow::fragment(self, ctx, None); - debug_assert!(remaining.is_none()); - } - - fn fragment( - &mut self, - layout_context: &LayoutContext, - fragmentation_context: Option<FragmentationContext>, - ) -> Option<Arc<dyn Flow>> { - if self.fragment.is_replaced() { - let _scope = layout_debug_scope!( - "assign_replaced_block_size_if_necessary {:x}", - self.base.debug_id() - ); - - // Assign block-size for fragment if it is an image fragment. - self.fragment.assign_replaced_block_size_if_necessary(); - if !self - .base - .flags - .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) - { - self.base.position.size.block = self.fragment.border_box.size.block; - let mut block_start = - AdjoiningMargins::from_margin(self.fragment.margin.block_start); - let block_end = AdjoiningMargins::from_margin(self.fragment.margin.block_end); - if self.fragment.border_box.size.block == Au(0) { - block_start.union(block_end); - self.base.collapsible_margins = - CollapsibleMargins::CollapseThrough(block_start); - } else { - self.base.collapsible_margins = - CollapsibleMargins::Collapse(block_start, block_end); - } - self.base - .restyle_damage - .remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW); - self.fragment - .restyle_damage - .remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW); - } - None - } else if self.is_root() || - self.formatting_context_type() != FormattingContextType::None || - self.base.flags.contains(FlowFlags::MARGINS_CANNOT_COLLAPSE) - { - // Root element margins should never be collapsed according to CSS § 8.3.1. - debug!( - "assign_block_size: assigning block_size for root flow {:?}", - self.base().debug_id() - ); - self.assign_block_size_block_base( - layout_context, - fragmentation_context, - MarginsMayCollapseFlag::MarginsMayNotCollapse, - ) - } else { - debug!( - "assign_block_size: assigning block_size for block {:?}", - self.base().debug_id() - ); - self.assign_block_size_block_base( - layout_context, - fragmentation_context, - MarginsMayCollapseFlag::MarginsMayCollapse, - ) - } - } - - fn compute_stacking_relative_position(&mut self, _layout_context: &LayoutContext) { - // FIXME (mbrubeck): Get the real container size, taking the container writing mode into - // account. Must handle vertical writing modes. - let container_size = Size2D::new(self.base.block_container_inline_size, Au(0)); - - if self.is_root() { - self.base.clip = Rect::max_rect(); - } - - if self - .base - .flags - .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) - { - let position_start = self - .base - .position - .start - .to_physical(self.base.writing_mode, container_size); - - // Compute our position relative to the nearest ancestor stacking context. This will be - // passed down later as part of containing block details for absolute descendants. - let absolute_stacking_relative_position = if self.is_fixed() { - // The viewport is initially at (0, 0). - position_start - } else { - // Absolute position of the containing block + position of absolute - // flow w.r.t. the containing block. - self.base - .late_absolute_position_info - .stacking_relative_position_of_absolute_containing_block + - position_start.to_vector() - }; - - if !self.base.writing_mode.is_vertical() { - if !self - .base - .flags - .contains(FlowFlags::INLINE_POSITION_IS_STATIC) - { - self.base.stacking_relative_position.x = absolute_stacking_relative_position.x - } - if !self - .base - .flags - .contains(FlowFlags::BLOCK_POSITION_IS_STATIC) - { - self.base.stacking_relative_position.y = absolute_stacking_relative_position.y - } - } else { - if !self - .base - .flags - .contains(FlowFlags::INLINE_POSITION_IS_STATIC) - { - self.base.stacking_relative_position.y = absolute_stacking_relative_position.y - } - if !self - .base - .flags - .contains(FlowFlags::BLOCK_POSITION_IS_STATIC) - { - self.base.stacking_relative_position.x = absolute_stacking_relative_position.x - } - } - } - - // For relatively-positioned descendants, the containing block formed by a block is just - // the content box. The containing block for absolutely-positioned descendants, on the - // other hand, is established in other circumstances (see `is_absolute_containing_block'). - let relative_offset = self.fragment.relative_position( - &self - .base - .early_absolute_position_info - .relative_containing_block_size, - ); - if self.is_absolute_containing_block() { - let border_box_origin = - (self.fragment.border_box - self.fragment.style.logical_border_width()).start; - self.base - .late_absolute_position_info - .stacking_relative_position_of_absolute_containing_block = - self.base.stacking_relative_position.to_point() + - (border_box_origin + relative_offset) - .to_physical(self.base.writing_mode, container_size) - .to_vector() - } - - // Compute absolute position info for children. - let stacking_relative_position_of_absolute_containing_block_for_children = - if self.fragment.establishes_stacking_context() { - let logical_border_width = self.fragment.style().logical_border_width(); - let position = LogicalPoint::new( - self.base.writing_mode, - logical_border_width.inline_start, - logical_border_width.block_start, - ); - let position = position.to_physical(self.base.writing_mode, container_size); - - // Some blocks establish a stacking context, but not a containing block for - // absolutely positioned elements. An example of this might be a block that has - // `position: static` and `opacity` set. In these cases, absolutely-positioned - // children will not be positioned relative to us but will instead be positioned - // relative to our containing block. - if self.is_absolute_containing_block() { - position - } else { - position - self.base.stacking_relative_position - } - } else { - self.base - .late_absolute_position_info - .stacking_relative_position_of_absolute_containing_block - }; - let late_absolute_position_info_for_children = LateAbsolutePositionInfo { - stacking_relative_position_of_absolute_containing_block: - stacking_relative_position_of_absolute_containing_block_for_children, - }; - let container_size_for_children = - self.base.position.size.to_physical(self.base.writing_mode); - - // Compute the origin and clipping rectangle for children. - let relative_offset = relative_offset - .to_physical(self.base.writing_mode) - .to_vector(); - let is_stacking_context = self.fragment.establishes_stacking_context(); - let origin_for_children = if is_stacking_context { - // We establish a stacking context, so the position of our children is vertically - // correct, but has to be adjusted to accommodate horizontal margins. (Note the - // calculation involving `position` below and recall that inline-direction flow - // positions are relative to the edges of the margin box.) - // - // FIXME(pcwalton): Is this vertical-writing-direction-safe? - let margin = self.fragment.margin.to_physical(self.base.writing_mode); - Point2D::new(-margin.left, Au(0)) - } else { - self.base.stacking_relative_position.to_point() + relative_offset - }; - - // Process children. - for kid in self.base.child_iter_mut() { - if kid - .base() - .flags - .contains(FlowFlags::INLINE_POSITION_IS_STATIC) || - kid.base() - .flags - .contains(FlowFlags::BLOCK_POSITION_IS_STATIC) - { - let kid_base = kid.mut_base(); - let physical_position = kid_base - .position - .to_physical(kid_base.writing_mode, container_size_for_children); - - // Set the inline and block positions as necessary. - if !kid_base.writing_mode.is_vertical() { - if kid_base - .flags - .contains(FlowFlags::INLINE_POSITION_IS_STATIC) - { - kid_base.stacking_relative_position.x = - origin_for_children.x + physical_position.origin.x - } - if kid_base.flags.contains(FlowFlags::BLOCK_POSITION_IS_STATIC) { - kid_base.stacking_relative_position.y = - origin_for_children.y + physical_position.origin.y - } - } else { - if kid_base - .flags - .contains(FlowFlags::INLINE_POSITION_IS_STATIC) - { - kid_base.stacking_relative_position.y = - origin_for_children.y + physical_position.origin.y - } - if kid_base.flags.contains(FlowFlags::BLOCK_POSITION_IS_STATIC) { - kid_base.stacking_relative_position.x = - origin_for_children.x + physical_position.origin.x - } - } - } - - kid.mut_base().late_absolute_position_info = late_absolute_position_info_for_children; - } - } - - fn mark_as_root(&mut self) { - self.flags.insert(BlockFlowFlags::IS_ROOT) - } - - fn is_root(&self) -> bool { - self.flags.contains(BlockFlowFlags::IS_ROOT) - } - - /// The 'position' property of this flow. - fn positioning(&self) -> Position { - self.fragment.style.get_box().position - } - - /// Return the dimensions of the containing block generated by this flow for absolutely- - /// positioned descendants. For block flows, this is the padding box. - fn generated_containing_block_size(&self, _: OpaqueFlow) -> LogicalSize<Au> { - (self.fragment.border_box - self.fragment.style().logical_border_width()).size - } - - /// Returns true if this flow contains fragments that are roots of an absolute flow tree. - fn contains_roots_of_absolute_flow_tree(&self) -> bool { - self.contains_relatively_positioned_fragments() || - self.is_root() || - self.fragment.has_filter_transform_or_perspective() - } - - /// Returns true if this is an absolute containing block. - fn is_absolute_containing_block(&self) -> bool { - self.contains_positioned_fragments() || self.fragment.has_filter_transform_or_perspective() - } - - fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) { - if self - .base - .flags - .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) && - self.fragment.style().logical_position().inline_start == LengthPercentageOrAuto::Auto && - self.fragment.style().logical_position().inline_end == LengthPercentageOrAuto::Auto - { - self.base.position.start.i = inline_position - } - } - - fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) { - if self - .base - .flags - .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) && - self.fragment.style().logical_position().block_start == LengthPercentageOrAuto::Auto && - self.fragment.style().logical_position().block_end == LengthPercentageOrAuto::Auto - { - self.base.position.start.b = block_position - } - } - - fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) { - self.collect_stacking_contexts_for_block(state, StackingContextCollectionFlags::empty()); - } - - fn build_display_list(&mut self, state: &mut DisplayListBuildState) { - self.build_display_list_for_block(state, BorderPaintingMode::Separate); - } - - fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>) { - self.fragment.repair_style(new_style) - } - - fn compute_overflow(&self) -> Overflow { - let flow_size = self.base.position.size.to_physical(self.base.writing_mode); - let overflow = self.fragment.compute_overflow( - &flow_size, - &self - .base - .early_absolute_position_info - .relative_containing_block_size, - ); - overflow - } - - fn iterate_through_fragment_border_boxes( - &self, - iterator: &mut dyn FragmentBorderBoxIterator, - level: i32, - stacking_context_position: &Point2D<Au>, - ) { - if !iterator.should_process(&self.fragment) { - return; - } - - iterator.process( - &self.fragment, - level, - &self - .fragment - .stacking_relative_border_box( - &self.base.stacking_relative_position, - &self - .base - .early_absolute_position_info - .relative_containing_block_size, - self.base - .early_absolute_position_info - .relative_containing_block_mode, - CoordinateSystem::Own, - ) - .translate(&stacking_context_position.to_vector()), - ); - } - - fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) { - (*mutator)(&mut self.fragment) - } - - fn print_extra_flow_children(&self, print_tree: &mut PrintTree) { - print_tree.add_item(format!("↑↑ Fragment for block:{:?}", self.fragment)); - } -} - -impl fmt::Debug for BlockFlow { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "{:?}({:x}) {:?}", - self.class(), - self.base.debug_id(), - self.base - ) - } -} - -/// The inputs for the inline-sizes-and-margins constraint equation. -#[derive(Clone, Copy, Debug)] -pub struct ISizeConstraintInput { - pub computed_inline_size: MaybeAuto, - pub inline_start_margin: MaybeAuto, - pub inline_end_margin: MaybeAuto, - pub inline_start: MaybeAuto, - pub inline_end: MaybeAuto, - pub text_align: TextAlign, - pub available_inline_size: Au, -} - -impl ISizeConstraintInput { - pub fn new( - computed_inline_size: MaybeAuto, - inline_start_margin: MaybeAuto, - inline_end_margin: MaybeAuto, - inline_start: MaybeAuto, - inline_end: MaybeAuto, - text_align: TextAlign, - available_inline_size: Au, - ) -> ISizeConstraintInput { - ISizeConstraintInput { - computed_inline_size: computed_inline_size, - inline_start_margin: inline_start_margin, - inline_end_margin: inline_end_margin, - inline_start: inline_start, - inline_end: inline_end, - text_align: text_align, - available_inline_size: available_inline_size, - } - } -} - -/// The solutions for the inline-size-and-margins constraint equation. -#[derive(Clone, Copy, Debug)] -pub struct ISizeConstraintSolution { - pub inline_start: Au, - pub inline_size: Au, - pub margin_inline_start: Au, - pub margin_inline_end: Au, -} - -impl ISizeConstraintSolution { - pub fn new( - inline_size: Au, - margin_inline_start: Au, - margin_inline_end: Au, - ) -> ISizeConstraintSolution { - ISizeConstraintSolution { - inline_start: Au(0), - inline_size: inline_size, - margin_inline_start: margin_inline_start, - margin_inline_end: margin_inline_end, - } - } - - fn for_absolute_flow( - inline_start: Au, - inline_size: Au, - margin_inline_start: Au, - margin_inline_end: Au, - ) -> ISizeConstraintSolution { - ISizeConstraintSolution { - inline_start: inline_start, - inline_size: inline_size, - margin_inline_start: margin_inline_start, - margin_inline_end: margin_inline_end, - } - } -} - -// Trait to encapsulate the ISize and Margin calculation. -// -// CSS Section 10.3 -pub trait ISizeAndMarginsComputer { - /// Instructs the fragment to compute its border and padding. - fn compute_border_and_padding(&self, block: &mut BlockFlow, containing_block_inline_size: Au) { - block - .fragment - .compute_border_and_padding(containing_block_inline_size); - } - - /// Compute the inputs for the ISize constraint equation. - /// - /// This is called only once to compute the initial inputs. For calculations involving - /// minimum and maximum inline-size, we don't need to recompute these. - fn compute_inline_size_constraint_inputs( - &self, - block: &mut BlockFlow, - parent_flow_inline_size: Au, - shared_context: &SharedStyleContext, - ) -> ISizeConstraintInput { - let containing_block_inline_size = - self.containing_block_inline_size(block, parent_flow_inline_size, shared_context); - - block - .fragment - .compute_block_direction_margins(containing_block_inline_size); - block - .fragment - .compute_inline_direction_margins(containing_block_inline_size); - self.compute_border_and_padding(block, containing_block_inline_size); - - let mut computed_inline_size = - self.initial_computed_inline_size(block, parent_flow_inline_size, shared_context); - let style = block.fragment.style(); - match (computed_inline_size, style.get_position().box_sizing) { - (MaybeAuto::Specified(size), BoxSizing::BorderBox) => { - computed_inline_size = - MaybeAuto::Specified(size - block.fragment.border_padding.inline_start_end()) - }, - (MaybeAuto::Auto, BoxSizing::BorderBox) | (_, BoxSizing::ContentBox) => {}, - } - - let margin = style.logical_margin(); - let position = style.logical_position(); - - let available_inline_size = - containing_block_inline_size - block.fragment.border_padding.inline_start_end(); - ISizeConstraintInput::new( - computed_inline_size, - MaybeAuto::from_style(margin.inline_start, containing_block_inline_size), - MaybeAuto::from_style(margin.inline_end, containing_block_inline_size), - MaybeAuto::from_style(position.inline_start, containing_block_inline_size), - MaybeAuto::from_style(position.inline_end, containing_block_inline_size), - style.get_inherited_text().text_align, - available_inline_size, - ) - } - - /// Set the used values for inline-size and margins from the relevant constraint equation. - /// This is called only once. - /// - /// Set: - /// * Used values for content inline-size, inline-start margin, and inline-end margin for this - /// flow's box; - /// * Inline-start coordinate of this flow's box; - /// * Inline-start coordinate of the flow with respect to its containing block (if this is an - /// absolute flow). - fn set_inline_size_constraint_solutions( - &self, - block: &mut BlockFlow, - solution: ISizeConstraintSolution, - ) { - let inline_size; - let extra_inline_size_from_margin; - { - let block_mode = block.base.writing_mode; - - // FIXME (mbrubeck): Get correct containing block for positioned blocks? - let container_mode = block.base.block_container_writing_mode; - let container_size = block.base.block_container_inline_size; - - let fragment = block.fragment(); - fragment.margin.inline_start = solution.margin_inline_start; - fragment.margin.inline_end = solution.margin_inline_end; - - // The associated fragment has the border box of this flow. - inline_size = solution.inline_size + fragment.border_padding.inline_start_end(); - fragment.border_box.size.inline = inline_size; - - // Start border edge. - // FIXME (mbrubeck): Handle vertical writing modes. - fragment.border_box.start.i = - if container_mode.is_bidi_ltr() == block_mode.is_bidi_ltr() { - fragment.margin.inline_start - } else { - // The parent's "start" direction is the child's "end" direction. - container_size - inline_size - fragment.margin.inline_end - }; - - // To calculate the total size of this block, we also need to account for any - // additional size contribution from positive margins. Negative margins means the block - // isn't made larger at all by the margin. - extra_inline_size_from_margin = - max(Au(0), fragment.margin.inline_start) + max(Au(0), fragment.margin.inline_end); - } - - // We also resize the block itself, to ensure that overflow is not calculated - // as the inline-size of our parent. We might be smaller and we might be larger if we - // overflow. - block.mut_base().position.size.inline = inline_size + extra_inline_size_from_margin; - } - - /// Set the inline coordinate of the given flow if it is absolutely positioned. - fn set_inline_position_of_flow_if_necessary( - &self, - _: &mut BlockFlow, - _: ISizeConstraintSolution, - ) { - } - - /// Solve the inline-size and margins constraints for this block flow. - fn solve_inline_size_constraints( - &self, - block: &mut BlockFlow, - input: &ISizeConstraintInput, - ) -> ISizeConstraintSolution; - - fn initial_computed_inline_size( - &self, - block: &mut BlockFlow, - parent_flow_inline_size: Au, - shared_context: &SharedStyleContext, - ) -> MaybeAuto { - MaybeAuto::from_option( - block - .fragment() - .style() - .content_inline_size() - .to_used_value(self.containing_block_inline_size( - block, - parent_flow_inline_size, - shared_context, - )), - ) - } - - fn containing_block_inline_size( - &self, - _: &mut BlockFlow, - parent_flow_inline_size: Au, - _: &SharedStyleContext, - ) -> Au { - parent_flow_inline_size - } - - /// Compute the used value of inline-size, taking care of min-inline-size and max-inline-size. - /// - /// CSS Section 10.4: Minimum and Maximum inline-sizes - fn compute_used_inline_size( - &self, - block: &mut BlockFlow, - shared_context: &SharedStyleContext, - parent_flow_inline_size: Au, - ) { - let mut input = self.compute_inline_size_constraint_inputs( - block, - parent_flow_inline_size, - shared_context, - ); - - let containing_block_inline_size = - self.containing_block_inline_size(block, parent_flow_inline_size, shared_context); - - let mut solution = self.solve_inline_size_constraints(block, &input); - - // If the tentative used inline-size is greater than 'max-inline-size', inline-size should - // be recalculated, but this time using the computed value of 'max-inline-size' as the - // computed value for 'inline-size'. - match block - .fragment() - .style() - .max_inline_size() - .to_used_value(containing_block_inline_size) - { - Some(max_inline_size) if max_inline_size < solution.inline_size => { - input.computed_inline_size = MaybeAuto::Specified(max_inline_size); - solution = self.solve_inline_size_constraints(block, &input); - }, - _ => {}, - } - - // If the resulting inline-size is smaller than 'min-inline-size', inline-size should be - // recalculated, but this time using the value of 'min-inline-size' as the computed value - // for 'inline-size'. - let computed_min_inline_size = block - .fragment() - .style() - .min_inline_size() - .to_used_value(containing_block_inline_size) - .unwrap_or(Au(0)); - if computed_min_inline_size > solution.inline_size { - input.computed_inline_size = MaybeAuto::Specified(computed_min_inline_size); - solution = self.solve_inline_size_constraints(block, &input); - } - - self.set_inline_size_constraint_solutions(block, solution); - self.set_inline_position_of_flow_if_necessary(block, solution); - } - - /// Computes inline-start and inline-end margins and inline-size. - /// - /// This is used by both replaced and non-replaced Blocks. - /// - /// CSS 2.1 Section 10.3.3. - /// Constraint Equation: margin-inline-start + margin-inline-end + inline-size = - /// available_inline-size - /// where available_inline-size = CB inline-size - (horizontal border + padding) - fn solve_block_inline_size_constraints( - &self, - block: &mut BlockFlow, - input: &ISizeConstraintInput, - ) -> ISizeConstraintSolution { - let (computed_inline_size, inline_start_margin, inline_end_margin, available_inline_size) = ( - input.computed_inline_size, - input.inline_start_margin, - input.inline_end_margin, - input.available_inline_size, - ); - - // Check for direction of parent flow (NOT Containing Block) - let block_mode = block.base.writing_mode; - let container_mode = block.base.block_container_writing_mode; - let block_align = block.base.flags.text_align(); - - // FIXME (mbrubeck): Handle vertical writing modes. - let parent_has_same_direction = container_mode.is_bidi_ltr() == block_mode.is_bidi_ltr(); - - // If inline-size is not 'auto', and inline-size + margins > available_inline-size, all - // 'auto' margins are treated as 0. - let (inline_start_margin, inline_end_margin) = match computed_inline_size { - MaybeAuto::Auto => (inline_start_margin, inline_end_margin), - MaybeAuto::Specified(inline_size) => { - let inline_start = inline_start_margin.specified_or_zero(); - let inline_end = inline_end_margin.specified_or_zero(); - - if (inline_start + inline_end + inline_size) > available_inline_size { - ( - MaybeAuto::Specified(inline_start), - MaybeAuto::Specified(inline_end), - ) - } else { - (inline_start_margin, inline_end_margin) - } - }, - }; - - // Invariant: inline-start_margin + inline-size + inline-end_margin == - // available_inline-size - let (inline_start_margin, inline_size, inline_end_margin) = - match (inline_start_margin, computed_inline_size, inline_end_margin) { - // If all have a computed value other than 'auto', the system is over-constrained. - ( - MaybeAuto::Specified(margin_start), - MaybeAuto::Specified(inline_size), - MaybeAuto::Specified(margin_end), - ) => { - // servo_left, servo_right, and servo_center are used to implement - // the "align descendants" rule in HTML5 § 14.2. - if block_align == TextAlign::ServoCenter { - // Ignore any existing margins, and make the inline-start and - // inline-end margins equal. - let margin = (available_inline_size - inline_size).scale_by(0.5); - (margin, inline_size, margin) - } else { - let ignore_end_margin = match block_align { - TextAlign::ServoLeft => block_mode.is_bidi_ltr(), - TextAlign::ServoRight => !block_mode.is_bidi_ltr(), - _ => parent_has_same_direction, - }; - if ignore_end_margin { - ( - margin_start, - inline_size, - available_inline_size - (margin_start + inline_size), - ) - } else { - ( - available_inline_size - (margin_end + inline_size), - inline_size, - margin_end, - ) - } - } - }, - // If exactly one value is 'auto', solve for it - ( - MaybeAuto::Auto, - MaybeAuto::Specified(inline_size), - MaybeAuto::Specified(margin_end), - ) => ( - available_inline_size - (inline_size + margin_end), - inline_size, - margin_end, - ), - ( - MaybeAuto::Specified(margin_start), - MaybeAuto::Auto, - MaybeAuto::Specified(margin_end), - ) => ( - margin_start, - available_inline_size - (margin_start + margin_end), - margin_end, - ), - ( - MaybeAuto::Specified(margin_start), - MaybeAuto::Specified(inline_size), - MaybeAuto::Auto, - ) => ( - margin_start, - inline_size, - available_inline_size - (margin_start + inline_size), - ), - - // If inline-size is set to 'auto', any other 'auto' value becomes '0', - // and inline-size is solved for - (MaybeAuto::Auto, MaybeAuto::Auto, MaybeAuto::Specified(margin_end)) => { - (Au(0), available_inline_size - margin_end, margin_end) - }, - (MaybeAuto::Specified(margin_start), MaybeAuto::Auto, MaybeAuto::Auto) => { - (margin_start, available_inline_size - margin_start, Au(0)) - }, - (MaybeAuto::Auto, MaybeAuto::Auto, MaybeAuto::Auto) => { - (Au(0), available_inline_size, Au(0)) - }, - - // If inline-start and inline-end margins are auto, they become equal - (MaybeAuto::Auto, MaybeAuto::Specified(inline_size), MaybeAuto::Auto) => { - let margin = (available_inline_size - inline_size).scale_by(0.5); - (margin, inline_size, margin) - }, - }; - - ISizeConstraintSolution::new(inline_size, inline_start_margin, inline_end_margin) - } -} - -/// The different types of Blocks. -/// -/// They mainly differ in the way inline-size and block-sizes and margins are calculated -/// for them. -pub struct AbsoluteNonReplaced; -pub struct AbsoluteReplaced; -pub struct BlockNonReplaced; -pub struct BlockReplaced; -pub struct FloatNonReplaced; -pub struct FloatReplaced; -pub struct InlineBlockNonReplaced; -pub struct InlineBlockReplaced; -pub struct InlineFlexItem; - -impl ISizeAndMarginsComputer for AbsoluteNonReplaced { - /// Solve the horizontal constraint equation for absolute non-replaced elements. - /// - /// CSS Section 10.3.7 - /// Constraint equation: - /// inline-start + inline-end + inline-size + margin-inline-start + margin-inline-end - /// = absolute containing block inline-size - (horizontal padding and border) - /// [aka available inline-size] - /// - /// Return the solution for the equation. - fn solve_inline_size_constraints( - &self, - block: &mut BlockFlow, - input: &ISizeConstraintInput, - ) -> ISizeConstraintSolution { - let &ISizeConstraintInput { - computed_inline_size, - inline_start_margin, - inline_end_margin, - inline_start, - inline_end, - available_inline_size, - .. - } = input; - - // Check for direction of parent flow (NOT Containing Block) - let block_mode = block.base.writing_mode; - let container_mode = block.base.block_container_writing_mode; - - // FIXME (mbrubeck): Handle vertical writing modes. - let parent_has_same_direction = container_mode.is_bidi_ltr() == block_mode.is_bidi_ltr(); - - let (inline_start, inline_size, margin_inline_start, margin_inline_end) = - match (inline_start, inline_end, computed_inline_size) { - (MaybeAuto::Auto, MaybeAuto::Auto, MaybeAuto::Auto) => { - let margin_start = inline_start_margin.specified_or_zero(); - let margin_end = inline_end_margin.specified_or_zero(); - // Now it is the same situation as inline-start Specified and inline-end - // and inline-size Auto. - - // Set inline-end to zero to calculate inline-size. - let inline_size = block.get_shrink_to_fit_inline_size( - available_inline_size - (margin_start + margin_end), - ); - (Au(0), inline_size, margin_start, margin_end) - }, - ( - MaybeAuto::Specified(inline_start), - MaybeAuto::Specified(inline_end), - MaybeAuto::Specified(inline_size), - ) => { - match (inline_start_margin, inline_end_margin) { - (MaybeAuto::Auto, MaybeAuto::Auto) => { - let total_margin_val = - available_inline_size - inline_start - inline_end - inline_size; - if total_margin_val < Au(0) { - if parent_has_same_direction { - // margin-inline-start becomes 0 - (inline_start, inline_size, Au(0), total_margin_val) - } else { - // margin-inline-end becomes 0, because it's toward the parent's - // inline-start edge. - (inline_start, inline_size, total_margin_val, Au(0)) - } - } else { - // Equal margins - ( - inline_start, - inline_size, - total_margin_val.scale_by(0.5), - total_margin_val.scale_by(0.5), - ) - } - }, - (MaybeAuto::Specified(margin_start), MaybeAuto::Auto) => { - let sum = inline_start + inline_end + inline_size + margin_start; - ( - inline_start, - inline_size, - margin_start, - available_inline_size - sum, - ) - }, - (MaybeAuto::Auto, MaybeAuto::Specified(margin_end)) => { - let sum = inline_start + inline_end + inline_size + margin_end; - ( - inline_start, - inline_size, - available_inline_size - sum, - margin_end, - ) - }, - (MaybeAuto::Specified(margin_start), MaybeAuto::Specified(margin_end)) => { - // Values are over-constrained. - let sum = inline_start + inline_size + margin_start + margin_end; - if parent_has_same_direction { - // Ignore value for 'inline-end' - (inline_start, inline_size, margin_start, margin_end) - } else { - // Ignore value for 'inline-start' - ( - available_inline_size - sum, - inline_size, - margin_start, - margin_end, - ) - } - }, - } - }, - // For the rest of the cases, auto values for margin are set to 0 - - // If only one is Auto, solve for it - ( - MaybeAuto::Auto, - MaybeAuto::Specified(inline_end), - MaybeAuto::Specified(inline_size), - ) => { - let margin_start = inline_start_margin.specified_or_zero(); - let margin_end = inline_end_margin.specified_or_zero(); - let sum = inline_end + inline_size + margin_start + margin_end; - ( - available_inline_size - sum, - inline_size, - margin_start, - margin_end, - ) - }, - ( - MaybeAuto::Specified(inline_start), - MaybeAuto::Auto, - MaybeAuto::Specified(inline_size), - ) => { - let margin_start = inline_start_margin.specified_or_zero(); - let margin_end = inline_end_margin.specified_or_zero(); - (inline_start, inline_size, margin_start, margin_end) - }, - ( - MaybeAuto::Specified(inline_start), - MaybeAuto::Specified(inline_end), - MaybeAuto::Auto, - ) => { - let margin_start = inline_start_margin.specified_or_zero(); - let margin_end = inline_end_margin.specified_or_zero(); - let sum = inline_start + inline_end + margin_start + margin_end; - ( - inline_start, - available_inline_size - sum, - margin_start, - margin_end, - ) - }, - - // If inline-size is auto, then inline-size is shrink-to-fit. Solve for the - // non-auto value. - (MaybeAuto::Specified(inline_start), MaybeAuto::Auto, MaybeAuto::Auto) => { - let margin_start = inline_start_margin.specified_or_zero(); - let margin_end = inline_end_margin.specified_or_zero(); - // Set inline-end to zero to calculate inline-size - let inline_size = block.get_shrink_to_fit_inline_size( - available_inline_size - (margin_start + margin_end), - ); - (inline_start, inline_size, margin_start, margin_end) - }, - (MaybeAuto::Auto, MaybeAuto::Specified(inline_end), MaybeAuto::Auto) => { - let margin_start = inline_start_margin.specified_or_zero(); - let margin_end = inline_end_margin.specified_or_zero(); - // Set inline-start to zero to calculate inline-size - let inline_size = block.get_shrink_to_fit_inline_size( - available_inline_size - (margin_start + margin_end), - ); - let sum = inline_end + inline_size + margin_start + margin_end; - ( - available_inline_size - sum, - inline_size, - margin_start, - margin_end, - ) - }, - - (MaybeAuto::Auto, MaybeAuto::Auto, MaybeAuto::Specified(inline_size)) => { - let margin_start = inline_start_margin.specified_or_zero(); - let margin_end = inline_end_margin.specified_or_zero(); - // Setting 'inline-start' to static position because direction is 'ltr'. - // TODO: Handle 'rtl' when it is implemented. - (Au(0), inline_size, margin_start, margin_end) - }, - }; - ISizeConstraintSolution::for_absolute_flow( - inline_start, - inline_size, - margin_inline_start, - margin_inline_end, - ) - } - - fn containing_block_inline_size( - &self, - block: &mut BlockFlow, - _: Au, - shared_context: &SharedStyleContext, - ) -> Au { - let opaque_block = OpaqueFlow::from_flow(block); - block - .containing_block_size(&shared_context.viewport_size(), opaque_block) - .inline - } - - fn set_inline_position_of_flow_if_necessary( - &self, - block: &mut BlockFlow, - solution: ISizeConstraintSolution, - ) { - // Set the inline position of the absolute flow wrt to its containing block. - if !block - .base - .flags - .contains(FlowFlags::INLINE_POSITION_IS_STATIC) - { - block.base.position.start.i = solution.inline_start; - } - } -} - -impl ISizeAndMarginsComputer for AbsoluteReplaced { - /// Solve the horizontal constraint equation for absolute replaced elements. - /// - /// CSS Section 10.3.8 - /// Constraint equation: - /// inline-start + inline-end + inline-size + margin-inline-start + margin-inline-end - /// = absolute containing block inline-size - (horizontal padding and border) - /// [aka available_inline-size] - /// - /// Return the solution for the equation. - fn solve_inline_size_constraints( - &self, - _: &mut BlockFlow, - input: &ISizeConstraintInput, - ) -> ISizeConstraintSolution { - let &ISizeConstraintInput { - computed_inline_size, - inline_start_margin, - inline_end_margin, - inline_start, - inline_end, - available_inline_size, - .. - } = input; - // TODO: Check for direction of static-position Containing Block (aka - // parent flow, _not_ the actual Containing Block) when right-to-left - // is implemented - // Assume direction is 'ltr' for now - // TODO: Handle all the cases for 'rtl' direction. - - let inline_size = match computed_inline_size { - MaybeAuto::Specified(w) => w, - _ => panic!( - "{} {}", - "The used value for inline_size for absolute replaced flow", - "should have already been calculated by now." - ), - }; - - let (inline_start, inline_size, margin_inline_start, margin_inline_end) = - match (inline_start, inline_end) { - (MaybeAuto::Auto, MaybeAuto::Auto) => { - let margin_start = inline_start_margin.specified_or_zero(); - let margin_end = inline_end_margin.specified_or_zero(); - (Au(0), inline_size, margin_start, margin_end) - }, - // If only one is Auto, solve for it - (MaybeAuto::Auto, MaybeAuto::Specified(inline_end)) => { - let margin_start = inline_start_margin.specified_or_zero(); - let margin_end = inline_end_margin.specified_or_zero(); - let sum = inline_end + inline_size + margin_start + margin_end; - ( - available_inline_size - sum, - inline_size, - margin_start, - margin_end, - ) - }, - (MaybeAuto::Specified(inline_start), MaybeAuto::Auto) => { - let margin_start = inline_start_margin.specified_or_zero(); - let margin_end = inline_end_margin.specified_or_zero(); - (inline_start, inline_size, margin_start, margin_end) - }, - (MaybeAuto::Specified(inline_start), MaybeAuto::Specified(inline_end)) => { - match (inline_start_margin, inline_end_margin) { - (MaybeAuto::Auto, MaybeAuto::Auto) => { - let total_margin_val = - available_inline_size - inline_start - inline_end - inline_size; - if total_margin_val < Au(0) { - // margin-inline-start becomes 0 because direction is 'ltr'. - (inline_start, inline_size, Au(0), total_margin_val) - } else { - // Equal margins - ( - inline_start, - inline_size, - total_margin_val.scale_by(0.5), - total_margin_val.scale_by(0.5), - ) - } - }, - (MaybeAuto::Specified(margin_start), MaybeAuto::Auto) => { - let sum = inline_start + inline_end + inline_size + margin_start; - ( - inline_start, - inline_size, - margin_start, - available_inline_size - sum, - ) - }, - (MaybeAuto::Auto, MaybeAuto::Specified(margin_end)) => { - let sum = inline_start + inline_end + inline_size + margin_end; - ( - inline_start, - inline_size, - available_inline_size - sum, - margin_end, - ) - }, - (MaybeAuto::Specified(margin_start), MaybeAuto::Specified(margin_end)) => { - // Values are over-constrained. - // Ignore value for 'inline-end' cos direction is 'ltr'. - (inline_start, inline_size, margin_start, margin_end) - }, - } - }, - }; - ISizeConstraintSolution::for_absolute_flow( - inline_start, - inline_size, - margin_inline_start, - margin_inline_end, - ) - } - - /// Calculate used value of inline-size just like we do for inline replaced elements. - fn initial_computed_inline_size( - &self, - block: &mut BlockFlow, - _: Au, - shared_context: &SharedStyleContext, - ) -> MaybeAuto { - let opaque_block = OpaqueFlow::from_flow(block); - let containing_block_inline_size = block - .containing_block_size(&shared_context.viewport_size(), opaque_block) - .inline; - let container_block_size = block.explicit_block_containing_size(shared_context); - let fragment = block.fragment(); - fragment.assign_replaced_inline_size_if_necessary( - containing_block_inline_size, - container_block_size, - ); - // For replaced absolute flow, the rest of the constraint solving will - // take inline-size to be specified as the value computed here. - MaybeAuto::Specified(fragment.content_box().size.inline) - } - - fn containing_block_inline_size( - &self, - block: &mut BlockFlow, - _: Au, - shared_context: &SharedStyleContext, - ) -> Au { - let opaque_block = OpaqueFlow::from_flow(block); - block - .containing_block_size(&shared_context.viewport_size(), opaque_block) - .inline - } - - fn set_inline_position_of_flow_if_necessary( - &self, - block: &mut BlockFlow, - solution: ISizeConstraintSolution, - ) { - // Set the x-coordinate of the absolute flow wrt to its containing block. - block.base.position.start.i = solution.inline_start; - } -} - -impl ISizeAndMarginsComputer for BlockNonReplaced { - /// Compute inline-start and inline-end margins and inline-size. - fn solve_inline_size_constraints( - &self, - block: &mut BlockFlow, - input: &ISizeConstraintInput, - ) -> ISizeConstraintSolution { - self.solve_block_inline_size_constraints(block, input) - } -} - -impl ISizeAndMarginsComputer for BlockReplaced { - /// Compute inline-start and inline-end margins and inline-size. - /// - /// ISize has already been calculated. We now calculate the margins just - /// like for non-replaced blocks. - fn solve_inline_size_constraints( - &self, - block: &mut BlockFlow, - input: &ISizeConstraintInput, - ) -> ISizeConstraintSolution { - match input.computed_inline_size { - MaybeAuto::Specified(_) => {}, - MaybeAuto::Auto => { - panic!("BlockReplaced: inline_size should have been computed by now") - }, - }; - self.solve_block_inline_size_constraints(block, input) - } - - /// Calculate used value of inline-size just like we do for inline replaced elements. - fn initial_computed_inline_size( - &self, - block: &mut BlockFlow, - parent_flow_inline_size: Au, - shared_context: &SharedStyleContext, - ) -> MaybeAuto { - let container_block_size = block.explicit_block_containing_size(shared_context); - let fragment = block.fragment(); - fragment.assign_replaced_inline_size_if_necessary( - parent_flow_inline_size, - container_block_size, - ); - // For replaced block flow, the rest of the constraint solving will - // take inline-size to be specified as the value computed here. - MaybeAuto::Specified(fragment.content_box().size.inline) - } -} - -impl ISizeAndMarginsComputer for FloatNonReplaced { - /// CSS Section 10.3.5 - /// - /// If inline-size is computed as 'auto', the used value is the 'shrink-to-fit' inline-size. - fn solve_inline_size_constraints( - &self, - block: &mut BlockFlow, - input: &ISizeConstraintInput, - ) -> ISizeConstraintSolution { - let (computed_inline_size, inline_start_margin, inline_end_margin, available_inline_size) = ( - input.computed_inline_size, - input.inline_start_margin, - input.inline_end_margin, - input.available_inline_size, - ); - let margin_inline_start = inline_start_margin.specified_or_zero(); - let margin_inline_end = inline_end_margin.specified_or_zero(); - let available_inline_size_float = - available_inline_size - margin_inline_start - margin_inline_end; - let shrink_to_fit = block.get_shrink_to_fit_inline_size(available_inline_size_float); - let inline_size = computed_inline_size.specified_or_default(shrink_to_fit); - debug!( - "assign_inline_sizes_float -- inline_size: {:?}", - inline_size - ); - ISizeConstraintSolution::new(inline_size, margin_inline_start, margin_inline_end) - } -} - -impl ISizeAndMarginsComputer for FloatReplaced { - /// CSS Section 10.3.5 - /// - /// If inline-size is computed as 'auto', the used value is the 'shrink-to-fit' inline-size. - fn solve_inline_size_constraints( - &self, - _: &mut BlockFlow, - input: &ISizeConstraintInput, - ) -> ISizeConstraintSolution { - let (computed_inline_size, inline_start_margin, inline_end_margin) = ( - input.computed_inline_size, - input.inline_start_margin, - input.inline_end_margin, - ); - let margin_inline_start = inline_start_margin.specified_or_zero(); - let margin_inline_end = inline_end_margin.specified_or_zero(); - let inline_size = match computed_inline_size { - MaybeAuto::Specified(w) => w, - MaybeAuto::Auto => { - panic!("FloatReplaced: inline_size should have been computed by now") - }, - }; - debug!( - "assign_inline_sizes_float -- inline_size: {:?}", - inline_size - ); - ISizeConstraintSolution::new(inline_size, margin_inline_start, margin_inline_end) - } - - /// Calculate used value of inline-size just like we do for inline replaced elements. - fn initial_computed_inline_size( - &self, - block: &mut BlockFlow, - parent_flow_inline_size: Au, - shared_context: &SharedStyleContext, - ) -> MaybeAuto { - let container_block_size = block.explicit_block_containing_size(shared_context); - let fragment = block.fragment(); - fragment.assign_replaced_inline_size_if_necessary( - parent_flow_inline_size, - container_block_size, - ); - // For replaced block flow, the rest of the constraint solving will - // take inline-size to be specified as the value computed here. - MaybeAuto::Specified(fragment.content_box().size.inline) - } -} - -impl ISizeAndMarginsComputer for InlineBlockNonReplaced { - /// Compute inline-start and inline-end margins and inline-size. - fn solve_inline_size_constraints( - &self, - block: &mut BlockFlow, - input: &ISizeConstraintInput, - ) -> ISizeConstraintSolution { - let (computed_inline_size, inline_start_margin, inline_end_margin, available_inline_size) = ( - input.computed_inline_size, - input.inline_start_margin, - input.inline_end_margin, - input.available_inline_size, - ); - - // For inline-blocks, `auto` margins compute to 0. - let inline_start_margin = inline_start_margin.specified_or_zero(); - let inline_end_margin = inline_end_margin.specified_or_zero(); - - // If inline-size is set to 'auto', and this is an inline block, use the - // shrink to fit algorithm (see CSS 2.1 § 10.3.9) - let inline_size = match computed_inline_size { - MaybeAuto::Auto => block.get_shrink_to_fit_inline_size( - available_inline_size - (inline_start_margin + inline_end_margin), - ), - MaybeAuto::Specified(inline_size) => inline_size, - }; - - ISizeConstraintSolution::new(inline_size, inline_start_margin, inline_end_margin) - } -} - -impl ISizeAndMarginsComputer for InlineBlockReplaced { - /// Compute inline-start and inline-end margins and inline-size. - /// - /// ISize has already been calculated. We now calculate the margins just - /// like for non-replaced blocks. - fn solve_inline_size_constraints( - &self, - block: &mut BlockFlow, - input: &ISizeConstraintInput, - ) -> ISizeConstraintSolution { - debug_assert!(match input.computed_inline_size { - MaybeAuto::Specified(_) => true, - MaybeAuto::Auto => false, - }); - - let (computed_inline_size, inline_start_margin, inline_end_margin, available_inline_size) = ( - input.computed_inline_size, - input.inline_start_margin, - input.inline_end_margin, - input.available_inline_size, - ); - - // For inline-blocks, `auto` margins compute to 0. - let inline_start_margin = inline_start_margin.specified_or_zero(); - let inline_end_margin = inline_end_margin.specified_or_zero(); - - // If inline-size is set to 'auto', and this is an inline block, use the - // shrink to fit algorithm (see CSS 2.1 § 10.3.9) - let inline_size = match computed_inline_size { - MaybeAuto::Auto => block.get_shrink_to_fit_inline_size( - available_inline_size - (inline_start_margin + inline_end_margin), - ), - MaybeAuto::Specified(inline_size) => inline_size, - }; - - ISizeConstraintSolution::new(inline_size, inline_start_margin, inline_end_margin) - } - - /// Calculate used value of inline-size just like we do for inline replaced elements. - fn initial_computed_inline_size( - &self, - block: &mut BlockFlow, - parent_flow_inline_size: Au, - shared_context: &SharedStyleContext, - ) -> MaybeAuto { - let container_block_size = block.explicit_block_containing_size(shared_context); - let fragment = block.fragment(); - fragment.assign_replaced_inline_size_if_necessary( - parent_flow_inline_size, - container_block_size, - ); - // For replaced block flow, the rest of the constraint solving will - // take inline-size to be specified as the value computed here. - MaybeAuto::Specified(fragment.content_box().size.inline) - } -} - -impl ISizeAndMarginsComputer for InlineFlexItem { - // Replace the default method directly to prevent recalculating and setting margins again - // which has already been set by its parent. - fn compute_used_inline_size( - &self, - block: &mut BlockFlow, - shared_context: &SharedStyleContext, - parent_flow_inline_size: Au, - ) { - let container_block_size = block.explicit_block_containing_size(shared_context); - block.fragment.assign_replaced_inline_size_if_necessary( - parent_flow_inline_size, - container_block_size, - ); - } - - // The used inline size and margins are set by parent flex flow, do nothing here. - fn solve_inline_size_constraints( - &self, - block: &mut BlockFlow, - _: &ISizeConstraintInput, - ) -> ISizeConstraintSolution { - let fragment = block.fragment(); - ISizeConstraintSolution::new( - fragment.border_box.size.inline, - fragment.margin.inline_start, - fragment.margin.inline_end, - ) - } -} diff --git a/components/layout_2020/construct.rs b/components/layout_2020/construct.rs 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<ComputedValues>, - RestyleDamage, - ), - /// TableColumn Fragment - TableColumnFragment(Fragment), -} - -/// Represents inline fragments and {ib} splits that are bubbling up from an inline. -#[derive(Clone)] -pub struct InlineFragmentsConstructionResult { - /// Any {ib} splits that we're bubbling up. - pub splits: LinkedList<InlineBlockSplit>, - - /// Any fragments that succeed the {ib} splits. - pub fragments: IntermediateInlineFragments, -} - -/// Represents an {ib} split that has not yet found the containing block that it belongs to. This -/// is somewhat tricky. An example may be helpful. For this DOM fragment: -/// -/// ```html -/// <span> -/// A -/// <div>B</div> -/// C -/// </span> -/// ``` -/// -/// The resulting `ConstructionItem` for the outer `span` will be: -/// -/// ```rust,ignore -/// ConstructionItem::InlineFragments( -/// InlineFragmentsConstructionResult { -/// splits: linked_list![ -/// InlineBlockSplit { -/// predecessors: IntermediateInlineFragments { -/// fragments: linked_list![A], -/// absolute_descendents: AbsoluteDescendents { -/// descendant_links: vec![] -/// }, -/// }, -/// flow: B, -/// } -/// ], -/// fragments: linked_list![C], -/// }, -/// ) -/// ``` -#[derive(Clone)] -pub struct InlineBlockSplit { - /// The inline fragments that precede the flow. - pub predecessors: IntermediateInlineFragments, - - /// The flow that caused this {ib} split. - pub flow: FlowRef, -} - -impl InlineBlockSplit { - /// Flushes the given accumulator to the new split and makes a new accumulator to hold any - /// subsequent fragments. - fn new<ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode>( - fragment_accumulator: &mut InlineFragmentsAccumulator, - node: &ConcreteThreadSafeLayoutNode, - style_context: &SharedStyleContext, - flow: FlowRef, - ) -> InlineBlockSplit { - fragment_accumulator.enclosing_node.as_mut().expect( - "enclosing_node is None; Are {ib} splits being generated outside of an inline node?" - ).flags.remove(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT); - - let split = InlineBlockSplit { - predecessors: mem::replace( - fragment_accumulator, - InlineFragmentsAccumulator::from_inline_node(node, style_context), - ) - .to_intermediate_inline_fragments::<ConcreteThreadSafeLayoutNode>(style_context), - flow: flow, - }; - - fragment_accumulator - .enclosing_node - .as_mut() - .unwrap() - .flags - .remove(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT); - - split - } -} - -/// Holds inline fragments and absolute descendants. -#[derive(Clone)] -pub struct IntermediateInlineFragments { - /// The list of fragments. - pub fragments: LinkedList<Fragment>, - - /// The list of absolute descendants of those inline fragments. - pub absolute_descendants: AbsoluteDescendants, -} - -impl IntermediateInlineFragments { - fn new() -> IntermediateInlineFragments { - IntermediateInlineFragments { - fragments: LinkedList::new(), - absolute_descendants: AbsoluteDescendants::new(), - } - } - - fn is_empty(&self) -> bool { - self.fragments.is_empty() && self.absolute_descendants.is_empty() - } - - fn push_all(&mut self, mut other: IntermediateInlineFragments) { - self.fragments.append(&mut other.fragments); - self.absolute_descendants - .push_descendants(other.absolute_descendants); - } -} - -/// Holds inline fragments that we're gathering for children of an inline node. -struct InlineFragmentsAccumulator { - /// The list of fragments. - fragments: IntermediateInlineFragments, - - /// Information about the inline box directly enclosing the fragments being gathered, if any. - /// - /// `inline::InlineFragmentNodeInfo` also stores flags indicating whether a fragment is the - /// first and/or last of the corresponding inline box. This `InlineFragmentsAccumulator` may - /// represent only one side of an {ib} split, so we store these flags as if it represented only - /// one fragment. `to_intermediate_inline_fragments` later splits this hypothetical fragment - /// into pieces, leaving the `FIRST_FRAGMENT_OF_ELEMENT` and `LAST_FRAGMENT_OF_ELEMENT` flags, - /// if present, on the first and last fragments of the output. - enclosing_node: Option<InlineFragmentNodeInfo>, - - /// Restyle damage to use for fragments created in this node. - restyle_damage: RestyleDamage, - - /// Bidi control characters to insert before and after these fragments. - bidi_control_chars: Option<(&'static str, &'static str)>, -} - -impl InlineFragmentsAccumulator { - fn new() -> InlineFragmentsAccumulator { - InlineFragmentsAccumulator { - fragments: IntermediateInlineFragments::new(), - enclosing_node: None, - bidi_control_chars: None, - restyle_damage: RestyleDamage::empty(), - } - } - - fn from_inline_node<N>( - node: &N, - style_context: &SharedStyleContext, - ) -> InlineFragmentsAccumulator - where - N: ThreadSafeLayoutNode, - { - InlineFragmentsAccumulator { - fragments: IntermediateInlineFragments::new(), - enclosing_node: Some(InlineFragmentNodeInfo { - address: node.opaque(), - pseudo: node.get_pseudo_element_type(), - style: node.style(style_context), - selected_style: node.selected_style(), - flags: InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT | - InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT, - }), - bidi_control_chars: None, - restyle_damage: node.restyle_damage(), - } - } - - fn push(&mut self, fragment: Fragment) { - self.fragments.fragments.push_back(fragment) - } - - fn push_all(&mut self, mut fragments: IntermediateInlineFragments) { - self.fragments.fragments.append(&mut fragments.fragments); - self.fragments - .absolute_descendants - .push_descendants(fragments.absolute_descendants); - } - - fn to_intermediate_inline_fragments<N>( - self, - context: &SharedStyleContext, - ) -> IntermediateInlineFragments - where - N: ThreadSafeLayoutNode, - { - let InlineFragmentsAccumulator { - mut fragments, - enclosing_node, - bidi_control_chars, - restyle_damage, - } = self; - if let Some(mut enclosing_node) = enclosing_node { - let fragment_count = fragments.fragments.len(); - for (index, fragment) in fragments.fragments.iter_mut().enumerate() { - let mut enclosing_node = enclosing_node.clone(); - if index != 0 { - enclosing_node - .flags - .remove(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT) - } - if index != fragment_count - 1 { - enclosing_node - .flags - .remove(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT) - } - fragment.add_inline_context_style(enclosing_node); - } - - // Control characters are later discarded in transform_text, so they don't affect the - // is_first/is_last styles above. - enclosing_node.flags.remove( - InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT | - InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT, - ); - - if let Some((start, end)) = bidi_control_chars { - fragments - .fragments - .push_front(control_chars_to_fragment::<N::ConcreteElement>( - &enclosing_node, - context, - start, - restyle_damage, - )); - fragments - .fragments - .push_back(control_chars_to_fragment::<N::ConcreteElement>( - &enclosing_node, - context, - end, - restyle_damage, - )); - } - } - fragments - } -} - -/// An object that knows how to create flows. -pub struct FlowConstructor<'a, N: ThreadSafeLayoutNode> { - /// The layout context. - pub layout_context: &'a LayoutContext<'a>, - /// Satisfy the compiler about the unused parameters, which we use to improve the ergonomics of - /// the ensuing impl {} by removing the need to parameterize all the methods individually. - phantom2: PhantomData<N>, -} - -impl<'a, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode> - FlowConstructor<'a, ConcreteThreadSafeLayoutNode> -{ - /// Creates a new flow constructor. - pub fn new(layout_context: &'a LayoutContext<'a>) -> Self { - FlowConstructor { - layout_context: layout_context, - phantom2: PhantomData, - } - } - - #[inline] - fn style_context(&self) -> &SharedStyleContext { - self.layout_context.shared_context() - } - - #[inline] - fn set_flow_construction_result( - &self, - node: &ConcreteThreadSafeLayoutNode, - result: ConstructionResult, - ) { - node.set_flow_construction_result(result); - } - - /// Builds the fragment for the given block or subclass thereof. - fn build_fragment_for_block(&self, node: &ConcreteThreadSafeLayoutNode) -> Fragment { - let specific_fragment_info = match node.type_id() { - Some(LayoutNodeType::Element(LayoutElementType::HTMLIFrameElement)) => { - SpecificFragmentInfo::Iframe(IframeFragmentInfo::new(node)) - }, - Some(LayoutNodeType::Element(LayoutElementType::HTMLImageElement)) => { - let image_info = Box::new(ImageFragmentInfo::new( - node.image_url(), - node.image_density(), - node, - &self.layout_context, - )); - SpecificFragmentInfo::Image(image_info) - }, - Some(LayoutNodeType::Element(LayoutElementType::HTMLMediaElement)) => { - let data = node.media_data().unwrap(); - SpecificFragmentInfo::Media(Box::new(MediaFragmentInfo::new(data))) - }, - Some(LayoutNodeType::Element(LayoutElementType::HTMLObjectElement)) => { - let elem = node.as_element().unwrap(); - let type_and_data = ( - elem.get_attr(&ns!(), &local_name!("type")), - elem.get_attr(&ns!(), &local_name!("data")), - ); - let object_data = match type_and_data { - (None, Some(uri)) if is_image_data(uri) => ServoUrl::parse(uri).ok(), - _ => None, - }; - let image_info = Box::new(ImageFragmentInfo::new( - object_data, - None, - node, - &self.layout_context, - )); - SpecificFragmentInfo::Image(image_info) - }, - Some(LayoutNodeType::Element(LayoutElementType::HTMLTableElement)) => { - SpecificFragmentInfo::TableWrapper - }, - Some(LayoutNodeType::Element(LayoutElementType::HTMLTableColElement)) => { - SpecificFragmentInfo::TableColumn(TableColumnFragmentInfo::new(node)) - }, - Some(LayoutNodeType::Element(LayoutElementType::HTMLTableCellElement)) => { - SpecificFragmentInfo::TableCell - }, - Some(LayoutNodeType::Element(LayoutElementType::HTMLTableRowElement)) | - Some(LayoutNodeType::Element(LayoutElementType::HTMLTableSectionElement)) => { - SpecificFragmentInfo::TableRow - }, - Some(LayoutNodeType::Element(LayoutElementType::HTMLCanvasElement)) => { - let data = node.canvas_data().unwrap(); - SpecificFragmentInfo::Canvas(Box::new(CanvasFragmentInfo::new(data))) - }, - Some(LayoutNodeType::Element(LayoutElementType::SVGSVGElement)) => { - let data = node.svg_data().unwrap(); - SpecificFragmentInfo::Svg(Box::new(SvgFragmentInfo::new(data))) - }, - _ => { - // This includes pseudo-elements. - SpecificFragmentInfo::Generic - }, - }; - - Fragment::new(node, specific_fragment_info, self.layout_context) - } - - /// Creates an inline flow from a set of inline fragments, then adds it as a child of the given - /// flow or pushes it onto the given flow list. - /// - /// `#[inline(always)]` because this is performance critical and LLVM will not inline it - /// otherwise. - #[inline(always)] - fn flush_inline_fragments_to_flow( - &mut self, - fragment_accumulator: InlineFragmentsAccumulator, - flow: &mut FlowRef, - absolute_descendants: &mut AbsoluteDescendants, - legalizer: &mut Legalizer, - node: &ConcreteThreadSafeLayoutNode, - ) { - let mut fragments = fragment_accumulator - .to_intermediate_inline_fragments::<ConcreteThreadSafeLayoutNode>(self.style_context()); - if fragments.is_empty() { - return; - }; - - strip_ignorable_whitespace_from_start(&mut fragments.fragments); - strip_ignorable_whitespace_from_end(&mut fragments.fragments); - if fragments.fragments.is_empty() { - absolute_descendants.push_descendants(fragments.absolute_descendants); - return; - } - - // Build a list of all the inline-block fragments before fragments is moved. - let mut inline_block_flows = vec![]; - for fragment in &fragments.fragments { - match fragment.specific { - SpecificFragmentInfo::InlineBlock(ref info) => { - inline_block_flows.push(info.flow_ref.clone()) - }, - SpecificFragmentInfo::InlineAbsoluteHypothetical(ref info) => { - inline_block_flows.push(info.flow_ref.clone()) - }, - SpecificFragmentInfo::InlineAbsolute(ref info) => { - inline_block_flows.push(info.flow_ref.clone()) - }, - _ => {}, - } - } - - // We must scan for runs before computing minimum ascent and descent because scanning - // for runs might collapse so much whitespace away that only hypothetical fragments - // remain. In that case the inline flow will compute its ascent and descent to be zero. - let scanned_fragments = - with_thread_local_font_context(self.layout_context, |font_context| { - TextRunScanner::new().scan_for_runs( - font_context, - mem::replace(&mut fragments.fragments, LinkedList::new()), - ) - }); - let mut inline_flow_ref = FlowRef::new(Arc::new(InlineFlow::from_fragments( - scanned_fragments, - node.style(self.style_context()).writing_mode, - ))); - - // Add all the inline-block fragments as children of the inline flow. - for inline_block_flow in &inline_block_flows { - inline_flow_ref.add_new_child(inline_block_flow.clone()); - } - - // Set up absolute descendants as necessary. - // - // The inline flow itself may need to become the containing block for absolute descendants - // in order to handle cases like: - // - // <div> - // <span style="position: relative"> - // <span style="position: absolute; ..."></span> - // </span> - // </div> - // - // See the comment above `flow::AbsoluteDescendantInfo` for more information. - inline_flow_ref.take_applicable_absolute_descendants(&mut fragments.absolute_descendants); - absolute_descendants.push_descendants(fragments.absolute_descendants); - - { - // FIXME(#6503): Use Arc::get_mut().unwrap() here. - let inline_flow = FlowRef::deref_mut(&mut inline_flow_ref).as_mut_inline(); - inline_flow.minimum_line_metrics = - with_thread_local_font_context(self.layout_context, |font_context| { - inline_flow - .minimum_line_metrics(font_context, &node.style(self.style_context())) - }); - } - - inline_flow_ref.finish(); - legalizer.add_child::<ConcreteThreadSafeLayoutNode::ConcreteElement>( - self.style_context(), - flow, - inline_flow_ref, - ) - } - - fn build_block_flow_using_construction_result_of_child( - &mut self, - flow: &mut FlowRef, - node: &ConcreteThreadSafeLayoutNode, - kid: ConcreteThreadSafeLayoutNode, - inline_fragment_accumulator: &mut InlineFragmentsAccumulator, - abs_descendants: &mut AbsoluteDescendants, - legalizer: &mut Legalizer, - ) { - match kid.get_construction_result() { - ConstructionResult::None => {}, - ConstructionResult::Flow(kid_flow, kid_abs_descendants) => { - // If kid_flow is TableCaptionFlow, kid_flow should be added under - // TableWrapperFlow. - if flow.is_table() && kid_flow.is_table_caption() { - let construction_result = - ConstructionResult::Flow(kid_flow, AbsoluteDescendants::new()); - self.set_flow_construction_result(&kid, construction_result) - } else { - if !kid_flow - .base() - .flags - .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) - { - // Flush any inline fragments that we were gathering up. This allows us to - // handle {ib} splits. - let old_inline_fragment_accumulator = mem::replace( - inline_fragment_accumulator, - InlineFragmentsAccumulator::new(), - ); - self.flush_inline_fragments_to_flow( - old_inline_fragment_accumulator, - flow, - abs_descendants, - legalizer, - node, - ); - } - legalizer.add_child::<ConcreteThreadSafeLayoutNode::ConcreteElement>( - self.style_context(), - flow, - kid_flow, - ) - } - abs_descendants.push_descendants(kid_abs_descendants); - }, - ConstructionResult::ConstructionItem(ConstructionItem::InlineFragments( - InlineFragmentsConstructionResult { - splits, - fragments: successor_fragments, - }, - )) => { - // Add any {ib} splits. - for split in splits { - // Pull apart the {ib} split object and push its predecessor fragments - // onto the list. - let InlineBlockSplit { - predecessors, - flow: kid_flow, - } = split; - inline_fragment_accumulator.push_all(predecessors); - - // Flush any inline fragments that we were gathering up. - debug!( - "flushing {} inline box(es) to flow A", - inline_fragment_accumulator.fragments.fragments.len() - ); - let old_inline_fragment_accumulator = mem::replace( - inline_fragment_accumulator, - InlineFragmentsAccumulator::new(), - ); - let absolute_descendants = - &mut inline_fragment_accumulator.fragments.absolute_descendants; - self.flush_inline_fragments_to_flow( - old_inline_fragment_accumulator, - flow, - absolute_descendants, - legalizer, - node, - ); - - // Push the flow generated by the {ib} split onto our list of flows. - legalizer.add_child::<ConcreteThreadSafeLayoutNode::ConcreteElement>( - self.style_context(), - flow, - kid_flow, - ) - } - - // Add the fragments to the list we're maintaining. - inline_fragment_accumulator.push_all(successor_fragments); - }, - ConstructionResult::ConstructionItem(ConstructionItem::Whitespace( - whitespace_node, - whitespace_pseudo, - whitespace_style, - whitespace_damage, - )) => { - // Add whitespace results. They will be stripped out later on when - // between block elements, and retained when between inline elements. - let fragment_info = SpecificFragmentInfo::UnscannedText(Box::new( - UnscannedTextFragmentInfo::new(Box::<str>::from(" "), None), - )); - let fragment = Fragment::from_opaque_node_and_style( - whitespace_node, - whitespace_pseudo, - whitespace_style, - node.selected_style(), - whitespace_damage, - fragment_info, - ); - inline_fragment_accumulator - .fragments - .fragments - .push_back(fragment); - }, - ConstructionResult::ConstructionItem(ConstructionItem::TableColumnFragment(_)) => { - // TODO: Implement anonymous table objects for missing parents - // CSS 2.1 § 17.2.1, step 3-2 - }, - } - } - - /// Constructs a block flow, beginning with the given `initial_fragments` if present and then - /// appending the construction results of children to the child list of the block flow. {ib} - /// splits and absolutely-positioned descendants are handled correctly. - fn build_flow_for_block_starting_with_fragments( - &mut self, - mut flow: FlowRef, - node: &ConcreteThreadSafeLayoutNode, - initial_fragments: IntermediateInlineFragments, - ) -> ConstructionResult { - // Gather up fragments for the inline flows we might need to create. - let mut inline_fragment_accumulator = InlineFragmentsAccumulator::new(); - - inline_fragment_accumulator - .fragments - .push_all(initial_fragments); - - // List of absolute descendants, in tree order. - let mut abs_descendants = AbsoluteDescendants::new(); - let mut legalizer = Legalizer::new(); - let is_media_element_with_widget = node.type_id() == - Some(LayoutNodeType::Element(LayoutElementType::HTMLMediaElement)) && - node.as_element().unwrap().is_shadow_host(); - if !node.is_replaced_content() || is_media_element_with_widget { - for kid in node.children() { - if kid.get_pseudo_element_type() != PseudoElementType::Normal { - if node.is_replaced_content() { - // Replaced elements don't have pseudo-elements per spec. - continue; - } - self.process(&kid); - } - - self.build_block_flow_using_construction_result_of_child( - &mut flow, - node, - kid, - &mut inline_fragment_accumulator, - &mut abs_descendants, - &mut legalizer, - ); - } - } - - // Perform a final flush of any inline fragments that we were gathering up to handle {ib} - // splits, after stripping ignorable whitespace. - self.flush_inline_fragments_to_flow( - inline_fragment_accumulator, - &mut flow, - &mut abs_descendants, - &mut legalizer, - node, - ); - - // The flow is done. - legalizer.finish(&mut flow); - flow.finish(); - - // Set up the absolute descendants. - if flow.is_absolute_containing_block() { - // This is the containing block for all the absolute descendants. - flow.set_absolute_descendants(abs_descendants); - - abs_descendants = AbsoluteDescendants::new(); - if flow - .base() - .flags - .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) - { - // This is now the only absolute flow in the subtree which hasn't yet - // reached its CB. - abs_descendants.push(flow.clone()); - } - } - ConstructionResult::Flow(flow, abs_descendants) - } - - /// Constructs a flow for the given block node and its children. This method creates an - /// initial fragment as appropriate and then dispatches to - /// `build_flow_for_block_starting_with_fragments`. Currently the following kinds of flows get - /// initial content: - /// - /// * Generated content gets the initial content specified by the `content` attribute of the - /// CSS. - /// * `<input>` and `<textarea>` elements get their content. - /// - /// FIXME(pcwalton): It is not clear to me that there isn't a cleaner way to handle - /// `<textarea>`. - fn build_flow_for_block_like( - &mut self, - flow: FlowRef, - node: &ConcreteThreadSafeLayoutNode, - ) -> ConstructionResult { - let mut fragments = IntermediateInlineFragments::new(); - let node_is_input_or_text_area = node.type_id() == - Some(LayoutNodeType::Element(LayoutElementType::HTMLInputElement)) || - node.type_id() == - Some(LayoutNodeType::Element( - LayoutElementType::HTMLTextAreaElement, - )); - if node.get_pseudo_element_type().is_replaced_content() || node_is_input_or_text_area { - // A TextArea's text contents are displayed through the input text - // box, so don't construct them. - if node.type_id() == - Some(LayoutNodeType::Element( - LayoutElementType::HTMLTextAreaElement, - )) - { - for kid in node.children() { - self.set_flow_construction_result(&kid, ConstructionResult::None) - } - } - - let context = self.style_context(); - let mut style = node.style(context); - style = context - .stylist - .style_for_anonymous::<ConcreteThreadSafeLayoutNode::ConcreteElement>( - &context.guards, - &PseudoElement::ServoText, - &style, - ); - if node_is_input_or_text_area { - style = context - .stylist - .style_for_anonymous::<ConcreteThreadSafeLayoutNode::ConcreteElement>( - &context.guards, - &PseudoElement::ServoInputText, - &style, - ) - } - - self.create_fragments_for_node_text_content(&mut fragments, node, &style) - } - self.build_flow_for_block_starting_with_fragments(flow, node, fragments) - } - - /// Pushes fragments appropriate for the content of the given node onto the given list. - fn create_fragments_for_node_text_content( - &self, - fragments: &mut IntermediateInlineFragments, - node: &ConcreteThreadSafeLayoutNode, - style: &ServoArc<ComputedValues>, - ) { - // Fast path: If there is no text content, return immediately. - let text_content = node.text_content(); - if text_content.is_empty() { - return; - } - - let style = (*style).clone(); - let selected_style = node.selected_style(); - - match text_content { - TextContent::Text(string) => { - let info = Box::new(UnscannedTextFragmentInfo::new(string, node.selection())); - let specific_fragment_info = SpecificFragmentInfo::UnscannedText(info); - fragments - .fragments - .push_back(Fragment::from_opaque_node_and_style( - node.opaque(), - node.get_pseudo_element_type(), - style, - selected_style, - node.restyle_damage(), - specific_fragment_info, - )) - }, - TextContent::GeneratedContent(content_items) => { - for content_item in content_items.into_iter() { - let specific_fragment_info = match content_item { - ContentItem::String(string) => { - let info = Box::new(UnscannedTextFragmentInfo::new(string, None)); - SpecificFragmentInfo::UnscannedText(info) - }, - content_item => { - let content_item = - Box::new(GeneratedContentInfo::ContentItem(content_item)); - SpecificFragmentInfo::GeneratedContent(content_item) - }, - }; - fragments - .fragments - .push_back(Fragment::from_opaque_node_and_style( - node.opaque(), - node.get_pseudo_element_type(), - style.clone(), - selected_style.clone(), - node.restyle_damage(), - specific_fragment_info, - )) - } - }, - } - } - - /// Builds a flow for a node with `display: block`. This yields a `BlockFlow` with possibly - /// other `BlockFlow`s or `InlineFlow`s underneath it, depending on whether {ib} splits needed - /// to happen. - fn build_flow_for_block( - &mut self, - node: &ConcreteThreadSafeLayoutNode, - float_kind: Option<FloatKind>, - ) -> ConstructionResult { - if node.style(self.style_context()).is_multicol() { - return self.build_flow_for_multicol(node, float_kind); - } - - let fragment = self.build_fragment_for_block(node); - let flow = FlowRef::new(Arc::new(BlockFlow::from_fragment_and_float_kind( - fragment, float_kind, - ))); - self.build_flow_for_block_like(flow, node) - } - - /// Bubbles up {ib} splits. - fn accumulate_inline_block_splits( - &mut self, - splits: LinkedList<InlineBlockSplit>, - node: &ConcreteThreadSafeLayoutNode, - fragment_accumulator: &mut InlineFragmentsAccumulator, - opt_inline_block_splits: &mut LinkedList<InlineBlockSplit>, - ) { - for split in splits { - let InlineBlockSplit { - predecessors, - flow: kid_flow, - } = split; - fragment_accumulator.push_all(predecessors); - - opt_inline_block_splits.push_back(InlineBlockSplit::new( - fragment_accumulator, - node, - self.style_context(), - kid_flow, - )); - } - } - - /// Concatenates the fragments of kids, adding in our own borders/padding/margins if necessary. - /// Returns the `InlineFragmentsConstructionResult`, if any. There will be no - /// `InlineFragmentsConstructionResult` if this node consisted entirely of ignorable - /// whitespace. - fn build_fragments_for_nonreplaced_inline_content( - &mut self, - node: &ConcreteThreadSafeLayoutNode, - ) -> ConstructionResult { - let mut opt_inline_block_splits: LinkedList<InlineBlockSplit> = LinkedList::new(); - let mut fragment_accumulator = - InlineFragmentsAccumulator::from_inline_node(node, self.style_context()); - fragment_accumulator.bidi_control_chars = - bidi_control_chars(&node.style(self.style_context())); - - let mut abs_descendants = AbsoluteDescendants::new(); - - // Concatenate all the fragments of our kids, creating {ib} splits as necessary. - let mut is_empty = true; - for kid in node.children() { - is_empty = false; - if kid.get_pseudo_element_type() != PseudoElementType::Normal { - self.process(&kid); - } - match kid.get_construction_result() { - ConstructionResult::None => {}, - ConstructionResult::Flow(flow, kid_abs_descendants) => { - if !flow - .base() - .flags - .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) - { - opt_inline_block_splits.push_back(InlineBlockSplit::new( - &mut fragment_accumulator, - node, - self.style_context(), - flow, - )); - abs_descendants.push_descendants(kid_abs_descendants); - } else { - // Push the absolutely-positioned kid as an inline containing block. - let kid_node = flow.as_block().fragment.node; - let kid_pseudo = flow.as_block().fragment.pseudo.clone(); - let kid_style = flow.as_block().fragment.style.clone(); - let kid_selected_style = flow.as_block().fragment.selected_style.clone(); - let kid_restyle_damage = flow.as_block().fragment.restyle_damage; - let fragment_info = SpecificFragmentInfo::InlineAbsolute( - InlineAbsoluteFragmentInfo::new(flow), - ); - fragment_accumulator.push(Fragment::from_opaque_node_and_style( - kid_node, - kid_pseudo, - kid_style, - kid_selected_style, - kid_restyle_damage, - fragment_info, - )); - fragment_accumulator - .fragments - .absolute_descendants - .push_descendants(kid_abs_descendants); - } - }, - ConstructionResult::ConstructionItem(ConstructionItem::InlineFragments( - InlineFragmentsConstructionResult { - splits, - fragments: successors, - }, - )) => { - // Bubble up {ib} splits. - self.accumulate_inline_block_splits( - splits, - node, - &mut fragment_accumulator, - &mut opt_inline_block_splits, - ); - - // Push residual fragments. - fragment_accumulator.push_all(successors); - }, - ConstructionResult::ConstructionItem(ConstructionItem::Whitespace( - whitespace_node, - whitespace_pseudo, - whitespace_style, - whitespace_damage, - )) => { - // Instantiate the whitespace fragment. - let fragment_info = SpecificFragmentInfo::UnscannedText(Box::new( - UnscannedTextFragmentInfo::new(Box::<str>::from(" "), None), - )); - let fragment = Fragment::from_opaque_node_and_style( - whitespace_node, - whitespace_pseudo, - whitespace_style, - node.selected_style(), - whitespace_damage, - fragment_info, - ); - fragment_accumulator.fragments.fragments.push_back(fragment) - }, - ConstructionResult::ConstructionItem(ConstructionItem::TableColumnFragment(_)) => { - // TODO: Implement anonymous table objects for missing parents - // CSS 2.1 § 17.2.1, step 3-2 - }, - } - } - - let node_style = node.style(self.style_context()); - if is_empty && has_padding_or_border(&node_style) { - // An empty inline box needs at least one fragment to draw its background and borders. - let info = SpecificFragmentInfo::UnscannedText(Box::new( - UnscannedTextFragmentInfo::new(Box::<str>::from(""), None), - )); - let fragment = Fragment::from_opaque_node_and_style( - node.opaque(), - node.get_pseudo_element_type(), - node_style.clone(), - node.selected_style(), - node.restyle_damage(), - info, - ); - fragment_accumulator.fragments.fragments.push_back(fragment) - } - - // Finally, make a new construction result. - if opt_inline_block_splits.len() > 0 || - !fragment_accumulator.fragments.is_empty() || - abs_descendants.len() > 0 - { - fragment_accumulator - .fragments - .absolute_descendants - .push_descendants(abs_descendants); - - // If the node is positioned, then it's the containing block for all absolutely- - // positioned descendants. - if node_style.get_box().position != Position::Static { - fragment_accumulator - .fragments - .absolute_descendants - .mark_as_having_reached_containing_block(); - } - - let construction_item = - ConstructionItem::InlineFragments(InlineFragmentsConstructionResult { - splits: opt_inline_block_splits, - fragments: fragment_accumulator - .to_intermediate_inline_fragments::<ConcreteThreadSafeLayoutNode>( - self.style_context(), - ), - }); - ConstructionResult::ConstructionItem(construction_item) - } else { - ConstructionResult::None - } - } - - /// Creates an `InlineFragmentsConstructionResult` for replaced content. Replaced content - /// doesn't render its children, so this just nukes a child's fragments and creates a - /// `Fragment`. - fn build_fragments_for_replaced_inline_content( - &mut self, - node: &ConcreteThreadSafeLayoutNode, - ) -> ConstructionResult { - for kid in node.children() { - self.set_flow_construction_result(&kid, ConstructionResult::None) - } - - let context = self.style_context(); - let style = node.style(context); - // If this node is ignorable whitespace, bail out now. - if node.is_ignorable_whitespace(context) { - return ConstructionResult::ConstructionItem(ConstructionItem::Whitespace( - node.opaque(), - node.get_pseudo_element_type(), - context - .stylist - .style_for_anonymous::<ConcreteThreadSafeLayoutNode::ConcreteElement>( - &context.guards, - &PseudoElement::ServoText, - &style, - ), - node.restyle_damage(), - )); - } - - // If this is generated content, then we need to initialize the accumulator with the - // fragment corresponding to that content. Otherwise, just initialize with the ordinary - // fragment that needs to be generated for this inline node. - let mut fragments = IntermediateInlineFragments::new(); - match (node.get_pseudo_element_type(), node.type_id()) { - (_, Some(LayoutNodeType::Text)) => { - let text_style = context - .stylist - .style_for_anonymous::<ConcreteThreadSafeLayoutNode::ConcreteElement>( - &context.guards, - &PseudoElement::ServoText, - &style, - ); - self.create_fragments_for_node_text_content(&mut fragments, node, &text_style) - }, - (PseudoElementType::Normal, _) => { - fragments - .fragments - .push_back(self.build_fragment_for_block(node)); - }, - (_, _) => self.create_fragments_for_node_text_content(&mut fragments, node, &style), - } - - let construction_item = - ConstructionItem::InlineFragments(InlineFragmentsConstructionResult { - splits: LinkedList::new(), - fragments: fragments, - }); - ConstructionResult::ConstructionItem(construction_item) - } - - /// Build the fragment for an inline-block or inline-flex, based on the `display` flag - fn build_fragment_for_inline_block_or_inline_flex( - &mut self, - node: &ConcreteThreadSafeLayoutNode, - display: Display, - ) -> ConstructionResult { - let block_flow_result = match display { - Display::InlineBlock => self.build_flow_for_block(node, None), - Display::InlineFlex => self.build_flow_for_flex(node, None), - _ => panic!("The flag should be inline-block or inline-flex"), - }; - let (block_flow, abs_descendants) = match block_flow_result { - ConstructionResult::Flow(block_flow, abs_descendants) => (block_flow, abs_descendants), - _ => unreachable!(), - }; - - let context = self.style_context(); - let style = node.style(context); - let style = context - .stylist - .style_for_anonymous::<ConcreteThreadSafeLayoutNode::ConcreteElement>( - &context.guards, - &PseudoElement::ServoInlineBlockWrapper, - &style, - ); - let fragment_info = - SpecificFragmentInfo::InlineBlock(InlineBlockFragmentInfo::new(block_flow)); - let fragment = Fragment::from_opaque_node_and_style( - node.opaque(), - node.get_pseudo_element_type(), - style, - node.selected_style(), - node.restyle_damage(), - fragment_info, - ); - - let mut fragment_accumulator = InlineFragmentsAccumulator::new(); - fragment_accumulator.fragments.fragments.push_back(fragment); - fragment_accumulator - .fragments - .absolute_descendants - .push_descendants(abs_descendants); - - let construction_item = - ConstructionItem::InlineFragments(InlineFragmentsConstructionResult { - splits: LinkedList::new(), - fragments: fragment_accumulator - .to_intermediate_inline_fragments::<ConcreteThreadSafeLayoutNode>(context), - }); - ConstructionResult::ConstructionItem(construction_item) - } - - /// This is an annoying case, because the computed `display` value is `block`, but the - /// hypothetical box is inline. - fn build_fragment_for_absolutely_positioned_inline( - &mut self, - node: &ConcreteThreadSafeLayoutNode, - ) -> ConstructionResult { - let block_flow_result = self.build_flow_for_block(node, None); - let (block_flow, abs_descendants) = match block_flow_result { - ConstructionResult::Flow(block_flow, abs_descendants) => (block_flow, abs_descendants), - _ => unreachable!(), - }; - - let fragment_info = SpecificFragmentInfo::InlineAbsoluteHypothetical( - InlineAbsoluteHypotheticalFragmentInfo::new(block_flow), - ); - let style_context = self.style_context(); - let style = node.style(style_context); - let style = style_context - .stylist - .style_for_anonymous::<ConcreteThreadSafeLayoutNode::ConcreteElement>( - &style_context.guards, - &PseudoElement::ServoInlineAbsolute, - &style, - ); - let fragment = Fragment::from_opaque_node_and_style( - node.opaque(), - PseudoElementType::Normal, - style, - node.selected_style(), - node.restyle_damage(), - fragment_info, - ); - - let mut fragment_accumulator = - InlineFragmentsAccumulator::from_inline_node(node, self.style_context()); - fragment_accumulator.fragments.fragments.push_back(fragment); - fragment_accumulator - .fragments - .absolute_descendants - .push_descendants(abs_descendants); - - let construction_item = - ConstructionItem::InlineFragments(InlineFragmentsConstructionResult { - splits: LinkedList::new(), - fragments: fragment_accumulator - .to_intermediate_inline_fragments::<ConcreteThreadSafeLayoutNode>( - style_context, - ), - }); - ConstructionResult::ConstructionItem(construction_item) - } - - /// Builds one or more fragments for a node with `display: inline`. This yields an - /// `InlineFragmentsConstructionResult`. - fn build_fragments_for_inline( - &mut self, - node: &ConcreteThreadSafeLayoutNode, - ) -> ConstructionResult { - // Is this node replaced content? - if !node.is_replaced_content() { - // Go to a path that concatenates our kids' fragments. - self.build_fragments_for_nonreplaced_inline_content(node) - } else { - // Otherwise, just nuke our kids' fragments, create our fragment if any, and be done - // with it. - self.build_fragments_for_replaced_inline_content(node) - } - } - - /// Places any table captions found under the given table wrapper, if the value of their - /// `caption-side` property is equal to the given `side`. - fn place_table_caption_under_table_wrapper_on_side( - &mut self, - table_wrapper_flow: &mut FlowRef, - node: &ConcreteThreadSafeLayoutNode, - side: CaptionSide, - ) { - // Only flows that are table captions are matched here. - for kid in node.children() { - match kid.get_construction_result() { - ConstructionResult::Flow(kid_flow, _) => { - if kid_flow.is_table_caption() && - kid_flow - .as_block() - .fragment - .style() - .get_inherited_table() - .caption_side == - side - { - table_wrapper_flow.add_new_child(kid_flow); - } - }, - ConstructionResult::None | ConstructionResult::ConstructionItem(_) => {}, - } - } - } - - /// Builds a flow for a node with `column-count` or `column-width` non-`auto`. - /// This yields a `MulticolFlow` with a single `MulticolColumnFlow` underneath it. - fn build_flow_for_multicol( - &mut self, - node: &ConcreteThreadSafeLayoutNode, - float_kind: Option<FloatKind>, - ) -> ConstructionResult { - let fragment = Fragment::new(node, SpecificFragmentInfo::Multicol, self.layout_context); - let mut flow = FlowRef::new(Arc::new(MulticolFlow::from_fragment(fragment, float_kind))); - - let column_fragment = Fragment::new( - node, - SpecificFragmentInfo::MulticolColumn, - self.layout_context, - ); - let column_flow = - FlowRef::new(Arc::new(MulticolColumnFlow::from_fragment(column_fragment))); - - // First populate the column flow with its children. - let construction_result = self.build_flow_for_block_like(column_flow, node); - - let mut abs_descendants = AbsoluteDescendants::new(); - - if let ConstructionResult::Flow(column_flow, column_abs_descendants) = construction_result { - flow.add_new_child(column_flow); - abs_descendants.push_descendants(column_abs_descendants); - } - - // The flow is done. - flow.finish(); - if flow.is_absolute_containing_block() { - // This is the containing block for all the absolute descendants. - flow.set_absolute_descendants(abs_descendants); - - abs_descendants = AbsoluteDescendants::new(); - - if flow - .base() - .flags - .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) - { - // This is now the only absolute flow in the subtree which hasn't yet - // reached its containing block. - abs_descendants.push(flow.clone()); - } - } - - ConstructionResult::Flow(flow, abs_descendants) - } - - /// Builds a flow for a node with `display: table`. This yields a `TableWrapperFlow` with - /// possibly other `TableCaptionFlow`s or `TableFlow`s underneath it. - fn build_flow_for_table( - &mut self, - node: &ConcreteThreadSafeLayoutNode, - float_value: Float, - ) -> ConstructionResult { - let mut legalizer = Legalizer::new(); - - let table_style; - let wrapper_style; - { - let context = self.style_context(); - table_style = node.style(context); - wrapper_style = context - .stylist - .style_for_anonymous::<ConcreteThreadSafeLayoutNode::ConcreteElement>( - &context.guards, - &PseudoElement::ServoTableWrapper, - &table_style, - ); - } - let wrapper_fragment = Fragment::from_opaque_node_and_style( - node.opaque(), - PseudoElementType::Normal, - wrapper_style, - node.selected_style(), - node.restyle_damage(), - SpecificFragmentInfo::TableWrapper, - ); - let wrapper_float_kind = FloatKind::from_property(float_value); - let mut wrapper_flow = FlowRef::new(Arc::new( - TableWrapperFlow::from_fragment_and_float_kind(wrapper_fragment, wrapper_float_kind), - )); - - let table_fragment = Fragment::new(node, SpecificFragmentInfo::Table, self.layout_context); - let table_flow = FlowRef::new(Arc::new(TableFlow::from_fragment(table_fragment))); - - // First populate the table flow with its children. - let construction_result = self.build_flow_for_block_like(table_flow, node); - - let mut abs_descendants = AbsoluteDescendants::new(); - - // The order of the caption and the table are not necessarily the same order as in the DOM - // tree. All caption blocks are placed before or after the table flow, depending on the - // value of `caption-side`. - self.place_table_caption_under_table_wrapper_on_side( - &mut wrapper_flow, - node, - CaptionSide::Top, - ); - - if let ConstructionResult::Flow(table_flow, table_abs_descendants) = construction_result { - legalizer.add_child::<ConcreteThreadSafeLayoutNode::ConcreteElement>( - self.style_context(), - &mut wrapper_flow, - table_flow, - ); - abs_descendants.push_descendants(table_abs_descendants); - } - - // If the value of `caption-side` is `bottom`, place it now. - self.place_table_caption_under_table_wrapper_on_side( - &mut wrapper_flow, - node, - CaptionSide::Bottom, - ); - - // The flow is done. - legalizer.finish(&mut wrapper_flow); - wrapper_flow.finish(); - - if wrapper_flow.is_absolute_containing_block() { - // This is the containing block for all the absolute descendants. - wrapper_flow.set_absolute_descendants(abs_descendants); - - abs_descendants = AbsoluteDescendants::new(); - - if wrapper_flow - .base() - .flags - .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) - { - // This is now the only absolute flow in the subtree which hasn't yet - // reached its containing block. - abs_descendants.push(wrapper_flow.clone()); - } - } - - ConstructionResult::Flow(wrapper_flow, abs_descendants) - } - - /// Builds a flow for a node with `display: table-caption`. This yields a `TableCaptionFlow` - /// with possibly other `BlockFlow`s or `InlineFlow`s underneath it. - fn build_flow_for_table_caption( - &mut self, - node: &ConcreteThreadSafeLayoutNode, - ) -> ConstructionResult { - let fragment = self.build_fragment_for_block(node); - let flow = FlowRef::new(Arc::new(TableCaptionFlow::from_fragment(fragment))); - self.build_flow_for_block_like(flow, node) - } - - /// Builds a flow for a node with `display: table-row-group`. This yields a `TableRowGroupFlow` - /// with possibly other `TableRowFlow`s underneath it. - fn build_flow_for_table_rowgroup( - &mut self, - node: &ConcreteThreadSafeLayoutNode, - ) -> ConstructionResult { - let fragment = Fragment::new(node, SpecificFragmentInfo::TableRow, self.layout_context); - let flow = FlowRef::new(Arc::new(TableRowGroupFlow::from_fragment(fragment))); - self.build_flow_for_block_like(flow, node) - } - - /// Builds a flow for a node with `display: table-row`. This yields a `TableRowFlow` with - /// possibly other `TableCellFlow`s underneath it. - fn build_flow_for_table_row( - &mut self, - node: &ConcreteThreadSafeLayoutNode, - ) -> ConstructionResult { - let fragment = Fragment::new(node, SpecificFragmentInfo::TableRow, self.layout_context); - let flow = FlowRef::new(Arc::new(TableRowFlow::from_fragment(fragment))); - self.build_flow_for_block_like(flow, node) - } - - /// Builds a flow for a node with `display: table-cell`. This yields a `TableCellFlow` with - /// possibly other `BlockFlow`s or `InlineFlow`s underneath it. - fn build_flow_for_table_cell( - &mut self, - node: &ConcreteThreadSafeLayoutNode, - ) -> ConstructionResult { - let fragment = Fragment::new(node, SpecificFragmentInfo::TableCell, self.layout_context); - - // Determine if the table cell should be hidden. Per CSS 2.1 § 17.6.1.1, this will be true - // if the cell has any in-flow elements (even empty ones!) and has `empty-cells` set to - // `hide`. - let hide = node - .style(self.style_context()) - .get_inherited_table() - .empty_cells == - EmptyCells::Hide && - node.children().all(|kid| { - let position = kid.style(self.style_context()).get_box().position; - !kid.is_content() || position == Position::Absolute || position == Position::Fixed - }); - - let flow = FlowRef::new(Arc::new( - TableCellFlow::from_node_fragment_and_visibility_flag(node, fragment, !hide), - )); - self.build_flow_for_block_like(flow, node) - } - - /// Builds a flow for a node with `display: list-item`. This yields a `ListItemFlow` with - /// possibly other `BlockFlow`s or `InlineFlow`s underneath it. - fn build_flow_for_list_item( - &mut self, - node: &ConcreteThreadSafeLayoutNode, - flotation: Float, - ) -> ConstructionResult { - let flotation = FloatKind::from_property(flotation); - let marker_fragments = match node.style(self.style_context()).get_list().list_style_image { - ImageUrlOrNone::Url(ref url_value) => { - let image_info = Box::new(ImageFragmentInfo::new( - url_value.url().map(|u| u.clone()), - None, - node, - &self.layout_context, - )); - vec![Fragment::new( - node, - SpecificFragmentInfo::Image(image_info), - self.layout_context, - )] - }, - ImageUrlOrNone::None => match ListStyleTypeContent::from_list_style_type( - node.style(self.style_context()).get_list().list_style_type, - ) { - ListStyleTypeContent::None => Vec::new(), - ListStyleTypeContent::StaticText(ch) => { - let text = format!("{}\u{a0}", ch); - let mut unscanned_marker_fragments = LinkedList::new(); - unscanned_marker_fragments.push_back(Fragment::new( - node, - SpecificFragmentInfo::UnscannedText(Box::new( - UnscannedTextFragmentInfo::new(Box::<str>::from(text), None), - )), - self.layout_context, - )); - let marker_fragments = - with_thread_local_font_context(self.layout_context, |mut font_context| { - TextRunScanner::new() - .scan_for_runs(&mut font_context, unscanned_marker_fragments) - }); - marker_fragments.fragments - }, - ListStyleTypeContent::GeneratedContent(info) => vec![Fragment::new( - node, - SpecificFragmentInfo::GeneratedContent(info), - self.layout_context, - )], - }, - }; - - // If the list marker is outside, it becomes the special "outside fragment" that list item - // flows have. If it's inside, it's just a plain old fragment. Note that this means that - // we adopt Gecko's behavior rather than WebKit's when the marker causes an {ib} split, - // which has caused some malaise (Bugzilla #36854) but CSS 2.1 § 12.5.1 lets me do it, so - // there. - let mut initial_fragments = IntermediateInlineFragments::new(); - let main_fragment = self.build_fragment_for_block(node); - let flow = match node - .style(self.style_context()) - .get_list() - .list_style_position - { - ListStylePosition::Outside => Arc::new(ListItemFlow::from_fragments_and_flotation( - main_fragment, - marker_fragments, - flotation, - )), - ListStylePosition::Inside => { - for marker_fragment in marker_fragments { - initial_fragments.fragments.push_back(marker_fragment) - } - Arc::new(ListItemFlow::from_fragments_and_flotation( - main_fragment, - vec![], - flotation, - )) - }, - }; - - self.build_flow_for_block_starting_with_fragments( - FlowRef::new(flow), - node, - initial_fragments, - ) - } - - /// Creates a fragment for a node with `display: table-column`. - fn build_fragments_for_table_column( - &mut self, - node: &ConcreteThreadSafeLayoutNode, - ) -> ConstructionResult { - // CSS 2.1 § 17.2.1. Treat all child fragments of a `table-column` as `display: none`. - for kid in node.children() { - self.set_flow_construction_result(&kid, ConstructionResult::None) - } - - let specific = SpecificFragmentInfo::TableColumn(TableColumnFragmentInfo::new(node)); - let construction_item = ConstructionItem::TableColumnFragment(Fragment::new( - node, - specific, - self.layout_context, - )); - ConstructionResult::ConstructionItem(construction_item) - } - - /// Builds a flow for a node with `display: table-column-group`. - /// This yields a `TableColGroupFlow`. - fn build_flow_for_table_colgroup( - &mut self, - node: &ConcreteThreadSafeLayoutNode, - ) -> ConstructionResult { - let fragment = Fragment::new( - node, - SpecificFragmentInfo::TableColumn(TableColumnFragmentInfo::new(node)), - self.layout_context, - ); - let mut col_fragments = vec![]; - for kid in node.children() { - // CSS 2.1 § 17.2.1. Treat all non-column child fragments of `table-column-group` - // as `display: none`. - if let ConstructionResult::ConstructionItem(ConstructionItem::TableColumnFragment( - fragment, - )) = kid.get_construction_result() - { - col_fragments.push(fragment) - } - } - if col_fragments.is_empty() { - debug!("add SpecificFragmentInfo::TableColumn for empty colgroup"); - let specific = SpecificFragmentInfo::TableColumn(TableColumnFragmentInfo::new(node)); - col_fragments.push(Fragment::new(node, specific, self.layout_context)); - } - let mut flow = FlowRef::new(Arc::new(TableColGroupFlow::from_fragments( - fragment, - col_fragments, - ))); - flow.finish(); - - ConstructionResult::Flow(flow, AbsoluteDescendants::new()) - } - - /// Builds a flow for a node with 'display: flex'. - fn build_flow_for_flex( - &mut self, - node: &ConcreteThreadSafeLayoutNode, - float_kind: Option<FloatKind>, - ) -> ConstructionResult { - let fragment = self.build_fragment_for_block(node); - let flow = FlowRef::new(Arc::new(FlexFlow::from_fragment(fragment, float_kind))); - self.build_flow_for_block_like(flow, node) - } - - /// Attempts to perform incremental repair to account for recent changes to this node. This - /// can fail and return false, indicating that flows will need to be reconstructed. - /// - /// TODO(pcwalton): Add some more fast paths, like toggling `display: none`, adding block kids - /// to block parents with no {ib} splits, adding out-of-flow kids, etc. - pub fn repair_if_possible(&mut self, node: &ConcreteThreadSafeLayoutNode) -> bool { - // We can skip reconstructing the flow if we don't have to reconstruct and none of our kids - // did either. - // - // We visit the kids first and reset their HAS_NEWLY_CONSTRUCTED_FLOW flags after checking - // them. NOTE: Make sure not to bail out early before resetting all the flags! - let mut need_to_reconstruct = false; - - // If the node has display: none, it's possible that we haven't even - // styled the children once, so we need to bailout early here. - if node.style(self.style_context()).get_box().clone_display() == Display::None { - return false; - } - - for kid in node.children() { - if kid - .flags() - .contains(LayoutDataFlags::HAS_NEWLY_CONSTRUCTED_FLOW) - { - kid.remove_flags(LayoutDataFlags::HAS_NEWLY_CONSTRUCTED_FLOW); - need_to_reconstruct = true - } - } - - if need_to_reconstruct { - return false; - } - - if node - .restyle_damage() - .contains(ServoRestyleDamage::RECONSTRUCT_FLOW) - { - return false; - } - - let mut set_has_newly_constructed_flow_flag = false; - let result = { - let style = node.style(self.style_context()); - - if style.can_be_fragmented() || style.is_multicol() { - return false; - } - - let damage = node.restyle_damage(); - let mut data = node.mutate_layout_data().unwrap(); - - match *node.construction_result_mut(&mut *data) { - ConstructionResult::None => true, - ConstructionResult::Flow(ref mut flow, _) => { - // The node's flow is of the same type and has the same set of children and can - // therefore be repaired by simply propagating damage and style to the flow. - if !flow.is_block_flow() { - return false; - } - - let flow = FlowRef::deref_mut(flow); - flow.mut_base().restyle_damage.insert(damage); - flow.repair_style_and_bubble_inline_sizes(&style); - true - }, - ConstructionResult::ConstructionItem(ConstructionItem::InlineFragments( - ref mut inline_fragments_construction_result, - )) => { - if !inline_fragments_construction_result.splits.is_empty() { - return false; - } - - for fragment in inline_fragments_construction_result - .fragments - .fragments - .iter_mut() - { - // Only mutate the styles of fragments that represent the dirty node (including - // pseudo-element). - if fragment.node != node.opaque() { - continue; - } - if fragment.pseudo != node.get_pseudo_element_type() { - continue; - } - - match fragment.specific { - SpecificFragmentInfo::InlineBlock(ref mut inline_block_fragment) => { - let flow_ref = - FlowRef::deref_mut(&mut inline_block_fragment.flow_ref); - flow_ref.mut_base().restyle_damage.insert(damage); - // FIXME(pcwalton): Fragment restyle damage too? - flow_ref.repair_style_and_bubble_inline_sizes(&style); - }, - SpecificFragmentInfo::InlineAbsoluteHypothetical( - ref mut inline_absolute_hypothetical_fragment, - ) => { - let flow_ref = FlowRef::deref_mut( - &mut inline_absolute_hypothetical_fragment.flow_ref, - ); - flow_ref.mut_base().restyle_damage.insert(damage); - // FIXME(pcwalton): Fragment restyle damage too? - flow_ref.repair_style_and_bubble_inline_sizes(&style); - }, - SpecificFragmentInfo::InlineAbsolute( - ref mut inline_absolute_fragment, - ) => { - let flow_ref = - FlowRef::deref_mut(&mut inline_absolute_fragment.flow_ref); - flow_ref.mut_base().restyle_damage.insert(damage); - // FIXME(pcwalton): Fragment restyle damage too? - flow_ref.repair_style_and_bubble_inline_sizes(&style); - }, - SpecificFragmentInfo::ScannedText(_) => { - // Text fragments in ConstructionResult haven't been scanned yet - unreachable!() - }, - SpecificFragmentInfo::GeneratedContent(_) | - SpecificFragmentInfo::UnscannedText(_) => { - // We can't repair this unscanned text; we need to update the - // scanned text fragments. - // - // TODO: Add code to find and repair the ScannedText fragments? - return false; - }, - _ => { - fragment.repair_style(&style); - set_has_newly_constructed_flow_flag = true; - }, - } - } - true - }, - ConstructionResult::ConstructionItem(_) => false, - } - }; - if set_has_newly_constructed_flow_flag { - node.insert_flags(LayoutDataFlags::HAS_NEWLY_CONSTRUCTED_FLOW); - } - return result; - } -} - -impl<'a, ConcreteThreadSafeLayoutNode> PostorderNodeMutTraversal<ConcreteThreadSafeLayoutNode> - for FlowConstructor<'a, ConcreteThreadSafeLayoutNode> -where - ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode, -{ - // Construct Flow based on 'display', 'position', and 'float' values. - // - // CSS 2.1 Section 9.7 - // - // TODO: This should actually consult the table in that section to get the - // final computed value for 'display'. - fn process(&mut self, node: &ConcreteThreadSafeLayoutNode) { - node.insert_flags(LayoutDataFlags::HAS_NEWLY_CONSTRUCTED_FLOW); - - let style = node.style(self.style_context()); - - // Bail out if this node is display: none. The style system guarantees - // that we don't arrive here for children of those. - if style.get_box().display.is_none() { - self.set_flow_construction_result(node, ConstructionResult::None); - return; - } - - // Get the `display` property for this node, and determine whether this node is floated. - let (display, float, positioning) = match node.type_id() { - None => { - // Pseudo-element. - ( - style.get_box().display, - style.get_box().float, - style.get_box().position, - ) - }, - Some(LayoutNodeType::Element(_)) => { - let original_display = style.get_box().original_display; - // FIXME(emilio, #19771): This munged_display business is pretty - // wrong. After we fix this we should be able to unify the - // pseudo-element path too. - let munged_display = match original_display { - Display::Inline | Display::InlineBlock => original_display, - _ => style.get_box().display, - }; - ( - munged_display, - style.get_box().float, - style.get_box().position, - ) - }, - Some(LayoutNodeType::Text) => (Display::Inline, Float::None, Position::Static), - }; - - debug!( - "building flow for node: {:?} {:?} {:?} {:?}", - display, - float, - positioning, - node.type_id() - ); - - // Switch on display and floatedness. - match (display, float, positioning) { - // `display: none` contributes no flow construction result. - (Display::None, _, _) => { - self.set_flow_construction_result(node, ConstructionResult::None); - }, - - // Table items contribute table flow construction results. - (Display::Table, float_value, _) => { - let construction_result = self.build_flow_for_table(node, float_value); - self.set_flow_construction_result(node, construction_result) - }, - - // Absolutely positioned elements will have computed value of - // `float` as 'none' and `display` as per the table. - // Only match here for block items. If an item is absolutely - // positioned, but inline we shouldn't try to construct a block - // flow here - instead, let it match the inline case - // below. - (Display::Block, _, Position::Absolute) | (Display::Block, _, Position::Fixed) => { - let construction_result = self.build_flow_for_block(node, None); - self.set_flow_construction_result(node, construction_result) - }, - - // List items contribute their own special flows. - (Display::ListItem, float_value, _) => { - let construction_result = self.build_flow_for_list_item(node, float_value); - self.set_flow_construction_result(node, construction_result) - }, - - // Inline items that are absolutely-positioned contribute inline fragment construction - // results with a hypothetical fragment. - (Display::Inline, _, Position::Absolute) | - (Display::InlineBlock, _, Position::Absolute) => { - let construction_result = - self.build_fragment_for_absolutely_positioned_inline(node); - self.set_flow_construction_result(node, construction_result) - }, - - // Inline items contribute inline fragment construction results. - // - // FIXME(pcwalton, #3307): This is not sufficient to handle floated generated content. - (Display::Inline, Float::None, _) => { - let construction_result = self.build_fragments_for_inline(node); - self.set_flow_construction_result(node, construction_result) - }, - - // Inline-block items contribute inline fragment construction results. - (Display::InlineBlock, Float::None, _) => { - let construction_result = - self.build_fragment_for_inline_block_or_inline_flex(node, Display::InlineBlock); - self.set_flow_construction_result(node, construction_result) - }, - - // Table items contribute table flow construction results. - (Display::TableCaption, _, _) => { - let construction_result = self.build_flow_for_table_caption(node); - self.set_flow_construction_result(node, construction_result) - }, - - // Table items contribute table flow construction results. - (Display::TableColumnGroup, _, _) => { - let construction_result = self.build_flow_for_table_colgroup(node); - self.set_flow_construction_result(node, construction_result) - }, - - // Table items contribute table flow construction results. - (Display::TableColumn, _, _) => { - let construction_result = self.build_fragments_for_table_column(node); - self.set_flow_construction_result(node, construction_result) - }, - - // Table items contribute table flow construction results. - (Display::TableRowGroup, _, _) | - (Display::TableHeaderGroup, _, _) | - (Display::TableFooterGroup, _, _) => { - let construction_result = self.build_flow_for_table_rowgroup(node); - self.set_flow_construction_result(node, construction_result) - }, - - // Table items contribute table flow construction results. - (Display::TableRow, _, _) => { - let construction_result = self.build_flow_for_table_row(node); - self.set_flow_construction_result(node, construction_result) - }, - - // Table items contribute table flow construction results. - (Display::TableCell, _, _) => { - let construction_result = self.build_flow_for_table_cell(node); - self.set_flow_construction_result(node, construction_result) - }, - - // Flex items contribute flex flow construction results. - (Display::Flex, float_value, _) => { - let float_kind = FloatKind::from_property(float_value); - let construction_result = self.build_flow_for_flex(node, float_kind); - self.set_flow_construction_result(node, construction_result) - }, - - (Display::InlineFlex, _, _) => { - let construction_result = - self.build_fragment_for_inline_block_or_inline_flex(node, Display::InlineFlex); - self.set_flow_construction_result(node, construction_result) - }, - - // Block flows that are not floated contribute block flow construction results. - // - // TODO(pcwalton): Make this only trigger for blocks and handle the other `display` - // properties separately. - (_, float_value, _) => { - let float_kind = FloatKind::from_property(float_value); - let construction_result = self.build_flow_for_block(node, float_kind); - self.set_flow_construction_result(node, construction_result) - }, - } - } -} - -/// A utility trait with some useful methods for node queries. -trait NodeUtils { - /// Returns true if this node doesn't render its kids and false otherwise. - fn is_replaced_content(&self) -> bool; - - fn construction_result_mut(self, layout_data: &mut LayoutData) -> &mut ConstructionResult; - - /// Sets the construction result of a flow. - fn set_flow_construction_result(self, result: ConstructionResult); - - /// Returns the construction result for this node. - fn get_construction_result(self) -> ConstructionResult; -} - -impl<ConcreteThreadSafeLayoutNode> NodeUtils for ConcreteThreadSafeLayoutNode -where - ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode, -{ - fn is_replaced_content(&self) -> bool { - match self.type_id() { - Some(LayoutNodeType::Text) | - Some(LayoutNodeType::Element(LayoutElementType::HTMLImageElement)) | - Some(LayoutNodeType::Element(LayoutElementType::HTMLMediaElement)) | - Some(LayoutNodeType::Element(LayoutElementType::HTMLIFrameElement)) | - Some(LayoutNodeType::Element(LayoutElementType::HTMLCanvasElement)) | - Some(LayoutNodeType::Element(LayoutElementType::SVGSVGElement)) => true, - Some(LayoutNodeType::Element(LayoutElementType::HTMLObjectElement)) => { - let elem = self.as_element().unwrap(); - let type_and_data = ( - elem.get_attr(&ns!(), &local_name!("type")), - elem.get_attr(&ns!(), &local_name!("data")), - ); - match type_and_data { - (None, Some(uri)) => is_image_data(uri), - _ => false, - } - }, - Some(LayoutNodeType::Element(_)) => false, - None => self.get_pseudo_element_type().is_replaced_content(), - } - } - - fn construction_result_mut(self, data: &mut LayoutData) -> &mut ConstructionResult { - match self.get_pseudo_element_type() { - PseudoElementType::Before => &mut data.before_flow_construction_result, - PseudoElementType::After => &mut data.after_flow_construction_result, - PseudoElementType::DetailsSummary => &mut data.details_summary_flow_construction_result, - PseudoElementType::DetailsContent => &mut data.details_content_flow_construction_result, - PseudoElementType::Normal => &mut data.flow_construction_result, - } - } - - #[inline(always)] - fn set_flow_construction_result(self, result: ConstructionResult) { - let mut layout_data = self.mutate_layout_data().unwrap(); - let dst = self.construction_result_mut(&mut *layout_data); - *dst = result; - } - - #[inline(always)] - fn get_construction_result(self) -> ConstructionResult { - let mut layout_data = self.mutate_layout_data().unwrap(); - self.construction_result_mut(&mut *layout_data).get() - } -} - -impl FlowRef { - /// Adds a new flow as a child of this flow. Fails if this flow is marked as a leaf. - fn add_new_child(&mut self, mut new_child: FlowRef) { - { - let kid_base = FlowRef::deref_mut(&mut new_child).mut_base(); - kid_base.parallel.parent = parallel::mut_owned_flow_to_unsafe_flow(self); - } - - let base = FlowRef::deref_mut(self).mut_base(); - base.children.push_back(new_child); - let _ = base.parallel.children_count.fetch_add(1, Ordering::Relaxed); - } - - /// Finishes a flow. Once a flow is finished, no more child flows or fragments may be added to - /// it. This will normally run the bubble-inline-sizes (minimum and preferred -- i.e. intrinsic - /// -- inline-size) calculation, unless the global `bubble_inline-sizes_separately` flag is on. - /// - /// All flows must be finished at some point, or they will not have their intrinsic inline-sizes - /// properly computed. (This is not, however, a memory safety problem.) - fn finish(&mut self) { - if !opts::get().bubble_inline_sizes_separately { - FlowRef::deref_mut(self).bubble_inline_sizes(); - FlowRef::deref_mut(self) - .mut_base() - .restyle_damage - .remove(ServoRestyleDamage::BUBBLE_ISIZES); - } - } -} - -/// Strips ignorable whitespace from the start of a list of fragments. -pub fn strip_ignorable_whitespace_from_start(this: &mut LinkedList<Fragment>) { - if this.is_empty() { - return; // Fast path. - } - - let mut leading_fragments_consisting_of_solely_bidi_control_characters = LinkedList::new(); - while !this.is_empty() { - match this - .front_mut() - .as_mut() - .unwrap() - .strip_leading_whitespace_if_necessary() - { - WhitespaceStrippingResult::RetainFragment => break, - WhitespaceStrippingResult::FragmentContainedOnlyBidiControlCharacters => { - leading_fragments_consisting_of_solely_bidi_control_characters - .push_back(this.pop_front().unwrap()) - }, - WhitespaceStrippingResult::FragmentContainedOnlyWhitespace => { - let removed_fragment = this.pop_front().unwrap(); - if let Some(ref mut remaining_fragment) = this.front_mut() { - remaining_fragment.meld_with_prev_inline_fragment(&removed_fragment); - } - }, - } - } - prepend_from( - this, - &mut leading_fragments_consisting_of_solely_bidi_control_characters, - ); -} - -/// Strips ignorable whitespace from the end of a list of fragments. -pub fn strip_ignorable_whitespace_from_end(this: &mut LinkedList<Fragment>) { - if this.is_empty() { - return; - } - - let mut trailing_fragments_consisting_of_solely_bidi_control_characters = LinkedList::new(); - while !this.is_empty() { - match this - .back_mut() - .as_mut() - .unwrap() - .strip_trailing_whitespace_if_necessary() - { - WhitespaceStrippingResult::RetainFragment => break, - WhitespaceStrippingResult::FragmentContainedOnlyBidiControlCharacters => { - trailing_fragments_consisting_of_solely_bidi_control_characters - .push_front(this.pop_back().unwrap()) - }, - WhitespaceStrippingResult::FragmentContainedOnlyWhitespace => { - let removed_fragment = this.pop_back().unwrap(); - if let Some(ref mut remaining_fragment) = this.back_mut() { - remaining_fragment.meld_with_next_inline_fragment(&removed_fragment); - } - }, - } - } - this.append(&mut trailing_fragments_consisting_of_solely_bidi_control_characters); -} - -/// If the 'unicode-bidi' property has a value other than 'normal', return the bidi control codes -/// to inject before and after the text content of the element. -fn bidi_control_chars(style: &ServoArc<ComputedValues>) -> Option<(&'static str, &'static str)> { - use style::computed_values::direction::T::*; - use style::computed_values::unicode_bidi::T::*; - - let unicode_bidi = style.get_text().unicode_bidi; - let direction = style.get_inherited_box().direction; - - // See the table in http://dev.w3.org/csswg/css-writing-modes/#unicode-bidi - match (unicode_bidi, direction) { - (Normal, _) => None, - (Embed, Ltr) => Some(("\u{202A}", "\u{202C}")), - (Embed, Rtl) => Some(("\u{202B}", "\u{202C}")), - (Isolate, Ltr) => Some(("\u{2066}", "\u{2069}")), - (Isolate, Rtl) => Some(("\u{2067}", "\u{2069}")), - (BidiOverride, Ltr) => Some(("\u{202D}", "\u{202C}")), - (BidiOverride, Rtl) => Some(("\u{202E}", "\u{202C}")), - (IsolateOverride, Ltr) => Some(("\u{2068}\u{202D}", "\u{202C}\u{2069}")), - (IsolateOverride, Rtl) => Some(("\u{2068}\u{202E}", "\u{202C}\u{2069}")), - (Plaintext, _) => Some(("\u{2068}", "\u{2069}")), - } -} - -fn control_chars_to_fragment<E>( - node: &InlineFragmentNodeInfo, - context: &SharedStyleContext, - text: &str, - restyle_damage: RestyleDamage, -) -> Fragment -where - E: TElement, -{ - let info = SpecificFragmentInfo::UnscannedText(Box::new(UnscannedTextFragmentInfo::new( - Box::<str>::from(text), - None, - ))); - let text_style = context.stylist.style_for_anonymous::<E>( - &context.guards, - &PseudoElement::ServoText, - &node.style, - ); - - Fragment::from_opaque_node_and_style( - node.address, - node.pseudo, - text_style, - node.selected_style.clone(), - restyle_damage, - info, - ) -} - -/// Returns true if this node has non-zero padding or border. -fn has_padding_or_border(values: &ComputedValues) -> bool { - let padding = values.get_padding(); - let border = values.get_border(); - - !padding.padding_top.is_definitely_zero() || - !padding.padding_right.is_definitely_zero() || - !padding.padding_bottom.is_definitely_zero() || - !padding.padding_left.is_definitely_zero() || - border.border_top_width.px() != 0. || - border.border_right_width.px() != 0. || - border.border_bottom_width.px() != 0. || - border.border_left_width.px() != 0. -} - -/// Maintains a stack of anonymous boxes needed to ensure that the flow tree is *legal*. The tree -/// is legal if it follows the rules in CSS 2.1 § 17.2.1. -/// -/// As an example, the legalizer makes sure that table row flows contain only table cells. If the -/// flow constructor attempts to place, say, a block flow directly underneath the table row, the -/// legalizer generates an anonymous table cell in between to hold the block. -/// -/// Generally, the flow constructor should use `Legalizer::add_child()` instead of calling -/// `Flow::add_new_child()` directly. This ensures that the flow tree remains legal at all times -/// and centralizes the anonymous flow generation logic in one place. -struct Legalizer { - /// A stack of anonymous flows that have yet to be finalized (i.e. that still could acquire new - /// children). - stack: Vec<FlowRef>, -} - -impl Legalizer { - /// Creates a new legalizer. - fn new() -> Legalizer { - Legalizer { stack: vec![] } - } - - /// Makes the `child` flow a new child of `parent`. Anonymous flows are automatically inserted - /// to keep the tree legal. - fn add_child<E>( - &mut self, - context: &SharedStyleContext, - parent: &mut FlowRef, - mut child: FlowRef, - ) where - E: TElement, - { - while !self.stack.is_empty() { - if self.try_to_add_child::<E>(context, parent, &mut child) { - return; - } - self.flush_top_of_stack(parent) - } - - while !self.try_to_add_child::<E>(context, parent, &mut child) { - self.push_next_anonymous_flow::<E>(context, parent) - } - } - - /// Flushes all flows we've been gathering up. - fn finish(mut self, parent: &mut FlowRef) { - while !self.stack.is_empty() { - self.flush_top_of_stack(parent) - } - } - - /// Attempts to make `child` a child of `parent`. On success, this returns true. If this would - /// make the tree illegal, this method does nothing and returns false. - /// - /// This method attempts to create anonymous blocks in between `parent` and `child` if and only - /// if those blocks will only ever have `child` as their sole child. At present, this is only - /// true for anonymous block children of flex flows. - fn try_to_add_child<E>( - &mut self, - context: &SharedStyleContext, - parent: &mut FlowRef, - child: &mut FlowRef, - ) -> bool - where - E: TElement, - { - let parent = self.stack.last_mut().unwrap_or(parent); - let (parent_class, child_class) = (parent.class(), child.class()); - match (parent_class, child_class) { - (FlowClass::TableWrapper, FlowClass::Table) | - (FlowClass::Table, FlowClass::TableColGroup) | - (FlowClass::Table, FlowClass::TableRowGroup) | - (FlowClass::Table, FlowClass::TableRow) | - (FlowClass::Table, FlowClass::TableCaption) | - (FlowClass::TableRowGroup, FlowClass::TableRow) | - (FlowClass::TableRow, FlowClass::TableCell) => { - parent.add_new_child((*child).clone()); - true - }, - - (FlowClass::TableWrapper, _) | - (FlowClass::Table, _) | - (FlowClass::TableRowGroup, _) | - (FlowClass::TableRow, _) | - (_, FlowClass::Table) | - (_, FlowClass::TableColGroup) | - (_, FlowClass::TableRowGroup) | - (_, FlowClass::TableRow) | - (_, FlowClass::TableCaption) | - (_, FlowClass::TableCell) => false, - - (FlowClass::Flex, FlowClass::Inline) => { - FlowRef::deref_mut(child) - .mut_base() - .flags - .insert(FlowFlags::MARGINS_CANNOT_COLLAPSE); - let mut block_wrapper = Legalizer::create_anonymous_flow::<E, _>( - context, - parent, - &[PseudoElement::ServoAnonymousBlock], - SpecificFragmentInfo::Generic, - BlockFlow::from_fragment, - ); - - { - let flag = if parent.as_flex().main_mode() == Direction::Inline { - FragmentFlags::IS_INLINE_FLEX_ITEM - } else { - FragmentFlags::IS_BLOCK_FLEX_ITEM - }; - let block = FlowRef::deref_mut(&mut block_wrapper).as_mut_block(); - block.base.flags.insert(FlowFlags::MARGINS_CANNOT_COLLAPSE); - block.fragment.flags.insert(flag); - } - block_wrapper.add_new_child((*child).clone()); - block_wrapper.finish(); - parent.add_new_child(block_wrapper); - true - }, - - (FlowClass::Flex, _) => { - { - let flag = if parent.as_flex().main_mode() == Direction::Inline { - FragmentFlags::IS_INLINE_FLEX_ITEM - } else { - FragmentFlags::IS_BLOCK_FLEX_ITEM - }; - let block = FlowRef::deref_mut(child).as_mut_block(); - block.base.flags.insert(FlowFlags::MARGINS_CANNOT_COLLAPSE); - block.fragment.flags.insert(flag); - } - parent.add_new_child((*child).clone()); - true - }, - - _ => { - parent.add_new_child((*child).clone()); - true - }, - } - } - - /// Finalizes the flow on the top of the stack. - fn flush_top_of_stack(&mut self, parent: &mut FlowRef) { - let mut child = self.stack.pop().expect("flush_top_of_stack(): stack empty"); - child.finish(); - self.stack.last_mut().unwrap_or(parent).add_new_child(child) - } - - /// Adds the anonymous flow that would be necessary to make an illegal child of `parent` legal - /// to the stack. - fn push_next_anonymous_flow<E>(&mut self, context: &SharedStyleContext, parent: &FlowRef) - where - E: TElement, - { - let parent_class = self.stack.last().unwrap_or(parent).class(); - match parent_class { - FlowClass::TableRow => self.push_new_anonymous_flow::<E, _>( - context, - parent, - &[PseudoElement::ServoAnonymousTableCell], - SpecificFragmentInfo::TableCell, - TableCellFlow::from_fragment, - ), - FlowClass::Table | FlowClass::TableRowGroup => self.push_new_anonymous_flow::<E, _>( - context, - parent, - &[PseudoElement::ServoAnonymousTableRow], - SpecificFragmentInfo::TableRow, - TableRowFlow::from_fragment, - ), - FlowClass::TableWrapper => self.push_new_anonymous_flow::<E, _>( - context, - parent, - &[PseudoElement::ServoAnonymousTable], - SpecificFragmentInfo::Table, - TableFlow::from_fragment, - ), - _ => self.push_new_anonymous_flow::<E, _>( - context, - parent, - &[ - PseudoElement::ServoTableWrapper, - PseudoElement::ServoAnonymousTableWrapper, - ], - SpecificFragmentInfo::TableWrapper, - TableWrapperFlow::from_fragment, - ), - } - } - - /// Creates an anonymous flow and pushes it onto the stack. - fn push_new_anonymous_flow<E, F>( - &mut self, - context: &SharedStyleContext, - reference: &FlowRef, - pseudos: &[PseudoElement], - specific_fragment_info: SpecificFragmentInfo, - constructor: fn(Fragment) -> F, - ) where - E: TElement, - F: Flow, - { - let new_flow = Self::create_anonymous_flow::<E, _>( - context, - reference, - pseudos, - specific_fragment_info, - constructor, - ); - self.stack.push(new_flow) - } - - /// Creates a new anonymous flow. The new flow is identical to `reference` except with all - /// styles applying to every pseudo-element in `pseudos` applied. - /// - /// This method invokes the supplied constructor function on the given specific fragment info - /// in order to actually generate the flow. - fn create_anonymous_flow<E, F>( - context: &SharedStyleContext, - reference: &FlowRef, - pseudos: &[PseudoElement], - specific_fragment_info: SpecificFragmentInfo, - constructor: fn(Fragment) -> F, - ) -> FlowRef - where - E: TElement, - F: Flow, - { - let reference_block = reference.as_block(); - let mut new_style = reference_block.fragment.style.clone(); - for pseudo in pseudos { - new_style = - context - .stylist - .style_for_anonymous::<E>(&context.guards, pseudo, &new_style); - } - let fragment = reference_block - .fragment - .create_similar_anonymous_fragment(new_style, specific_fragment_info); - FlowRef::new(Arc::new(constructor(fragment))) - } -} - -pub fn is_image_data(uri: &str) -> bool { - static TYPES: &'static [&'static str] = - &["data:image/png", "data:image/gif", "data:image/jpeg"]; - TYPES.iter().any(|&type_| uri.starts_with(type_)) -} diff --git a/components/layout_2020/context.rs b/components/layout_2020/context.rs index 015cc9ac40d..72398d85a80 100644 --- a/components/layout_2020/context.rs +++ b/components/layout_2020/context.rs @@ -2,100 +2,12 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -//! Data needed by the layout thread. - -use crate::display_list::items::{OpaqueNode, WebRenderImageInfo}; -use crate::opaque_node::OpaqueNodeMethods; -use fnv::FnvHasher; -use gfx::font_cache_thread::FontCacheThread; -use gfx::font_context::FontContext; -use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use msg::constellation_msg::PipelineId; -use net_traits::image_cache::{CanRequestImages, ImageCache, ImageState}; -use net_traits::image_cache::{ImageOrMetadataAvailable, UsePlaceholder}; -use parking_lot::RwLock; -use script_layout_interface::{PendingImage, PendingImageState}; -use script_traits::Painter; -use script_traits::UntrustedNodeAddress; -use servo_atoms::Atom; -use servo_url::ServoUrl; -use std::cell::{RefCell, RefMut}; -use std::collections::HashMap; -use std::hash::BuildHasherDefault; -use std::sync::{Arc, Mutex}; -use std::thread; -use style::context::RegisteredSpeculativePainter; use style::context::SharedStyleContext; -pub type LayoutFontContext = FontContext<FontCacheThread>; - -thread_local!(static FONT_CONTEXT_KEY: RefCell<Option<LayoutFontContext>> = RefCell::new(None)); - -pub fn with_thread_local_font_context<F, R>(layout_context: &LayoutContext, f: F) -> R -where - F: FnOnce(&mut LayoutFontContext) -> R, -{ - FONT_CONTEXT_KEY.with(|k| { - let mut font_context = k.borrow_mut(); - if font_context.is_none() { - let font_cache_thread = layout_context.font_cache_thread.lock().unwrap().clone(); - *font_context = Some(FontContext::new(font_cache_thread)); - } - f(&mut RefMut::map(font_context, |x| x.as_mut().unwrap())) - }) -} - -pub fn malloc_size_of_persistent_local_context(ops: &mut MallocSizeOfOps) -> usize { - FONT_CONTEXT_KEY.with(|r| { - if let Some(ref context) = *r.borrow() { - context.size_of(ops) - } else { - 0 - } - }) -} - -/// Layout information shared among all workers. This must be thread-safe. pub struct LayoutContext<'a> { - /// The pipeline id of this LayoutContext. pub id: PipelineId, - - /// Bits shared by the layout and style system. pub style_context: SharedStyleContext<'a>, - - /// Reference to the script thread image cache. - pub image_cache: Arc<dyn ImageCache>, - - /// Interface to the font cache thread. - pub font_cache_thread: Mutex<FontCacheThread>, - - /// A cache of WebRender image info. - pub webrender_image_cache: Arc< - RwLock< - HashMap<(ServoUrl, UsePlaceholder), WebRenderImageInfo, BuildHasherDefault<FnvHasher>>, - >, - >, - - /// Paint worklets - pub registered_painters: &'a dyn RegisteredPainters, - - /// A list of in-progress image loads to be shared with the script thread. - /// A None value means that this layout was not initiated by the script thread. - pub pending_images: Option<Mutex<Vec<PendingImage>>>, - - /// A list of nodes that have just initiated a CSS transition. - /// A None value means that this layout was not initiated by the script thread. - pub newly_transitioning_nodes: Option<Mutex<Vec<UntrustedNodeAddress>>>, -} - -impl<'a> Drop for LayoutContext<'a> { - fn drop(&mut self) { - if !thread::panicking() { - if let Some(ref pending_images) = self.pending_images { - assert!(pending_images.lock().unwrap().is_empty()); - } - } - } } impl<'a> LayoutContext<'a> { @@ -103,99 +15,4 @@ impl<'a> LayoutContext<'a> { pub fn shared_context(&self) -> &SharedStyleContext { &self.style_context } - - pub fn get_or_request_image_or_meta( - &self, - node: OpaqueNode, - url: ServoUrl, - use_placeholder: UsePlaceholder, - ) -> Option<ImageOrMetadataAvailable> { - //XXXjdm For cases where we do not request an image, we still need to - // ensure the node gets another script-initiated reflow or it - // won't be requested at all. - let can_request = if self.pending_images.is_some() { - CanRequestImages::Yes - } else { - CanRequestImages::No - }; - - // See if the image is already available - let result = - self.image_cache - .find_image_or_metadata(url.clone(), use_placeholder, can_request); - match result { - Ok(image_or_metadata) => Some(image_or_metadata), - // Image failed to load, so just return nothing - Err(ImageState::LoadError) => None, - // Not yet requested - request image or metadata from the cache - Err(ImageState::NotRequested(id)) => { - let image = PendingImage { - state: PendingImageState::Unrequested(url), - node: node.to_untrusted_node_address(), - id: id, - }; - self.pending_images - .as_ref() - .unwrap() - .lock() - .unwrap() - .push(image); - None - }, - // Image has been requested, is still pending. Return no image for this paint loop. - // When the image loads it will trigger a reflow and/or repaint. - Err(ImageState::Pending(id)) => { - //XXXjdm if self.pending_images is not available, we should make sure that - // this node gets marked dirty again so it gets a script-initiated - // reflow that deals with this properly. - if let Some(ref pending_images) = self.pending_images { - let image = PendingImage { - state: PendingImageState::PendingResponse, - node: node.to_untrusted_node_address(), - id: id, - }; - pending_images.lock().unwrap().push(image); - } - None - }, - } - } - - pub fn get_webrender_image_for_url( - &self, - node: OpaqueNode, - url: ServoUrl, - use_placeholder: UsePlaceholder, - ) -> Option<WebRenderImageInfo> { - if let Some(existing_webrender_image) = self - .webrender_image_cache - .read() - .get(&(url.clone(), use_placeholder)) - { - return Some((*existing_webrender_image).clone()); - } - - match self.get_or_request_image_or_meta(node, url.clone(), use_placeholder) { - Some(ImageOrMetadataAvailable::ImageAvailable(image, _)) => { - let image_info = WebRenderImageInfo::from_image(&*image); - if image_info.key.is_none() { - Some(image_info) - } else { - let mut webrender_image_cache = self.webrender_image_cache.write(); - webrender_image_cache.insert((url, use_placeholder), image_info); - Some(image_info) - } - }, - None | Some(ImageOrMetadataAvailable::MetadataAvailable(_)) => None, - } - } -} - -/// A registered painter -pub trait RegisteredPainter: RegisteredSpeculativePainter + Painter {} - -/// A set of registered painters -pub trait RegisteredPainters: Sync { - /// Look up a painter - fn get(&self, name: &Atom) -> Option<&dyn RegisteredPainter>; } diff --git a/components/layout_2020/data.rs b/components/layout_2020/data.rs index 5f6672d1488..c406a1bc4e7 100644 --- a/components/layout_2020/data.rs +++ b/components/layout_2020/data.rs @@ -2,67 +2,17 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use crate::construct::ConstructionResult; -use atomic_refcell::AtomicRefCell; use script_layout_interface::StyleData; #[repr(C)] pub struct StyleAndLayoutData { - /// Data accessed by script_layout_interface. This must be first to allow - /// casting between StyleAndLayoutData and StyleData. pub style_data: StyleData, - /// The layout data associated with a node. - pub layout_data: AtomicRefCell<LayoutData>, } impl StyleAndLayoutData { pub fn new() -> Self { Self { style_data: StyleData::new(), - layout_data: AtomicRefCell::new(LayoutData::new()), } } } - -/// Data that layout associates with a node. -#[repr(C)] -pub struct LayoutData { - /// The current results of flow construction for this node. This is either a - /// flow or a `ConstructionItem`. See comments in `construct.rs` for more - /// details. - pub flow_construction_result: ConstructionResult, - - pub before_flow_construction_result: ConstructionResult, - - pub after_flow_construction_result: ConstructionResult, - - pub details_summary_flow_construction_result: ConstructionResult, - - pub details_content_flow_construction_result: ConstructionResult, - - /// Various flags. - pub flags: LayoutDataFlags, -} - -impl LayoutData { - /// Creates new layout data. - pub fn new() -> LayoutData { - Self { - flow_construction_result: ConstructionResult::None, - before_flow_construction_result: ConstructionResult::None, - after_flow_construction_result: ConstructionResult::None, - details_summary_flow_construction_result: ConstructionResult::None, - details_content_flow_construction_result: ConstructionResult::None, - flags: LayoutDataFlags::empty(), - } - } -} - -bitflags! { - pub struct LayoutDataFlags: u8 { - #[doc = "Whether a flow has been newly constructed."] - const HAS_NEWLY_CONSTRUCTED_FLOW = 0x01; - #[doc = "Whether this node has been traversed by layout."] - const HAS_BEEN_TRAVERSED = 0x02; - } -} diff --git a/components/layout_2020/display_list/background.rs b/components/layout_2020/display_list/background.rs deleted file mode 100644 index c6fa1691e46..00000000000 --- a/components/layout_2020/display_list/background.rs +++ /dev/null @@ -1,336 +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/. */ - -use crate::display_list::border; -use app_units::Au; -use euclid::{Point2D, Rect, SideOffsets2D, Size2D}; -use style::computed_values::background_attachment::single_value::T as BackgroundAttachment; -use style::computed_values::background_clip::single_value::T as BackgroundClip; -use style::computed_values::background_origin::single_value::T as BackgroundOrigin; -use style::properties::style_structs::Background; -use style::values::computed::{BackgroundSize, NonNegativeLengthPercentageOrAuto}; -use style::values::specified::background::BackgroundRepeatKeyword; -use webrender_api::BorderRadius; - -/// Placment information for both image and gradient backgrounds. -#[derive(Clone, Copy, Debug)] -pub struct BackgroundPlacement { - /// Rendering bounds. The background will start in the uppper-left corner - /// and fill the whole area. - pub bounds: Rect<Au>, - /// Background tile size. Some backgrounds are repeated. These are the - /// dimensions of a single image of the background. - pub tile_size: Size2D<Au>, - /// Spacing between tiles. Some backgrounds are not repeated seamless - /// but have seams between them like tiles in real life. - pub tile_spacing: Size2D<Au>, - /// A clip area. While the background is rendered according to all the - /// measures above it is only shown within these bounds. - pub clip_rect: Rect<Au>, - /// Rounded corners for the clip_rect. - pub clip_radii: BorderRadius, - /// Whether or not the background is fixed to the viewport. - pub fixed: bool, -} - -/// Access element at index modulo the array length. -/// -/// Obviously it does not work with empty arrays. -/// -/// This is used for multiple layered background images. -/// See: https://drafts.csswg.org/css-backgrounds-3/#layering -pub fn get_cyclic<T>(arr: &[T], index: usize) -> &T { - &arr[index % arr.len()] -} - -/// For a given area and an image compute how big the -/// image should be displayed on the background. -fn compute_background_image_size( - bg_size: BackgroundSize, - bounds_size: Size2D<Au>, - intrinsic_size: Option<Size2D<Au>>, -) -> Size2D<Au> { - match intrinsic_size { - None => match bg_size { - BackgroundSize::Cover | BackgroundSize::Contain => bounds_size, - BackgroundSize::ExplicitSize { width, height } => Size2D::new( - width - .to_used_value(bounds_size.width) - .unwrap_or(bounds_size.width), - height - .to_used_value(bounds_size.height) - .unwrap_or(bounds_size.height), - ), - }, - Some(own_size) => { - // If `image_aspect_ratio` < `bounds_aspect_ratio`, the image is tall; otherwise, it is - // wide. - let image_aspect_ratio = own_size.width.to_f32_px() / own_size.height.to_f32_px(); - let bounds_aspect_ratio = - bounds_size.width.to_f32_px() / bounds_size.height.to_f32_px(); - match (bg_size, image_aspect_ratio < bounds_aspect_ratio) { - (BackgroundSize::Contain, false) | (BackgroundSize::Cover, true) => Size2D::new( - bounds_size.width, - bounds_size.width.scale_by(image_aspect_ratio.recip()), - ), - (BackgroundSize::Contain, true) | (BackgroundSize::Cover, false) => Size2D::new( - bounds_size.height.scale_by(image_aspect_ratio), - bounds_size.height, - ), - ( - BackgroundSize::ExplicitSize { - width, - height: NonNegativeLengthPercentageOrAuto::Auto, - }, - _, - ) => { - let width = width - .to_used_value(bounds_size.width) - .unwrap_or(own_size.width); - Size2D::new(width, width.scale_by(image_aspect_ratio.recip())) - }, - ( - BackgroundSize::ExplicitSize { - width: NonNegativeLengthPercentageOrAuto::Auto, - height, - }, - _, - ) => { - let height = height - .to_used_value(bounds_size.height) - .unwrap_or(own_size.height); - Size2D::new(height.scale_by(image_aspect_ratio), height) - }, - (BackgroundSize::ExplicitSize { width, height }, _) => Size2D::new( - width - .to_used_value(bounds_size.width) - .unwrap_or(own_size.width), - height - .to_used_value(bounds_size.height) - .unwrap_or(own_size.height), - ), - } - }, - } -} - -/// Compute a rounded clip rect for the background. -pub fn clip( - bg_clip: BackgroundClip, - absolute_bounds: Rect<Au>, - border: SideOffsets2D<Au>, - border_padding: SideOffsets2D<Au>, - border_radii: BorderRadius, -) -> (Rect<Au>, BorderRadius) { - match bg_clip { - BackgroundClip::BorderBox => (absolute_bounds, border_radii), - BackgroundClip::PaddingBox => ( - absolute_bounds.inner_rect(border), - border::inner_radii(border_radii, border), - ), - BackgroundClip::ContentBox => ( - absolute_bounds.inner_rect(border_padding), - border::inner_radii(border_radii, border_padding), - ), - } -} - -/// Determines where to place an element background image or gradient. -/// -/// Photos have their resolution as intrinsic size while gradients have -/// no intrinsic size. -pub fn placement( - bg: &Background, - viewport_size: Size2D<Au>, - absolute_bounds: Rect<Au>, - intrinsic_size: Option<Size2D<Au>>, - border: SideOffsets2D<Au>, - border_padding: SideOffsets2D<Au>, - border_radii: BorderRadius, - index: usize, -) -> BackgroundPlacement { - let bg_attachment = *get_cyclic(&bg.background_attachment.0, index); - let bg_clip = *get_cyclic(&bg.background_clip.0, index); - let bg_origin = *get_cyclic(&bg.background_origin.0, index); - let bg_position_x = get_cyclic(&bg.background_position_x.0, index); - let bg_position_y = get_cyclic(&bg.background_position_y.0, index); - let bg_repeat = get_cyclic(&bg.background_repeat.0, index); - let bg_size = *get_cyclic(&bg.background_size.0, index); - - let (clip_rect, clip_radii) = clip( - bg_clip, - absolute_bounds, - border, - border_padding, - border_radii, - ); - - let mut fixed = false; - let mut bounds = match bg_attachment { - BackgroundAttachment::Scroll => match bg_origin { - BackgroundOrigin::BorderBox => absolute_bounds, - BackgroundOrigin::PaddingBox => absolute_bounds.inner_rect(border), - BackgroundOrigin::ContentBox => absolute_bounds.inner_rect(border_padding), - }, - BackgroundAttachment::Fixed => { - fixed = true; - Rect::new(Point2D::origin(), viewport_size) - }, - }; - - let mut tile_size = compute_background_image_size(bg_size, bounds.size, intrinsic_size); - - let mut tile_spacing = Size2D::zero(); - let own_position = bounds.size - tile_size; - let pos_x = bg_position_x.to_used_value(own_position.width); - let pos_y = bg_position_y.to_used_value(own_position.height); - tile_image_axis( - bg_repeat.0, - &mut bounds.origin.x, - &mut bounds.size.width, - &mut tile_size.width, - &mut tile_spacing.width, - pos_x, - clip_rect.origin.x, - clip_rect.size.width, - ); - tile_image_axis( - bg_repeat.1, - &mut bounds.origin.y, - &mut bounds.size.height, - &mut tile_size.height, - &mut tile_spacing.height, - pos_y, - clip_rect.origin.y, - clip_rect.size.height, - ); - - BackgroundPlacement { - bounds, - tile_size, - tile_spacing, - clip_rect, - clip_radii, - fixed, - } -} - -fn tile_image_round( - position: &mut Au, - size: &mut Au, - absolute_anchor_origin: Au, - image_size: &mut Au, -) { - if *size == Au(0) || *image_size == Au(0) { - *position = Au(0); - *size = Au(0); - return; - } - - let number_of_tiles = (size.to_f32_px() / image_size.to_f32_px()).round().max(1.0); - *image_size = *size / (number_of_tiles as i32); - tile_image(position, size, absolute_anchor_origin, *image_size); -} - -fn tile_image_spaced( - position: &mut Au, - size: &mut Au, - tile_spacing: &mut Au, - absolute_anchor_origin: Au, - image_size: Au, -) { - if *size == Au(0) || image_size == Au(0) { - *position = Au(0); - *size = Au(0); - *tile_spacing = Au(0); - return; - } - - // Per the spec, if the space available is not enough for two images, just tile as - // normal but only display a single tile. - if image_size * 2 >= *size { - tile_image(position, size, absolute_anchor_origin, image_size); - *tile_spacing = Au(0); - *size = image_size; - return; - } - - // Take the box size, remove room for two tiles on the edges, and then calculate how many - // other tiles fit in between them. - let size_remaining = *size - (image_size * 2); - let num_middle_tiles = (size_remaining.to_f32_px() / image_size.to_f32_px()).floor() as i32; - - // Allocate the remaining space as padding between tiles. background-position is ignored - // as per the spec, so the position is just the box origin. We are also ignoring - // background-attachment here, which seems unspecced when combined with - // background-repeat: space. - let space_for_middle_tiles = image_size * num_middle_tiles; - *tile_spacing = (size_remaining - space_for_middle_tiles) / (num_middle_tiles + 1); -} - -/// Tile an image -fn tile_image(position: &mut Au, size: &mut Au, absolute_anchor_origin: Au, image_size: Au) { - // Avoid division by zero below! - // Images with a zero width or height are not displayed. - // Therefore the positions do not matter and can be left unchanged. - // NOTE: A possible optimization is not to build - // display items in this case at all. - if image_size == Au(0) { - return; - } - - let delta_pixels = absolute_anchor_origin - *position; - let image_size_px = image_size.to_f32_px(); - let tile_count = ((delta_pixels.to_f32_px() + image_size_px - 1.0) / image_size_px).floor(); - let offset = image_size * (tile_count as i32); - let new_position = absolute_anchor_origin - offset; - *size = *position - new_position + *size; - *position = new_position; -} - -/// For either the x or the y axis ajust various values to account for tiling. -/// -/// This is done separately for both axes because the repeat keywords may differ. -fn tile_image_axis( - repeat: BackgroundRepeatKeyword, - position: &mut Au, - size: &mut Au, - tile_size: &mut Au, - tile_spacing: &mut Au, - offset: Au, - clip_origin: Au, - clip_size: Au, -) { - let absolute_anchor_origin = *position + offset; - match repeat { - BackgroundRepeatKeyword::NoRepeat => { - *position += offset; - *size = *tile_size; - }, - BackgroundRepeatKeyword::Repeat => { - *position = clip_origin; - *size = clip_size; - tile_image(position, size, absolute_anchor_origin, *tile_size); - }, - BackgroundRepeatKeyword::Space => { - tile_image_spaced( - position, - size, - tile_spacing, - absolute_anchor_origin, - *tile_size, - ); - let combined_tile_size = *tile_size + *tile_spacing; - *position = clip_origin; - *size = clip_size; - tile_image(position, size, absolute_anchor_origin, combined_tile_size); - }, - BackgroundRepeatKeyword::Round => { - tile_image_round(position, size, absolute_anchor_origin, tile_size); - *position = clip_origin; - *size = clip_size; - tile_image(position, size, absolute_anchor_origin, *tile_size); - }, - } -} diff --git a/components/layout_2020/display_list/border.rs b/components/layout_2020/display_list/border.rs deleted file mode 100644 index fff18fcf6c0..00000000000 --- a/components/layout_2020/display_list/border.rs +++ /dev/null @@ -1,199 +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/. */ - -use crate::display_list::ToLayout; -use app_units::Au; -use euclid::{Rect, SideOffsets2D, Size2D}; -use style::computed_values::border_image_outset::T as BorderImageOutset; -use style::properties::style_structs::Border; -use style::values::computed::NumberOrPercentage; -use style::values::computed::{BorderCornerRadius, BorderImageWidth}; -use style::values::computed::{BorderImageSideWidth, NonNegativeLengthOrNumber}; -use style::values::generics::rect::Rect as StyleRect; -use style::values::generics::NonNegative; -use webrender_api::units::{LayoutSideOffsets, LayoutSize}; -use webrender_api::{BorderRadius, BorderSide, BorderStyle, ColorF, NormalBorder}; - -/// Computes a border radius size against the containing size. -/// -/// Note that percentages in `border-radius` are resolved against the relevant -/// box dimension instead of only against the width per [1]: -/// -/// > Percentages: Refer to corresponding dimension of the border box. -/// -/// [1]: https://drafts.csswg.org/css-backgrounds-3/#border-radius -fn corner_radius(radius: BorderCornerRadius, containing_size: Size2D<Au>) -> Size2D<Au> { - let w = radius.0.width().to_used_value(containing_size.width); - let h = radius.0.height().to_used_value(containing_size.height); - Size2D::new(w, h) -} - -fn scaled_radii(radii: BorderRadius, factor: f32) -> BorderRadius { - BorderRadius { - top_left: radii.top_left * factor, - top_right: radii.top_right * factor, - bottom_left: radii.bottom_left * factor, - bottom_right: radii.bottom_right * factor, - } -} - -fn overlapping_radii(size: LayoutSize, radii: BorderRadius) -> BorderRadius { - // No two corners' border radii may add up to more than the length of the edge - // between them. To prevent that, all radii are scaled down uniformly. - fn scale_factor(radius_a: f32, radius_b: f32, edge_length: f32) -> f32 { - let required = radius_a + radius_b; - - if required <= edge_length { - 1.0 - } else { - edge_length / required - } - } - - let top_factor = scale_factor(radii.top_left.width, radii.top_right.width, size.width); - let bottom_factor = scale_factor( - radii.bottom_left.width, - radii.bottom_right.width, - size.width, - ); - let left_factor = scale_factor(radii.top_left.height, radii.bottom_left.height, size.height); - let right_factor = scale_factor( - radii.top_right.height, - radii.bottom_right.height, - size.height, - ); - let min_factor = top_factor - .min(bottom_factor) - .min(left_factor) - .min(right_factor); - if min_factor < 1.0 { - scaled_radii(radii, min_factor) - } else { - radii - } -} - -/// Determine the four corner radii of a border. -/// -/// Radii may either be absolute or relative to the absolute bounds. -/// Each corner radius has a width and a height which may differ. -/// Lastly overlapping radii are shrank so they don't collide anymore. -pub fn radii(abs_bounds: Rect<Au>, border_style: &Border) -> BorderRadius { - // TODO(cgaebel): Support border radii even in the case of multiple border widths. - // This is an extension of supporting elliptical radii. For now, all percentage - // radii will be relative to the width. - - overlapping_radii( - abs_bounds.size.to_layout(), - BorderRadius { - top_left: corner_radius(border_style.border_top_left_radius, abs_bounds.size) - .to_layout(), - top_right: corner_radius(border_style.border_top_right_radius, abs_bounds.size) - .to_layout(), - bottom_right: corner_radius(border_style.border_bottom_right_radius, abs_bounds.size) - .to_layout(), - bottom_left: corner_radius(border_style.border_bottom_left_radius, abs_bounds.size) - .to_layout(), - }, - ) -} - -/// Calculates radii for the inner side. -/// -/// Radii usually describe the outer side of a border but for the lines to look nice -/// the inner radii need to be smaller depending on the line width. -/// -/// This is used to determine clipping areas. -pub fn inner_radii(mut radii: BorderRadius, offsets: SideOffsets2D<Au>) -> BorderRadius { - fn inner_length(x: f32, offset: Au) -> f32 { - 0.0_f32.max(x - offset.to_f32_px()) - } - radii.top_left.width = inner_length(radii.top_left.width, offsets.left); - radii.bottom_left.width = inner_length(radii.bottom_left.width, offsets.left); - - radii.top_right.width = inner_length(radii.top_right.width, offsets.right); - radii.bottom_right.width = inner_length(radii.bottom_right.width, offsets.right); - - radii.top_left.height = inner_length(radii.top_left.height, offsets.top); - radii.top_right.height = inner_length(radii.top_right.height, offsets.top); - - radii.bottom_left.height = inner_length(radii.bottom_left.height, offsets.bottom); - radii.bottom_right.height = inner_length(radii.bottom_right.height, offsets.bottom); - radii -} - -/// Creates a four-sided border with square corners and uniform color and width. -pub fn simple(color: ColorF, style: BorderStyle) -> NormalBorder { - let side = BorderSide { color, style }; - NormalBorder { - left: side, - right: side, - top: side, - bottom: side, - radius: BorderRadius::zero(), - do_aa: true, - } -} - -fn side_image_outset(outset: NonNegativeLengthOrNumber, border_width: Au) -> Au { - match outset { - NonNegativeLengthOrNumber::Length(length) => length.into(), - NonNegativeLengthOrNumber::Number(factor) => border_width.scale_by(factor.0), - } -} - -/// Compute the additional border-image area. -pub fn image_outset(outset: BorderImageOutset, border: SideOffsets2D<Au>) -> SideOffsets2D<Au> { - SideOffsets2D::new( - side_image_outset(outset.0, border.top), - side_image_outset(outset.1, border.right), - side_image_outset(outset.2, border.bottom), - side_image_outset(outset.3, border.left), - ) -} - -fn side_image_width( - border_image_width: BorderImageSideWidth, - border_width: f32, - total_length: Au, -) -> f32 { - match border_image_width { - BorderImageSideWidth::LengthPercentage(v) => v.to_used_value(total_length).to_f32_px(), - BorderImageSideWidth::Number(x) => border_width * x.0, - BorderImageSideWidth::Auto => border_width, - } -} - -pub fn image_width( - width: &BorderImageWidth, - border: LayoutSideOffsets, - border_area: Size2D<Au>, -) -> LayoutSideOffsets { - LayoutSideOffsets::new( - side_image_width(width.0, border.top, border_area.height), - side_image_width(width.1, border.right, border_area.width), - side_image_width(width.2, border.bottom, border_area.height), - side_image_width(width.3, border.left, border_area.width), - ) -} - -fn resolve_percentage(value: NonNegative<NumberOrPercentage>, length: i32) -> i32 { - match value.0 { - NumberOrPercentage::Percentage(p) => (p.0 * length as f32).round() as i32, - NumberOrPercentage::Number(n) => n.round() as i32, - } -} - -pub fn image_slice( - border_image_slice: &StyleRect<NonNegative<NumberOrPercentage>>, - width: i32, - height: i32, -) -> SideOffsets2D<i32> { - SideOffsets2D::new( - resolve_percentage(border_image_slice.0, height), - resolve_percentage(border_image_slice.1, width), - resolve_percentage(border_image_slice.2, height), - resolve_percentage(border_image_slice.3, width), - ) -} diff --git a/components/layout_2020/display_list/builder.rs b/components/layout_2020/display_list/builder.rs index e6ead2505ca..bc3756654fa 100644 --- a/components/layout_2020/display_list/builder.rs +++ b/components/layout_2020/display_list/builder.rs @@ -8,2968 +8,14 @@ //! list building, as the actual painting does not happen here—only deciding *what* we're going to //! paint. -use crate::block::BlockFlow; -use crate::context::LayoutContext; -use crate::display_list::background::{self, get_cyclic}; -use crate::display_list::border; -use crate::display_list::gradient; -use crate::display_list::items::{self, BaseDisplayItem, ClipScrollNode}; -use crate::display_list::items::{ClipScrollNodeIndex, ClipScrollNodeType, ClippingAndScrolling}; -use crate::display_list::items::{ClippingRegion, DisplayItem, DisplayItemMetadata, DisplayList}; -use crate::display_list::items::{CommonDisplayItem, DisplayListSection}; -use crate::display_list::items::{IframeDisplayItem, OpaqueNode}; -use crate::display_list::items::{PopAllTextShadowsDisplayItem, PushTextShadowDisplayItem}; -use crate::display_list::items::{StackingContext, StackingContextType, StickyFrameData}; -use crate::display_list::items::{TextOrientation, WebRenderImageInfo}; -use crate::display_list::ToLayout; -use crate::flow::{BaseFlow, Flow, FlowFlags}; -use crate::flow_ref::FlowRef; -use crate::fragment::SpecificFragmentInfo; -use crate::fragment::{CanvasFragmentSource, CoordinateSystem, Fragment, ScannedTextFragmentInfo}; -use crate::inline::InlineFragmentNodeFlags; -use crate::model::MaybeAuto; -use crate::table_cell::CollapsedBordersForCell; -use app_units::{Au, AU_PER_PX}; -use canvas_traits::canvas::{CanvasMsg, FromLayoutMsg}; -use embedder_traits::Cursor; -use euclid::{rect, Point2D, Rect, SideOffsets2D, Size2D, TypedRect, TypedSize2D}; +use crate::display_list::items::OpaqueNode; +use app_units::Au; +use euclid::Point2D; use fnv::FnvHashMap; use gfx::text::glyph::ByteIndex; use gfx::text::TextRun; -use gfx_traits::{combine_id_with_fragment_type, FragmentType, StackingContextId}; -use ipc_channel::ipc; -use msg::constellation_msg::PipelineId; -use net_traits::image_cache::UsePlaceholder; use range::Range; -use script_traits::IFrameSize; -use servo_config::opts; -use servo_geometry::{self, MaxRect}; -use std::default::Default; -use std::f32; -use std::mem; use std::sync::Arc; -use style::computed_values::border_style::T as BorderStyle; -use style::computed_values::overflow_x::T as StyleOverflow; -use style::computed_values::pointer_events::T as PointerEvents; -use style::computed_values::position::T as StylePosition; -use style::computed_values::visibility::T as Visibility; -use style::logical_geometry::{LogicalMargin, LogicalPoint, LogicalRect}; -use style::properties::{style_structs, ComputedValues}; -use style::servo::restyle_damage::ServoRestyleDamage; -use style::values::computed::effects::SimpleShadow; -use style::values::computed::image::{Image, ImageLayer}; -use style::values::computed::{Gradient, LengthOrAuto}; -use style::values::generics::background::BackgroundSize; -use style::values::generics::image::{GradientKind, PaintWorklet}; -use style::values::specified::ui::CursorKind; -use style::values::{Either, RGBA}; -use style_traits::ToCss; -use webrender_api::units::{LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D}; -use webrender_api::{self, BorderDetails, BorderRadius, BorderSide, BoxShadowClipMode, ColorF}; -use webrender_api::{ColorU, ExternalScrollId, FilterOp, GlyphInstance, ImageRendering, LineStyle}; -use webrender_api::{NinePatchBorder, NinePatchBorderSource, NormalBorder}; -use webrender_api::{ScrollSensitivity, StickyOffsetBounds}; - -static THREAD_TINT_COLORS: [ColorF; 8] = [ - ColorF { - r: 6.0 / 255.0, - g: 153.0 / 255.0, - b: 198.0 / 255.0, - a: 0.7, - }, - ColorF { - r: 255.0 / 255.0, - g: 212.0 / 255.0, - b: 83.0 / 255.0, - a: 0.7, - }, - ColorF { - r: 116.0 / 255.0, - g: 29.0 / 255.0, - b: 109.0 / 255.0, - a: 0.7, - }, - ColorF { - r: 204.0 / 255.0, - g: 158.0 / 255.0, - b: 199.0 / 255.0, - a: 0.7, - }, - ColorF { - r: 242.0 / 255.0, - g: 46.0 / 255.0, - b: 121.0 / 255.0, - a: 0.7, - }, - ColorF { - r: 116.0 / 255.0, - g: 203.0 / 255.0, - b: 196.0 / 255.0, - a: 0.7, - }, - ColorF { - r: 255.0 / 255.0, - g: 249.0 / 255.0, - b: 201.0 / 255.0, - a: 0.7, - }, - ColorF { - r: 137.0 / 255.0, - g: 196.0 / 255.0, - b: 78.0 / 255.0, - a: 0.7, - }, -]; - -pub struct InlineNodeBorderInfo { - is_first_fragment_of_element: bool, - is_last_fragment_of_element: bool, -} - -#[derive(Debug)] -struct StackingContextInfo { - children: Vec<StackingContext>, - clip_scroll_nodes: Vec<ClipScrollNodeIndex>, - real_stacking_context_id: StackingContextId, -} - -impl StackingContextInfo { - fn new(real_stacking_context_id: StackingContextId) -> StackingContextInfo { - StackingContextInfo { - children: Vec::new(), - clip_scroll_nodes: Vec::new(), - real_stacking_context_id, - } - } - - fn take_children(&mut self) -> Vec<StackingContext> { - mem::replace(&mut self.children, Vec::new()) - } -} - -pub struct StackingContextCollectionState { - /// The PipelineId of this stacking context collection. - pub pipeline_id: PipelineId, - - /// The root of the StackingContext tree. - pub root_stacking_context: StackingContext, - - /// StackingContext and ClipScrollNode children for each StackingContext. - stacking_context_info: FnvHashMap<StackingContextId, StackingContextInfo>, - - pub clip_scroll_nodes: Vec<ClipScrollNode>, - - /// The current stacking context id, used to keep track of state when building. - /// recursively building and processing the display list. - pub current_stacking_context_id: StackingContextId, - - /// The current reference frame ClipScrollNodeIndex. - pub current_real_stacking_context_id: StackingContextId, - - /// The next stacking context id that we will assign to a stacking context. - pub next_stacking_context_id: StackingContextId, - - /// The current reference frame id. This is used to assign items to the parent - /// reference frame when we encounter a fixed position stacking context. - pub current_parent_reference_frame_id: ClipScrollNodeIndex, - - /// The current clip and scroll info, used to keep track of state when - /// recursively building and processing the display list. - pub current_clipping_and_scrolling: ClippingAndScrolling, - - /// The clip and scroll info of the first ancestor which defines a containing block. - /// This is necessary because absolutely positioned items should be clipped - /// by their containing block's scroll root. - pub containing_block_clipping_and_scrolling: ClippingAndScrolling, - - /// A stack of clips used to cull display list entries that are outside the - /// rendered region. - pub clip_stack: Vec<Rect<Au>>, - - /// A stack of clips used to cull display list entries that are outside the - /// rendered region, but only collected at containing block boundaries. - pub containing_block_clip_stack: Vec<Rect<Au>>, - - /// The flow parent's content box, used to calculate sticky constraints. - parent_stacking_relative_content_box: Rect<Au>, -} - -impl StackingContextCollectionState { - pub fn new(pipeline_id: PipelineId) -> StackingContextCollectionState { - let root_clip_indices = - ClippingAndScrolling::simple(ClipScrollNodeIndex::root_scroll_node()); - - let mut stacking_context_info = FnvHashMap::default(); - stacking_context_info.insert( - StackingContextId::root(), - StackingContextInfo::new(StackingContextId::root()), - ); - - // We add two empty nodes to represent the WebRender root reference frame and - // root scroll nodes. WebRender adds these automatically and we add them here - // so that the ids in the array match up with the ones we assign during display - // list building. We ignore these two nodes during conversion to WebRender - // display lists. - let clip_scroll_nodes = vec![ClipScrollNode::placeholder(), ClipScrollNode::placeholder()]; - - StackingContextCollectionState { - pipeline_id: pipeline_id, - root_stacking_context: StackingContext::root(), - stacking_context_info, - clip_scroll_nodes, - current_stacking_context_id: StackingContextId::root(), - current_real_stacking_context_id: StackingContextId::root(), - next_stacking_context_id: StackingContextId::root().next(), - current_parent_reference_frame_id: ClipScrollNodeIndex::root_reference_frame(), - current_clipping_and_scrolling: root_clip_indices, - containing_block_clipping_and_scrolling: root_clip_indices, - clip_stack: Vec::new(), - containing_block_clip_stack: Vec::new(), - parent_stacking_relative_content_box: Rect::zero(), - } - } - - fn allocate_stacking_context_info( - &mut self, - stacking_context_type: StackingContextType, - ) -> StackingContextId { - let next_stacking_context_id = self.next_stacking_context_id.next(); - let allocated_id = - mem::replace(&mut self.next_stacking_context_id, next_stacking_context_id); - - let real_stacking_context_id = match stacking_context_type { - StackingContextType::Real => allocated_id, - _ => self.current_real_stacking_context_id, - }; - - self.stacking_context_info.insert( - allocated_id, - StackingContextInfo::new(real_stacking_context_id), - ); - - allocated_id - } - - fn add_stacking_context( - &mut self, - parent_id: StackingContextId, - stacking_context: StackingContext, - ) { - self.stacking_context_info - .get_mut(&parent_id) - .unwrap() - .children - .push(stacking_context); - } - - fn add_clip_scroll_node(&mut self, clip_scroll_node: ClipScrollNode) -> ClipScrollNodeIndex { - let is_placeholder = clip_scroll_node.is_placeholder(); - - self.clip_scroll_nodes.push(clip_scroll_node); - let index = ClipScrollNodeIndex::new(self.clip_scroll_nodes.len() - 1); - - // If this node is a placeholder node (currently just reference frames), then don't add - // it to the stacking context list. Placeholder nodes are created automatically by - // WebRender and we don't want to explicitly create them in the display list. The node - // is just there to take up a spot in the global list of ClipScrollNodes. - if !is_placeholder { - // We want the scroll root to be defined before any possible item that could use it, - // so we make sure that it is added to the beginning of the parent "real" (non-pseudo) - // stacking context. This ensures that item reordering will not result in an item using - // the scroll root before it is defined. - self.stacking_context_info - .get_mut(&self.current_real_stacking_context_id) - .unwrap() - .clip_scroll_nodes - .push(index); - } - - index - } -} - -pub struct DisplayListBuildState<'a> { - /// A LayoutContext reference important for creating WebRender images. - pub layout_context: &'a LayoutContext<'a>, - - /// The root of the StackingContext tree. - pub root_stacking_context: StackingContext, - - /// StackingContext and ClipScrollNode children for each StackingContext. - stacking_context_info: FnvHashMap<StackingContextId, StackingContextInfo>, - - /// A vector of ClipScrollNodes which will be given ids during WebRender DL conversion. - pub clip_scroll_nodes: Vec<ClipScrollNode>, - - /// The items in this display list. - pub items: FnvHashMap<StackingContextId, Vec<DisplayItem>>, - - /// Whether or not we are processing an element that establishes scrolling overflow. Used - /// to determine what ClipScrollNode to place backgrounds and borders into. - pub processing_scrolling_overflow_element: bool, - - /// The current stacking context id, used to keep track of state when building. - /// recursively building and processing the display list. - pub current_stacking_context_id: StackingContextId, - - /// The current clip and scroll info, used to keep track of state when - /// recursively building and processing the display list. - pub current_clipping_and_scrolling: ClippingAndScrolling, - - /// Vector containing iframe sizes, used to inform the constellation about - /// new iframe sizes - pub iframe_sizes: Vec<IFrameSize>, - - /// Stores text runs to answer text queries used to place a cursor inside text. - pub indexable_text: IndexableText, -} - -impl<'a> DisplayListBuildState<'a> { - pub fn new( - layout_context: &'a LayoutContext, - state: StackingContextCollectionState, - ) -> DisplayListBuildState<'a> { - DisplayListBuildState { - layout_context: layout_context, - root_stacking_context: state.root_stacking_context, - items: FnvHashMap::default(), - stacking_context_info: state.stacking_context_info, - clip_scroll_nodes: state.clip_scroll_nodes, - processing_scrolling_overflow_element: false, - current_stacking_context_id: StackingContextId::root(), - current_clipping_and_scrolling: ClippingAndScrolling::simple( - ClipScrollNodeIndex::root_scroll_node(), - ), - iframe_sizes: Vec::new(), - indexable_text: IndexableText::default(), - } - } - - pub fn add_display_item(&mut self, display_item: DisplayItem) { - let items = self - .items - .entry(display_item.stacking_context_id()) - .or_insert(Vec::new()); - items.push(display_item); - } - - fn add_image_item(&mut self, base: BaseDisplayItem, item: webrender_api::ImageDisplayItem) { - if item.stretch_size == LayoutSize::zero() { - return; - } - self.add_display_item(DisplayItem::Image(CommonDisplayItem::new(base, item))) - } - - fn parent_clip_scroll_node_index(&self, index: ClipScrollNodeIndex) -> ClipScrollNodeIndex { - if index.is_root_scroll_node() { - return index; - } - - self.clip_scroll_nodes[index.to_index()].parent_index - } - - fn is_background_or_border_of_clip_scroll_node(&self, section: DisplayListSection) -> bool { - (section == DisplayListSection::BackgroundAndBorders || - section == DisplayListSection::BlockBackgroundsAndBorders) && - self.processing_scrolling_overflow_element - } - - pub fn create_base_display_item( - &self, - clip_rect: Rect<Au>, - node: OpaqueNode, - cursor: Option<Cursor>, - section: DisplayListSection, - ) -> BaseDisplayItem { - let clipping_and_scrolling = if self.is_background_or_border_of_clip_scroll_node(section) { - ClippingAndScrolling::simple( - self.parent_clip_scroll_node_index(self.current_clipping_and_scrolling.scrolling), - ) - } else { - self.current_clipping_and_scrolling - }; - self.create_base_display_item_with_clipping_and_scrolling( - clip_rect, - node, - cursor, - section, - clipping_and_scrolling, - ) - } - - fn create_base_display_item_with_clipping_and_scrolling( - &self, - clip_rect: Rect<Au>, - node: OpaqueNode, - cursor: Option<Cursor>, - section: DisplayListSection, - clipping_and_scrolling: ClippingAndScrolling, - ) -> BaseDisplayItem { - BaseDisplayItem::new( - DisplayItemMetadata { - node, - // Store cursor id in display list. - pointing: cursor.map(|x| x as u16), - }, - clip_rect.to_layout(), - section, - self.current_stacking_context_id, - clipping_and_scrolling, - ) - } - - fn add_late_clip_node(&mut self, rect: LayoutRect, radii: BorderRadius) -> ClipScrollNodeIndex { - let mut clip = ClippingRegion::from_rect(rect); - clip.intersect_with_rounded_rect(rect, radii); - - let node = ClipScrollNode { - parent_index: self.current_clipping_and_scrolling.scrolling, - clip, - content_rect: LayoutRect::zero(), // content_rect isn't important for clips. - node_type: ClipScrollNodeType::Clip, - }; - - // We want the scroll root to be defined before any possible item that could use it, - // so we make sure that it is added to the beginning of the parent "real" (non-pseudo) - // stacking context. This ensures that item reordering will not result in an item using - // the scroll root before it is defined. - self.clip_scroll_nodes.push(node); - let index = ClipScrollNodeIndex::new(self.clip_scroll_nodes.len() - 1); - let real_stacking_context_id = - self.stacking_context_info[&self.current_stacking_context_id].real_stacking_context_id; - self.stacking_context_info - .get_mut(&real_stacking_context_id) - .unwrap() - .clip_scroll_nodes - .push(index); - - index - } - - pub fn to_display_list(mut self) -> DisplayList { - let mut list = Vec::new(); - let root_context = mem::replace(&mut self.root_stacking_context, StackingContext::root()); - - self.to_display_list_for_stacking_context(&mut list, root_context); - - DisplayList { - list: list, - clip_scroll_nodes: self.clip_scroll_nodes, - } - } - - fn to_display_list_for_stacking_context( - &mut self, - list: &mut Vec<DisplayItem>, - stacking_context: StackingContext, - ) { - let mut child_items = self - .items - .remove(&stacking_context.id) - .unwrap_or(Vec::new()); - child_items.sort_by(|a, b| a.base().section.cmp(&b.base().section)); - child_items.reverse(); - - let mut info = self - .stacking_context_info - .remove(&stacking_context.id) - .unwrap(); - - info.children.sort(); - - if stacking_context.context_type != StackingContextType::Real { - list.extend( - info.clip_scroll_nodes - .into_iter() - .map(|index| index.to_define_item()), - ); - self.to_display_list_for_items(list, child_items, info.children); - } else { - let (push_item, pop_item) = stacking_context.to_display_list_items(); - list.push(push_item); - list.extend( - info.clip_scroll_nodes - .into_iter() - .map(|index| index.to_define_item()), - ); - self.to_display_list_for_items(list, child_items, info.children); - list.push(pop_item); - } - } - - fn to_display_list_for_items( - &mut self, - list: &mut Vec<DisplayItem>, - mut child_items: Vec<DisplayItem>, - child_stacking_contexts: Vec<StackingContext>, - ) { - // Properly order display items that make up a stacking context. "Steps" here - // refer to the steps in CSS 2.1 Appendix E. - // Steps 1 and 2: Borders and background for the root. - while child_items.last().map_or(false, |child| { - child.section() == DisplayListSection::BackgroundAndBorders - }) { - list.push(child_items.pop().unwrap()); - } - - // Step 3: Positioned descendants with negative z-indices. - let mut child_stacking_contexts = child_stacking_contexts.into_iter().peekable(); - while child_stacking_contexts - .peek() - .map_or(false, |child| child.z_index < 0) - { - let context = child_stacking_contexts.next().unwrap(); - self.to_display_list_for_stacking_context(list, context); - } - - // Step 4: Block backgrounds and borders. - while child_items.last().map_or(false, |child| { - child.section() == DisplayListSection::BlockBackgroundsAndBorders - }) { - list.push(child_items.pop().unwrap()); - } - - // Step 5: Floats. - while child_stacking_contexts.peek().map_or(false, |child| { - child.context_type == StackingContextType::PseudoFloat - }) { - let context = child_stacking_contexts.next().unwrap(); - self.to_display_list_for_stacking_context(list, context); - } - - // Step 6 & 7: Content and inlines that generate stacking contexts. - while child_items.last().map_or(false, |child| { - child.section() == DisplayListSection::Content - }) { - list.push(child_items.pop().unwrap()); - } - - // Step 8 & 9: Positioned descendants with nonnegative, numeric z-indices. - for child in child_stacking_contexts { - self.to_display_list_for_stacking_context(list, child); - } - - // Step 10: Outlines. - for item in child_items.drain(..) { - list.push(item); - } - } - - fn clipping_and_scrolling_scope<R, F: FnOnce(&mut Self) -> R>(&mut self, function: F) -> R { - let previous_clipping_and_scrolling = self.current_clipping_and_scrolling; - let ret = function(self); - self.current_clipping_and_scrolling = previous_clipping_and_scrolling; - ret - } -} - -/// The logical width of an insertion point: at the moment, a one-pixel-wide line. -const INSERTION_POINT_LOGICAL_WIDTH: Au = Au(1 * AU_PER_PX); - -/// Get the border radius for the rectangle inside of a rounded border. This is useful -/// for building the clip for the content inside the border. -fn build_border_radius_for_inner_rect( - outer_rect: Rect<Au>, - style: &ComputedValues, -) -> BorderRadius { - let radii = border::radii(outer_rect, style.get_border()); - if radii.is_zero() { - return radii; - } - - // Since we are going to using the inner rectangle (outer rectangle minus - // border width), we need to adjust to border radius so that we are smaller - // rectangle with the same border curve. - let border_widths = style.logical_border_width().to_physical(style.writing_mode); - border::inner_radii(radii, border_widths) -} - -impl Fragment { - pub fn collect_stacking_contexts_for_blocklike_fragment( - &mut self, - state: &mut StackingContextCollectionState, - ) -> bool { - match self.specific { - SpecificFragmentInfo::InlineBlock(ref mut block_flow) => { - let block_flow = FlowRef::deref_mut(&mut block_flow.flow_ref); - block_flow.collect_stacking_contexts(state); - true - }, - SpecificFragmentInfo::InlineAbsoluteHypothetical(ref mut block_flow) => { - let block_flow = FlowRef::deref_mut(&mut block_flow.flow_ref); - block_flow.collect_stacking_contexts(state); - true - }, - SpecificFragmentInfo::InlineAbsolute(ref mut block_flow) => { - let block_flow = FlowRef::deref_mut(&mut block_flow.flow_ref); - block_flow.collect_stacking_contexts(state); - true - }, - // FIXME: In the future, if #15144 is fixed we can remove this case. See #18510. - SpecificFragmentInfo::TruncatedFragment(ref mut info) => info - .full - .collect_stacking_contexts_for_blocklike_fragment(state), - _ => false, - } - } - - pub fn create_stacking_context_for_inline_block( - &mut self, - base: &BaseFlow, - state: &mut StackingContextCollectionState, - ) -> bool { - self.stacking_context_id = state.allocate_stacking_context_info(StackingContextType::Real); - - let established_reference_frame = if self.can_establish_reference_frame() { - // WebRender currently creates reference frames automatically, so just add - // a placeholder node to allocate a ClipScrollNodeIndex for this reference frame. - self.established_reference_frame = - Some(state.add_clip_scroll_node(ClipScrollNode::placeholder())); - self.established_reference_frame - } else { - None - }; - - let current_stacking_context_id = state.current_stacking_context_id; - let stacking_context = self.create_stacking_context( - self.stacking_context_id, - &base, - StackingContextType::Real, - established_reference_frame, - state.current_clipping_and_scrolling, - ); - state.add_stacking_context(current_stacking_context_id, stacking_context); - true - } - - /// Adds the display items necessary to paint the background of this fragment to the display - /// list if necessary. - fn build_display_list_for_background_if_applicable( - &self, - state: &mut DisplayListBuildState, - style: &ComputedValues, - display_list_section: DisplayListSection, - absolute_bounds: Rect<Au>, - ) { - let background = style.get_background(); - let background_color = style.resolve_color(background.background_color); - // XXXManishearth the below method should ideally use an iterator over - // backgrounds - self.build_display_list_for_background_if_applicable_with_background( - state, - style, - background, - background_color, - display_list_section, - absolute_bounds, - ) - } - - /// Same as build_display_list_for_background_if_applicable, but lets you - /// override the actual background used - fn build_display_list_for_background_if_applicable_with_background( - &self, - state: &mut DisplayListBuildState, - style: &ComputedValues, - background: &style_structs::Background, - background_color: RGBA, - display_list_section: DisplayListSection, - absolute_bounds: Rect<Au>, - ) { - // FIXME: This causes a lot of background colors to be displayed when they are clearly not - // needed. We could use display list optimization to clean this up, but it still seems - // inefficient. What we really want is something like "nearest ancestor element that - // doesn't have a fragment". - - // Quote from CSS Backgrounds and Borders Module Level 3: - // - // > The background color is clipped according to the background-clip value associated - // > with the bottom-most background image layer. - let last_background_image_index = background.background_image.0.len() - 1; - let color_clip = *get_cyclic(&background.background_clip.0, last_background_image_index); - let (bounds, border_radii) = background::clip( - color_clip, - absolute_bounds, - style.logical_border_width().to_physical(style.writing_mode), - self.border_padding.to_physical(self.style.writing_mode), - border::radii(absolute_bounds, style.get_border()), - ); - - state.clipping_and_scrolling_scope(|state| { - if !border_radii.is_zero() { - let clip_id = state.add_late_clip_node(bounds.to_layout(), border_radii); - state.current_clipping_and_scrolling = ClippingAndScrolling::simple(clip_id); - } - - let base = state.create_base_display_item( - bounds, - self.node, - get_cursor(&style, Cursor::Default), - display_list_section, - ); - state.add_display_item(DisplayItem::Rectangle(CommonDisplayItem::new( - base, - webrender_api::RectangleDisplayItem { - color: background_color.to_layout(), - common: items::empty_common_item_properties(), - }, - ))); - }); - - // The background image is painted on top of the background color. - // Implements background image, per spec: - // http://www.w3.org/TR/CSS21/colors.html#background - let background = style.get_background(); - for (i, background_image) in background.background_image.0.iter().enumerate().rev() { - let background_image = match *background_image { - ImageLayer::None => continue, - ImageLayer::Image(ref image) => image, - }; - - match *background_image { - Image::Gradient(ref gradient) => { - self.build_display_list_for_background_gradient( - state, - display_list_section, - absolute_bounds, - gradient, - style, - i, - ); - }, - Image::Url(ref image_url) => { - if let Some(url) = image_url.url() { - let webrender_image = state.layout_context.get_webrender_image_for_url( - self.node, - url.clone(), - UsePlaceholder::No, - ); - if let Some(webrender_image) = webrender_image { - self.build_display_list_for_webrender_image( - state, - style, - display_list_section, - absolute_bounds, - webrender_image, - i, - ); - } - } - }, - Image::PaintWorklet(ref paint_worklet) => { - let bounding_box = self.border_box - style.logical_border_width(); - let bounding_box_size = bounding_box.size.to_physical(style.writing_mode); - let background_size = - get_cyclic(&style.get_background().background_size.0, i).clone(); - let size = match background_size { - BackgroundSize::ExplicitSize { width, height } => Size2D::new( - width - .to_used_value(bounding_box_size.width) - .unwrap_or(bounding_box_size.width), - height - .to_used_value(bounding_box_size.height) - .unwrap_or(bounding_box_size.height), - ), - _ => bounding_box_size, - }; - let webrender_image = self.get_webrender_image_for_paint_worklet( - state, - style, - paint_worklet, - size, - ); - if let Some(webrender_image) = webrender_image { - self.build_display_list_for_webrender_image( - state, - style, - display_list_section, - absolute_bounds, - webrender_image, - i, - ); - } - }, - Image::Rect(_) => { - // TODO: Implement `-moz-image-rect` - }, - Image::Element(_) => { - // TODO: Implement `-moz-element` - }, - } - } - } - - /// Adds the display items necessary to paint a webrender image of this fragment to the - /// appropriate section of the display list. - fn build_display_list_for_webrender_image( - &self, - state: &mut DisplayListBuildState, - style: &ComputedValues, - display_list_section: DisplayListSection, - absolute_bounds: Rect<Au>, - webrender_image: WebRenderImageInfo, - index: usize, - ) { - debug!("(building display list) building background image"); - if webrender_image.key.is_none() { - return; - } - - let image = Size2D::new( - Au::from_px(webrender_image.width as i32), - Au::from_px(webrender_image.height as i32), - ); - let placement = background::placement( - style.get_background(), - state.layout_context.shared_context().viewport_size(), - absolute_bounds, - Some(image), - style.logical_border_width().to_physical(style.writing_mode), - self.border_padding.to_physical(self.style.writing_mode), - border::radii(absolute_bounds, style.get_border()), - index, - ); - - state.clipping_and_scrolling_scope(|state| { - if !placement.clip_radii.is_zero() { - let clip_id = - state.add_late_clip_node(placement.clip_rect.to_layout(), placement.clip_radii); - state.current_clipping_and_scrolling = ClippingAndScrolling::simple(clip_id); - } - - // Create the image display item. - let base = state.create_base_display_item( - placement.clip_rect, - self.node, - get_cursor(&style, Cursor::Default), - display_list_section, - ); - - debug!("(building display list) adding background image."); - state.add_image_item( - base, - webrender_api::ImageDisplayItem { - bounds: placement.bounds.to_f32_px(), - common: items::empty_common_item_properties(), - image_key: webrender_image.key.unwrap(), - stretch_size: placement.tile_size.to_layout(), - tile_spacing: placement.tile_spacing.to_layout(), - image_rendering: style.get_inherited_box().image_rendering.to_layout(), - alpha_type: webrender_api::AlphaType::PremultipliedAlpha, - color: webrender_api::ColorF::WHITE, - }, - ); - }); - } - - /// Calculates the webrender image for a paint worklet. - /// Returns None if the worklet is not registered. - /// If the worklet has missing image URLs, it passes them to the image cache for loading. - fn get_webrender_image_for_paint_worklet( - &self, - state: &mut DisplayListBuildState, - style: &ComputedValues, - paint_worklet: &PaintWorklet, - size_in_au: Size2D<Au>, - ) -> Option<WebRenderImageInfo> { - let device_pixel_ratio = state.layout_context.style_context.device_pixel_ratio(); - let size_in_px = - TypedSize2D::new(size_in_au.width.to_f32_px(), size_in_au.height.to_f32_px()); - - // TODO: less copying. - let name = paint_worklet.name.clone(); - let arguments = paint_worklet - .arguments - .iter() - .map(|argument| argument.to_css_string()) - .collect(); - - let draw_result = match state.layout_context.registered_painters.get(&name) { - Some(painter) => { - debug!( - "Drawing a paint image {}({},{}).", - name, size_in_px.width, size_in_px.height - ); - let properties = painter - .properties() - .iter() - .filter_map(|(name, id)| id.as_shorthand().err().map(|id| (name, id))) - .map(|(name, id)| (name.clone(), style.computed_value_to_string(id))) - .collect(); - painter.draw_a_paint_image(size_in_px, device_pixel_ratio, properties, arguments) - }, - None => { - debug!("Worklet {} called before registration.", name); - return None; - }, - }; - - if let Ok(draw_result) = draw_result { - let webrender_image = WebRenderImageInfo { - width: draw_result.width, - height: draw_result.height, - key: draw_result.image_key, - }; - - for url in draw_result.missing_image_urls.into_iter() { - debug!("Requesting missing image URL {}.", url); - state.layout_context.get_webrender_image_for_url( - self.node, - url, - UsePlaceholder::No, - ); - } - Some(webrender_image) - } else { - None - } - } - - /// Adds the display items necessary to paint the background linear gradient of this fragment - /// to the appropriate section of the display list. - fn build_display_list_for_background_gradient( - &self, - state: &mut DisplayListBuildState, - display_list_section: DisplayListSection, - absolute_bounds: Rect<Au>, - gradient: &Gradient, - style: &ComputedValues, - index: usize, - ) { - let placement = background::placement( - style.get_background(), - state.layout_context.shared_context().viewport_size(), - absolute_bounds, - None, - style.logical_border_width().to_physical(style.writing_mode), - self.border_padding.to_physical(self.style.writing_mode), - border::radii(absolute_bounds, style.get_border()), - index, - ); - - state.clipping_and_scrolling_scope(|state| { - if !placement.clip_radii.is_zero() { - let clip_id = - state.add_late_clip_node(placement.clip_rect.to_layout(), placement.clip_radii); - state.current_clipping_and_scrolling = ClippingAndScrolling::simple(clip_id); - } - - let base = state.create_base_display_item( - placement.clip_rect, - self.node, - get_cursor(&style, Cursor::Default), - display_list_section, - ); - - let display_item = match gradient.kind { - GradientKind::Linear(angle_or_corner) => { - let (gradient, stops) = gradient::linear( - style, - placement.tile_size, - &gradient.items[..], - angle_or_corner, - gradient.repeating, - ); - let item = webrender_api::GradientDisplayItem { - gradient, - bounds: placement.bounds.to_f32_px(), - common: items::empty_common_item_properties(), - tile_size: placement.tile_size.to_layout(), - tile_spacing: placement.tile_spacing.to_layout(), - }; - DisplayItem::Gradient(CommonDisplayItem::with_data(base, item, stops)) - }, - GradientKind::Radial(shape, center) => { - let (gradient, stops) = gradient::radial( - style, - placement.tile_size, - &gradient.items[..], - shape, - center, - gradient.repeating, - ); - let item = webrender_api::RadialGradientDisplayItem { - gradient, - bounds: placement.bounds.to_f32_px(), - common: items::empty_common_item_properties(), - tile_size: placement.tile_size.to_layout(), - tile_spacing: placement.tile_spacing.to_layout(), - }; - DisplayItem::RadialGradient(CommonDisplayItem::with_data(base, item, stops)) - }, - }; - state.add_display_item(display_item); - }); - } - - /// Adds the display items necessary to paint the box shadow of this fragment to the display - /// list if necessary. - fn build_display_list_for_box_shadow_if_applicable( - &self, - state: &mut DisplayListBuildState, - style: &ComputedValues, - display_list_section: DisplayListSection, - absolute_bounds: Rect<Au>, - clip: Rect<Au>, - ) { - // NB: According to CSS-BACKGROUNDS, box shadows render in *reverse* order (front to back). - for box_shadow in style.get_effects().box_shadow.0.iter().rev() { - let base = state.create_base_display_item( - clip, - self.node, - get_cursor(&style, Cursor::Default), - display_list_section, - ); - let border_radius = border::radii(absolute_bounds, style.get_border()); - state.add_display_item(DisplayItem::BoxShadow(CommonDisplayItem::new( - base, - webrender_api::BoxShadowDisplayItem { - common: items::empty_common_item_properties(), - box_bounds: absolute_bounds.to_layout(), - color: style.resolve_color(box_shadow.base.color).to_layout(), - offset: LayoutVector2D::new( - box_shadow.base.horizontal.px(), - box_shadow.base.vertical.px(), - ), - blur_radius: box_shadow.base.blur.px(), - spread_radius: box_shadow.spread.px(), - border_radius: border_radius, - clip_mode: if box_shadow.inset { - BoxShadowClipMode::Inset - } else { - BoxShadowClipMode::Outset - }, - }, - ))); - } - } - - /// Adds the display items necessary to paint the borders of this fragment to a display list if - /// necessary. - fn build_display_list_for_borders_if_applicable( - &self, - state: &mut DisplayListBuildState, - style: &ComputedValues, - inline_info: Option<InlineNodeBorderInfo>, - border_painting_mode: BorderPaintingMode, - mut bounds: Rect<Au>, - display_list_section: DisplayListSection, - clip: Rect<Au>, - ) { - let mut border = style.logical_border_width(); - - if let Some(inline_info) = inline_info { - modify_border_width_for_inline_sides(&mut border, inline_info); - } - - match border_painting_mode { - BorderPaintingMode::Separate => {}, - BorderPaintingMode::Collapse(collapsed_borders) => { - collapsed_borders.adjust_border_widths_for_painting(&mut border) - }, - BorderPaintingMode::Hidden => return, - } - - let border_style_struct = style.get_border(); - let mut colors = SideOffsets2D::new( - border_style_struct.border_top_color, - border_style_struct.border_right_color, - border_style_struct.border_bottom_color, - border_style_struct.border_left_color, - ); - let mut border_style = SideOffsets2D::new( - border_style_struct.border_top_style, - border_style_struct.border_right_style, - border_style_struct.border_bottom_style, - border_style_struct.border_left_style, - ); - - if let BorderPaintingMode::Collapse(collapsed_borders) = border_painting_mode { - collapsed_borders.adjust_border_colors_and_styles_for_painting( - &mut colors, - &mut border_style, - style.writing_mode, - ); - } - - // If this border collapses, then we draw outside the boundaries we were given. - if let BorderPaintingMode::Collapse(collapsed_borders) = border_painting_mode { - collapsed_borders.adjust_border_bounds_for_painting(&mut bounds, style.writing_mode) - } - - // Append the border to the display list. - let base = state.create_base_display_item( - clip, - self.node, - get_cursor(&style, Cursor::Default), - display_list_section, - ); - - let border_radius = border::radii(bounds, border_style_struct); - let border_widths = border.to_physical(style.writing_mode); - - if let ImageLayer::Image(ref image) = border_style_struct.border_image_source { - if self - .build_display_list_for_border_image( - state, - style, - base.clone(), - bounds, - image, - border_widths, - ) - .is_some() - { - return; - } - // Fallback to rendering a solid border. - } - if border_widths == SideOffsets2D::zero() { - return; - } - let details = BorderDetails::Normal(NormalBorder { - left: BorderSide { - color: style.resolve_color(colors.left).to_layout(), - style: border_style.left.to_layout(), - }, - right: BorderSide { - color: style.resolve_color(colors.right).to_layout(), - style: border_style.right.to_layout(), - }, - top: BorderSide { - color: style.resolve_color(colors.top).to_layout(), - style: border_style.top.to_layout(), - }, - bottom: BorderSide { - color: style.resolve_color(colors.bottom).to_layout(), - style: border_style.bottom.to_layout(), - }, - radius: border_radius, - do_aa: true, - }); - state.add_display_item(DisplayItem::Border(CommonDisplayItem::with_data( - base, - webrender_api::BorderDisplayItem { - bounds: bounds.to_layout(), - common: items::empty_common_item_properties(), - widths: border_widths.to_layout(), - details, - }, - Vec::new(), - ))); - } - - /// Add display item for image border. - /// - /// Returns `Some` if the addition was successful. - fn build_display_list_for_border_image( - &self, - state: &mut DisplayListBuildState, - style: &ComputedValues, - base: BaseDisplayItem, - bounds: Rect<Au>, - image: &Image, - border_width: SideOffsets2D<Au>, - ) -> Option<()> { - let border_style_struct = style.get_border(); - let border_image_outset = - border::image_outset(border_style_struct.border_image_outset, border_width); - let border_image_area = bounds.outer_rect(border_image_outset).size; - let border_image_width = border::image_width( - &border_style_struct.border_image_width, - border_width.to_layout(), - border_image_area, - ); - let border_image_repeat = &border_style_struct.border_image_repeat; - let border_image_fill = border_style_struct.border_image_slice.fill; - let border_image_slice = &border_style_struct.border_image_slice.offsets; - - let mut stops = Vec::new(); - let mut width = border_image_area.width.to_px() as u32; - let mut height = border_image_area.height.to_px() as u32; - let source = match image { - Image::Url(ref image_url) => { - let url = image_url.url()?; - let image = state.layout_context.get_webrender_image_for_url( - self.node, - url.clone(), - UsePlaceholder::No, - )?; - width = image.width; - height = image.height; - NinePatchBorderSource::Image(image.key?) - }, - Image::PaintWorklet(ref paint_worklet) => { - let image = self.get_webrender_image_for_paint_worklet( - state, - style, - paint_worklet, - border_image_area, - )?; - width = image.width; - height = image.height; - NinePatchBorderSource::Image(image.key?) - }, - Image::Gradient(ref gradient) => match gradient.kind { - GradientKind::Linear(angle_or_corner) => { - let (wr_gradient, linear_stops) = gradient::linear( - style, - border_image_area, - &gradient.items[..], - angle_or_corner, - gradient.repeating, - ); - stops = linear_stops; - NinePatchBorderSource::Gradient(wr_gradient) - }, - GradientKind::Radial(shape, center) => { - let (wr_gradient, radial_stops) = gradient::radial( - style, - border_image_area, - &gradient.items[..], - shape, - center, - gradient.repeating, - ); - stops = radial_stops; - NinePatchBorderSource::RadialGradient(wr_gradient) - }, - }, - _ => return None, - }; - - let details = BorderDetails::NinePatch(NinePatchBorder { - source, - width: width as i32, - height: height as i32, - slice: border::image_slice(border_image_slice, width as i32, height as i32), - fill: border_image_fill, - repeat_horizontal: border_image_repeat.0.to_layout(), - repeat_vertical: border_image_repeat.1.to_layout(), - outset: SideOffsets2D::new( - border_image_outset.top.to_f32_px(), - border_image_outset.right.to_f32_px(), - border_image_outset.bottom.to_f32_px(), - border_image_outset.left.to_f32_px(), - ), - }); - state.add_display_item(DisplayItem::Border(CommonDisplayItem::with_data( - base, - webrender_api::BorderDisplayItem { - bounds: bounds.to_layout(), - common: items::empty_common_item_properties(), - widths: border_image_width, - details, - }, - stops, - ))); - Some(()) - } - - /// Adds the display items necessary to paint the outline of this fragment to the display list - /// if necessary. - fn build_display_list_for_outline_if_applicable( - &self, - state: &mut DisplayListBuildState, - style: &ComputedValues, - mut bounds: Rect<Au>, - clip: Rect<Au>, - ) { - use style::values::specified::outline::OutlineStyle; - - let width = Au::from(style.get_outline().outline_width); - if width == Au(0) { - return; - } - - let outline_style = match style.get_outline().outline_style { - OutlineStyle::Auto => BorderStyle::Solid, - // FIXME(emilio): I don't think this border-style check is - // necessary, since border-style: none implies an outline-width of - // zero at computed value time. - OutlineStyle::BorderStyle(BorderStyle::None) => return, - OutlineStyle::BorderStyle(s) => s, - }; - - // Outlines are not accounted for in the dimensions of the border box, so adjust the - // absolute bounds. - let offset = width + Au::from(style.get_outline().outline_offset); - bounds = bounds.inflate(offset, offset); - - // Append the outline to the display list. - let color = style - .resolve_color(style.get_outline().outline_color) - .to_layout(); - let base = state.create_base_display_item( - clip, - self.node, - get_cursor(&style, Cursor::Default), - DisplayListSection::Outlines, - ); - state.add_display_item(DisplayItem::Border(CommonDisplayItem::with_data( - base, - webrender_api::BorderDisplayItem { - bounds: bounds.to_layout(), - common: items::empty_common_item_properties(), - widths: SideOffsets2D::new_all_same(width).to_layout(), - details: BorderDetails::Normal(border::simple(color, outline_style.to_layout())), - }, - Vec::new(), - ))); - } - - /// Adds display items necessary to draw debug boxes around a scanned text fragment. - fn build_debug_borders_around_text_fragments( - &self, - state: &mut DisplayListBuildState, - style: &ComputedValues, - stacking_relative_border_box: Rect<Au>, - stacking_relative_content_box: Rect<Au>, - text_fragment: &ScannedTextFragmentInfo, - clip: Rect<Au>, - ) { - // FIXME(pcwalton, #2795): Get the real container size. - let container_size = Size2D::zero(); - - // Compute the text fragment bounds and draw a border surrounding them. - let base = state.create_base_display_item( - clip, - self.node, - get_cursor(&style, Cursor::Default), - DisplayListSection::Content, - ); - state.add_display_item(DisplayItem::Border(CommonDisplayItem::with_data( - base, - webrender_api::BorderDisplayItem { - bounds: stacking_relative_border_box.to_layout(), - common: items::empty_common_item_properties(), - widths: SideOffsets2D::new_all_same(Au::from_px(1)).to_layout(), - details: BorderDetails::Normal(border::simple( - ColorU::new(0, 0, 200, 1).into(), - webrender_api::BorderStyle::Solid, - )), - }, - Vec::new(), - ))); - - // Draw a rectangle representing the baselines. - let mut baseline = LogicalRect::from_physical( - self.style.writing_mode, - stacking_relative_content_box, - container_size, - ); - baseline.start.b = baseline.start.b + text_fragment.run.ascent(); - baseline.size.block = Au(0); - let baseline = baseline.to_physical(self.style.writing_mode, container_size); - - let base = state.create_base_display_item( - clip, - self.node, - get_cursor(&style, Cursor::Default), - DisplayListSection::Content, - ); - // TODO(gw): Use a better estimate for wavy line thickness. - let area = baseline.to_layout(); - let wavy_line_thickness = (0.33 * area.size.height).ceil(); - state.add_display_item(DisplayItem::Line(CommonDisplayItem::new( - base, - webrender_api::LineDisplayItem { - common: items::empty_common_item_properties(), - area, - orientation: webrender_api::LineOrientation::Horizontal, - wavy_line_thickness, - color: ColorU::new(0, 200, 0, 1).into(), - style: LineStyle::Dashed, - }, - ))); - } - - /// Adds display items necessary to draw debug boxes around this fragment. - fn build_debug_borders_around_fragment( - &self, - state: &mut DisplayListBuildState, - stacking_relative_border_box: Rect<Au>, - clip: Rect<Au>, - ) { - // This prints a debug border around the border of this fragment. - let base = state.create_base_display_item( - clip, - self.node, - get_cursor(&self.style, Cursor::Default), - DisplayListSection::Content, - ); - state.add_display_item(DisplayItem::Border(CommonDisplayItem::with_data( - base, - webrender_api::BorderDisplayItem { - bounds: stacking_relative_border_box.to_layout(), - common: items::empty_common_item_properties(), - widths: SideOffsets2D::new_all_same(Au::from_px(1)).to_layout(), - details: BorderDetails::Normal(border::simple( - ColorU::new(0, 0, 200, 1).into(), - webrender_api::BorderStyle::Solid, - )), - }, - Vec::new(), - ))); - } - - /// Builds the display items necessary to paint the selection and/or caret for this fragment, - /// if any. - fn build_display_items_for_selection_if_necessary( - &self, - state: &mut DisplayListBuildState, - stacking_relative_border_box: Rect<Au>, - display_list_section: DisplayListSection, - ) { - let scanned_text_fragment_info = match self.specific { - SpecificFragmentInfo::ScannedText(ref scanned_text_fragment_info) => { - scanned_text_fragment_info - }, - _ => return, - }; - - // Draw a highlighted background if the text is selected. - // - // TODO: Allow non-text fragments to be selected too. - if scanned_text_fragment_info.selected() { - let style = self.selected_style(); - let background_color = style.resolve_color(style.get_background().background_color); - let base = state.create_base_display_item( - stacking_relative_border_box, - self.node, - get_cursor(&self.style, Cursor::Default), - display_list_section, - ); - state.add_display_item(DisplayItem::Rectangle(CommonDisplayItem::new( - base, - webrender_api::RectangleDisplayItem { - common: items::empty_common_item_properties(), - color: background_color.to_layout(), - }, - ))); - } - - // Draw a caret at the insertion point. - let insertion_point_index = match scanned_text_fragment_info.insertion_point { - Some(insertion_point_index) => insertion_point_index, - None => return, - }; - let range = Range::new( - scanned_text_fragment_info.range.begin(), - insertion_point_index - scanned_text_fragment_info.range.begin(), - ); - let advance = scanned_text_fragment_info.run.advance_for_range(&range); - - let insertion_point_bounds; - let cursor; - if !self.style.writing_mode.is_vertical() { - insertion_point_bounds = rect( - stacking_relative_border_box.origin.x + advance, - stacking_relative_border_box.origin.y, - INSERTION_POINT_LOGICAL_WIDTH, - stacking_relative_border_box.size.height, - ); - cursor = Cursor::Text; - } else { - insertion_point_bounds = rect( - stacking_relative_border_box.origin.x, - stacking_relative_border_box.origin.y + advance, - stacking_relative_border_box.size.width, - INSERTION_POINT_LOGICAL_WIDTH, - ); - cursor = Cursor::VerticalText; - }; - - let base = state.create_base_display_item( - insertion_point_bounds, - self.node, - get_cursor(&self.style, cursor), - display_list_section, - ); - state.add_display_item(DisplayItem::Rectangle(CommonDisplayItem::new( - base, - webrender_api::RectangleDisplayItem { - common: items::empty_common_item_properties(), - color: self.style().get_inherited_text().color.to_layout(), - }, - ))); - } - - /// Adds the display items for this fragment to the given display list. - /// - /// Arguments: - /// - /// * `state`: The display building state, including the display list currently - /// under construction and other metadata useful for constructing it. - /// * `dirty`: The dirty rectangle in the coordinate system of the owning flow. - /// * `clip`: The region to clip the display items to. - /// * `overflow_content_size`: The size of content associated with this fragment - /// that must have overflow handling applied to it. For a scrollable block - /// flow, it is expected that this is the size of the child boxes. - pub fn build_display_list( - &mut self, - state: &mut DisplayListBuildState, - stacking_relative_border_box: Rect<Au>, - border_painting_mode: BorderPaintingMode, - display_list_section: DisplayListSection, - clip: Rect<Au>, - overflow_content_size: Option<Size2D<Au>>, - ) { - let previous_clipping_and_scrolling = state.current_clipping_and_scrolling; - if let Some(index) = self.established_reference_frame { - state.current_clipping_and_scrolling = ClippingAndScrolling::simple(index); - } - - self.restyle_damage.remove(ServoRestyleDamage::REPAINT); - self.build_display_list_no_damage( - state, - stacking_relative_border_box, - border_painting_mode, - display_list_section, - clip, - overflow_content_size, - ); - - state.current_clipping_and_scrolling = previous_clipping_and_scrolling; - } - - /// build_display_list, but don't update the restyle damage - /// - /// Must be paired with a self.restyle_damage.remove(REPAINT) somewhere - fn build_display_list_no_damage( - &self, - state: &mut DisplayListBuildState, - stacking_relative_border_box: Rect<Au>, - border_painting_mode: BorderPaintingMode, - display_list_section: DisplayListSection, - clip: Rect<Au>, - overflow_content_size: Option<Size2D<Au>>, - ) { - if self.style().get_inherited_box().visibility != Visibility::Visible { - return; - } - - debug!( - "Fragment::build_display_list at rel={:?}, abs={:?}: {:?}", - self.border_box, stacking_relative_border_box, self - ); - - // Check the clip rect. If there's nothing to render at all, don't even construct display - // list items. - let empty_rect = !clip.intersects(&stacking_relative_border_box); - if self.is_primary_fragment() && !empty_rect { - // Add shadows, background, borders, and outlines, if applicable. - if let Some(ref inline_context) = self.inline_context { - for node in inline_context.nodes.iter().rev() { - self.build_display_list_for_background_if_applicable( - state, - &*node.style, - display_list_section, - stacking_relative_border_box, - ); - - self.build_display_list_for_box_shadow_if_applicable( - state, - &*node.style, - display_list_section, - stacking_relative_border_box, - clip, - ); - - self.build_display_list_for_borders_if_applicable( - state, - &*node.style, - Some(InlineNodeBorderInfo { - is_first_fragment_of_element: node - .flags - .contains(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT), - is_last_fragment_of_element: node - .flags - .contains(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT), - }), - border_painting_mode, - stacking_relative_border_box, - display_list_section, - clip, - ); - - // FIXME(emilio): Why does outline not do the same width - // fixup as border? - self.build_display_list_for_outline_if_applicable( - state, - &*node.style, - stacking_relative_border_box, - clip, - ); - } - } - - if !self.is_scanned_text_fragment() { - self.build_display_list_for_background_if_applicable( - state, - &*self.style, - display_list_section, - stacking_relative_border_box, - ); - - self.build_display_list_for_box_shadow_if_applicable( - state, - &*self.style, - display_list_section, - stacking_relative_border_box, - clip, - ); - - self.build_display_list_for_borders_if_applicable( - state, - &*self.style, - /* inline_node_info = */ None, - border_painting_mode, - stacking_relative_border_box, - display_list_section, - clip, - ); - - self.build_display_list_for_outline_if_applicable( - state, - &*self.style, - stacking_relative_border_box, - clip, - ); - } - } - - if self.is_primary_fragment() { - // Paint the selection point if necessary. Even an empty text fragment may have an - // insertion point, so we do this even if `empty_rect` is true. - self.build_display_items_for_selection_if_necessary( - state, - stacking_relative_border_box, - display_list_section, - ); - } - - if empty_rect { - return; - } - - debug!("Fragment::build_display_list: intersected. Adding display item..."); - - if let Some(content_size) = overflow_content_size { - // Create a transparent rectangle for hit-testing purposes that exists in front - // of this fragment's background but behind its content. This ensures that any - // hit tests inside the content box but not on actual content target the current - // scrollable ancestor. - let content_size = TypedRect::new(stacking_relative_border_box.origin, content_size); - let base = state.create_base_display_item_with_clipping_and_scrolling( - content_size, - self.node, - // FIXME(emilio): Why does this ignore pointer-events? - get_cursor(&self.style, Cursor::Default).or(Some(Cursor::Default)), - display_list_section, - state.current_clipping_and_scrolling, - ); - state.add_display_item(DisplayItem::Rectangle(CommonDisplayItem::new( - base, - webrender_api::RectangleDisplayItem { - common: items::empty_common_item_properties(), - color: ColorF::TRANSPARENT, - }, - ))); - } - - // Create special per-fragment-type display items. - state.clipping_and_scrolling_scope(|state| { - self.build_fragment_type_specific_display_items( - state, - stacking_relative_border_box, - clip, - ); - }); - - if opts::get().show_debug_fragment_borders { - self.build_debug_borders_around_fragment(state, stacking_relative_border_box, clip) - } - } - - /// A helper method that `build_display_list` calls to create per-fragment-type display items. - fn build_fragment_type_specific_display_items( - &self, - state: &mut DisplayListBuildState, - stacking_relative_border_box: Rect<Au>, - clip: Rect<Au>, - ) { - // Compute the context box position relative to the parent stacking context. - let stacking_relative_content_box = - self.stacking_relative_content_box(stacking_relative_border_box); - - let create_base_display_item = |state: &mut DisplayListBuildState| { - // Adjust the clipping region as necessary to account for `border-radius`. - let radii = - build_border_radius_for_inner_rect(stacking_relative_border_box, &self.style); - - if !radii.is_zero() { - let clip_id = - state.add_late_clip_node(stacking_relative_border_box.to_layout(), radii); - state.current_clipping_and_scrolling = ClippingAndScrolling::simple(clip_id); - } - - state.create_base_display_item( - stacking_relative_border_box, - self.node, - get_cursor(&self.style, Cursor::Default), - DisplayListSection::Content, - ) - }; - - match self.specific { - SpecificFragmentInfo::TruncatedFragment(ref truncated_fragment) - if truncated_fragment.text_info.is_some() => - { - let text_fragment = truncated_fragment.text_info.as_ref().unwrap(); - // Create the main text display item. - self.build_display_list_for_text_fragment( - state, - &text_fragment, - stacking_relative_content_box, - &self.style.get_inherited_text().text_shadow.0, - clip, - ); - - if opts::get().show_debug_fragment_borders { - self.build_debug_borders_around_text_fragments( - state, - self.style(), - stacking_relative_border_box, - stacking_relative_content_box, - &text_fragment, - clip, - ); - } - } - SpecificFragmentInfo::ScannedText(ref text_fragment) => { - // Create the main text display item. - self.build_display_list_for_text_fragment( - state, - &text_fragment, - stacking_relative_content_box, - &self.style.get_inherited_text().text_shadow.0, - clip, - ); - - if opts::get().show_debug_fragment_borders { - self.build_debug_borders_around_text_fragments( - state, - self.style(), - stacking_relative_border_box, - stacking_relative_content_box, - &text_fragment, - clip, - ); - } - }, - SpecificFragmentInfo::Generic | - SpecificFragmentInfo::GeneratedContent(..) | - SpecificFragmentInfo::Table | - SpecificFragmentInfo::TableCell | - SpecificFragmentInfo::TableRow | - SpecificFragmentInfo::TableWrapper | - SpecificFragmentInfo::Multicol | - SpecificFragmentInfo::MulticolColumn | - SpecificFragmentInfo::InlineBlock(_) | - SpecificFragmentInfo::InlineAbsoluteHypothetical(_) | - SpecificFragmentInfo::InlineAbsolute(_) | - SpecificFragmentInfo::TruncatedFragment(_) | - SpecificFragmentInfo::Svg(_) => { - if opts::get().show_debug_fragment_borders { - self.build_debug_borders_around_fragment( - state, - stacking_relative_border_box, - clip, - ); - } - }, - SpecificFragmentInfo::Iframe(ref fragment_info) => { - if !stacking_relative_content_box.is_empty() { - let browsing_context_id = match fragment_info.browsing_context_id { - Some(browsing_context_id) => browsing_context_id, - None => return warn!("No browsing context id for iframe."), - }; - let pipeline_id = match fragment_info.pipeline_id { - Some(pipeline_id) => pipeline_id, - None => return warn!("No pipeline id for iframe {}.", browsing_context_id), - }; - - let base = create_base_display_item(state); - let bounds = stacking_relative_content_box.to_layout(); - let item = DisplayItem::Iframe(Box::new(IframeDisplayItem { - base, - bounds, - iframe: pipeline_id, - })); - - // XXXjdm: This sleight-of-hand to convert LayoutRect -> Size2D<CSSPixel> - // looks bogus. - let size = Size2D::new(bounds.size.width, bounds.size.height); - state.iframe_sizes.push(IFrameSize { - id: browsing_context_id, - size: TypedSize2D::from_untyped(&size), - }); - - state.add_display_item(item); - } - }, - SpecificFragmentInfo::Image(ref image_fragment) => { - // Place the image into the display list. - if let Some(ref image) = image_fragment.image { - if let Some(id) = image.id { - let base = create_base_display_item(state); - state.add_image_item( - base, - webrender_api::ImageDisplayItem { - bounds: stacking_relative_content_box.to_layout(), - common: items::empty_common_item_properties(), - image_key: id, - stretch_size: stacking_relative_content_box.size.to_layout(), - tile_spacing: LayoutSize::zero(), - image_rendering: self - .style - .get_inherited_box() - .image_rendering - .to_layout(), - alpha_type: webrender_api::AlphaType::PremultipliedAlpha, - color: webrender_api::ColorF::WHITE, - }, - ); - } - } - }, - SpecificFragmentInfo::Media(ref fragment_info) => { - if let Some((ref image_key, _, _)) = fragment_info.current_frame { - let base = create_base_display_item(state); - state.add_image_item( - base, - webrender_api::ImageDisplayItem { - bounds: stacking_relative_content_box.to_layout(), - common: items::empty_common_item_properties(), - image_key: *image_key, - stretch_size: stacking_relative_border_box.size.to_layout(), - tile_spacing: LayoutSize::zero(), - image_rendering: ImageRendering::Auto, - alpha_type: webrender_api::AlphaType::PremultipliedAlpha, - color: webrender_api::ColorF::WHITE, - }, - ); - } - }, - SpecificFragmentInfo::Canvas(ref canvas_fragment_info) => { - let image_key = match canvas_fragment_info.source { - CanvasFragmentSource::WebGL(image_key) => image_key, - CanvasFragmentSource::Image(ref ipc_renderer) => match *ipc_renderer { - Some(ref ipc_renderer) => { - let ipc_renderer = ipc_renderer.lock().unwrap(); - let (sender, receiver) = ipc::channel().unwrap(); - ipc_renderer - .send(CanvasMsg::FromLayout( - FromLayoutMsg::SendData(sender), - canvas_fragment_info.canvas_id.clone(), - )) - .unwrap(); - receiver.recv().unwrap().image_key - }, - None => return, - }, - }; - - let base = create_base_display_item(state); - let display_item = webrender_api::ImageDisplayItem { - bounds: stacking_relative_border_box.to_layout(), - common: items::empty_common_item_properties(), - image_key, - stretch_size: stacking_relative_content_box.size.to_layout(), - tile_spacing: LayoutSize::zero(), - image_rendering: ImageRendering::Auto, - alpha_type: webrender_api::AlphaType::PremultipliedAlpha, - color: webrender_api::ColorF::WHITE, - }; - - state.add_image_item(base, display_item); - }, - SpecificFragmentInfo::UnscannedText(_) => { - panic!("Shouldn't see unscanned fragments here.") - }, - SpecificFragmentInfo::TableColumn(_) => { - panic!("Shouldn't see table column fragments here.") - }, - } - } - - /// Creates a stacking context for associated fragment. - fn create_stacking_context( - &self, - id: StackingContextId, - base_flow: &BaseFlow, - context_type: StackingContextType, - established_reference_frame: Option<ClipScrollNodeIndex>, - parent_clipping_and_scrolling: ClippingAndScrolling, - ) -> StackingContext { - let border_box = self.stacking_relative_border_box( - &base_flow.stacking_relative_position, - &base_flow - .early_absolute_position_info - .relative_containing_block_size, - base_flow - .early_absolute_position_info - .relative_containing_block_mode, - CoordinateSystem::Parent, - ); - // First, compute the offset of our border box (including relative positioning) - // from our flow origin, since that is what `BaseFlow::overflow` is relative to. - let border_box_offset = border_box - .translate(&-base_flow.stacking_relative_position) - .origin; - // Then, using that, compute our overflow region relative to our border box. - let overflow = base_flow - .overflow - .paint - .translate(&-border_box_offset.to_vector()); - - // Create the filter pipeline. - let effects = self.style().get_effects(); - let mut filters: Vec<FilterOp> = effects.filter.0.iter().map(ToLayout::to_layout).collect(); - if effects.opacity != 1.0 { - filters.push(FilterOp::Opacity(effects.opacity.into(), effects.opacity)); - } - - StackingContext::new( - id, - context_type, - border_box.to_layout(), - overflow.to_layout(), - self.effective_z_index(), - self.style().get_box()._servo_top_layer, - filters, - self.style().get_effects().mix_blend_mode.to_layout(), - self.transform_matrix(&border_box), - self.style().get_used_transform_style().to_layout(), - self.perspective_matrix(&border_box), - parent_clipping_and_scrolling, - established_reference_frame, - ) - } - - /// Creates the text display item for one text fragment. This can be called multiple times for - /// one fragment if there are text shadows. - /// - /// `text_shadow` will be `Some` if this is rendering a shadow. - fn build_display_list_for_text_fragment( - &self, - state: &mut DisplayListBuildState, - text_fragment: &ScannedTextFragmentInfo, - stacking_relative_content_box: Rect<Au>, - text_shadows: &[SimpleShadow], - clip: Rect<Au>, - ) { - // NB: The order for painting text components (CSS Text Decoration Module Level 3) is: - // shadows, underline, overline, text, text-emphasis, and then line-through. - - // TODO(emilio): Allow changing more properties by ::selection - // Paint the text with the color as described in its styling. - let text_color = if text_fragment.selected() { - self.selected_style().get_inherited_text().color - } else { - self.style().get_inherited_text().color - }; - - // Determine the orientation and cursor to use. - let (_orientation, cursor) = if self.style.writing_mode.is_vertical() { - // TODO: Distinguish between 'sideways-lr' and 'sideways-rl' writing modes in CSS - // Writing Modes Level 4. - (TextOrientation::SidewaysRight, Cursor::VerticalText) - } else { - (TextOrientation::Upright, Cursor::Text) - }; - - // Compute location of the baseline. - // - // FIXME(pcwalton): Get the real container size. - let container_size = Size2D::zero(); - let metrics = &text_fragment.run.font_metrics; - let baseline_origin = stacking_relative_content_box.origin + - LogicalPoint::new(self.style.writing_mode, Au(0), metrics.ascent) - .to_physical(self.style.writing_mode, container_size) - .to_vector(); - - // Base item for all text/shadows - let base = state.create_base_display_item( - clip, - self.node, - get_cursor(&self.style, cursor), - DisplayListSection::Content, - ); - - // NB: According to CSS-BACKGROUNDS, text shadows render in *reverse* order (front - // to back). - - // Shadows - for shadow in text_shadows.iter().rev() { - state.add_display_item(DisplayItem::PushTextShadow(Box::new( - PushTextShadowDisplayItem { - base: base.clone(), - shadow: webrender_api::Shadow { - offset: LayoutVector2D::new(shadow.horizontal.px(), shadow.vertical.px()), - color: self.style.resolve_color(shadow.color).to_layout(), - blur_radius: shadow.blur.px(), - }, - }, - ))); - } - - // Create display items for text decorations. - let text_decorations = self.style().get_inherited_text().text_decorations_in_effect; - - let logical_stacking_relative_content_box = LogicalRect::from_physical( - self.style.writing_mode, - stacking_relative_content_box, - container_size, - ); - - // Underline - if text_decorations.underline { - let mut stacking_relative_box = logical_stacking_relative_content_box; - stacking_relative_box.start.b = logical_stacking_relative_content_box.start.b + - metrics.ascent - - metrics.underline_offset; - stacking_relative_box.size.block = metrics.underline_size; - self.build_display_list_for_text_decoration( - state, - &text_color, - &stacking_relative_box, - clip, - ); - } - - // Overline - if text_decorations.overline { - let mut stacking_relative_box = logical_stacking_relative_content_box; - stacking_relative_box.size.block = metrics.underline_size; - self.build_display_list_for_text_decoration( - state, - &text_color, - &stacking_relative_box, - clip, - ); - } - - // Text - let glyphs = convert_text_run_to_glyphs( - text_fragment.run.clone(), - text_fragment.range, - baseline_origin, - ); - if !glyphs.is_empty() { - let indexable_text = IndexableTextItem { - origin: stacking_relative_content_box.origin, - text_run: text_fragment.run.clone(), - range: text_fragment.range, - baseline_origin, - }; - state.indexable_text.insert(self.node, indexable_text); - - state.add_display_item(DisplayItem::Text(CommonDisplayItem::with_data( - base.clone(), - webrender_api::TextDisplayItem { - bounds: stacking_relative_content_box.to_layout(), - common: items::empty_common_item_properties(), - font_key: text_fragment.run.font_key, - color: text_color.to_layout(), - glyph_options: None, - }, - glyphs, - ))); - } - - // TODO(#17715): emit text-emphasis marks here. - // (just push another TextDisplayItem?) - - // Line-Through - if text_decorations.line_through { - let mut stacking_relative_box = logical_stacking_relative_content_box; - stacking_relative_box.start.b = - stacking_relative_box.start.b + metrics.ascent - metrics.strikeout_offset; - stacking_relative_box.size.block = metrics.strikeout_size; - self.build_display_list_for_text_decoration( - state, - &text_color, - &stacking_relative_box, - clip, - ); - } - - // Pop all the PushTextShadows - if !text_shadows.is_empty() { - state.add_display_item(DisplayItem::PopAllTextShadows(Box::new( - PopAllTextShadowsDisplayItem { base }, - ))); - } - } - - /// Creates the display item for a text decoration: underline, overline, or line-through. - fn build_display_list_for_text_decoration( - &self, - state: &mut DisplayListBuildState, - color: &RGBA, - stacking_relative_box: &LogicalRect<Au>, - clip: Rect<Au>, - ) { - // FIXME(pcwalton, #2795): Get the real container size. - let container_size = Size2D::zero(); - let stacking_relative_box = - stacking_relative_box.to_physical(self.style.writing_mode, container_size); - let base = state.create_base_display_item( - clip, - self.node, - get_cursor(&self.style, Cursor::Default), - DisplayListSection::Content, - ); - - // TODO(gw): Use a better estimate for wavy line thickness. - let area = stacking_relative_box.to_layout(); - let wavy_line_thickness = (0.33 * area.size.height).ceil(); - state.add_display_item(DisplayItem::Line(CommonDisplayItem::new( - base, - webrender_api::LineDisplayItem { - common: items::empty_common_item_properties(), - area, - orientation: webrender_api::LineOrientation::Horizontal, - wavy_line_thickness, - color: color.to_layout(), - style: LineStyle::Solid, - }, - ))); - } - - fn unique_id(&self) -> u64 { - let fragment_type = self.fragment_type(); - let id = self.node.id() as usize; - combine_id_with_fragment_type(id, fragment_type) as u64 - } - - fn fragment_type(&self) -> FragmentType { - self.pseudo.fragment_type() - } -} - -bitflags! { - pub struct StackingContextCollectionFlags: u8 { - /// This flow never establishes a containing block. - const POSITION_NEVER_CREATES_CONTAINING_BLOCK = 0b001; - /// This flow never creates a ClipScrollNode. - const NEVER_CREATES_CLIP_SCROLL_NODE = 0b010; - /// This flow never creates a stacking context. - const NEVER_CREATES_STACKING_CONTEXT = 0b100; - } -} - -/// This structure manages ensuring that modification to StackingContextCollectionState is -/// only temporary. It's useful for moving recursively down the flow tree and ensuring -/// that the state is restored for siblings. To use this structure, we must call -/// SavedStackingContextCollectionState::restore in order to restore the state. -/// TODO(mrobinson): It would be nice to use RAII here to avoid having to call restore. -pub struct SavedStackingContextCollectionState { - stacking_context_id: StackingContextId, - real_stacking_context_id: StackingContextId, - parent_reference_frame_id: ClipScrollNodeIndex, - clipping_and_scrolling: ClippingAndScrolling, - containing_block_clipping_and_scrolling: ClippingAndScrolling, - clips_pushed: usize, - containing_block_clips_pushed: usize, - stacking_relative_content_box: Rect<Au>, -} - -impl SavedStackingContextCollectionState { - fn new(state: &mut StackingContextCollectionState) -> SavedStackingContextCollectionState { - SavedStackingContextCollectionState { - stacking_context_id: state.current_stacking_context_id, - real_stacking_context_id: state.current_real_stacking_context_id, - parent_reference_frame_id: state.current_parent_reference_frame_id, - clipping_and_scrolling: state.current_clipping_and_scrolling, - containing_block_clipping_and_scrolling: state.containing_block_clipping_and_scrolling, - clips_pushed: 0, - containing_block_clips_pushed: 0, - stacking_relative_content_box: state.parent_stacking_relative_content_box, - } - } - - fn switch_to_containing_block_clip(&mut self, state: &mut StackingContextCollectionState) { - let clip = state - .containing_block_clip_stack - .last() - .cloned() - .unwrap_or_else(MaxRect::max_rect); - state.clip_stack.push(clip); - self.clips_pushed += 1; - } - - fn restore(self, state: &mut StackingContextCollectionState) { - state.current_stacking_context_id = self.stacking_context_id; - state.current_real_stacking_context_id = self.real_stacking_context_id; - state.current_parent_reference_frame_id = self.parent_reference_frame_id; - state.current_clipping_and_scrolling = self.clipping_and_scrolling; - state.containing_block_clipping_and_scrolling = - self.containing_block_clipping_and_scrolling; - state.parent_stacking_relative_content_box = self.stacking_relative_content_box; - - let truncate_length = state.clip_stack.len() - self.clips_pushed; - state.clip_stack.truncate(truncate_length); - - let truncate_length = - state.containing_block_clip_stack.len() - self.containing_block_clips_pushed; - state.containing_block_clip_stack.truncate(truncate_length); - } - - fn push_clip( - &mut self, - state: &mut StackingContextCollectionState, - mut clip: Rect<Au>, - positioning: StylePosition, - ) { - if positioning != StylePosition::Fixed { - if let Some(old_clip) = state.clip_stack.last() { - clip = old_clip.intersection(&clip).unwrap_or_else(Rect::zero); - } - } - - state.clip_stack.push(clip); - self.clips_pushed += 1; - - if StylePosition::Absolute == positioning { - state.containing_block_clip_stack.push(clip); - self.containing_block_clips_pushed += 1; - } - } -} - -impl BlockFlow { - fn transform_clip_to_coordinate_space( - &mut self, - state: &mut StackingContextCollectionState, - preserved_state: &mut SavedStackingContextCollectionState, - ) { - if state.clip_stack.is_empty() { - return; - } - let border_box = self.stacking_relative_border_box(CoordinateSystem::Parent); - let transform = match self.fragment.transform_matrix(&border_box) { - Some(transform) => transform, - None => return, - }; - - let perspective = self - .fragment - .perspective_matrix(&border_box) - .unwrap_or(LayoutTransform::identity()); - let transform = transform.pre_mul(&perspective).inverse(); - - let origin = border_box.origin; - let transform_clip = |clip: Rect<Au>| { - if clip == Rect::max_rect() { - return clip; - } - - match transform { - Some(transform) if transform.m13 != 0.0 || transform.m23 != 0.0 => { - // We cannot properly handle perspective transforms, because there may be a - // situation where an element is transformed from outside the clip into the - // clip region. Here we don't have enough information to detect when that is - // happening. For the moment we just punt on trying to optimize the display - // list for those cases. - Rect::max_rect() - }, - Some(transform) => { - let clip = rect( - (clip.origin.x - origin.x).to_f32_px(), - (clip.origin.y - origin.y).to_f32_px(), - clip.size.width.to_f32_px(), - clip.size.height.to_f32_px(), - ); - - let clip = transform.transform_rect(&clip).unwrap(); - - rect( - Au::from_f32_px(clip.origin.x), - Au::from_f32_px(clip.origin.y), - Au::from_f32_px(clip.size.width), - Au::from_f32_px(clip.size.height), - ) - }, - None => Rect::zero(), - } - }; - - if let Some(clip) = state.clip_stack.last().cloned() { - state.clip_stack.push(transform_clip(clip)); - preserved_state.clips_pushed += 1; - } - - if let Some(clip) = state.containing_block_clip_stack.last().cloned() { - state.containing_block_clip_stack.push(transform_clip(clip)); - preserved_state.containing_block_clips_pushed += 1; - } - } - - /// Returns true if this fragment may establish a reference frame and this block - /// creates a stacking context. Both are necessary in order to establish a reference - /// frame. - fn is_reference_frame(&self, context_type: Option<StackingContextType>) -> bool { - match context_type { - Some(StackingContextType::Real) => self.fragment.can_establish_reference_frame(), - _ => false, - } - } - - pub fn collect_stacking_contexts_for_block( - &mut self, - state: &mut StackingContextCollectionState, - flags: StackingContextCollectionFlags, - ) { - let mut preserved_state = SavedStackingContextCollectionState::new(state); - - let stacking_context_type = self.stacking_context_type(flags); - self.base.stacking_context_id = match stacking_context_type { - None => state.current_stacking_context_id, - Some(sc_type) => state.allocate_stacking_context_info(sc_type), - }; - state.current_stacking_context_id = self.base.stacking_context_id; - - if stacking_context_type == Some(StackingContextType::Real) { - state.current_real_stacking_context_id = self.base.stacking_context_id; - } - - let established_reference_frame = if self.is_reference_frame(stacking_context_type) { - // WebRender currently creates reference frames automatically, so just add - // a placeholder node to allocate a ClipScrollNodeIndex for this reference frame. - Some(state.add_clip_scroll_node(ClipScrollNode::placeholder())) - } else { - None - }; - - // We are getting the id of the scroll root that contains us here, not the id of - // any scroll root that we create. If we create a scroll root, its index will be - // stored in state.current_clipping_and_scrolling. If we create a stacking context, - // we don't want it to be contained by its own scroll root. - let containing_clipping_and_scrolling = self.setup_clipping_for_block( - state, - &mut preserved_state, - stacking_context_type, - established_reference_frame, - flags, - ); - - let creates_containing_block = !flags - .contains(StackingContextCollectionFlags::POSITION_NEVER_CREATES_CONTAINING_BLOCK); - let abspos_containing_block = established_reference_frame.is_some() || - (creates_containing_block && self.positioning() != StylePosition::Static); - if abspos_containing_block { - state.containing_block_clipping_and_scrolling = state.current_clipping_and_scrolling; - } - - match stacking_context_type { - None => self.base.collect_stacking_contexts_for_children(state), - Some(StackingContextType::Real) => { - self.create_real_stacking_context_for_block( - preserved_state.stacking_context_id, - containing_clipping_and_scrolling, - established_reference_frame, - state, - ); - }, - Some(stacking_context_type) => { - self.create_pseudo_stacking_context_for_block( - stacking_context_type, - preserved_state.stacking_context_id, - containing_clipping_and_scrolling, - state, - ); - }, - } - - preserved_state.restore(state); - } - - fn setup_clipping_for_block( - &mut self, - state: &mut StackingContextCollectionState, - preserved_state: &mut SavedStackingContextCollectionState, - stacking_context_type: Option<StackingContextType>, - established_reference_frame: Option<ClipScrollNodeIndex>, - flags: StackingContextCollectionFlags, - ) -> ClippingAndScrolling { - // If this block is absolutely positioned, we should be clipped and positioned by - // the scroll root of our nearest ancestor that establishes a containing block. - let containing_clipping_and_scrolling = match self.positioning() { - StylePosition::Absolute => { - preserved_state.switch_to_containing_block_clip(state); - state.current_clipping_and_scrolling = - state.containing_block_clipping_and_scrolling; - state.containing_block_clipping_and_scrolling - }, - StylePosition::Fixed => { - // If we are a fixed positioned stacking context, we want to be scrolled by - // our reference frame instead of the clip scroll node that we are inside. - preserved_state.push_clip(state, Rect::max_rect(), StylePosition::Fixed); - state.current_clipping_and_scrolling.scrolling = - state.current_parent_reference_frame_id; - state.current_clipping_and_scrolling - }, - _ => state.current_clipping_and_scrolling, - }; - self.base.clipping_and_scrolling = Some(containing_clipping_and_scrolling); - - if let Some(reference_frame_index) = established_reference_frame { - let clipping_and_scrolling = ClippingAndScrolling::simple(reference_frame_index); - state.current_clipping_and_scrolling = clipping_and_scrolling; - self.base.clipping_and_scrolling = Some(clipping_and_scrolling); - } - - let stacking_relative_border_box = if self.fragment.establishes_stacking_context() { - self.stacking_relative_border_box(CoordinateSystem::Own) - } else { - self.stacking_relative_border_box(CoordinateSystem::Parent) - }; - - if stacking_context_type == Some(StackingContextType::Real) { - self.transform_clip_to_coordinate_space(state, preserved_state); - } - - if !flags.contains(StackingContextCollectionFlags::NEVER_CREATES_CLIP_SCROLL_NODE) { - self.setup_clip_scroll_node_for_position(state, stacking_relative_border_box); - self.setup_clip_scroll_node_for_overflow(state, stacking_relative_border_box); - self.setup_clip_scroll_node_for_css_clip( - state, - preserved_state, - stacking_relative_border_box, - ); - } - self.base.clip = state - .clip_stack - .last() - .cloned() - .unwrap_or_else(Rect::max_rect); - - // We keep track of our position so that any stickily positioned elements can - // properly determine the extent of their movement relative to scrolling containers. - if !flags.contains(StackingContextCollectionFlags::POSITION_NEVER_CREATES_CONTAINING_BLOCK) - { - let border_box = if self.fragment.establishes_stacking_context() { - stacking_relative_border_box - } else { - self.stacking_relative_border_box(CoordinateSystem::Own) - }; - state.parent_stacking_relative_content_box = - self.fragment.stacking_relative_content_box(border_box) - } - - match self.positioning() { - StylePosition::Absolute | StylePosition::Relative | StylePosition::Fixed => { - state.containing_block_clipping_and_scrolling = state.current_clipping_and_scrolling - }, - _ => {}, - } - - containing_clipping_and_scrolling - } - - fn setup_clip_scroll_node_for_position( - &mut self, - state: &mut StackingContextCollectionState, - border_box: Rect<Au>, - ) { - if self.positioning() != StylePosition::Sticky { - return; - } - - let sticky_position = self.sticky_position(); - if sticky_position.left == MaybeAuto::Auto && - sticky_position.right == MaybeAuto::Auto && - sticky_position.top == MaybeAuto::Auto && - sticky_position.bottom == MaybeAuto::Auto - { - return; - } - - // Since position: sticky elements always establish a stacking context, we will - // have previously calculated our border box in our own coordinate system. In - // order to properly calculate max offsets we need to compare our size and - // position in our parent's coordinate system. - let border_box_in_parent = self.stacking_relative_border_box(CoordinateSystem::Parent); - let margins = self.fragment.margin.to_physical( - self.base - .early_absolute_position_info - .relative_containing_block_mode, - ); - - // Position:sticky elements are always restricted based on the size and position of - // their containing block, which for sticky items is like relative and statically - // positioned items: just the parent block. - let constraint_rect = state.parent_stacking_relative_content_box; - - let to_offset_bound = |constraint_edge: Au, moving_edge: Au| -> f32 { - (constraint_edge - moving_edge).to_f32_px() - }; - - // This is the minimum negative offset and then the maximum positive offset. We just - // specify every edge, but if the corresponding margin is None, that offset has no effect. - let vertical_offset_bounds = StickyOffsetBounds::new( - to_offset_bound( - constraint_rect.min_y(), - border_box_in_parent.min_y() - margins.top, - ), - to_offset_bound(constraint_rect.max_y(), border_box_in_parent.max_y()), - ); - let horizontal_offset_bounds = StickyOffsetBounds::new( - to_offset_bound( - constraint_rect.min_x(), - border_box_in_parent.min_x() - margins.left, - ), - to_offset_bound(constraint_rect.max_x(), border_box_in_parent.max_x()), - ); - - // The margins control which edges have sticky behavior. - let sticky_frame_data = StickyFrameData { - margins: SideOffsets2D::new( - sticky_position.top.to_option().map(|v| v.to_f32_px()), - sticky_position.right.to_option().map(|v| v.to_f32_px()), - sticky_position.bottom.to_option().map(|v| v.to_f32_px()), - sticky_position.left.to_option().map(|v| v.to_f32_px()), - ), - vertical_offset_bounds, - horizontal_offset_bounds, - }; - - let new_clip_scroll_index = state.add_clip_scroll_node(ClipScrollNode { - parent_index: self.clipping_and_scrolling().scrolling, - clip: ClippingRegion::from_rect(border_box.to_layout()), - content_rect: LayoutRect::zero(), - node_type: ClipScrollNodeType::StickyFrame(sticky_frame_data), - }); - - let new_clipping_and_scrolling = ClippingAndScrolling::simple(new_clip_scroll_index); - self.base.clipping_and_scrolling = Some(new_clipping_and_scrolling); - state.current_clipping_and_scrolling = new_clipping_and_scrolling; - } - - fn setup_clip_scroll_node_for_overflow( - &mut self, - state: &mut StackingContextCollectionState, - border_box: Rect<Au>, - ) { - if !self.overflow_style_may_require_clip_scroll_node() { - return; - } - - let content_box = self.fragment.stacking_relative_content_box(border_box); - let has_scrolling_overflow = self.base.overflow.scroll.origin != Point2D::zero() || - self.base.overflow.scroll.size.width > content_box.size.width || - self.base.overflow.scroll.size.height > content_box.size.height || - StyleOverflow::Hidden == self.fragment.style.get_box().overflow_x || - StyleOverflow::Hidden == self.fragment.style.get_box().overflow_y; - - self.mark_scrolling_overflow(has_scrolling_overflow); - if !has_scrolling_overflow { - return; - } - - let sensitivity = if StyleOverflow::Hidden == self.fragment.style.get_box().overflow_x && - StyleOverflow::Hidden == self.fragment.style.get_box().overflow_y - { - ScrollSensitivity::Script - } else { - ScrollSensitivity::ScriptAndInputEvents - }; - - let border_widths = self - .fragment - .style - .logical_border_width() - .to_physical(self.fragment.style.writing_mode); - let clip_rect = border_box.inner_rect(border_widths); - - let mut clip = ClippingRegion::from_rect(clip_rect.to_layout()); - let radii = build_border_radius_for_inner_rect(border_box, &self.fragment.style); - if !radii.is_zero() { - clip.intersect_with_rounded_rect(clip_rect.to_layout(), radii) - } - - let content_size = self.base.overflow.scroll.origin + self.base.overflow.scroll.size; - let content_size = Size2D::new(content_size.x, content_size.y); - - let external_id = - ExternalScrollId(self.fragment.unique_id(), state.pipeline_id.to_webrender()); - let new_clip_scroll_index = state.add_clip_scroll_node(ClipScrollNode { - parent_index: self.clipping_and_scrolling().scrolling, - clip: clip, - content_rect: Rect::new(content_box.origin, content_size).to_layout(), - node_type: ClipScrollNodeType::ScrollFrame(sensitivity, external_id), - }); - - let new_clipping_and_scrolling = ClippingAndScrolling::simple(new_clip_scroll_index); - self.base.clipping_and_scrolling = Some(new_clipping_and_scrolling); - state.current_clipping_and_scrolling = new_clipping_and_scrolling; - } - - /// Adds a scroll root for a block to take the `clip` property into account - /// per CSS 2.1 § 11.1.2. - fn setup_clip_scroll_node_for_css_clip( - &mut self, - state: &mut StackingContextCollectionState, - preserved_state: &mut SavedStackingContextCollectionState, - stacking_relative_border_box: Rect<Au>, - ) { - // Account for `clip` per CSS 2.1 § 11.1.2. - let style_clip_rect = match self.fragment.style().get_effects().clip { - Either::First(style_clip_rect) => style_clip_rect, - _ => return, - }; - - // CSS `clip` should only apply to position:absolute or positione:fixed elements. - // CSS Masking Appendix A: "Applies to: Absolutely positioned elements." - match self.positioning() { - StylePosition::Absolute | StylePosition::Fixed => {}, - _ => return, - } - - fn extract_clip_component(p: &LengthOrAuto) -> Option<Au> { - match *p { - LengthOrAuto::Auto => None, - LengthOrAuto::LengthPercentage(ref length) => Some(Au::from(*length)), - } - } - - let clip_origin = Point2D::new( - stacking_relative_border_box.origin.x + - extract_clip_component(&style_clip_rect.left).unwrap_or_default(), - stacking_relative_border_box.origin.y + - extract_clip_component(&style_clip_rect.top).unwrap_or_default(), - ); - let right = extract_clip_component(&style_clip_rect.right) - .unwrap_or(stacking_relative_border_box.size.width); - let bottom = extract_clip_component(&style_clip_rect.bottom) - .unwrap_or(stacking_relative_border_box.size.height); - let clip_size = Size2D::new(right - clip_origin.x, bottom - clip_origin.y); - - let clip_rect = Rect::new(clip_origin, clip_size); - preserved_state.push_clip(state, clip_rect, self.positioning()); - - let new_index = state.add_clip_scroll_node(ClipScrollNode { - parent_index: self.clipping_and_scrolling().scrolling, - clip: ClippingRegion::from_rect(clip_rect.to_layout()), - content_rect: LayoutRect::zero(), // content_rect isn't important for clips. - node_type: ClipScrollNodeType::Clip, - }); - - let new_indices = ClippingAndScrolling::new(new_index, new_index); - self.base.clipping_and_scrolling = Some(new_indices); - state.current_clipping_and_scrolling = new_indices; - } - - fn create_pseudo_stacking_context_for_block( - &mut self, - stacking_context_type: StackingContextType, - parent_stacking_context_id: StackingContextId, - parent_clipping_and_scrolling: ClippingAndScrolling, - state: &mut StackingContextCollectionState, - ) { - let new_context = self.fragment.create_stacking_context( - self.base.stacking_context_id, - &self.base, - stacking_context_type, - None, - parent_clipping_and_scrolling, - ); - state.add_stacking_context(parent_stacking_context_id, new_context); - - self.base.collect_stacking_contexts_for_children(state); - - let children = state - .stacking_context_info - .get_mut(&self.base.stacking_context_id) - .map(|info| info.take_children()); - if let Some(children) = children { - for child in children { - if child.context_type == StackingContextType::PseudoFloat { - state.add_stacking_context(self.base.stacking_context_id, child); - } else { - state.add_stacking_context(parent_stacking_context_id, child); - } - } - } - } - - fn create_real_stacking_context_for_block( - &mut self, - parent_stacking_context_id: StackingContextId, - parent_clipping_and_scrolling: ClippingAndScrolling, - established_reference_frame: Option<ClipScrollNodeIndex>, - state: &mut StackingContextCollectionState, - ) { - let stacking_context = self.fragment.create_stacking_context( - self.base.stacking_context_id, - &self.base, - StackingContextType::Real, - established_reference_frame, - parent_clipping_and_scrolling, - ); - - state.add_stacking_context(parent_stacking_context_id, stacking_context); - self.base.collect_stacking_contexts_for_children(state); - } - - pub fn build_display_list_for_block_no_damage( - &self, - state: &mut DisplayListBuildState, - border_painting_mode: BorderPaintingMode, - ) { - let background_border_section = self.background_border_section(); - - state.processing_scrolling_overflow_element = self.has_scrolling_overflow(); - - let content_size = if state.processing_scrolling_overflow_element { - let content_size = self.base.overflow.scroll.origin + self.base.overflow.scroll.size; - Some(Size2D::new(content_size.x, content_size.y)) - } else { - None - }; - - let stacking_relative_border_box = self - .base - .stacking_relative_border_box_for_display_list(&self.fragment); - // Add the box that starts the block context. - self.fragment.build_display_list_no_damage( - state, - stacking_relative_border_box, - border_painting_mode, - background_border_section, - self.base.clip, - content_size, - ); - - self.base - .build_display_items_for_debugging_tint(state, self.fragment.node); - - state.processing_scrolling_overflow_element = false; - } - - pub fn build_display_list_for_block( - &mut self, - state: &mut DisplayListBuildState, - border_painting_mode: BorderPaintingMode, - ) { - self.fragment - .restyle_damage - .remove(ServoRestyleDamage::REPAINT); - self.build_display_list_for_block_no_damage(state, border_painting_mode); - } - - pub fn build_display_list_for_background_if_applicable_with_background( - &self, - state: &mut DisplayListBuildState, - background: &style_structs::Background, - background_color: RGBA, - ) { - let stacking_relative_border_box = self - .base - .stacking_relative_border_box_for_display_list(&self.fragment); - let background_border_section = self.background_border_section(); - - self.fragment - .build_display_list_for_background_if_applicable_with_background( - state, - self.fragment.style(), - background, - background_color, - background_border_section, - stacking_relative_border_box, - ) - } - - #[inline] - fn stacking_context_type( - &self, - flags: StackingContextCollectionFlags, - ) -> Option<StackingContextType> { - if flags.contains(StackingContextCollectionFlags::NEVER_CREATES_STACKING_CONTEXT) { - return None; - } - - if self.fragment.establishes_stacking_context() { - return Some(StackingContextType::Real); - } - - if self - .base - .flags - .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) - { - return Some(StackingContextType::PseudoPositioned); - } - - if self.fragment.style.get_box().position != StylePosition::Static { - return Some(StackingContextType::PseudoPositioned); - } - - if self.base.flags.is_float() { - return Some(StackingContextType::PseudoFloat); - } - - None - } -} - -impl BaseFlow { - pub fn build_display_items_for_debugging_tint( - &self, - state: &mut DisplayListBuildState, - node: OpaqueNode, - ) { - if !opts::get().show_debug_parallel_layout { - return; - } - - let thread_id = self.thread_id; - let stacking_context_relative_bounds = Rect::new( - self.stacking_relative_position.to_point(), - self.position.size.to_physical(self.writing_mode), - ); - - let mut color = THREAD_TINT_COLORS[thread_id as usize % THREAD_TINT_COLORS.len()]; - color.a = 1.0; - let base = - state.create_base_display_item(self.clip, node, None, DisplayListSection::Content); - let bounds = stacking_context_relative_bounds.inflate(Au::from_px(2), Au::from_px(2)); - state.add_display_item(DisplayItem::Border(CommonDisplayItem::with_data( - base, - webrender_api::BorderDisplayItem { - bounds: bounds.to_layout(), - common: items::empty_common_item_properties(), - widths: SideOffsets2D::new_all_same(Au::from_px(2)).to_layout(), - details: BorderDetails::Normal(border::simple( - color, - webrender_api::BorderStyle::Solid, - )), - }, - Vec::new(), - ))); - } -} - -/// Gets the cursor to use given the specific ComputedValues. `default_cursor` specifies -/// the cursor to use if `cursor` is `auto`. Typically, this will be `PointerCursor`, but for -/// text display items it may be `TextCursor` or `VerticalTextCursor`. -#[inline] -fn get_cursor(values: &ComputedValues, default_cursor: Cursor) -> Option<Cursor> { - let inherited_ui = values.get_inherited_ui(); - if inherited_ui.pointer_events == PointerEvents::None { - return None; - } - - Some(match inherited_ui.cursor.keyword { - CursorKind::Auto => default_cursor, - CursorKind::None => Cursor::None, - CursorKind::Default => Cursor::Default, - CursorKind::Pointer => Cursor::Pointer, - CursorKind::ContextMenu => Cursor::ContextMenu, - CursorKind::Help => Cursor::Help, - CursorKind::Progress => Cursor::Progress, - CursorKind::Wait => Cursor::Wait, - CursorKind::Cell => Cursor::Cell, - CursorKind::Crosshair => Cursor::Crosshair, - CursorKind::Text => Cursor::Text, - CursorKind::VerticalText => Cursor::VerticalText, - CursorKind::Alias => Cursor::Alias, - CursorKind::Copy => Cursor::Copy, - CursorKind::Move => Cursor::Move, - CursorKind::NoDrop => Cursor::NoDrop, - CursorKind::NotAllowed => Cursor::NotAllowed, - CursorKind::Grab => Cursor::Grab, - CursorKind::Grabbing => Cursor::Grabbing, - CursorKind::EResize => Cursor::EResize, - CursorKind::NResize => Cursor::NResize, - CursorKind::NeResize => Cursor::NeResize, - CursorKind::NwResize => Cursor::NwResize, - CursorKind::SResize => Cursor::SResize, - CursorKind::SeResize => Cursor::SeResize, - CursorKind::SwResize => Cursor::SwResize, - CursorKind::WResize => Cursor::WResize, - CursorKind::EwResize => Cursor::EwResize, - CursorKind::NsResize => Cursor::NsResize, - CursorKind::NeswResize => Cursor::NeswResize, - CursorKind::NwseResize => Cursor::NwseResize, - CursorKind::ColResize => Cursor::ColResize, - CursorKind::RowResize => Cursor::RowResize, - CursorKind::AllScroll => Cursor::AllScroll, - CursorKind::ZoomIn => Cursor::ZoomIn, - CursorKind::ZoomOut => Cursor::ZoomOut, - }) -} - -/// Adjusts borders as appropriate to account for a fragment's status as the -/// first or last fragment within the range of an element. -/// -/// Specifically, this function sets border widths to zero on the sides for -/// which the fragment is not outermost. -fn modify_border_width_for_inline_sides( - border_width: &mut LogicalMargin<Au>, - inline_border_info: InlineNodeBorderInfo, -) { - if !inline_border_info.is_first_fragment_of_element { - border_width.inline_start = Au(0); - } - - if !inline_border_info.is_last_fragment_of_element { - border_width.inline_end = Au(0); - } -} - -/// Describes how to paint the borders. -#[derive(Clone, Copy)] -pub enum BorderPaintingMode<'a> { - /// Paint borders separately (`border-collapse: separate`). - Separate, - /// Paint collapsed borders. - Collapse(&'a CollapsedBordersForCell), - /// Paint no borders. - Hidden, -} - -fn convert_text_run_to_glyphs( - text_run: Arc<TextRun>, - range: Range<ByteIndex>, - mut origin: Point2D<Au>, -) -> Vec<GlyphInstance> { - let mut glyphs = vec![]; - - for slice in text_run.natural_word_slices_in_visual_order(&range) { - for glyph in slice.glyphs.iter_glyphs_for_byte_range(&slice.range) { - let glyph_advance = if glyph.char_is_space() { - glyph.advance() + text_run.extra_word_spacing - } else { - glyph.advance() - }; - if !slice.glyphs.is_whitespace() { - let glyph_offset = glyph.offset().unwrap_or(Point2D::zero()); - let point = origin + glyph_offset.to_vector(); - let glyph = GlyphInstance { - index: glyph.id(), - point: point.to_layout(), - }; - glyphs.push(glyph); - } - origin.x += glyph_advance; - } - } - return glyphs; -} pub struct IndexableTextItem { /// The placement of the text item on the plane. @@ -2988,11 +34,6 @@ pub struct IndexableText { } impl IndexableText { - fn insert(&mut self, node: OpaqueNode, item: IndexableTextItem) { - let entries = self.inner.entry(node).or_insert(Vec::new()); - entries.push(item); - } - pub fn get(&self, node: OpaqueNode) -> Option<&[IndexableTextItem]> { self.inner.get(&node).map(|x| x.as_slice()) } @@ -3010,15 +51,3 @@ impl IndexableText { ) } } - -trait ToF32Px { - type Output; - fn to_f32_px(&self) -> Self::Output; -} - -impl ToF32Px for TypedRect<Au> { - type Output = LayoutRect; - fn to_f32_px(&self) -> LayoutRect { - LayoutRect::from_untyped(&servo_geometry::au_rect_to_f32_rect(*self)) - } -} diff --git a/components/layout_2020/display_list/conversions.rs b/components/layout_2020/display_list/conversions.rs deleted file mode 100644 index 4624b5f8dd0..00000000000 --- a/components/layout_2020/display_list/conversions.rs +++ /dev/null @@ -1,167 +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/. */ - -use app_units::Au; -use euclid::{Point2D, Rect, SideOffsets2D, Size2D, Vector2D}; -use style::computed_values::image_rendering::T as ImageRendering; -use style::computed_values::mix_blend_mode::T as MixBlendMode; -use style::computed_values::transform_style::T as TransformStyle; -use style::values::computed::{BorderStyle, Filter}; -use style::values::specified::border::BorderImageRepeatKeyword; -use style::values::RGBA; -use webrender_api as wr; - -pub trait ToLayout { - type Type; - fn to_layout(&self) -> Self::Type; -} - -impl ToLayout for BorderStyle { - type Type = wr::BorderStyle; - fn to_layout(&self) -> Self::Type { - match *self { - BorderStyle::None => wr::BorderStyle::None, - BorderStyle::Solid => wr::BorderStyle::Solid, - BorderStyle::Double => wr::BorderStyle::Double, - BorderStyle::Dotted => wr::BorderStyle::Dotted, - BorderStyle::Dashed => wr::BorderStyle::Dashed, - BorderStyle::Hidden => wr::BorderStyle::Hidden, - BorderStyle::Groove => wr::BorderStyle::Groove, - BorderStyle::Ridge => wr::BorderStyle::Ridge, - BorderStyle::Inset => wr::BorderStyle::Inset, - BorderStyle::Outset => wr::BorderStyle::Outset, - } - } -} - -impl ToLayout for Filter { - type Type = wr::FilterOp; - fn to_layout(&self) -> Self::Type { - match *self { - Filter::Blur(radius) => wr::FilterOp::Blur(radius.px()), - Filter::Brightness(amount) => wr::FilterOp::Brightness(amount.0), - Filter::Contrast(amount) => wr::FilterOp::Contrast(amount.0), - Filter::Grayscale(amount) => wr::FilterOp::Grayscale(amount.0), - Filter::HueRotate(angle) => wr::FilterOp::HueRotate(angle.radians()), - Filter::Invert(amount) => wr::FilterOp::Invert(amount.0), - Filter::Opacity(amount) => wr::FilterOp::Opacity(amount.0.into(), amount.0), - Filter::Saturate(amount) => wr::FilterOp::Saturate(amount.0), - Filter::Sepia(amount) => wr::FilterOp::Sepia(amount.0), - // Statically check that DropShadow is impossible. - Filter::DropShadow(ref shadow) => match *shadow {}, - // Statically check that Url is impossible. - Filter::Url(ref url) => match *url {}, - } - } -} - -impl ToLayout for ImageRendering { - type Type = wr::ImageRendering; - fn to_layout(&self) -> Self::Type { - match *self { - ImageRendering::Auto => wr::ImageRendering::Auto, - ImageRendering::CrispEdges => wr::ImageRendering::CrispEdges, - ImageRendering::Pixelated => wr::ImageRendering::Pixelated, - } - } -} - -impl ToLayout for MixBlendMode { - type Type = wr::MixBlendMode; - fn to_layout(&self) -> Self::Type { - match *self { - MixBlendMode::Normal => wr::MixBlendMode::Normal, - MixBlendMode::Multiply => wr::MixBlendMode::Multiply, - MixBlendMode::Screen => wr::MixBlendMode::Screen, - MixBlendMode::Overlay => wr::MixBlendMode::Overlay, - MixBlendMode::Darken => wr::MixBlendMode::Darken, - MixBlendMode::Lighten => wr::MixBlendMode::Lighten, - MixBlendMode::ColorDodge => wr::MixBlendMode::ColorDodge, - MixBlendMode::ColorBurn => wr::MixBlendMode::ColorBurn, - MixBlendMode::HardLight => wr::MixBlendMode::HardLight, - MixBlendMode::SoftLight => wr::MixBlendMode::SoftLight, - MixBlendMode::Difference => wr::MixBlendMode::Difference, - MixBlendMode::Exclusion => wr::MixBlendMode::Exclusion, - MixBlendMode::Hue => wr::MixBlendMode::Hue, - MixBlendMode::Saturation => wr::MixBlendMode::Saturation, - MixBlendMode::Color => wr::MixBlendMode::Color, - MixBlendMode::Luminosity => wr::MixBlendMode::Luminosity, - } - } -} - -impl ToLayout for TransformStyle { - type Type = wr::TransformStyle; - fn to_layout(&self) -> Self::Type { - match *self { - TransformStyle::Auto | TransformStyle::Flat => wr::TransformStyle::Flat, - TransformStyle::Preserve3d => wr::TransformStyle::Preserve3D, - } - } -} - -impl ToLayout for RGBA { - type Type = wr::ColorF; - fn to_layout(&self) -> Self::Type { - wr::ColorF::new( - self.red_f32(), - self.green_f32(), - self.blue_f32(), - self.alpha_f32(), - ) - } -} - -impl ToLayout for Point2D<Au> { - type Type = wr::units::LayoutPoint; - fn to_layout(&self) -> Self::Type { - wr::units::LayoutPoint::new(self.x.to_f32_px(), self.y.to_f32_px()) - } -} - -impl ToLayout for Rect<Au> { - type Type = wr::units::LayoutRect; - fn to_layout(&self) -> Self::Type { - wr::units::LayoutRect::new(self.origin.to_layout(), self.size.to_layout()) - } -} - -impl ToLayout for SideOffsets2D<Au> { - type Type = wr::units::LayoutSideOffsets; - fn to_layout(&self) -> Self::Type { - wr::units::LayoutSideOffsets::new( - self.top.to_f32_px(), - self.right.to_f32_px(), - self.bottom.to_f32_px(), - self.left.to_f32_px(), - ) - } -} - -impl ToLayout for Size2D<Au> { - type Type = wr::units::LayoutSize; - fn to_layout(&self) -> Self::Type { - wr::units::LayoutSize::new(self.width.to_f32_px(), self.height.to_f32_px()) - } -} - -impl ToLayout for Vector2D<Au> { - type Type = wr::units::LayoutVector2D; - fn to_layout(&self) -> Self::Type { - wr::units::LayoutVector2D::new(self.x.to_f32_px(), self.y.to_f32_px()) - } -} - -impl ToLayout for BorderImageRepeatKeyword { - type Type = wr::RepeatMode; - - fn to_layout(&self) -> Self::Type { - match *self { - BorderImageRepeatKeyword::Stretch => wr::RepeatMode::Stretch, - BorderImageRepeatKeyword::Repeat => wr::RepeatMode::Repeat, - BorderImageRepeatKeyword::Round => wr::RepeatMode::Round, - BorderImageRepeatKeyword::Space => wr::RepeatMode::Space, - } - } -} diff --git a/components/layout_2020/display_list/gradient.rs b/components/layout_2020/display_list/gradient.rs deleted file mode 100644 index ab56b1d6c9c..00000000000 --- a/components/layout_2020/display_list/gradient.rs +++ /dev/null @@ -1,323 +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/. */ - -use crate::display_list::ToLayout; -use app_units::Au; -use euclid::{Point2D, Size2D, Vector2D}; -use style::properties::ComputedValues; -use style::values::computed::image::{EndingShape, LineDirection}; -use style::values::computed::{Angle, GradientItem, LengthPercentage, Percentage, Position}; -use style::values::generics::image::{Circle, ColorStop, Ellipse, ShapeExtent}; -use webrender_api::{ExtendMode, Gradient, GradientBuilder, GradientStop, RadialGradient}; - -/// A helper data structure for gradients. -#[derive(Clone, Copy)] -struct StopRun { - start_offset: f32, - end_offset: f32, - start_index: usize, - stop_count: usize, -} - -/// Determines the radius of a circle if it was not explictly provided. -/// <https://drafts.csswg.org/css-images-3/#typedef-size> -fn circle_size_keyword( - keyword: ShapeExtent, - size: &Size2D<Au>, - center: &Point2D<Au>, -) -> Size2D<Au> { - let radius = match keyword { - ShapeExtent::ClosestSide | ShapeExtent::Contain => { - let dist = distance_to_sides(size, center, ::std::cmp::min); - ::std::cmp::min(dist.width, dist.height) - }, - ShapeExtent::FarthestSide => { - let dist = distance_to_sides(size, center, ::std::cmp::max); - ::std::cmp::max(dist.width, dist.height) - }, - ShapeExtent::ClosestCorner => distance_to_corner(size, center, ::std::cmp::min), - ShapeExtent::FarthestCorner | ShapeExtent::Cover => { - distance_to_corner(size, center, ::std::cmp::max) - }, - }; - Size2D::new(radius, radius) -} - -/// Returns the radius for an ellipse with the same ratio as if it was matched to the sides. -fn ellipse_radius<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Size2D<Au> -where - F: Fn(Au, Au) -> Au, -{ - let dist = distance_to_sides(size, center, cmp); - Size2D::new( - dist.width.scale_by(::std::f32::consts::FRAC_1_SQRT_2 * 2.0), - dist.height - .scale_by(::std::f32::consts::FRAC_1_SQRT_2 * 2.0), - ) -} - -/// Determines the radius of an ellipse if it was not explictly provided. -/// <https://drafts.csswg.org/css-images-3/#typedef-size> -fn ellipse_size_keyword( - keyword: ShapeExtent, - size: &Size2D<Au>, - center: &Point2D<Au>, -) -> Size2D<Au> { - match keyword { - ShapeExtent::ClosestSide | ShapeExtent::Contain => { - distance_to_sides(size, center, ::std::cmp::min) - }, - ShapeExtent::FarthestSide => distance_to_sides(size, center, ::std::cmp::max), - ShapeExtent::ClosestCorner => ellipse_radius(size, center, ::std::cmp::min), - ShapeExtent::FarthestCorner | ShapeExtent::Cover => { - ellipse_radius(size, center, ::std::cmp::max) - }, - } -} - -fn convert_gradient_stops( - style: &ComputedValues, - gradient_items: &[GradientItem], - total_length: Au, -) -> GradientBuilder { - // Determine the position of each stop per CSS-IMAGES § 3.4. - - // Only keep the color stops, discard the color interpolation hints. - let mut stop_items = gradient_items - .iter() - .filter_map(|item| match *item { - GradientItem::SimpleColorStop(color) => Some(ColorStop { - color, - position: None, - }), - GradientItem::ComplexColorStop { color, position } => Some(ColorStop { - color, - position: Some(position), - }), - _ => None, - }) - .collect::<Vec<_>>(); - - assert!(stop_items.len() >= 2); - - // Run the algorithm from - // https://drafts.csswg.org/css-images-3/#color-stop-syntax - - // Step 1: - // If the first color stop does not have a position, set its position to 0%. - { - let first = stop_items.first_mut().unwrap(); - if first.position.is_none() { - first.position = Some(LengthPercentage::new_percent(Percentage(0.))); - } - } - // If the last color stop does not have a position, set its position to 100%. - { - let last = stop_items.last_mut().unwrap(); - if last.position.is_none() { - last.position = Some(LengthPercentage::new_percent(Percentage(1.0))); - } - } - - // Step 2: Move any stops placed before earlier stops to the - // same position as the preceding stop. - let mut last_stop_position = stop_items.first().unwrap().position.unwrap(); - for stop in stop_items.iter_mut().skip(1) { - if let Some(pos) = stop.position { - if position_to_offset(last_stop_position, total_length) > - position_to_offset(pos, total_length) - { - stop.position = Some(last_stop_position); - } - last_stop_position = stop.position.unwrap(); - } - } - - // Step 3: Evenly space stops without position. - let mut stops = GradientBuilder::new(); - let mut stop_run = None; - for (i, stop) in stop_items.iter().enumerate() { - let offset = match stop.position { - None => { - if stop_run.is_none() { - // Initialize a new stop run. - // `unwrap()` here should never fail because this is the beginning of - // a stop run, which is always bounded by a length or percentage. - let start_offset = - position_to_offset(stop_items[i - 1].position.unwrap(), total_length); - // `unwrap()` here should never fail because this is the end of - // a stop run, which is always bounded by a length or percentage. - let (end_index, end_stop) = stop_items[(i + 1)..] - .iter() - .enumerate() - .find(|&(_, ref stop)| stop.position.is_some()) - .unwrap(); - let end_offset = position_to_offset(end_stop.position.unwrap(), total_length); - stop_run = Some(StopRun { - start_offset, - end_offset, - start_index: i - 1, - stop_count: end_index, - }) - } - - let stop_run = stop_run.unwrap(); - let stop_run_length = stop_run.end_offset - stop_run.start_offset; - stop_run.start_offset + - stop_run_length * (i - stop_run.start_index) as f32 / - ((2 + stop_run.stop_count) as f32) - }, - Some(position) => { - stop_run = None; - position_to_offset(position, total_length) - }, - }; - assert!(offset.is_finite()); - stops.push(GradientStop { - offset: offset, - color: style.resolve_color(stop.color).to_layout(), - }) - } - stops -} - -fn extend_mode(repeating: bool) -> ExtendMode { - if repeating { - ExtendMode::Repeat - } else { - ExtendMode::Clamp - } -} -/// Returns the the distance to the nearest or farthest corner depending on the comperator. -fn distance_to_corner<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Au -where - F: Fn(Au, Au) -> Au, -{ - let dist = distance_to_sides(size, center, cmp); - Au::from_f32_px(dist.width.to_f32_px().hypot(dist.height.to_f32_px())) -} - -/// Returns the distance to the nearest or farthest sides depending on the comparator. -/// -/// The first return value is horizontal distance the second vertical distance. -fn distance_to_sides<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Size2D<Au> -where - F: Fn(Au, Au) -> Au, -{ - let top_side = center.y; - let right_side = size.width - center.x; - let bottom_side = size.height - center.y; - let left_side = center.x; - Size2D::new(cmp(left_side, right_side), cmp(top_side, bottom_side)) -} - -fn position_to_offset(position: LengthPercentage, total_length: Au) -> f32 { - if total_length == Au(0) { - return 0.0; - } - position.to_used_value(total_length).0 as f32 / total_length.0 as f32 -} - -pub fn linear( - style: &ComputedValues, - size: Size2D<Au>, - stops: &[GradientItem], - direction: LineDirection, - repeating: bool, -) -> (Gradient, Vec<GradientStop>) { - use style::values::specified::position::HorizontalPositionKeyword::*; - use style::values::specified::position::VerticalPositionKeyword::*; - let angle = match direction { - LineDirection::Angle(angle) => angle.radians(), - LineDirection::Horizontal(x) => match x { - Left => Angle::from_degrees(270.).radians(), - Right => Angle::from_degrees(90.).radians(), - }, - LineDirection::Vertical(y) => match y { - Top => Angle::from_degrees(0.).radians(), - Bottom => Angle::from_degrees(180.).radians(), - }, - LineDirection::Corner(horizontal, vertical) => { - // This the angle for one of the diagonals of the box. Our angle - // will either be this one, this one + PI, or one of the other - // two perpendicular angles. - let atan = (size.height.to_f32_px() / size.width.to_f32_px()).atan(); - match (horizontal, vertical) { - (Right, Bottom) => ::std::f32::consts::PI - atan, - (Left, Bottom) => ::std::f32::consts::PI + atan, - (Right, Top) => atan, - (Left, Top) => -atan, - } - }, - }; - - // Get correct gradient line length, based on: - // https://drafts.csswg.org/css-images-3/#linear-gradients - let dir = Point2D::new(angle.sin(), -angle.cos()); - - let line_length = - (dir.x * size.width.to_f32_px()).abs() + (dir.y * size.height.to_f32_px()).abs(); - - let inv_dir_length = 1.0 / (dir.x * dir.x + dir.y * dir.y).sqrt(); - - // This is the vector between the center and the ending point; i.e. half - // of the distance between the starting point and the ending point. - let delta = Vector2D::new( - Au::from_f32_px(dir.x * inv_dir_length * line_length / 2.0), - Au::from_f32_px(dir.y * inv_dir_length * line_length / 2.0), - ); - - // This is the length of the gradient line. - let length = Au::from_f32_px((delta.x.to_f32_px() * 2.0).hypot(delta.y.to_f32_px() * 2.0)); - - let mut builder = convert_gradient_stops(style, stops, length); - - let center = Point2D::new(size.width / 2, size.height / 2); - - ( - builder.gradient( - (center - delta).to_layout(), - (center + delta).to_layout(), - extend_mode(repeating), - ), - builder.into_stops(), - ) -} - -pub fn radial( - style: &ComputedValues, - size: Size2D<Au>, - stops: &[GradientItem], - shape: EndingShape, - center: Position, - repeating: bool, -) -> (RadialGradient, Vec<GradientStop>) { - let center = Point2D::new( - center.horizontal.to_used_value(size.width), - center.vertical.to_used_value(size.height), - ); - let radius = match shape { - EndingShape::Circle(Circle::Radius(length)) => { - let length = Au::from(length); - Size2D::new(length, length) - }, - EndingShape::Circle(Circle::Extent(extent)) => circle_size_keyword(extent, &size, ¢er), - EndingShape::Ellipse(Ellipse::Radii(x, y)) => { - Size2D::new(x.to_used_value(size.width), y.to_used_value(size.height)) - }, - EndingShape::Ellipse(Ellipse::Extent(extent)) => { - ellipse_size_keyword(extent, &size, ¢er) - }, - }; - - let mut builder = convert_gradient_stops(style, stops, radius.width); - ( - builder.radial_gradient( - center.to_layout(), - radius.to_layout(), - extend_mode(repeating), - ), - builder.into_stops(), - ) -} diff --git a/components/layout_2020/display_list/items.rs b/components/layout_2020/display_list/items.rs index b6674d972fe..8cfa145b43c 100644 --- a/components/layout_2020/display_list/items.rs +++ b/components/layout_2020/display_list/items.rs @@ -2,794 +2,22 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -//! Servo heavily uses display lists, which are retained-mode lists of painting commands to -//! perform. Using a list instead of painting elements in immediate mode allows transforms, hit -//! testing, and invalidation to be performed using the same primitives as painting. It also allows -//! Servo to aggressively cull invisible and out-of-bounds painting elements, to reduce overdraw. -//! -//! Display items describe relatively high-level drawing operations (for example, entire borders -//! and shadows instead of lines and blur operations), to reduce the amount of allocation required. -//! They are therefore not exactly analogous to constructs like Skia pictures, which consist of -//! low-level drawing primitives. - -use euclid::{SideOffsets2D, Vector2D}; -use gfx_traits::print_tree::PrintTree; -use gfx_traits::{self, StackingContextId}; -use msg::constellation_msg::PipelineId; -use net_traits::image::base::Image; -use servo_geometry::MaxRect; -use std::cmp::Ordering; +use euclid::Vector2D; +use gfx_traits; use std::collections::HashMap; use std::f32; -use std::fmt; -use style::computed_values::_servo_top_layer::T as InTopLayer; -use webrender_api as wr; -use webrender_api::units::{LayoutPoint, LayoutRect, LayoutSize, LayoutTransform}; -use webrender_api::{BorderRadius, ClipId, ClipMode, CommonItemProperties, ComplexClipRegion}; -use webrender_api::{ExternalScrollId, FilterOp, GlyphInstance, GradientStop, ImageKey}; -use webrender_api::{MixBlendMode, ScrollSensitivity, Shadow, SpatialId}; -use webrender_api::{StickyOffsetBounds, TransformStyle}; +use webrender_api::ExternalScrollId; pub use style::dom::OpaqueNode; -/// The factor that we multiply the blur radius by in order to inflate the boundaries of display -/// items that involve a blur. This ensures that the display item boundaries include all the ink. -pub static BLUR_INFLATION_FACTOR: i32 = 3; - -/// An index into the vector of ClipScrollNodes. During WebRender conversion these nodes -/// are given ClipIds. -#[derive(Clone, Copy, Debug, PartialEq, Serialize)] -pub struct ClipScrollNodeIndex(usize); - -impl ClipScrollNodeIndex { - pub fn root_scroll_node() -> ClipScrollNodeIndex { - ClipScrollNodeIndex(1) - } - - pub fn root_reference_frame() -> ClipScrollNodeIndex { - ClipScrollNodeIndex(0) - } - - pub fn new(index: usize) -> ClipScrollNodeIndex { - assert_ne!(index, 0, "Use the root_reference_frame constructor"); - assert_ne!(index, 1, "Use the root_scroll_node constructor"); - ClipScrollNodeIndex(index) - } - - pub fn is_root_scroll_node(&self) -> bool { - *self == Self::root_scroll_node() - } - - pub fn to_define_item(&self) -> DisplayItem { - DisplayItem::DefineClipScrollNode(Box::new(DefineClipScrollNodeItem { - base: BaseDisplayItem::empty(), - node_index: *self, - })) - } - - pub fn to_index(self) -> usize { - self.0 - } -} - -/// A set of indices into the clip scroll node vector for a given item. -#[derive(Clone, Copy, Debug, PartialEq, Serialize)] -pub struct ClippingAndScrolling { - pub scrolling: ClipScrollNodeIndex, - pub clipping: Option<ClipScrollNodeIndex>, -} - -impl ClippingAndScrolling { - pub fn simple(scrolling: ClipScrollNodeIndex) -> ClippingAndScrolling { - ClippingAndScrolling { - scrolling, - clipping: None, - } - } - - pub fn new(scrolling: ClipScrollNodeIndex, clipping: ClipScrollNodeIndex) -> Self { - ClippingAndScrolling { - scrolling, - clipping: Some(clipping), - } - } -} - #[derive(Serialize)] -pub struct DisplayList { - pub list: Vec<DisplayItem>, - pub clip_scroll_nodes: Vec<ClipScrollNode>, -} - -impl DisplayList { - /// Return the bounds of this display list based on the dimensions of the root - /// stacking context. - pub fn bounds(&self) -> LayoutRect { - match self.list.get(0) { - Some(&DisplayItem::PushStackingContext(ref item)) => item.stacking_context.bounds, - Some(_) => unreachable!("Root element of display list not stacking context."), - None => LayoutRect::zero(), - } - } - - pub fn print(&self) { - let mut print_tree = PrintTree::new("Display List".to_owned()); - self.print_with_tree(&mut print_tree); - } - - pub fn print_with_tree(&self, print_tree: &mut PrintTree) { - print_tree.new_level("ClipScrollNodes".to_owned()); - for node in &self.clip_scroll_nodes { - print_tree.add_item(format!("{:?}", node)); - } - print_tree.end_level(); - - print_tree.new_level("Items".to_owned()); - for item in &self.list { - print_tree.add_item(format!( - "{:?} StackingContext: {:?} {:?}", - item, - item.base().stacking_context_id, - item.clipping_and_scrolling() - )); - } - print_tree.end_level(); - } -} +pub struct DisplayList {} impl gfx_traits::DisplayList for DisplayList { - /// Analyze the display list to figure out if this may be the first - /// contentful paint (i.e. the display list contains items of type text, - /// image, non-white canvas or SVG). Used by metrics. fn is_contentful(&self) -> bool { - for item in &self.list { - match item { - &DisplayItem::Text(_) | &DisplayItem::Image(_) => return true, - _ => (), - } - } - false } } -/// Display list sections that make up a stacking context. Each section here refers -/// to the steps in CSS 2.1 Appendix E. -/// -#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)] -pub enum DisplayListSection { - BackgroundAndBorders, - BlockBackgroundsAndBorders, - Content, - Outlines, -} - -#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)] -pub enum StackingContextType { - Real, - PseudoPositioned, - PseudoFloat, -} - -#[derive(Clone, Serialize)] -/// Represents one CSS stacking context, which may or may not have a hardware layer. -pub struct StackingContext { - /// The ID of this StackingContext for uniquely identifying it. - pub id: StackingContextId, - - /// The type of this StackingContext. Used for collecting and sorting. - pub context_type: StackingContextType, - - /// The position and size of this stacking context. - pub bounds: LayoutRect, - - /// The overflow rect for this stacking context in its coordinate system. - pub overflow: LayoutRect, - - /// The `z-index` for this stacking context. - pub z_index: i32, - - /// Whether this is the top layer. - pub in_top_layer: InTopLayer, - - /// CSS filters to be applied to this stacking context (including opacity). - pub filters: Vec<FilterOp>, - - /// The blend mode with which this stacking context blends with its backdrop. - pub mix_blend_mode: MixBlendMode, - - /// A transform to be applied to this stacking context. - pub transform: Option<LayoutTransform>, - - /// The transform style of this stacking context. - pub transform_style: TransformStyle, - - /// The perspective matrix to be applied to children. - pub perspective: Option<LayoutTransform>, - - /// The clip and scroll info for this StackingContext. - pub parent_clipping_and_scrolling: ClippingAndScrolling, - - /// The index of the reference frame that this stacking context estalishes. - pub established_reference_frame: Option<ClipScrollNodeIndex>, -} - -impl StackingContext { - /// Creates a new stacking context. - #[inline] - pub fn new( - id: StackingContextId, - context_type: StackingContextType, - bounds: LayoutRect, - overflow: LayoutRect, - z_index: i32, - in_top_layer: InTopLayer, - filters: Vec<FilterOp>, - mix_blend_mode: MixBlendMode, - transform: Option<LayoutTransform>, - transform_style: TransformStyle, - perspective: Option<LayoutTransform>, - parent_clipping_and_scrolling: ClippingAndScrolling, - established_reference_frame: Option<ClipScrollNodeIndex>, - ) -> StackingContext { - StackingContext { - id, - context_type, - bounds, - overflow, - z_index, - in_top_layer, - filters, - mix_blend_mode, - transform, - transform_style, - perspective, - parent_clipping_and_scrolling, - established_reference_frame, - } - } - - #[inline] - pub fn root() -> StackingContext { - StackingContext::new( - StackingContextId::root(), - StackingContextType::Real, - LayoutRect::zero(), - LayoutRect::zero(), - 0, - InTopLayer::None, - vec![], - MixBlendMode::Normal, - None, - TransformStyle::Flat, - None, - ClippingAndScrolling::simple(ClipScrollNodeIndex::root_scroll_node()), - None, - ) - } - - pub fn to_display_list_items(self) -> (DisplayItem, DisplayItem) { - let mut base_item = BaseDisplayItem::empty(); - base_item.stacking_context_id = self.id; - base_item.clipping_and_scrolling = self.parent_clipping_and_scrolling; - - let pop_item = DisplayItem::PopStackingContext(Box::new(PopStackingContextItem { - base: base_item.clone(), - stacking_context_id: self.id, - })); - - let push_item = DisplayItem::PushStackingContext(Box::new(PushStackingContextItem { - base: base_item, - stacking_context: self, - })); - - (push_item, pop_item) - } -} - -impl Ord for StackingContext { - fn cmp(&self, other: &Self) -> Ordering { - if self.in_top_layer == InTopLayer::Top { - if other.in_top_layer == InTopLayer::Top { - return Ordering::Equal; - } else { - return Ordering::Greater; - } - } else if other.in_top_layer == InTopLayer::Top { - return Ordering::Less; - } - - if self.z_index != 0 || other.z_index != 0 { - return self.z_index.cmp(&other.z_index); - } - - match (self.context_type, other.context_type) { - (StackingContextType::PseudoFloat, StackingContextType::PseudoFloat) => Ordering::Equal, - (StackingContextType::PseudoFloat, _) => Ordering::Less, - (_, StackingContextType::PseudoFloat) => Ordering::Greater, - (_, _) => Ordering::Equal, - } - } -} - -impl PartialOrd for StackingContext { - fn partial_cmp(&self, other: &Self) -> Option<Ordering> { - Some(self.cmp(other)) - } -} - -impl Eq for StackingContext {} -impl PartialEq for StackingContext { - fn eq(&self, other: &Self) -> bool { - self.id == other.id - } -} - -impl fmt::Debug for StackingContext { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let type_string = if self.context_type == StackingContextType::Real { - "StackingContext" - } else { - "Pseudo-StackingContext" - }; - - write!( - f, - "{} at {:?} with overflow {:?}: {:?}", - type_string, self.bounds, self.overflow, self.id - ) - } -} - -#[derive(Clone, Debug, PartialEq, Serialize)] -pub struct StickyFrameData { - pub margins: SideOffsets2D<Option<f32>>, - pub vertical_offset_bounds: StickyOffsetBounds, - pub horizontal_offset_bounds: StickyOffsetBounds, -} - -#[derive(Clone, Debug, PartialEq, Serialize)] -pub enum ClipScrollNodeType { - Placeholder, - ScrollFrame(ScrollSensitivity, ExternalScrollId), - StickyFrame(StickyFrameData), - Clip, -} - -/// Defines a clip scroll node. -#[derive(Clone, Debug, Serialize)] -pub struct ClipScrollNode { - /// The index of the parent of this ClipScrollNode. - pub parent_index: ClipScrollNodeIndex, - - /// The position of this scroll root's frame in the parent stacking context. - pub clip: ClippingRegion, - - /// The rect of the contents that can be scrolled inside of the scroll root. - pub content_rect: LayoutRect, - - /// The type of this ClipScrollNode. - pub node_type: ClipScrollNodeType, -} - -impl ClipScrollNode { - pub fn placeholder() -> ClipScrollNode { - ClipScrollNode { - parent_index: ClipScrollNodeIndex(0), - clip: ClippingRegion::from_rect(LayoutRect::zero()), - content_rect: LayoutRect::zero(), - node_type: ClipScrollNodeType::Placeholder, - } - } - - pub fn is_placeholder(&self) -> bool { - self.node_type == ClipScrollNodeType::Placeholder - } -} - -/// One drawing command in the list. -#[derive(Clone, Serialize)] -pub enum DisplayItem { - Rectangle(Box<CommonDisplayItem<wr::RectangleDisplayItem>>), - Text(Box<CommonDisplayItem<wr::TextDisplayItem, Vec<GlyphInstance>>>), - Image(Box<CommonDisplayItem<wr::ImageDisplayItem>>), - Border(Box<CommonDisplayItem<wr::BorderDisplayItem, Vec<GradientStop>>>), - Gradient(Box<CommonDisplayItem<wr::GradientDisplayItem, Vec<GradientStop>>>), - RadialGradient(Box<CommonDisplayItem<wr::RadialGradientDisplayItem, Vec<GradientStop>>>), - Line(Box<CommonDisplayItem<wr::LineDisplayItem>>), - BoxShadow(Box<CommonDisplayItem<wr::BoxShadowDisplayItem>>), - PushTextShadow(Box<PushTextShadowDisplayItem>), - PopAllTextShadows(Box<PopAllTextShadowsDisplayItem>), - Iframe(Box<IframeDisplayItem>), - PushStackingContext(Box<PushStackingContextItem>), - PopStackingContext(Box<PopStackingContextItem>), - DefineClipScrollNode(Box<DefineClipScrollNodeItem>), -} - -/// Information common to all display items. -#[derive(Clone, Serialize)] -pub struct BaseDisplayItem { - /// Metadata attached to this display item. - pub metadata: DisplayItemMetadata, - - /// The clip rectangle to use for this item. - pub clip_rect: LayoutRect, - - /// The section of the display list that this item belongs to. - pub section: DisplayListSection, - - /// The id of the stacking context this item belongs to. - pub stacking_context_id: StackingContextId, - - /// The clip and scroll info for this item. - pub clipping_and_scrolling: ClippingAndScrolling, -} - -impl BaseDisplayItem { - #[inline(always)] - pub fn new( - metadata: DisplayItemMetadata, - clip_rect: LayoutRect, - section: DisplayListSection, - stacking_context_id: StackingContextId, - clipping_and_scrolling: ClippingAndScrolling, - ) -> BaseDisplayItem { - BaseDisplayItem { - metadata, - clip_rect, - section, - stacking_context_id, - clipping_and_scrolling, - } - } - - #[inline(always)] - pub fn empty() -> BaseDisplayItem { - BaseDisplayItem { - metadata: DisplayItemMetadata { - node: OpaqueNode(0), - pointing: None, - }, - // Create a rectangle of maximal size. - clip_rect: LayoutRect::max_rect(), - section: DisplayListSection::Content, - stacking_context_id: StackingContextId::root(), - clipping_and_scrolling: ClippingAndScrolling::simple( - ClipScrollNodeIndex::root_scroll_node(), - ), - } - } -} - -pub fn empty_common_item_properties() -> CommonItemProperties { - CommonItemProperties { - clip_rect: LayoutRect::max_rect(), - clip_id: ClipId::root(wr::PipelineId::dummy()), - spatial_id: SpatialId::root_scroll_node(wr::PipelineId::dummy()), - hit_info: None, - is_backface_visible: false, - } -} - -/// A clipping region for a display item. Currently, this can describe rectangles, rounded -/// rectangles (for `border-radius`), or arbitrary intersections of the two. Arbitrary transforms -/// are not supported because those are handled by the higher-level `StackingContext` abstraction. -#[derive(Clone, PartialEq, Serialize)] -pub struct ClippingRegion { - /// The main rectangular region. This does not include any corners. - pub main: LayoutRect, - /// Any complex regions. - /// - /// TODO(pcwalton): Atomically reference count these? Not sure if it's worth the trouble. - /// Measure and follow up. - pub complex: Vec<ComplexClipRegion>, -} - -impl ClippingRegion { - /// Returns an empty clipping region that, if set, will result in no pixels being visible. - #[inline] - pub fn empty() -> ClippingRegion { - ClippingRegion { - main: LayoutRect::zero(), - complex: Vec::new(), - } - } - - /// Returns an all-encompassing clipping region that clips no pixels out. - #[inline] - pub fn max() -> ClippingRegion { - ClippingRegion { - main: LayoutRect::max_rect(), - complex: Vec::new(), - } - } - - /// Returns a clipping region that represents the given rectangle. - #[inline] - pub fn from_rect(rect: LayoutRect) -> ClippingRegion { - ClippingRegion { - main: rect, - complex: Vec::new(), - } - } - - /// Intersects this clipping region with the given rounded rectangle. - #[inline] - pub fn intersect_with_rounded_rect(&mut self, rect: LayoutRect, radii: BorderRadius) { - let new_complex_region = ComplexClipRegion { - rect, - radii, - mode: ClipMode::Clip, - }; - - // FIXME(pcwalton): This is O(n²) worst case for disjoint clipping regions. Is that OK? - // They're slow anyway… - // - // Possibly relevant if we want to do better: - // - // http://www.inrg.csie.ntu.edu.tw/algorithm2014/presentation/D&C%20Lee-84.pdf - for existing_complex_region in &mut self.complex { - if completely_encloses(&existing_complex_region, &new_complex_region) { - *existing_complex_region = new_complex_region; - return; - } - if completely_encloses(&new_complex_region, &existing_complex_region) { - return; - } - } - - self.complex.push(new_complex_region); - } -} - -impl fmt::Debug for ClippingRegion { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if *self == ClippingRegion::max() { - write!(f, "ClippingRegion::Max") - } else if *self == ClippingRegion::empty() { - write!(f, "ClippingRegion::Empty") - } else if self.main == LayoutRect::max_rect() { - write!(f, "ClippingRegion(Complex={:?})", self.complex) - } else { - write!( - f, - "ClippingRegion(Rect={:?}, Complex={:?})", - self.main, self.complex - ) - } - } -} - -// TODO(pcwalton): This could be more aggressive by considering points that touch the inside of -// the border radius ellipse. -fn completely_encloses(this: &ComplexClipRegion, other: &ComplexClipRegion) -> bool { - let left = this.radii.top_left.width.max(this.radii.bottom_left.width); - let top = this.radii.top_left.height.max(this.radii.top_right.height); - let right = this - .radii - .top_right - .width - .max(this.radii.bottom_right.width); - let bottom = this - .radii - .bottom_left - .height - .max(this.radii.bottom_right.height); - let interior = LayoutRect::new( - LayoutPoint::new(this.rect.origin.x + left, this.rect.origin.y + top), - LayoutSize::new( - this.rect.size.width - left - right, - this.rect.size.height - top - bottom, - ), - ); - interior.origin.x <= other.rect.origin.x && - interior.origin.y <= other.rect.origin.y && - interior.max_x() >= other.rect.max_x() && - interior.max_y() >= other.rect.max_y() -} - -/// Metadata attached to each display item. This is useful for performing auxiliary threads with -/// the display list involving hit testing: finding the originating DOM node and determining the -/// cursor to use when the element is hovered over. -#[derive(Clone, Copy, Serialize)] -pub struct DisplayItemMetadata { - /// The DOM node from which this display item originated. - pub node: OpaqueNode, - /// The value of the `cursor` property when the mouse hovers over this display item. If `None`, - /// this display item is ineligible for pointer events (`pointer-events: none`). - pub pointing: Option<u16>, -} - -#[derive(Clone, Eq, PartialEq, Serialize)] -pub enum TextOrientation { - Upright, - SidewaysLeft, - SidewaysRight, -} - -/// Paints an iframe. -#[derive(Clone, Serialize)] -pub struct IframeDisplayItem { - pub base: BaseDisplayItem, - pub iframe: PipelineId, - pub bounds: LayoutRect, -} - -#[derive(Clone, Serialize)] -pub struct CommonDisplayItem<T, U = ()> { - pub base: BaseDisplayItem, - pub item: T, - pub data: U, -} - -impl<T> CommonDisplayItem<T> { - pub fn new(base: BaseDisplayItem, item: T) -> Box<CommonDisplayItem<T>> { - Box::new(CommonDisplayItem { - base, - item, - data: (), - }) - } -} - -impl<T, U> CommonDisplayItem<T, U> { - pub fn with_data(base: BaseDisplayItem, item: T, data: U) -> Box<CommonDisplayItem<T, U>> { - Box::new(CommonDisplayItem { base, item, data }) - } -} - -/// Defines a text shadow that affects all items until the paired PopTextShadow. -#[derive(Clone, Serialize)] -pub struct PushTextShadowDisplayItem { - /// Fields common to all display items. - pub base: BaseDisplayItem, - - pub shadow: Shadow, -} - -/// Defines a text shadow that affects all items until the next PopTextShadow. -#[derive(Clone, Serialize)] -pub struct PopAllTextShadowsDisplayItem { - /// Fields common to all display items. - pub base: BaseDisplayItem, -} - -/// Defines a stacking context. -#[derive(Clone, Serialize)] -pub struct PushStackingContextItem { - /// Fields common to all display items. - pub base: BaseDisplayItem, - - pub stacking_context: StackingContext, -} - -/// Defines a stacking context. -#[derive(Clone, Serialize)] -pub struct PopStackingContextItem { - /// Fields common to all display items. - pub base: BaseDisplayItem, - - pub stacking_context_id: StackingContextId, -} - -/// Starts a group of items inside a particular scroll root. -#[derive(Clone, Serialize)] -pub struct DefineClipScrollNodeItem { - /// Fields common to all display items. - pub base: BaseDisplayItem, - - /// The scroll root that this item starts. - pub node_index: ClipScrollNodeIndex, -} - -impl DisplayItem { - pub fn base(&self) -> &BaseDisplayItem { - match *self { - DisplayItem::Rectangle(ref rect) => &rect.base, - DisplayItem::Text(ref text) => &text.base, - DisplayItem::Image(ref image_item) => &image_item.base, - DisplayItem::Border(ref border) => &border.base, - DisplayItem::Gradient(ref gradient) => &gradient.base, - DisplayItem::RadialGradient(ref gradient) => &gradient.base, - DisplayItem::Line(ref line) => &line.base, - DisplayItem::BoxShadow(ref box_shadow) => &box_shadow.base, - DisplayItem::PushTextShadow(ref push_text_shadow) => &push_text_shadow.base, - DisplayItem::PopAllTextShadows(ref pop_text_shadow) => &pop_text_shadow.base, - DisplayItem::Iframe(ref iframe) => &iframe.base, - DisplayItem::PushStackingContext(ref stacking_context) => &stacking_context.base, - DisplayItem::PopStackingContext(ref item) => &item.base, - DisplayItem::DefineClipScrollNode(ref item) => &item.base, - } - } - - pub fn clipping_and_scrolling(&self) -> ClippingAndScrolling { - self.base().clipping_and_scrolling - } - - pub fn stacking_context_id(&self) -> StackingContextId { - self.base().stacking_context_id - } - - pub fn section(&self) -> DisplayListSection { - self.base().section - } - - pub fn bounds(&self) -> LayoutRect { - match *self { - DisplayItem::Rectangle(ref item) => item.item.common.clip_rect, - DisplayItem::Text(ref item) => item.item.bounds, - DisplayItem::Image(ref item) => item.item.bounds, - DisplayItem::Border(ref item) => item.item.bounds, - DisplayItem::Gradient(ref item) => item.item.bounds, - DisplayItem::RadialGradient(ref item) => item.item.bounds, - DisplayItem::Line(ref item) => item.item.area, - DisplayItem::BoxShadow(ref item) => item.item.box_bounds, - DisplayItem::PushTextShadow(_) => LayoutRect::zero(), - DisplayItem::PopAllTextShadows(_) => LayoutRect::zero(), - DisplayItem::Iframe(ref item) => item.bounds, - DisplayItem::PushStackingContext(ref item) => item.stacking_context.bounds, - DisplayItem::PopStackingContext(_) => LayoutRect::zero(), - DisplayItem::DefineClipScrollNode(_) => LayoutRect::zero(), - } - } -} - -impl fmt::Debug for DisplayItem { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if let DisplayItem::PushStackingContext(ref item) = *self { - return write!(f, "PushStackingContext({:?})", item.stacking_context); - } - - if let DisplayItem::PopStackingContext(ref item) = *self { - return write!(f, "PopStackingContext({:?}", item.stacking_context_id); - } - - if let DisplayItem::DefineClipScrollNode(ref item) = *self { - return write!(f, "DefineClipScrollNode({:?}", item.node_index); - } - - write!( - f, - "{} @ {:?} {:?}", - match *self { - DisplayItem::Rectangle(_) => "Rectangle".to_owned(), - DisplayItem::Text(_) => "Text".to_owned(), - DisplayItem::Image(_) => "Image".to_owned(), - DisplayItem::Border(_) => "Border".to_owned(), - DisplayItem::Gradient(_) => "Gradient".to_owned(), - DisplayItem::RadialGradient(_) => "RadialGradient".to_owned(), - DisplayItem::Line(_) => "Line".to_owned(), - DisplayItem::BoxShadow(_) => "BoxShadow".to_owned(), - DisplayItem::PushTextShadow(_) => "PushTextShadow".to_owned(), - DisplayItem::PopAllTextShadows(_) => "PopTextShadow".to_owned(), - DisplayItem::Iframe(_) => "Iframe".to_owned(), - DisplayItem::PushStackingContext(_) | - DisplayItem::PopStackingContext(_) | - DisplayItem::DefineClipScrollNode(_) => "".to_owned(), - }, - self.bounds(), - self.base().clip_rect - ) - } -} - -#[derive(Clone, Copy, Serialize)] -pub struct WebRenderImageInfo { - pub width: u32, - pub height: u32, - pub key: Option<ImageKey>, -} - -impl WebRenderImageInfo { - #[inline] - pub fn from_image(image: &Image) -> WebRenderImageInfo { - WebRenderImageInfo { - width: image.width, - height: image.height, - key: image.id, - } - } -} - /// The type of the scroll offset list. This is only populated if WebRender is in use. pub type ScrollOffsetMap = HashMap<ExternalScrollId, Vector2D<f32>>; diff --git a/components/layout_2020/display_list/mod.rs b/components/layout_2020/display_list/mod.rs index 302728fbf22..fc9e5359699 100644 --- a/components/layout_2020/display_list/mod.rs +++ b/components/layout_2020/display_list/mod.rs @@ -2,18 +2,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -pub use self::builder::BorderPaintingMode; -pub use self::builder::DisplayListBuildState; pub use self::builder::IndexableText; -pub use self::builder::StackingContextCollectionFlags; -pub use self::builder::StackingContextCollectionState; -pub use self::conversions::ToLayout; pub use self::webrender_helpers::WebRenderDisplayListConverter; -mod background; -mod border; mod builder; -mod conversions; -mod gradient; pub mod items; mod webrender_helpers; diff --git a/components/layout_2020/display_list/webrender_helpers.rs b/components/layout_2020/display_list/webrender_helpers.rs index 6bdb1bfcfdc..801e404a64b 100644 --- a/components/layout_2020/display_list/webrender_helpers.rs +++ b/components/layout_2020/display_list/webrender_helpers.rs @@ -2,320 +2,25 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -// TODO(gw): This contains helper traits and implementations for converting Servo display lists -// into WebRender display lists. In the future, this step should be completely removed. -// This might be achieved by sharing types between WR and Servo display lists, or -// completely converting layout to directly generate WebRender display lists, for example. - -use crate::display_list::items::{BaseDisplayItem, ClipScrollNode, ClipScrollNodeType}; -use crate::display_list::items::{DisplayItem, DisplayList, StackingContextType}; +use crate::display_list::items::DisplayList; use msg::constellation_msg::PipelineId; -use webrender_api::units::LayoutPoint; -use webrender_api::{self, ClipId, CommonItemProperties, DisplayItem as WrDisplayItem}; -use webrender_api::{DisplayListBuilder, PropertyBinding, PushStackingContextDisplayItem}; -use webrender_api::{ - RasterSpace, ReferenceFrameKind, SpaceAndClipInfo, SpatialId, StackingContext, -}; +use webrender_api::{self, DisplayListBuilder}; +use webrender_api::units::{LayoutSize}; pub trait WebRenderDisplayListConverter { fn convert_to_webrender(&mut self, pipeline_id: PipelineId) -> DisplayListBuilder; } -struct ClipScrollState { - clip_ids: Vec<Option<ClipId>>, - spatial_ids: Vec<Option<SpatialId>>, - active_clip_id: ClipId, - active_spatial_id: SpatialId, -} - -trait WebRenderDisplayItemConverter { - fn convert_to_webrender( - &mut self, - clip_scroll_nodes: &[ClipScrollNode], - state: &mut ClipScrollState, - builder: &mut DisplayListBuilder, - ); -} - impl WebRenderDisplayListConverter for DisplayList { fn convert_to_webrender(&mut self, pipeline_id: PipelineId) -> DisplayListBuilder { - let mut clip_ids = vec![None; self.clip_scroll_nodes.len()]; - let mut spatial_ids = vec![None; self.clip_scroll_nodes.len()]; - - // We need to add the WebRender root reference frame and root scroll node ids - // here manually, because WebRender creates these automatically. - // We also follow the "old" WebRender API for clip/scroll for now, - // hence both arrays are initialized based on FIRST_SPATIAL_NODE_INDEX, - // while FIRST_CLIP_NODE_INDEX is not taken into account. - let webrender_pipeline = pipeline_id.to_webrender(); - clip_ids[0] = Some(ClipId::root(webrender_pipeline)); - clip_ids[1] = Some(ClipId::root(webrender_pipeline)); - spatial_ids[0] = Some(SpatialId::root_reference_frame(webrender_pipeline)); - spatial_ids[1] = Some(SpatialId::root_scroll_node(webrender_pipeline)); - - let mut state = ClipScrollState { - clip_ids, - spatial_ids, - active_clip_id: ClipId::root(webrender_pipeline), - active_spatial_id: SpatialId::root_scroll_node(webrender_pipeline), - }; - let mut builder = DisplayListBuilder::with_capacity( + let builder = DisplayListBuilder::with_capacity( webrender_pipeline, - self.bounds().size, + LayoutSize::zero(), 1024 * 1024, // 1 MB of space ); - for item in &mut self.list { - item.convert_to_webrender(&self.clip_scroll_nodes, &mut state, &mut builder); - } - builder } } - -impl WebRenderDisplayItemConverter for DisplayItem { - fn convert_to_webrender( - &mut self, - clip_scroll_nodes: &[ClipScrollNode], - state: &mut ClipScrollState, - builder: &mut DisplayListBuilder, - ) { - // Note: for each time of a display item, if we register one of `clip_ids` or `spatial_ids`, - // we also register the other one as inherited from the current state or the stack. - // This is not an ideal behavior, but it is compatible with the old WebRender model - // of the clip-scroll tree. - - let clip_and_scroll_indices = self.base().clipping_and_scrolling; - trace!("converting {:?}", clip_and_scroll_indices); - - let cur_spatial_id = state.spatial_ids[clip_and_scroll_indices.scrolling.to_index()] - .expect("Tried to use WebRender SpatialId before it was defined."); - if cur_spatial_id != state.active_spatial_id { - state.active_spatial_id = cur_spatial_id; - } - - let internal_clip_id = clip_and_scroll_indices - .clipping - .unwrap_or(clip_and_scroll_indices.scrolling); - let cur_clip_id = state.clip_ids[internal_clip_id.to_index()] - .expect("Tried to use WebRender ClipId before it was defined."); - if cur_clip_id != state.active_clip_id { - state.active_clip_id = cur_clip_id; - } - - match *self { - DisplayItem::Rectangle(ref mut item) => { - item.item.common = build_common_item_properties(&item.base, state); - builder.push_item(&WrDisplayItem::Rectangle(item.item)); - }, - DisplayItem::Text(ref mut item) => { - item.item.common = build_common_item_properties(&item.base, state); - builder.push_item(&WrDisplayItem::Text(item.item)); - builder.push_iter(item.data.iter()); - }, - DisplayItem::Image(ref mut item) => { - item.item.common = build_common_item_properties(&item.base, state); - builder.push_item(&WrDisplayItem::Image(item.item)); - }, - DisplayItem::Border(ref mut item) => { - item.item.common = build_common_item_properties(&item.base, state); - if !item.data.is_empty() { - builder.push_stops(item.data.as_ref()); - } - builder.push_item(&WrDisplayItem::Border(item.item)); - }, - DisplayItem::Gradient(ref mut item) => { - item.item.common = build_common_item_properties(&item.base, state); - builder.push_stops(item.data.as_ref()); - builder.push_item(&WrDisplayItem::Gradient(item.item)); - }, - DisplayItem::RadialGradient(ref mut item) => { - item.item.common = build_common_item_properties(&item.base, state); - builder.push_stops(item.data.as_ref()); - builder.push_item(&WrDisplayItem::RadialGradient(item.item)); - }, - DisplayItem::Line(ref mut item) => { - item.item.common = build_common_item_properties(&item.base, state); - builder.push_item(&WrDisplayItem::Line(item.item)); - }, - DisplayItem::BoxShadow(ref mut item) => { - item.item.common = build_common_item_properties(&item.base, state); - builder.push_item(&WrDisplayItem::BoxShadow(item.item)); - }, - DisplayItem::PushTextShadow(ref mut item) => { - let common = build_common_item_properties(&item.base, state); - builder.push_shadow( - &SpaceAndClipInfo { - spatial_id: common.spatial_id, - clip_id: common.clip_id, - }, - item.shadow, - true, - ); - }, - DisplayItem::PopAllTextShadows(_) => { - builder.push_item(&WrDisplayItem::PopAllShadows); - }, - DisplayItem::Iframe(ref mut item) => { - let common = build_common_item_properties(&item.base, state); - builder.push_iframe( - item.bounds, - common.clip_rect, - &SpaceAndClipInfo { - spatial_id: common.spatial_id, - clip_id: common.clip_id, - }, - item.iframe.to_webrender(), - true, - ); - }, - DisplayItem::PushStackingContext(ref mut item) => { - let stacking_context = &item.stacking_context; - debug_assert_eq!(stacking_context.context_type, StackingContextType::Real); - - //let mut info = webrender_api::LayoutPrimitiveInfo::new(stacking_context.bounds); - let mut bounds = stacking_context.bounds; - let spatial_id = - if let Some(frame_index) = stacking_context.established_reference_frame { - let (transform, ref_frame) = - match (stacking_context.transform, stacking_context.perspective) { - (None, Some(p)) => ( - p, - ReferenceFrameKind::Perspective { - scrolling_relative_to: None, - }, - ), - (Some(t), None) => (t, ReferenceFrameKind::Transform), - (Some(t), Some(p)) => ( - t.pre_mul(&p), - ReferenceFrameKind::Perspective { - scrolling_relative_to: None, - }, - ), - (None, None) => unreachable!(), - }; - - let spatial_id = builder.push_reference_frame( - stacking_context.bounds.origin, - state.active_spatial_id, - stacking_context.transform_style, - PropertyBinding::Value(transform), - ref_frame, - ); - - state.spatial_ids[frame_index.to_index()] = Some(spatial_id); - state.clip_ids[frame_index.to_index()] = Some(cur_clip_id); - - bounds.origin = LayoutPoint::zero(); - spatial_id - } else { - state.active_spatial_id - }; - - if !stacking_context.filters.is_empty() { - builder.push_item(&WrDisplayItem::SetFilterOps); - builder.push_iter(&stacking_context.filters); - } - - let wr_item = PushStackingContextDisplayItem { - origin: bounds.origin, - spatial_id, - is_backface_visible: true, - stacking_context: StackingContext { - transform_style: stacking_context.transform_style, - mix_blend_mode: stacking_context.mix_blend_mode, - clip_id: None, - raster_space: RasterSpace::Screen, - // TODO(pcwalton): Enable picture caching? - cache_tiles: false, - }, - }; - - builder.push_item(&WrDisplayItem::PushStackingContext(wr_item)); - }, - DisplayItem::PopStackingContext(_) => builder.pop_stacking_context(), - DisplayItem::DefineClipScrollNode(ref mut item) => { - let node = &clip_scroll_nodes[item.node_index.to_index()]; - let item_rect = node.clip.main; - - let parent_spatial_id = state.spatial_ids[node.parent_index.to_index()] - .expect("Tried to use WebRender parent SpatialId before it was defined."); - let parent_clip_id = state.clip_ids[node.parent_index.to_index()] - .expect("Tried to use WebRender parent ClipId before it was defined."); - - match node.node_type { - ClipScrollNodeType::Clip => { - let id = builder.define_clip( - &SpaceAndClipInfo { - clip_id: parent_clip_id, - spatial_id: parent_spatial_id, - }, - item_rect, - node.clip.complex.clone(), - None, - ); - - state.spatial_ids[item.node_index.to_index()] = Some(parent_spatial_id); - state.clip_ids[item.node_index.to_index()] = Some(id); - }, - ClipScrollNodeType::ScrollFrame(scroll_sensitivity, external_id) => { - let space_clip_info = builder.define_scroll_frame( - &SpaceAndClipInfo { - clip_id: parent_clip_id, - spatial_id: parent_spatial_id, - }, - Some(external_id), - node.content_rect, - node.clip.main, - node.clip.complex.clone(), - None, - scroll_sensitivity, - webrender_api::units::LayoutVector2D::zero(), - ); - - state.clip_ids[item.node_index.to_index()] = Some(space_clip_info.clip_id); - state.spatial_ids[item.node_index.to_index()] = - Some(space_clip_info.spatial_id); - }, - ClipScrollNodeType::StickyFrame(ref sticky_data) => { - // TODO: Add define_sticky_frame_with_parent to WebRender. - let id = builder.define_sticky_frame( - parent_spatial_id, - item_rect, - sticky_data.margins, - sticky_data.vertical_offset_bounds, - sticky_data.horizontal_offset_bounds, - webrender_api::units::LayoutVector2D::zero(), - ); - - state.spatial_ids[item.node_index.to_index()] = Some(id); - state.clip_ids[item.node_index.to_index()] = Some(parent_clip_id); - }, - ClipScrollNodeType::Placeholder => { - unreachable!("Found DefineClipScrollNode for Placeholder type node."); - }, - }; - }, - } - } -} - -fn build_common_item_properties( - base: &BaseDisplayItem, - state: &ClipScrollState, -) -> CommonItemProperties { - let tag = match base.metadata.pointing { - Some(cursor) => Some((base.metadata.node.0 as u64, cursor)), - None => None, - }; - CommonItemProperties { - clip_rect: base.clip_rect, - spatial_id: state.active_spatial_id, - clip_id: state.active_clip_id, - // TODO(gw): Make use of the WR backface visibility functionality. - is_backface_visible: true, - hit_info: tag, - } -} diff --git a/components/layout_2020/flex.rs b/components/layout_2020/flex.rs deleted file mode 100644 index e6aa2f7b2c8..00000000000 --- a/components/layout_2020/flex.rs +++ /dev/null @@ -1,1128 +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 elements with a CSS `display` property of `flex`. - -use crate::block::{AbsoluteAssignBSizesTraversal, BlockFlow, MarginsMayCollapseFlag}; -use crate::context::LayoutContext; -use crate::display_list::{ - BorderPaintingMode, DisplayListBuildState, StackingContextCollectionState, -}; -use crate::floats::FloatKind; -use crate::flow::{Flow, FlowClass, FlowFlags, GetBaseFlow, ImmutableFlowUtils, OpaqueFlow}; -use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow}; -use crate::layout_debug; -use crate::model::{self, AdjoiningMargins, CollapsibleMargins}; -use crate::model::{IntrinsicISizes, MaybeAuto, SizeConstraint}; -use crate::traversal::PreorderFlowTraversal; -use app_units::{Au, MAX_AU}; -use euclid::Point2D; -use std::cmp::{max, min}; -use std::ops::Range; -use style::computed_values::align_content::T as AlignContent; -use style::computed_values::align_self::T as AlignSelf; -use style::computed_values::flex_direction::T as FlexDirection; -use style::computed_values::flex_wrap::T as FlexWrap; -use style::computed_values::justify_content::T as JustifyContent; -use style::logical_geometry::{Direction, LogicalSize}; -use style::properties::ComputedValues; -use style::servo::restyle_damage::ServoRestyleDamage; -use style::values::computed::flex::FlexBasis; -use style::values::computed::{MaxSize, Size}; - -/// The size of an axis. May be a specified size, a min/max -/// constraint, or an unlimited size -#[derive(Debug, Serialize)] -enum AxisSize { - Definite(Au), - MinMax(SizeConstraint), - Infinite, -} - -impl AxisSize { - /// Generate a new available cross or main axis size from the specified size of the container, - /// containing block size, min constraint, and max constraint - pub fn new(size: Size, content_size: Option<Au>, min: Size, max: MaxSize) -> AxisSize { - match size { - Size::Auto => AxisSize::MinMax(SizeConstraint::new(content_size, min, max, None)), - Size::LengthPercentage(ref lp) => match lp.maybe_to_used_value(content_size) { - Some(length) => AxisSize::Definite(length), - None => AxisSize::Infinite, - }, - } - } -} - -/// This function accepts the flex-basis and the size property in main direction from style, -/// and the container size, then return the used value of flex basis. it can be used to help -/// determining the flex base size and to indicate whether the main size of the item -/// is definite after flex size resolving. -fn from_flex_basis(flex_basis: FlexBasis, main_length: Size, containing_length: Au) -> MaybeAuto { - let width = match flex_basis { - FlexBasis::Content => return MaybeAuto::Auto, - FlexBasis::Size(width) => width, - }; - - let width = match width { - Size::Auto => main_length, - _ => width, - }; - - match width { - Size::Auto => MaybeAuto::Auto, - Size::LengthPercentage(ref lp) => MaybeAuto::Specified(lp.to_used_value(containing_length)), - } -} - -/// Represents a child in a flex container. Most fields here are used in -/// flex size resolving, and items are sorted by the 'order' property. -#[derive(Debug, Serialize)] -struct FlexItem { - /// Main size of a flex item, used to store results of flexible length calcuation. - pub main_size: Au, - /// Used flex base size. - pub base_size: Au, - /// The minimal size in main direction. - pub min_size: Au, - /// The maximal main size. If this property is not actually set by style - /// It will be the largest size available for code reuse. - pub max_size: Au, - /// The index of the actual flow in our child list. - pub index: usize, - /// The 'flex-grow' property of this item. - pub flex_grow: f32, - /// The 'flex-shrink' property of this item. - pub flex_shrink: f32, - /// The 'order' property of this item. - pub order: i32, - /// Whether the main size has met its constraint. - pub is_frozen: bool, - /// True if this flow has property 'visibility::collapse'. - pub is_strut: bool, -} - -impl FlexItem { - pub fn new(index: usize, flow: &dyn Flow) -> FlexItem { - let style = &flow.as_block().fragment.style; - let flex_grow = style.get_position().flex_grow; - let flex_shrink = style.get_position().flex_shrink; - let order = style.get_position().order; - // TODO(stshine): for item with 'visibility:collapse', set is_strut to true. - - FlexItem { - main_size: Au(0), - base_size: Au(0), - min_size: Au(0), - max_size: MAX_AU, - index: index, - flex_grow: flex_grow.into(), - flex_shrink: flex_shrink.into(), - order: order, - is_frozen: false, - is_strut: false, - } - } - - /// Initialize the used flex base size, minimal main size and maximal main size. - /// For block mode container this method should be called in assign_block_size() - /// pass so that the item has already been layouted. - pub fn init_sizes(&mut self, flow: &mut dyn Flow, containing_length: Au, direction: Direction) { - let block = flow.as_mut_block(); - match direction { - // TODO(stshine): the definition of min-{width, height} in style component - // should change to LengthPercentageOrAuto for automatic implied minimal size. - // https://drafts.csswg.org/css-flexbox-1/#min-size-auto - Direction::Inline => { - let basis = from_flex_basis( - block.fragment.style.get_position().flex_basis, - block.fragment.style.content_inline_size(), - containing_length, - ); - - // These methods compute auto margins to zero length, which is exactly what we want. - block.fragment.compute_border_and_padding(containing_length); - block - .fragment - .compute_inline_direction_margins(containing_length); - block - .fragment - .compute_block_direction_margins(containing_length); - - let (border_padding, margin) = block.fragment.surrounding_intrinsic_inline_size(); - let content_size = block.base.intrinsic_inline_sizes.preferred_inline_size - - border_padding - - margin + - block.fragment.box_sizing_boundary(direction); - self.base_size = basis.specified_or_default(content_size); - self.max_size = block - .fragment - .style - .max_inline_size() - .to_used_value(containing_length) - .unwrap_or(MAX_AU); - self.min_size = block - .fragment - .style - .min_inline_size() - .to_used_value(containing_length) - .unwrap_or(Au(0)); - }, - Direction::Block => { - let basis = from_flex_basis( - block.fragment.style.get_position().flex_basis, - block.fragment.style.content_block_size(), - containing_length, - ); - let content_size = block.fragment.border_box.size.block - - block.fragment.border_padding.block_start_end() + - block.fragment.box_sizing_boundary(direction); - self.base_size = basis.specified_or_default(content_size); - self.max_size = block - .fragment - .style - .max_block_size() - .to_used_value(containing_length) - .unwrap_or(MAX_AU); - self.min_size = block - .fragment - .style - .min_block_size() - .to_used_value(containing_length) - .unwrap_or(Au(0)); - }, - } - } - - /// Returns the outer main size of the item, including paddings and margins, - /// clamped by max and min size. - pub fn outer_main_size(&self, flow: &dyn Flow, direction: Direction) -> Au { - let ref fragment = flow.as_block().fragment; - let outer_width = match direction { - Direction::Inline => { - fragment.border_padding.inline_start_end() + fragment.margin.inline_start_end() - }, - Direction::Block => { - fragment.border_padding.block_start_end() + fragment.margin.block_start_end() - }, - }; - max(self.min_size, min(self.base_size, self.max_size)) - - fragment.box_sizing_boundary(direction) + - outer_width - } - - /// Returns the number of auto margins in given direction. - pub fn auto_margin_count(&self, flow: &dyn Flow, direction: Direction) -> i32 { - let margin = flow.as_block().fragment.style.logical_margin(); - let mut margin_count = 0; - match direction { - Direction::Inline => { - if margin.inline_start.is_auto() { - margin_count += 1; - } - if margin.inline_end.is_auto() { - margin_count += 1; - } - }, - Direction::Block => { - if margin.block_start.is_auto() { - margin_count += 1; - } - if margin.block_end.is_auto() { - margin_count += 1; - } - }, - } - margin_count - } -} - -/// A line in a flex container. -// TODO(stshine): More fields are required to handle collapsed items and baseline alignment. -#[derive(Debug, Serialize)] -struct FlexLine { - /// Range of items belong to this line in 'self.items'. - pub range: Range<usize>, - /// Remaining free space of this line, items will grow or shrink based on it being positive or negative. - pub free_space: Au, - /// The number of auto margins of items. - pub auto_margin_count: i32, - /// Line size in the block direction. - pub cross_size: Au, -} - -impl FlexLine { - pub fn new(range: Range<usize>, free_space: Au, auto_margin_count: i32) -> FlexLine { - FlexLine { - range: range, - auto_margin_count: auto_margin_count, - free_space: free_space, - cross_size: Au(0), - } - } - - /// This method implements the flexible lengths resolving algorithm. - /// The 'collapse' parameter is used to indicate whether items with 'visibility: collapse' - /// is included in length resolving. The result main size is stored in 'item.main_size'. - /// <https://drafts.csswg.org/css-flexbox/#resolve-flexible-lengths> - pub fn flex_resolve(&mut self, items: &mut [FlexItem], collapse: bool) { - let mut total_grow = 0.0; - let mut total_shrink = 0.0; - let mut total_scaled = 0.0; - let mut active_count = 0; - // Iterate through items, collect total factors and freeze those that have already met - // their constraints or won't grow/shrink in corresponding scenario. - // https://drafts.csswg.org/css-flexbox/#resolve-flexible-lengths - for item in items.iter_mut().filter(|i| !(i.is_strut && collapse)) { - item.main_size = max(item.min_size, min(item.base_size, item.max_size)); - if (self.free_space > Au(0) && - (item.flex_grow == 0.0 || item.base_size >= item.max_size)) || - (self.free_space < Au(0) && - (item.flex_shrink == 0.0 || item.base_size <= item.min_size)) - { - item.is_frozen = true; - } else { - item.is_frozen = false; - total_grow += item.flex_grow; - total_shrink += item.flex_shrink; - // The scaled factor is used to calculate flex shrink - total_scaled += item.flex_shrink * item.base_size.0 as f32; - active_count += 1; - } - } - - let initial_free_space = self.free_space; - let mut total_variation = Au(1); - // If there is no remaining free space or all items are frozen, stop loop. - while total_variation != Au(0) && self.free_space != Au(0) && active_count > 0 { - self.free_space = - // https://drafts.csswg.org/css-flexbox/#remaining-free-space - if self.free_space > Au(0) { - min(initial_free_space.scale_by(total_grow), self.free_space) - } else { - max(initial_free_space.scale_by(total_shrink), self.free_space) - }; - - total_variation = Au(0); - for item in items - .iter_mut() - .filter(|i| !i.is_frozen) - .filter(|i| !(i.is_strut && collapse)) - { - // Use this and the 'abs()' below to make the code work in both grow and shrink scenarios. - let (factor, end_size) = if self.free_space > Au(0) { - (item.flex_grow / total_grow, item.max_size) - } else { - ( - item.flex_shrink * item.base_size.0 as f32 / total_scaled, - item.min_size, - ) - }; - let variation = self.free_space.scale_by(factor); - if variation.0.abs() >= (end_size - item.main_size).0.abs() { - // Use constraint as the target main size, and freeze item. - total_variation += end_size - item.main_size; - item.main_size = end_size; - item.is_frozen = true; - active_count -= 1; - total_shrink -= item.flex_shrink; - total_grow -= item.flex_grow; - total_scaled -= item.flex_shrink * item.base_size.0 as f32; - } else { - total_variation += variation; - item.main_size += variation; - } - } - self.free_space -= total_variation; - } - } -} - -#[allow(unsafe_code)] -unsafe impl crate::flow::HasBaseFlow for FlexFlow {} - -/// A block with the CSS `display` property equal to `flex`. -#[derive(Debug, Serialize)] -#[repr(C)] -pub struct FlexFlow { - /// Data common to all block flows. - block_flow: BlockFlow, - /// The logical axis which the main axis will be parallel with. - /// The cross axis will be parallel with the opposite logical axis. - main_mode: Direction, - /// The available main axis size - available_main_size: AxisSize, - /// The available cross axis size - available_cross_size: AxisSize, - /// List of flex lines in the container. - lines: Vec<FlexLine>, - /// List of flex-items that belong to this flex-container - items: Vec<FlexItem>, - /// True if the flex-direction is *-reversed - main_reverse: bool, - /// True if this flex container can be multiline. - is_wrappable: bool, - /// True if the cross direction is reversed. - cross_reverse: bool, -} - -impl FlexFlow { - pub fn from_fragment(fragment: Fragment, flotation: Option<FloatKind>) -> FlexFlow { - let main_mode; - let main_reverse; - let is_wrappable; - let cross_reverse; - { - let style = fragment.style(); - let (mode, reverse) = match style.get_position().flex_direction { - FlexDirection::Row => (Direction::Inline, false), - FlexDirection::RowReverse => (Direction::Inline, true), - FlexDirection::Column => (Direction::Block, false), - FlexDirection::ColumnReverse => (Direction::Block, true), - }; - main_mode = mode; - main_reverse = reverse == style.writing_mode.is_bidi_ltr(); - let (wrappable, reverse) = match fragment.style.get_position().flex_wrap { - FlexWrap::Nowrap => (false, false), - FlexWrap::Wrap => (true, false), - FlexWrap::WrapReverse => (true, true), - }; - is_wrappable = wrappable; - // TODO(stshine): Handle vertical writing mode. - cross_reverse = reverse; - } - - FlexFlow { - block_flow: BlockFlow::from_fragment_and_float_kind(fragment, flotation), - main_mode: main_mode, - available_main_size: AxisSize::Infinite, - available_cross_size: AxisSize::Infinite, - lines: Vec::new(), - items: Vec::new(), - main_reverse: main_reverse, - is_wrappable: is_wrappable, - cross_reverse: cross_reverse, - } - } - - pub fn main_mode(&self) -> Direction { - self.main_mode - } - - /// Returns a line start after the last item that is already in a line. - /// Note that when the container main size is infinite(i.e. A column flexbox with auto height), - /// we do not need to do flex resolving and this can be considered as a fast-path, so the - /// 'container_size' param does not need to be 'None'. A line has to contain at least one item; - /// (except this) if the container can be multi-line the sum of outer main size of items should - /// be less than the container size; a line should be filled by items as much as possible. - /// After been collected in a line a item should have its main sizes initialized. - fn get_flex_line(&mut self, container_size: Au) -> Option<FlexLine> { - let start = self.lines.last().map(|line| line.range.end).unwrap_or(0); - if start == self.items.len() { - return None; - } - let mut end = start; - let mut total_line_size = Au(0); - let mut margin_count = 0; - - let items = &mut self.items[start..]; - let mut children = self.block_flow.base.children.random_access_mut(); - for item in items { - let kid = children.get(item.index); - item.init_sizes(kid, container_size, self.main_mode); - let outer_main_size = item.outer_main_size(kid, self.main_mode); - if total_line_size + outer_main_size > container_size && - end != start && - self.is_wrappable - { - break; - } - margin_count += item.auto_margin_count(kid, self.main_mode); - total_line_size += outer_main_size; - end += 1; - } - - let line = FlexLine::new(start..end, container_size - total_line_size, margin_count); - Some(line) - } - - // TODO(zentner): This function should use flex-basis. - // Currently, this is the core of BlockFlow::bubble_inline_sizes() with all float logic - // stripped out, and max replaced with union_nonbreaking_inline. - fn inline_mode_bubble_inline_sizes(&mut self) { - // FIXME(emilio): This doesn't handle at all writing-modes. - let fixed_width = - !model::style_length(self.block_flow.fragment.style().get_position().width, None) - .is_auto(); - - let mut computation = self.block_flow.fragment.compute_intrinsic_inline_sizes(); - if !fixed_width { - for kid in self.block_flow.base.children.iter_mut() { - let base = kid.mut_base(); - let is_absolutely_positioned = - base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED); - if !is_absolutely_positioned { - let flex_item_inline_sizes = IntrinsicISizes { - minimum_inline_size: base.intrinsic_inline_sizes.minimum_inline_size, - preferred_inline_size: base.intrinsic_inline_sizes.preferred_inline_size, - }; - computation.union_nonbreaking_inline(&flex_item_inline_sizes); - } - } - } - self.block_flow.base.intrinsic_inline_sizes = computation.finish(); - } - - // TODO(zentner): This function should use flex-basis. - // Currently, this is the core of BlockFlow::bubble_inline_sizes() with all float logic - // stripped out. - fn block_mode_bubble_inline_sizes(&mut self) { - let fixed_width = - !model::style_length(self.block_flow.fragment.style().get_position().width, None) - .is_auto(); - - let mut computation = self.block_flow.fragment.compute_intrinsic_inline_sizes(); - if !fixed_width { - for kid in self.block_flow.base.children.iter_mut() { - let base = kid.mut_base(); - let is_absolutely_positioned = - base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED); - if !is_absolutely_positioned { - computation.content_intrinsic_sizes.minimum_inline_size = max( - computation.content_intrinsic_sizes.minimum_inline_size, - base.intrinsic_inline_sizes.minimum_inline_size, - ); - - computation.content_intrinsic_sizes.preferred_inline_size = max( - computation.content_intrinsic_sizes.preferred_inline_size, - base.intrinsic_inline_sizes.preferred_inline_size, - ); - } - } - } - self.block_flow.base.intrinsic_inline_sizes = computation.finish(); - } - - // TODO(zentner): This function needs to be radically different for multi-line flexbox. - // Currently, this is the core of BlockFlow::propagate_assigned_inline_size_to_children() with - // all float and table logic stripped out. - fn block_mode_assign_inline_sizes( - &mut self, - _layout_context: &LayoutContext, - inline_start_content_edge: Au, - inline_end_content_edge: Au, - content_inline_size: Au, - ) { - let _scope = layout_debug_scope!("flex::block_mode_assign_inline_sizes"); - debug!("flex::block_mode_assign_inline_sizes"); - - // FIXME (mbrubeck): Get correct mode for absolute containing block - let containing_block_mode = self.block_flow.base.writing_mode; - - let container_block_size = match self.available_main_size { - AxisSize::Definite(length) => Some(length), - _ => None, - }; - let container_inline_size = match self.available_cross_size { - AxisSize::Definite(length) => length, - AxisSize::MinMax(ref constraint) => constraint.clamp(content_inline_size), - AxisSize::Infinite => content_inline_size, - }; - - let mut children = self.block_flow.base.children.random_access_mut(); - for kid in &mut self.items { - let kid_base = children.get(kid.index).mut_base(); - kid_base.block_container_explicit_block_size = container_block_size; - if kid_base - .flags - .contains(FlowFlags::INLINE_POSITION_IS_STATIC) - { - // The inline-start margin edge of the child flow is at our inline-start content - // edge, and its inline-size is our content inline-size. - kid_base.position.start.i = - if kid_base.writing_mode.is_bidi_ltr() == containing_block_mode.is_bidi_ltr() { - inline_start_content_edge - } else { - // The kid's inline 'start' is at the parent's 'end' - inline_end_content_edge - }; - } - kid_base.block_container_inline_size = container_inline_size; - kid_base.block_container_writing_mode = containing_block_mode; - kid_base.position.start.i = inline_start_content_edge; - } - } - - fn inline_mode_assign_inline_sizes( - &mut self, - layout_context: &LayoutContext, - inline_start_content_edge: Au, - _inline_end_content_edge: Au, - content_inline_size: Au, - ) { - let _scope = layout_debug_scope!("flex::inline_mode_assign_inline_sizes"); - debug!("inline_mode_assign_inline_sizes"); - - debug!("content_inline_size = {:?}", content_inline_size); - - let child_count = ImmutableFlowUtils::child_count(self as &dyn Flow) as i32; - debug!("child_count = {:?}", child_count); - if child_count == 0 { - return; - } - - let inline_size = match self.available_main_size { - AxisSize::Definite(length) => length, - AxisSize::MinMax(ref constraint) => constraint.clamp(content_inline_size), - AxisSize::Infinite => content_inline_size, - }; - - let container_mode = self.block_flow.base.block_container_writing_mode; - self.block_flow.base.position.size.inline = inline_size; - - // Calculate non-auto block size to pass to children. - let box_border = self - .block_flow - .fragment - .box_sizing_boundary(Direction::Block); - - let parent_container_size = self - .block_flow - .explicit_block_containing_size(layout_context.shared_context()); - // https://drafts.csswg.org/css-ui-3/#box-sizing - let explicit_content_size = self - .block_flow - .explicit_block_size(parent_container_size) - .map(|x| max(x - box_border, Au(0))); - let containing_block_text_align = self - .block_flow - .fragment - .style() - .get_inherited_text() - .text_align; - - while let Some(mut line) = self.get_flex_line(inline_size) { - let items = &mut self.items[line.range.clone()]; - line.flex_resolve(items, false); - // TODO(stshine): if this flex line contain children that have - // property visibility:collapse, exclude them and resolve again. - - let item_count = items.len() as i32; - let mut cur_i = inline_start_content_edge; - let item_interval = if line.free_space >= Au(0) && line.auto_margin_count == 0 { - match self - .block_flow - .fragment - .style() - .get_position() - .justify_content - { - JustifyContent::SpaceBetween => { - if item_count == 1 { - Au(0) - } else { - line.free_space / (item_count - 1) - } - }, - JustifyContent::SpaceAround => line.free_space / item_count, - _ => Au(0), - } - } else { - Au(0) - }; - - match self - .block_flow - .fragment - .style() - .get_position() - .justify_content - { - // Overflow equally in both ends of line. - JustifyContent::Center | JustifyContent::SpaceAround => { - cur_i += (line.free_space - item_interval * (item_count - 1)) / 2; - }, - JustifyContent::FlexEnd => { - cur_i += line.free_space; - }, - _ => {}, - } - - let mut children = self.block_flow.base.children.random_access_mut(); - for item in items.iter_mut() { - let block = children.get(item.index).as_mut_block(); - - block.base.block_container_writing_mode = container_mode; - block.base.block_container_inline_size = inline_size; - block.base.block_container_explicit_block_size = explicit_content_size; - // Per CSS 2.1 § 16.3.1, text alignment propagates to all children in flow. - // - // TODO(#2265, pcwalton): Do this in the cascade instead. - block.base.flags.set_text_align(containing_block_text_align); - - let margin = block.fragment.style().logical_margin(); - let auto_len = if line.auto_margin_count == 0 || line.free_space <= Au(0) { - Au(0) - } else { - line.free_space / line.auto_margin_count - }; - let margin_inline_start = MaybeAuto::from_style(margin.inline_start, inline_size) - .specified_or_default(auto_len); - let margin_inline_end = MaybeAuto::from_style(margin.inline_end, inline_size) - .specified_or_default(auto_len); - let item_inline_size = item.main_size - - block.fragment.box_sizing_boundary(self.main_mode) + - block.fragment.border_padding.inline_start_end(); - let item_outer_size = item_inline_size + block.fragment.margin.inline_start_end(); - - block.fragment.margin.inline_start = margin_inline_start; - block.fragment.margin.inline_end = margin_inline_end; - block.fragment.border_box.start.i = margin_inline_start; - block.fragment.border_box.size.inline = item_inline_size; - block.base.position.start.i = if !self.main_reverse { - cur_i - } else { - inline_start_content_edge * 2 + content_inline_size - cur_i - item_outer_size - }; - block.base.position.size.inline = item_outer_size; - cur_i += item_outer_size + item_interval; - } - self.lines.push(line); - } - } - - // TODO(zentner): This function should actually flex elements! - fn block_mode_assign_block_size(&mut self) { - let mut cur_b = if !self.main_reverse { - self.block_flow.fragment.border_padding.block_start - } else { - self.block_flow.fragment.border_box.size.block - }; - - let mut children = self.block_flow.base.children.random_access_mut(); - for item in &mut self.items { - let base = children.get(item.index).mut_base(); - if !self.main_reverse { - base.position.start.b = cur_b; - cur_b = cur_b + base.position.size.block; - } else { - cur_b = cur_b - base.position.size.block; - base.position.start.b = cur_b; - } - } - } - - fn inline_mode_assign_block_size(&mut self, layout_context: &LayoutContext) { - let _scope = layout_debug_scope!("flex::inline_mode_assign_block_size"); - - let line_count = self.lines.len() as i32; - let line_align = self - .block_flow - .fragment - .style() - .get_position() - .align_content; - let mut cur_b = self.block_flow.fragment.border_padding.block_start; - let mut total_cross_size = Au(0); - let mut line_interval = Au(0); - - { - let mut children = self.block_flow.base.children.random_access_mut(); - for line in self.lines.iter_mut() { - for item in &self.items[line.range.clone()] { - let fragment = &children.get(item.index).as_block().fragment; - line.cross_size = max( - line.cross_size, - fragment.border_box.size.block + fragment.margin.block_start_end(), - ); - } - total_cross_size += line.cross_size; - } - } - - let box_border = self - .block_flow - .fragment - .box_sizing_boundary(Direction::Block); - let parent_container_size = self - .block_flow - .explicit_block_containing_size(layout_context.shared_context()); - // https://drafts.csswg.org/css-ui-3/#box-sizing - let explicit_content_size = self - .block_flow - .explicit_block_size(parent_container_size) - .map(|x| max(x - box_border, Au(0))); - - if let Some(container_block_size) = explicit_content_size { - let free_space = container_block_size - total_cross_size; - total_cross_size = container_block_size; - - if line_align == AlignContent::Stretch && free_space > Au(0) { - for line in self.lines.iter_mut() { - line.cross_size += free_space / line_count; - } - } - - line_interval = match line_align { - AlignContent::SpaceBetween => { - if line_count <= 1 { - Au(0) - } else { - free_space / (line_count - 1) - } - }, - AlignContent::SpaceAround => { - if line_count == 0 { - Au(0) - } else { - free_space / line_count - } - }, - _ => Au(0), - }; - - match line_align { - AlignContent::Center | AlignContent::SpaceAround => { - cur_b += (free_space - line_interval * (line_count - 1)) / 2; - }, - AlignContent::FlexEnd => { - cur_b += free_space; - }, - _ => {}, - } - } - - let mut children = self.block_flow.base.children.random_access_mut(); - for line in &self.lines { - for item in self.items[line.range.clone()].iter_mut() { - let block = children.get(item.index).as_mut_block(); - let auto_margin_count = item.auto_margin_count(block, Direction::Block); - let margin = block.fragment.style().logical_margin(); - - let mut margin_block_start = block.fragment.margin.block_start; - let mut margin_block_end = block.fragment.margin.block_end; - let mut free_space = line.cross_size - - block.base.position.size.block - - block.fragment.margin.block_start_end(); - - // The spec is a little vague here, but if I understand it correctly, the outer - // cross size of item should equal to the line size if any auto margin exists. - // https://drafts.csswg.org/css-flexbox/#algo-cross-margins - if auto_margin_count > 0 { - if margin.block_start.is_auto() { - margin_block_start = if free_space < Au(0) { - Au(0) - } else { - free_space / auto_margin_count - }; - } - margin_block_end = - line.cross_size - margin_block_start - block.base.position.size.block; - free_space = Au(0); - } - - let self_align = block.fragment.style().get_position().align_self; - if self_align == AlignSelf::Stretch && - block.fragment.style().content_block_size().is_auto() - { - free_space = Au(0); - block.base.block_container_explicit_block_size = Some(line.cross_size); - block.base.position.size.block = - line.cross_size - margin_block_start - margin_block_end; - block.fragment.border_box.size.block = block.base.position.size.block; - // FIXME(stshine): item with 'align-self: stretch' and auto cross size should act - // as if it has a fixed cross size, all child blocks should resolve against it. - // block.assign_block_size(layout_context); - } - block.base.position.start.b = margin_block_start + - if !self.cross_reverse { - cur_b - } else { - self.block_flow.fragment.border_padding.block_start * 2 + total_cross_size - - cur_b - - line.cross_size - }; - // TODO(stshine): support baseline alignment. - if free_space != Au(0) { - let flex_cross = match self_align { - AlignSelf::FlexEnd => free_space, - AlignSelf::Center => free_space / 2, - _ => Au(0), - }; - block.base.position.start.b += if !self.cross_reverse { - flex_cross - } else { - free_space - flex_cross - }; - } - } - cur_b += line_interval + line.cross_size; - } - let total_block_size = - total_cross_size + self.block_flow.fragment.border_padding.block_start_end(); - self.block_flow.fragment.border_box.size.block = total_block_size; - self.block_flow.base.position.size.block = total_block_size; - } -} - -impl Flow for FlexFlow { - fn class(&self) -> FlowClass { - FlowClass::Flex - } - - fn as_flex(&self) -> &FlexFlow { - self - } - - fn as_block(&self) -> &BlockFlow { - &self.block_flow - } - - fn as_mut_block(&mut self) -> &mut BlockFlow { - &mut self.block_flow - } - - fn mark_as_root(&mut self) { - self.block_flow.mark_as_root(); - } - - fn bubble_inline_sizes(&mut self) { - let _scope = layout_debug_scope!( - "flex::bubble_inline_sizes {:x}", - self.block_flow.base.debug_id() - ); - - // Flexbox Section 9.0: Generate anonymous flex items: - // This part was handled in the flow constructor. - - // Flexbox Section 9.1: Re-order flex items according to their order. - // FIXME(stshine): This should be done during flow construction. - let mut items: Vec<FlexItem> = self - .block_flow - .base - .children - .iter() - .enumerate() - .filter(|&(_, flow)| { - !flow - .as_block() - .base - .flags - .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) - }) - .map(|(index, flow)| FlexItem::new(index, flow)) - .collect(); - - items.sort_by_key(|item| item.order); - self.items = items; - - match self.main_mode { - Direction::Inline => self.inline_mode_bubble_inline_sizes(), - Direction::Block => self.block_mode_bubble_inline_sizes(), - } - } - - fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) { - let _scope = layout_debug_scope!( - "flex::assign_inline_sizes {:x}", - self.block_flow.base.debug_id() - ); - debug!("assign_inline_sizes"); - - if !self - .block_flow - .base - .restyle_damage - .intersects(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW) - { - return; - } - - self.block_flow - .initialize_container_size_for_root(layout_context.shared_context()); - - // Our inline-size was set to the inline-size of the containing block by the flow's parent. - // Now compute the real value. - let containing_block_inline_size = self.block_flow.base.block_container_inline_size; - self.block_flow.compute_used_inline_size( - layout_context.shared_context(), - containing_block_inline_size, - ); - if self.block_flow.base.flags.is_float() { - self.block_flow - .float - .as_mut() - .unwrap() - .containing_inline_size = containing_block_inline_size - } - - let (available_block_size, available_inline_size) = { - let style = &self.block_flow.fragment.style; - let (specified_block_size, specified_inline_size) = if style.writing_mode.is_vertical() - { - (style.get_position().width, style.get_position().height) - } else { - (style.get_position().height, style.get_position().width) - }; - - let available_inline_size = AxisSize::new( - specified_inline_size, - Some(self.block_flow.base.block_container_inline_size), - style.min_inline_size(), - style.max_inline_size(), - ); - - let available_block_size = AxisSize::new( - specified_block_size, - self.block_flow.base.block_container_explicit_block_size, - style.min_block_size(), - style.max_block_size(), - ); - (available_block_size, available_inline_size) - }; - - // Move in from the inline-start border edge. - let inline_start_content_edge = self.block_flow.fragment.border_box.start.i + - self.block_flow.fragment.border_padding.inline_start; - - debug!( - "inline_start_content_edge = {:?}", - inline_start_content_edge - ); - - let padding_and_borders = self.block_flow.fragment.border_padding.inline_start_end(); - - // Distance from the inline-end margin edge to the inline-end content edge. - let inline_end_content_edge = self.block_flow.fragment.margin.inline_end + - self.block_flow.fragment.border_padding.inline_end; - - debug!("padding_and_borders = {:?}", padding_and_borders); - debug!( - "self.block_flow.fragment.border_box.size.inline = {:?}", - self.block_flow.fragment.border_box.size.inline - ); - let content_inline_size = - self.block_flow.fragment.border_box.size.inline - padding_and_borders; - - match self.main_mode { - Direction::Inline => { - self.available_main_size = available_inline_size; - self.available_cross_size = available_block_size; - self.inline_mode_assign_inline_sizes( - layout_context, - inline_start_content_edge, - inline_end_content_edge, - content_inline_size, - ) - }, - Direction::Block => { - self.available_main_size = available_block_size; - self.available_cross_size = available_inline_size; - self.block_mode_assign_inline_sizes( - layout_context, - inline_start_content_edge, - inline_end_content_edge, - content_inline_size, - ) - }, - } - } - - fn assign_block_size(&mut self, layout_context: &LayoutContext) { - match self.main_mode { - Direction::Inline => { - self.inline_mode_assign_block_size(layout_context); - let block_start = - AdjoiningMargins::from_margin(self.block_flow.fragment.margin.block_start); - let block_end = - AdjoiningMargins::from_margin(self.block_flow.fragment.margin.block_end); - self.block_flow.base.collapsible_margins = - CollapsibleMargins::Collapse(block_start, block_end); - - // TODO(stshine): assign proper static position for absolute descendants. - if (&*self as &dyn Flow).contains_roots_of_absolute_flow_tree() { - // Assign block-sizes for all flows in this absolute flow tree. - // This is preorder because the block-size of an absolute flow may depend on - // the block-size of its containing block, which may also be an absolute flow. - let assign_abs_b_sizes = - AbsoluteAssignBSizesTraversal(layout_context.shared_context()); - assign_abs_b_sizes.traverse_absolute_flows(&mut *self); - } - }, - Direction::Block => { - self.block_flow.assign_block_size_block_base( - layout_context, - None, - MarginsMayCollapseFlag::MarginsMayNotCollapse, - ); - self.block_mode_assign_block_size(); - }, - } - } - - fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) { - self.block_flow - .compute_stacking_relative_position(layout_context) - } - - fn place_float_if_applicable<'a>(&mut self) { - self.block_flow.place_float_if_applicable() - } - - fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) { - self.block_flow - .update_late_computed_inline_position_if_necessary(inline_position) - } - - fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) { - self.block_flow - .update_late_computed_block_position_if_necessary(block_position) - } - - fn build_display_list(&mut self, state: &mut DisplayListBuildState) { - // Draw the rest of the block. - self.as_mut_block() - .build_display_list_for_block(state, BorderPaintingMode::Separate) - } - - fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) { - self.block_flow.collect_stacking_contexts(state); - } - - fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>) { - self.block_flow.repair_style(new_style) - } - - fn compute_overflow(&self) -> Overflow { - self.block_flow.compute_overflow() - } - - fn contains_roots_of_absolute_flow_tree(&self) -> bool { - self.block_flow.contains_roots_of_absolute_flow_tree() - } - - fn is_absolute_containing_block(&self) -> bool { - self.block_flow.is_absolute_containing_block() - } - - fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> { - self.block_flow.generated_containing_block_size(flow) - } - - fn iterate_through_fragment_border_boxes( - &self, - iterator: &mut dyn FragmentBorderBoxIterator, - level: i32, - stacking_context_position: &Point2D<Au>, - ) { - self.block_flow.iterate_through_fragment_border_boxes( - iterator, - level, - stacking_context_position, - ); - } - - fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) { - self.block_flow.mutate_fragments(mutator); - } -} diff --git a/components/layout_2020/floats.rs b/components/layout_2020/floats.rs deleted file mode 100644 index 42df4e07dfd..00000000000 --- a/components/layout_2020/floats.rs +++ /dev/null @@ -1,605 +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/. */ - -use crate::block::FormattingContextType; -use crate::flow::{Flow, FlowFlags, GetBaseFlow, ImmutableFlowUtils}; -use crate::persistent_list::PersistentList; -use app_units::{Au, MAX_AU}; -use std::cmp::{max, min}; -use std::fmt; -use style::computed_values::float::T as StyleFloat; -use style::logical_geometry::{LogicalRect, LogicalSize, WritingMode}; -use style::values::computed::Size; - -/// The kind of float: left or right. -#[derive(Clone, Copy, Debug, Serialize)] -pub enum FloatKind { - Left, - Right, -} - -impl FloatKind { - pub fn from_property(property: StyleFloat) -> Option<FloatKind> { - match property { - StyleFloat::None => None, - StyleFloat::Left => Some(FloatKind::Left), - StyleFloat::Right => Some(FloatKind::Right), - } - } -} - -/// The kind of clearance: left, right, or both. -#[derive(Clone, Copy)] -pub enum ClearType { - Left, - Right, - Both, -} - -/// Information about a single float. -#[derive(Clone, Copy)] -struct Float { - /// The boundaries of this float. - bounds: LogicalRect<Au>, - /// The kind of float: left or right. - kind: FloatKind, -} - -impl fmt::Debug for Float { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "bounds={:?} kind={:?}", self.bounds, self.kind) - } -} - -/// Information about the floats next to a flow. -#[derive(Clone)] -struct FloatList { - /// Information about each of the floats here. - floats: PersistentList<Float>, - /// Cached copy of the maximum block-start offset of the float. - max_block_start: Option<Au>, -} - -impl FloatList { - fn new() -> FloatList { - FloatList { - floats: PersistentList::new(), - max_block_start: None, - } - } - - /// Returns true if the list is allocated and false otherwise. If false, there are guaranteed - /// not to be any floats. - fn is_present(&self) -> bool { - self.floats.len() > 0 - } -} - -impl fmt::Debug for FloatList { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "max_block_start={:?} floats={}", - self.max_block_start, - self.floats.len() - )?; - for float in self.floats.iter() { - write!(f, " {:?}", float)?; - } - Ok(()) - } -} - -/// All the information necessary to place a float. -pub struct PlacementInfo { - /// The dimensions of the float. - pub size: LogicalSize<Au>, - /// The minimum block-start of the float, as determined by earlier elements. - pub ceiling: Au, - /// The maximum inline-end position of the float, generally determined by the containing block. - pub max_inline_size: Au, - /// The kind of float. - pub kind: FloatKind, -} - -impl fmt::Debug for PlacementInfo { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "size={:?} ceiling={:?} max_inline_size={:?} kind={:?}", - self.size, self.ceiling, self.max_inline_size, self.kind - ) - } -} - -fn range_intersect( - block_start_1: Au, - block_end_1: Au, - block_start_2: Au, - block_end_2: Au, -) -> (Au, Au) { - ( - max(block_start_1, block_start_2), - min(block_end_1, block_end_2), - ) -} - -/// Encapsulates information about floats. This is optimized to avoid allocation if there are -/// no floats, and to avoid copying when translating the list of floats downward. -#[derive(Clone)] -pub struct Floats { - /// The list of floats. - list: FloatList, - /// The offset of the flow relative to the first float. - offset: LogicalSize<Au>, - /// The writing mode of these floats. - pub writing_mode: WritingMode, -} - -impl fmt::Debug for Floats { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if !self.list.is_present() { - write!(f, "[empty]") - } else { - write!(f, "offset={:?} floats={:?}", self.offset, self.list) - } - } -} - -impl Floats { - /// Creates a new `Floats` object. - pub fn new(writing_mode: WritingMode) -> Floats { - Floats { - list: FloatList::new(), - offset: LogicalSize::zero(writing_mode), - writing_mode: writing_mode, - } - } - - /// Adjusts the recorded offset of the flow relative to the first float. - pub fn translate(&mut self, delta: LogicalSize<Au>) { - self.offset = self.offset + delta - } - - /// Returns the position of the last float in flow coordinates. - pub fn last_float_pos(&self) -> Option<LogicalRect<Au>> { - match self.list.floats.front() { - None => None, - Some(float) => Some(float.bounds.translate_by_size(self.offset)), - } - } - - /// Returns a rectangle that encloses the region from block-start to block-start + block-size, - /// with inline-size small enough that it doesn't collide with any floats. max_x is the - /// inline-size beyond which floats have no effect. (Generally this is the containing block - /// inline-size.) - pub fn available_rect( - &self, - block_start: Au, - block_size: Au, - max_x: Au, - ) -> Option<LogicalRect<Au>> { - let list = &self.list; - let block_start = block_start - self.offset.block; - - debug!("available_rect: trying to find space at {:?}", block_start); - - // Relevant dimensions for the inline-end-most inline-start float - let mut max_inline_start = Au(0) - self.offset.inline; - let mut l_block_start = None; - let mut l_block_end = None; - // Relevant dimensions for the inline-start-most inline-end float - let mut min_inline_end = max_x - self.offset.inline; - let mut r_block_start = None; - let mut r_block_end = None; - - // Find the float collisions for the given range in the block direction. - for float in list.floats.iter() { - debug!("available_rect: Checking for collision against float"); - let float_pos = float.bounds.start; - let float_size = float.bounds.size; - - debug!("float_pos: {:?}, float_size: {:?}", float_pos, float_size); - match float.kind { - FloatKind::Left - if float_pos.i + float_size.inline > max_inline_start && - float_pos.b + float_size.block > block_start && - float_pos.b < block_start + block_size => - { - max_inline_start = float_pos.i + float_size.inline; - - l_block_start = Some(float_pos.b); - l_block_end = Some(float_pos.b + float_size.block); - - debug!( - "available_rect: collision with inline_start float: new \ - max_inline_start is {:?}", - max_inline_start - ); - } - FloatKind::Right - if float_pos.i < min_inline_end && - float_pos.b + float_size.block > block_start && - float_pos.b < block_start + block_size => - { - min_inline_end = float_pos.i; - - r_block_start = Some(float_pos.b); - r_block_end = Some(float_pos.b + float_size.block); - debug!( - "available_rect: collision with inline_end float: new min_inline_end \ - is {:?}", - min_inline_end - ); - } - FloatKind::Left | FloatKind::Right => {}, - } - } - - // Extend the vertical range of the rectangle to the closest floats. - // If there are floats on both sides, take the intersection of the - // two areas. Also make sure we never return a block-start smaller than the - // given upper bound. - let (block_start, block_end) = - match (r_block_start, r_block_end, l_block_start, l_block_end) { - ( - Some(r_block_start), - Some(r_block_end), - Some(l_block_start), - Some(l_block_end), - ) => range_intersect( - max(block_start, r_block_start), - r_block_end, - max(block_start, l_block_start), - l_block_end, - ), - (None, None, Some(l_block_start), Some(l_block_end)) => { - (max(block_start, l_block_start), l_block_end) - }, - (Some(r_block_start), Some(r_block_end), None, None) => { - (max(block_start, r_block_start), r_block_end) - }, - (None, None, None, None) => return None, - _ => panic!("Reached unreachable state when computing float area"), - }; - - // FIXME(eatkinson): This assertion is too strong and fails in some cases. It is OK to - // return negative inline-sizes since we check against that inline-end away, but we should - // still understand why they occur and add a stronger assertion here. - // assert!(max_inline-start < min_inline-end); - - assert!(block_start <= block_end, "Float position error"); - - Some(LogicalRect::new( - self.writing_mode, - max_inline_start + self.offset.inline, - block_start + self.offset.block, - min_inline_end - max_inline_start, - block_end - block_start, - )) - } - - /// Adds a new float to the list. - pub fn add_float(&mut self, info: &PlacementInfo) { - let new_info = PlacementInfo { - size: info.size, - ceiling: match self.list.max_block_start { - None => info.ceiling, - Some(max_block_start) => max(info.ceiling, max_block_start + self.offset.block), - }, - max_inline_size: info.max_inline_size, - kind: info.kind, - }; - - debug!("add_float: added float with info {:?}", new_info); - - let new_float = Float { - bounds: LogicalRect::from_point_size( - self.writing_mode, - self.place_between_floats(&new_info).start - self.offset, - info.size, - ), - kind: info.kind, - }; - - self.list.floats = self.list.floats.prepend_elem(new_float); - self.list.max_block_start = match self.list.max_block_start { - None => Some(new_float.bounds.start.b), - Some(max_block_start) => Some(max(max_block_start, new_float.bounds.start.b)), - } - } - - /// Given the three sides of the bounding rectangle in the block-start direction, finds the - /// largest block-size that will result in the rectangle not colliding with any floats. Returns - /// `None` if that block-size is infinite. - fn max_block_size_for_bounds( - &self, - inline_start: Au, - block_start: Au, - inline_size: Au, - ) -> Option<Au> { - let list = &self.list; - - let block_start = block_start - self.offset.block; - let inline_start = inline_start - self.offset.inline; - let mut max_block_size = None; - - for float in list.floats.iter() { - if float.bounds.start.b + float.bounds.size.block > block_start && - float.bounds.start.i + float.bounds.size.inline > inline_start && - float.bounds.start.i < inline_start + inline_size - { - let new_y = float.bounds.start.b; - max_block_size = Some(min(max_block_size.unwrap_or(new_y), new_y)); - } - } - - max_block_size.map(|h| h + self.offset.block) - } - - /// Given placement information, finds the closest place a fragment can be positioned without - /// colliding with any floats. - pub fn place_between_floats(&self, info: &PlacementInfo) -> LogicalRect<Au> { - debug!("place_between_floats: Placing object with {:?}", info.size); - - // If no floats, use this fast path. - if !self.list.is_present() { - match info.kind { - FloatKind::Left => { - return LogicalRect::new( - self.writing_mode, - Au(0), - info.ceiling, - info.max_inline_size, - MAX_AU, - ); - }, - FloatKind::Right => { - return LogicalRect::new( - self.writing_mode, - info.max_inline_size - info.size.inline, - info.ceiling, - info.max_inline_size, - MAX_AU, - ); - }, - } - } - - // Can't go any higher than previous floats or previous elements in the document. - let mut float_b = info.ceiling; - loop { - let maybe_location = - self.available_rect(float_b, info.size.block, info.max_inline_size); - debug!( - "place_float: got available rect: {:?} for block-pos: {:?}", - maybe_location, float_b - ); - match maybe_location { - // If there are no floats blocking us, return the current location - // TODO(eatkinson): integrate with overflow - None => { - return match info.kind { - FloatKind::Left => LogicalRect::new( - self.writing_mode, - Au(0), - float_b, - info.max_inline_size, - MAX_AU, - ), - FloatKind::Right => LogicalRect::new( - self.writing_mode, - info.max_inline_size - info.size.inline, - float_b, - info.max_inline_size, - MAX_AU, - ), - }; - }, - Some(rect) => { - assert_ne!( - rect.start.b + rect.size.block, - float_b, - "Non-terminating float placement" - ); - - // Place here if there is enough room - if rect.size.inline >= info.size.inline { - let block_size = self.max_block_size_for_bounds( - rect.start.i, - rect.start.b, - rect.size.inline, - ); - let block_size = block_size.unwrap_or(MAX_AU); - return match info.kind { - FloatKind::Left => LogicalRect::new( - self.writing_mode, - rect.start.i, - float_b, - rect.size.inline, - block_size, - ), - FloatKind::Right => LogicalRect::new( - self.writing_mode, - rect.start.i + rect.size.inline - info.size.inline, - float_b, - rect.size.inline, - block_size, - ), - }; - } - - // Try to place at the next-lowest location. - // Need to be careful of fencepost errors. - float_b = rect.start.b + rect.size.block; - }, - } - } - } - - pub fn clearance(&self, clear: ClearType) -> Au { - let list = &self.list; - let mut clearance = Au(0); - for float in list.floats.iter() { - match (clear, float.kind) { - (ClearType::Left, FloatKind::Left) | - (ClearType::Right, FloatKind::Right) | - (ClearType::Both, _) => { - let b = self.offset.block + float.bounds.start.b + float.bounds.size.block; - clearance = max(clearance, b); - }, - _ => {}, - } - } - clearance - } - - pub fn is_present(&self) -> bool { - self.list.is_present() - } -} - -/// The speculated inline sizes of floats flowing through or around a flow (depending on whether -/// the flow is a block formatting context). These speculations are always *upper bounds*; the -/// actual inline sizes might be less. Note that this implies that a speculated value of zero is a -/// guarantee that there will be no floats on that side. -/// -/// This is used for two purposes: (a) determining whether we can lay out blocks in parallel; (b) -/// guessing the inline-sizes of block formatting contexts in an effort to lay them out in -/// parallel. -#[derive(Clone, Copy)] -pub struct SpeculatedFloatPlacement { - /// The estimated inline size (an upper bound) of the left floats flowing through this flow. - pub left: Au, - /// The estimated inline size (an upper bound) of the right floats flowing through this flow. - pub right: Au, -} - -impl fmt::Debug for SpeculatedFloatPlacement { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "L {:?} R {:?}", self.left, self.right) - } -} - -impl SpeculatedFloatPlacement { - /// Returns a `SpeculatedFloatPlacement` objects with both left and right speculated inline - /// sizes initialized to zero. - pub fn zero() -> SpeculatedFloatPlacement { - SpeculatedFloatPlacement { - left: Au(0), - right: Au(0), - } - } - - /// Given the speculated inline size of the floats out for the inorder predecessor of this - /// flow, computes the speculated inline size of the floats flowing in. - pub fn compute_floats_in(&mut self, flow: &mut dyn Flow) { - let base_flow = flow.base(); - if base_flow.flags.contains(FlowFlags::CLEARS_LEFT) { - self.left = Au(0) - } - if base_flow.flags.contains(FlowFlags::CLEARS_RIGHT) { - self.right = Au(0) - } - } - - /// Given the speculated inline size of the floats out for this flow's last child, computes the - /// speculated inline size of the floats out for this flow. - pub fn compute_floats_out(&mut self, flow: &mut dyn Flow) { - if flow.is_block_like() { - let block_flow = flow.as_block(); - if block_flow.formatting_context_type() != FormattingContextType::None { - *self = block_flow.base.speculated_float_placement_in; - } else { - if self.left > Au(0) || self.right > Au(0) { - let speculated_inline_content_edge_offsets = - block_flow.fragment.guess_inline_content_edge_offsets(); - if self.left > Au(0) && speculated_inline_content_edge_offsets.start > Au(0) { - self.left = self.left + speculated_inline_content_edge_offsets.start - } - if self.right > Au(0) && speculated_inline_content_edge_offsets.end > Au(0) { - self.right = self.right + speculated_inline_content_edge_offsets.end - } - } - - self.left = max( - self.left, - block_flow.base.speculated_float_placement_in.left, - ); - self.right = max( - self.right, - block_flow.base.speculated_float_placement_in.right, - ); - } - } - - let base_flow = flow.base(); - if !base_flow.flags.is_float() { - return; - } - - let mut float_inline_size = base_flow.intrinsic_inline_sizes.preferred_inline_size; - if float_inline_size == Au(0) { - if flow.is_block_like() { - // Hack: If the size of the float is not fixed, then there's no - // way we can guess at its size now. So just pick an arbitrary - // nonzero value (in this case, 1px) so that the layout - // traversal logic will know that objects later in the document - // might flow around this float. - let inline_size = flow.as_block().fragment.style.content_inline_size(); - let fixed = match inline_size { - Size::Auto => false, - Size::LengthPercentage(ref lp) => { - lp.0.is_definitely_zero() || lp.0.maybe_to_used_value(None).is_some() - }, - }; - if !fixed { - float_inline_size = Au::from_px(1) - } - } - } - - match base_flow.flags.float_kind() { - StyleFloat::None => {}, - StyleFloat::Left => self.left = self.left + float_inline_size, - StyleFloat::Right => self.right = self.right + float_inline_size, - } - } - - /// Given a flow, computes the speculated inline size of the floats in of its first child. - pub fn compute_floats_in_for_first_child( - parent_flow: &mut dyn Flow, - ) -> SpeculatedFloatPlacement { - if !parent_flow.is_block_like() { - return parent_flow.base().speculated_float_placement_in; - } - - let parent_block_flow = parent_flow.as_block(); - if parent_block_flow.formatting_context_type() != FormattingContextType::None { - return SpeculatedFloatPlacement::zero(); - } - - let mut placement = parent_block_flow.base.speculated_float_placement_in; - let speculated_inline_content_edge_offsets = parent_block_flow - .fragment - .guess_inline_content_edge_offsets(); - - if speculated_inline_content_edge_offsets.start > Au(0) { - placement.left = if placement.left > speculated_inline_content_edge_offsets.start { - placement.left - speculated_inline_content_edge_offsets.start - } else { - Au(0) - } - } - if speculated_inline_content_edge_offsets.end > Au(0) { - placement.right = if placement.right > speculated_inline_content_edge_offsets.end { - placement.right - speculated_inline_content_edge_offsets.end - } else { - Au(0) - } - } - - placement - } -} diff --git a/components/layout_2020/flow.rs b/components/layout_2020/flow.rs index d7ab73cd8d3..4bf5e1e899f 100644 --- a/components/layout_2020/flow.rs +++ b/components/layout_2020/flow.rs @@ -2,80 +2,12 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -//! Servo's experimental layout system builds a tree of `Flow` and `Fragment` objects and solves -//! layout constraints to obtain positions and display attributes of tree nodes. Positions are -//! computed in several tree traversals driven by the fundamental data dependencies required by -//! inline and block layout. -//! -//! Flows are interior nodes in the layout tree and correspond closely to *flow contexts* in the -//! CSS specification. Flows are responsible for positioning their child flow contexts and -//! fragments. Flows have purpose-specific fields, such as auxiliary line structs, out-of-flow -//! child lists, and so on. -//! -//! Currently, the important types of flows are: -//! -//! * `BlockFlow`: A flow that establishes a block context. It has several child flows, each of -//! which are positioned according to block formatting context rules (CSS block boxes). Block -//! flows also contain a single box to represent their rendered borders, padding, etc. -//! The BlockFlow at the root of the tree has special behavior: it stretches to the boundaries of -//! the viewport. -//! -//! * `InlineFlow`: A flow that establishes an inline context. It has a flat list of child -//! fragments/flows that are subject to inline layout and line breaking and structs to represent -//! line breaks and mapping to CSS boxes, for the purpose of handling `getClientRects()` and -//! similar methods. - -use crate::block::{BlockFlow, FormattingContextType}; -use crate::context::LayoutContext; -use crate::display_list::items::ClippingAndScrolling; -use crate::display_list::{DisplayListBuildState, StackingContextCollectionState}; -use crate::flex::FlexFlow; -use crate::floats::{Floats, SpeculatedFloatPlacement}; -use crate::flow_list::{FlowList, FlowListIterator, MutFlowListIterator}; -use crate::flow_ref::{FlowRef, WeakFlowRef}; -use crate::fragment::{CoordinateSystem, Fragment, FragmentBorderBoxIterator, Overflow}; -use crate::inline::InlineFlow; -use crate::model::{CollapsibleMargins, IntrinsicISizes}; -use crate::parallel::FlowParallelInfo; -use crate::table::TableFlow; -use crate::table_cell::TableCellFlow; -use crate::table_colgroup::TableColGroupFlow; -use crate::table_row::TableRowFlow; -use crate::table_rowgroup::TableRowGroupFlow; -use crate::table_wrapper::TableWrapperFlow; -use app_units::Au; -use euclid::{Point2D, Rect, Size2D, Vector2D}; -use gfx_traits::print_tree::PrintTree; -use gfx_traits::StackingContextId; -use num_traits::cast::FromPrimitive; -use serde::ser::{Serialize, SerializeStruct, Serializer}; -use servo_geometry::{au_rect_to_f32_rect, f32_rect_to_au_rect, MaxRect}; use std::fmt; -use std::slice::IterMut; -use std::sync::atomic::Ordering; -use std::sync::Arc; -use style::computed_values::clear::T as Clear; -use style::computed_values::float::T as Float; -use style::computed_values::overflow_x::T as StyleOverflow; -use style::computed_values::position::T as Position; -use style::computed_values::text_align::T as TextAlign; -use style::context::SharedStyleContext; -use style::logical_geometry::{LogicalRect, LogicalSize, WritingMode}; -use style::properties::ComputedValues; use style::selector_parser::RestyleDamage; -use style::servo::restyle_damage::ServoRestyleDamage; -use style::values::computed::LengthPercentageOrAuto; -use webrender_api::units::LayoutTransform; -/// This marker trait indicates that a type is a struct with `#[repr(C)]` whose first field -/// is of type `BaseFlow` or some type that also implements this trait. -/// -/// In other words, the memory representation of `BaseFlow` must be a prefix -/// of the memory representation of types implementing `HasBaseFlow`. #[allow(unsafe_code)] pub unsafe trait HasBaseFlow {} -/// Methods to get the `BaseFlow` from any `HasBaseFlow` type. pub trait GetBaseFlow { fn base(&self) -> &BaseFlow; fn mut_base(&mut self) -> &mut BaseFlow; @@ -99,1360 +31,8 @@ impl<T: HasBaseFlow + ?Sized> GetBaseFlow for T { } } -/// Virtual methods that make up a float context. -/// -/// Note that virtual methods have a cost; we should not overuse them in Servo. Consider adding -/// methods to `ImmutableFlowUtils` or `MutableFlowUtils` before adding more methods here. -pub trait Flow: HasBaseFlow + fmt::Debug + Sync + Send + 'static { - // RTTI - // - // TODO(pcwalton): Use Rust's RTTI, once that works. - - /// Returns the class of flow that this is. - fn class(&self) -> FlowClass; - - /// If this is a block flow, returns the underlying object. Fails otherwise. - fn as_block(&self) -> &BlockFlow { - panic!("called as_block() on a non-block flow") - } - - /// If this is a block flow, returns the underlying object, borrowed mutably. Fails otherwise. - fn as_mut_block(&mut self) -> &mut BlockFlow { - debug!("called as_mut_block() on a flow of type {:?}", self.class()); - panic!("called as_mut_block() on a non-block flow") - } - - /// If this is a flex flow, returns the underlying object. Fails otherwise. - fn as_flex(&self) -> &FlexFlow { - panic!("called as_flex() on a non-flex flow") - } - - /// If this is an inline flow, returns the underlying object. Fails otherwise. - fn as_inline(&self) -> &InlineFlow { - panic!("called as_inline() on a non-inline flow") - } - - /// If this is an inline flow, returns the underlying object, borrowed mutably. Fails - /// otherwise. - fn as_mut_inline(&mut self) -> &mut InlineFlow { - panic!("called as_mut_inline() on a non-inline flow") - } - - /// If this is a table wrapper flow, returns the underlying object. Fails otherwise. - fn as_table_wrapper(&self) -> &TableWrapperFlow { - panic!("called as_table_wrapper() on a non-tablewrapper flow") - } - - /// If this is a table flow, returns the underlying object, borrowed mutably. Fails otherwise. - fn as_mut_table(&mut self) -> &mut TableFlow { - panic!("called as_mut_table() on a non-table flow") - } - - /// If this is a table flow, returns the underlying object. Fails otherwise. - fn as_table(&self) -> &TableFlow { - panic!("called as_table() on a non-table flow") - } - - /// If this is a table colgroup flow, returns the underlying object, borrowed mutably. Fails - /// otherwise. - fn as_mut_table_colgroup(&mut self) -> &mut TableColGroupFlow { - panic!("called as_mut_table_colgroup() on a non-tablecolgroup flow") - } - - /// If this is a table colgroup flow, returns the underlying object. Fails - /// otherwise. - fn as_table_colgroup(&self) -> &TableColGroupFlow { - panic!("called as_table_colgroup() on a non-tablecolgroup flow") - } - - /// If this is a table rowgroup flow, returns the underlying object, borrowed mutably. Fails - /// otherwise. - fn as_mut_table_rowgroup(&mut self) -> &mut TableRowGroupFlow { - panic!("called as_mut_table_rowgroup() on a non-tablerowgroup flow") - } - - /// If this is a table rowgroup flow, returns the underlying object. Fails otherwise. - fn as_table_rowgroup(&self) -> &TableRowGroupFlow { - panic!("called as_table_rowgroup() on a non-tablerowgroup flow") - } - - /// If this is a table row flow, returns the underlying object, borrowed mutably. Fails - /// otherwise. - fn as_mut_table_row(&mut self) -> &mut TableRowFlow { - panic!("called as_mut_table_row() on a non-tablerow flow") - } - - /// If this is a table row flow, returns the underlying object. Fails otherwise. - fn as_table_row(&self) -> &TableRowFlow { - panic!("called as_table_row() on a non-tablerow flow") - } - - /// If this is a table cell flow, returns the underlying object, borrowed mutably. Fails - /// otherwise. - fn as_mut_table_cell(&mut self) -> &mut TableCellFlow { - panic!("called as_mut_table_cell() on a non-tablecell flow") - } - - /// If this is a table cell flow, returns the underlying object. Fails otherwise. - fn as_table_cell(&self) -> &TableCellFlow { - panic!("called as_table_cell() on a non-tablecell flow") - } - - // Main methods - - /// Pass 1 of reflow: computes minimum and preferred inline-sizes. - /// - /// Recursively (bottom-up) determine the flow's minimum and preferred inline-sizes. When - /// called on this flow, all child flows have had their minimum and preferred inline-sizes set. - /// This function must decide minimum/preferred inline-sizes based on its children's inline- - /// sizes and the dimensions of any boxes it is responsible for flowing. - fn bubble_inline_sizes(&mut self) { - panic!("bubble_inline_sizes not yet implemented") - } - - /// Pass 2 of reflow: computes inline-size. - fn assign_inline_sizes(&mut self, _ctx: &LayoutContext) { - panic!("assign_inline_sizes not yet implemented") - } - - /// Pass 3a of reflow: computes block-size. - fn assign_block_size(&mut self, _ctx: &LayoutContext) { - panic!("assign_block_size not yet implemented") - } - - /// Like `assign_block_size`, but is recurses explicitly into descendants. - /// Fit as much content as possible within `available_block_size`. - /// If that’s not all of it, truncate the contents of `self` - /// and return a new flow similar to `self` with the rest of the content. - /// - /// The default is to make a flow "atomic": it can not be fragmented. - fn fragment( - &mut self, - layout_context: &LayoutContext, - _fragmentation_context: Option<FragmentationContext>, - ) -> Option<Arc<dyn Flow>> { - fn recursive_assign_block_size<F: ?Sized + Flow + GetBaseFlow>( - flow: &mut F, - ctx: &LayoutContext, - ) { - for child in flow.mut_base().child_iter_mut() { - recursive_assign_block_size(child, ctx) - } - flow.assign_block_size(ctx); - } - recursive_assign_block_size(self, layout_context); - None - } - - fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState); - - /// If this is a float, places it. The default implementation does nothing. - fn place_float_if_applicable<'a>(&mut self) {} - - /// Assigns block-sizes in-order; or, if this is a float, places the float. The default - /// implementation simply assigns block-sizes if this flow might have floats in. Returns true - /// if it was determined that this child might have had floats in or false otherwise. - /// - /// `parent_thread_id` is the thread ID of the parent. This is used for the layout tinting - /// debug mode; if the block size of this flow was determined by its parent, we should treat - /// it as laid out by its parent. - fn assign_block_size_for_inorder_child_if_necessary( - &mut self, - layout_context: &LayoutContext, - parent_thread_id: u8, - _content_box: LogicalRect<Au>, - ) -> bool { - let might_have_floats_in_or_out = - self.base().might_have_floats_in() || self.base().might_have_floats_out(); - if might_have_floats_in_or_out { - self.mut_base().thread_id = parent_thread_id; - self.assign_block_size(layout_context); - self.mut_base() - .restyle_damage - .remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW); - } - might_have_floats_in_or_out - } - - fn get_overflow_in_parent_coordinates(&self) -> Overflow { - // FIXME(#2795): Get the real container size. - let container_size = Size2D::zero(); - let position = self - .base() - .position - .to_physical(self.base().writing_mode, container_size); - - let mut overflow = self.base().overflow; - - match self.class() { - FlowClass::Block | FlowClass::TableCaption | FlowClass::TableCell => {}, - _ => { - overflow.translate(&position.origin.to_vector()); - return overflow; - }, - } - - let border_box = self.as_block().fragment.stacking_relative_border_box( - &self.base().stacking_relative_position, - &self - .base() - .early_absolute_position_info - .relative_containing_block_size, - self.base() - .early_absolute_position_info - .relative_containing_block_mode, - CoordinateSystem::Own, - ); - if StyleOverflow::Visible != self.as_block().fragment.style.get_box().overflow_x { - overflow.paint.origin.x = Au(0); - overflow.paint.size.width = border_box.size.width; - overflow.scroll.origin.x = Au(0); - overflow.scroll.size.width = border_box.size.width; - } - if StyleOverflow::Visible != self.as_block().fragment.style.get_box().overflow_y { - overflow.paint.origin.y = Au(0); - overflow.paint.size.height = border_box.size.height; - overflow.scroll.origin.y = Au(0); - overflow.scroll.size.height = border_box.size.height; - } - - if !self.as_block().fragment.establishes_stacking_context() || - self.as_block() - .fragment - .style - .get_box() - .transform - .0 - .is_empty() - { - overflow.translate(&position.origin.to_vector()); - return overflow; - } - - // TODO: Take into account 3d transforms, even though it's a fairly - // uncommon case. - let transform_2d = self - .as_block() - .fragment - .transform_matrix(&position) - .unwrap_or(LayoutTransform::identity()) - .to_2d() - .to_untyped(); - let transformed_overflow = Overflow { - paint: f32_rect_to_au_rect( - transform_2d.transform_rect(&au_rect_to_f32_rect(overflow.paint)), - ), - scroll: f32_rect_to_au_rect( - transform_2d.transform_rect(&au_rect_to_f32_rect(overflow.scroll)), - ), - }; - - // TODO: We are taking the union of the overflow and transformed overflow here, which - // happened implicitly in the previous version of this code. This will probably be - // unnecessary once we are taking into account 3D transformations above. - overflow.union(&transformed_overflow); - - overflow.translate(&position.origin.to_vector()); - overflow - } - - /// - /// CSS Section 11.1 - /// This is the union of rectangles of the flows for which we define the - /// Containing Block. - /// - /// FIXME(pcwalton): This should not be a virtual method, but currently is due to a compiler - /// bug ("the trait `Sized` is not implemented for `self`"). - /// - /// Assumption: This is called in a bottom-up traversal, so kids' overflows have - /// already been set. - /// Assumption: Absolute descendants have had their overflow calculated. - fn store_overflow(&mut self, _: &LayoutContext) { - // Calculate overflow on a per-fragment basis. - let mut overflow = self.compute_overflow(); - match self.class() { - FlowClass::Block | FlowClass::TableCaption | FlowClass::TableCell => { - for kid in self.mut_base().children.iter_mut() { - overflow.union(&kid.get_overflow_in_parent_coordinates()); - } - }, - _ => {}, - } - self.mut_base().overflow = overflow - } - - /// Phase 4 of reflow: Compute the stacking-relative position (origin of the content box, - /// in coordinates relative to the nearest ancestor stacking context). - fn compute_stacking_relative_position(&mut self, _: &LayoutContext) { - // The default implementation is a no-op. - } - - /// Phase 5 of reflow: builds display lists. - fn build_display_list(&mut self, state: &mut DisplayListBuildState); - - /// Returns the union of all overflow rects of all of this flow's fragments. - fn compute_overflow(&self) -> Overflow; - - /// Iterates through border boxes of all of this flow's fragments. - /// Level provides a zero based index indicating the current - /// depth of the flow tree during fragment iteration. - fn iterate_through_fragment_border_boxes( - &self, - iterator: &mut dyn FragmentBorderBoxIterator, - level: i32, - stacking_context_position: &Point2D<Au>, - ); - - /// Mutably iterates through fragments in this flow. - fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)); - - /// Marks this flow as the root flow. The default implementation is a no-op. - fn mark_as_root(&mut self) { - debug!("called mark_as_root() on a flow of type {:?}", self.class()); - panic!("called mark_as_root() on an unhandled flow"); - } - - // Note that the following functions are mostly called using static method - // dispatch, so it's ok to have them in this trait. Plus, they have - // different behaviour for different types of Flow, so they can't go into - // the Immutable / Mutable Flow Utils traits without additional casts. - - fn is_root(&self) -> bool { - false - } - - /// The 'position' property of this flow. - fn positioning(&self) -> Position { - Position::Static - } - - /// Return true if this flow has position 'fixed'. - fn is_fixed(&self) -> bool { - self.positioning() == Position::Fixed - } - - fn contains_positioned_fragments(&self) -> bool { - self.contains_relatively_positioned_fragments() || - self.base() - .flags - .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) - } - - fn contains_relatively_positioned_fragments(&self) -> bool { - self.positioning() == Position::Relative - } - - /// Returns true if this is an absolute containing block. - fn is_absolute_containing_block(&self) -> bool { - self.contains_positioned_fragments() - } - - /// Returns true if this flow contains fragments that are roots of an absolute flow tree. - fn contains_roots_of_absolute_flow_tree(&self) -> bool { - self.contains_relatively_positioned_fragments() || self.is_root() - } - - /// Updates the inline position of a child flow during the assign-height traversal. At present, - /// this is only used for absolutely-positioned inline-blocks. - fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au); - - /// Updates the block position of a child flow during the assign-height traversal. At present, - /// this is only used for absolutely-positioned inline-blocks. - fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au); - - /// Return the size of the containing block generated by this flow for the absolutely- - /// positioned descendant referenced by `for_flow`. For block flows, this is the padding box. - /// - /// NB: Do not change this `&self` to `&mut self` under any circumstances! It has security - /// implications because this can be called on parents concurrently from descendants! - fn generated_containing_block_size(&self, _: OpaqueFlow) -> LogicalSize<Au>; - - /// Attempts to perform incremental fixup of this flow by replacing its fragment's style with - /// the new style. This can only succeed if the flow has exactly one fragment. - fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>); - - /// Print any extra children (such as fragments) contained in this Flow - /// for debugging purposes. Any items inserted into the tree will become - /// children of this flow. - fn print_extra_flow_children(&self, _: &mut PrintTree) {} - - fn clipping_and_scrolling(&self) -> ClippingAndScrolling { - match self.base().clipping_and_scrolling { - Some(info) => info, - None => unreachable!("Tried to access scroll root id on Flow before assignment"), - } - } -} - -pub trait ImmutableFlowUtils { - // Convenience functions - - /// Returns true if this flow is a block flow or subclass thereof. - fn is_block_like(self) -> bool; - - /// Returns true if this flow is a table flow. - fn is_table(self) -> bool; - - /// Returns true if this flow is a table caption flow. - fn is_table_caption(self) -> bool; - - /// Returns true if this flow is a table row flow. - fn is_table_row(self) -> bool; - - /// Returns true if this flow is a table cell flow. - fn is_table_cell(self) -> bool; - - /// Returns true if this flow is a table colgroup flow. - fn is_table_colgroup(self) -> bool; - - /// Returns true if this flow is a table rowgroup flow. - fn is_table_rowgroup(self) -> bool; - - /// Returns the number of children that this flow possesses. - fn child_count(self) -> usize; - - /// Returns true if this flow is a block flow. - fn is_block_flow(self) -> bool; - - /// Returns true if this flow is an inline flow. - fn is_inline_flow(self) -> bool; - - /// Dumps the flow tree for debugging. - fn print(self, title: String); - - /// Dumps the flow tree for debugging into the given PrintTree. - fn print_with_tree(self, print_tree: &mut PrintTree); - - /// Returns true if floats might flow through this flow, as determined by the float placement - /// speculation pass. - fn floats_might_flow_through(self) -> bool; - - fn baseline_offset_of_last_line_box_in_flow(self) -> Option<Au>; -} - -pub trait MutableFlowUtils { - /// Calls `repair_style` and `bubble_inline_sizes`. You should use this method instead of - /// calling them individually, since there is no reason not to perform both operations. - fn repair_style_and_bubble_inline_sizes(self, style: &crate::ServoArc<ComputedValues>); -} - -pub trait MutableOwnedFlowUtils { - /// Set absolute descendants for this flow. - /// - /// Set this flow as the Containing Block for all the absolute descendants. - fn set_absolute_descendants(&mut self, abs_descendants: AbsoluteDescendants); - - /// Sets the flow as the containing block for all absolute descendants that have been marked - /// as having reached their containing block. This is needed in order to handle cases like: - /// - /// ```html - /// <div> - /// <span style="position: relative"> - /// <span style="position: absolute; ..."></span> - /// </span> - /// </div> - /// ``` - fn take_applicable_absolute_descendants( - &mut self, - absolute_descendants: &mut AbsoluteDescendants, - ); -} - -#[derive(Clone, Copy, Debug, PartialEq, Serialize)] -pub enum FlowClass { - Block, - Inline, - ListItem, - TableWrapper, - Table, - TableColGroup, - TableRowGroup, - TableRow, - TableCaption, - TableCell, - Multicol, - MulticolColumn, - Flex, -} - -impl FlowClass { - fn is_block_like(self) -> bool { - match self { - FlowClass::Block | - FlowClass::ListItem | - FlowClass::Table | - FlowClass::TableRowGroup | - FlowClass::TableRow | - FlowClass::TableCaption | - FlowClass::TableCell | - FlowClass::TableWrapper | - FlowClass::Flex => true, - _ => false, - } - } -} - -bitflags! { - #[doc = "Flags used in flows."] - pub struct FlowFlags: u32 { - // text align flags - #[doc = "Whether this flow is absolutely positioned. This is checked all over layout, so a"] - #[doc = "virtual call is too expensive."] - const IS_ABSOLUTELY_POSITIONED = 0b0000_0000_0000_0000_0100_0000; - #[doc = "Whether this flow clears to the left. This is checked all over layout, so a"] - #[doc = "virtual call is too expensive."] - const CLEARS_LEFT = 0b0000_0000_0000_0000_1000_0000; - #[doc = "Whether this flow clears to the right. This is checked all over layout, so a"] - #[doc = "virtual call is too expensive."] - const CLEARS_RIGHT = 0b0000_0000_0000_0001_0000_0000; - #[doc = "Whether this flow is left-floated. This is checked all over layout, so a"] - #[doc = "virtual call is too expensive."] - const FLOATS_LEFT = 0b0000_0000_0000_0010_0000_0000; - #[doc = "Whether this flow is right-floated. This is checked all over layout, so a"] - #[doc = "virtual call is too expensive."] - const FLOATS_RIGHT = 0b0000_0000_0000_0100_0000_0000; - #[doc = "Text alignment. \ - - NB: If you update this, update `TEXT_ALIGN_SHIFT` below."] - const TEXT_ALIGN = 0b0000_0000_0111_1000_0000_0000; - #[doc = "Whether this flow has a fragment with `counter-reset` or `counter-increment` \ - styles."] - const AFFECTS_COUNTERS = 0b0000_0000_1000_0000_0000_0000; - #[doc = "Whether this flow's descendants have fragments that affect `counter-reset` or \ - `counter-increment` styles."] - const HAS_COUNTER_AFFECTING_CHILDREN = 0b0000_0001_0000_0000_0000_0000; - #[doc = "Whether this flow behaves as though it had `position: static` for the purposes \ - of positioning in the inline direction. This is set for flows with `position: \ - static` and `position: relative` as well as absolutely-positioned flows with \ - unconstrained positions in the inline direction."] - const INLINE_POSITION_IS_STATIC = 0b0000_0010_0000_0000_0000_0000; - #[doc = "Whether this flow behaves as though it had `position: static` for the purposes \ - of positioning in the block direction. This is set for flows with `position: \ - static` and `position: relative` as well as absolutely-positioned flows with \ - unconstrained positions in the block direction."] - const BLOCK_POSITION_IS_STATIC = 0b0000_0100_0000_0000_0000_0000; - - /// Whether any ancestor is a fragmentation container - const CAN_BE_FRAGMENTED = 0b0000_1000_0000_0000_0000_0000; - - /// Whether this flow contains any text and/or replaced fragments. - const CONTAINS_TEXT_OR_REPLACED_FRAGMENTS = 0b0001_0000_0000_0000_0000_0000; - - /// Whether margins are prohibited from collapsing with this flow. - const MARGINS_CANNOT_COLLAPSE = 0b0010_0000_0000_0000_0000_0000; - } -} - -/// The number of bits we must shift off to handle the text alignment field. -/// -/// NB: If you update this, update `TEXT_ALIGN` above. -static TEXT_ALIGN_SHIFT: usize = 11; - -impl FlowFlags { - #[inline] - pub fn text_align(self) -> TextAlign { - TextAlign::from_u32((self & FlowFlags::TEXT_ALIGN).bits() >> TEXT_ALIGN_SHIFT).unwrap() - } - - #[inline] - pub fn set_text_align(&mut self, value: TextAlign) { - *self = (*self & !FlowFlags::TEXT_ALIGN) | - FlowFlags::from_bits((value as u32) << TEXT_ALIGN_SHIFT).unwrap(); - } - - #[inline] - pub fn float_kind(&self) -> Float { - if self.contains(FlowFlags::FLOATS_LEFT) { - Float::Left - } else if self.contains(FlowFlags::FLOATS_RIGHT) { - Float::Right - } else { - Float::None - } - } - - #[inline] - pub fn is_float(&self) -> bool { - self.contains(FlowFlags::FLOATS_LEFT) || self.contains(FlowFlags::FLOATS_RIGHT) - } - - #[inline] - pub fn clears_floats(&self) -> bool { - self.contains(FlowFlags::CLEARS_LEFT) || self.contains(FlowFlags::CLEARS_RIGHT) - } -} - -/// Absolutely-positioned descendants of this flow. -#[derive(Clone)] -pub struct AbsoluteDescendants { - /// Links to every descendant. This must be private because it is unsafe to leak `FlowRef`s to - /// layout. - descendant_links: Vec<AbsoluteDescendantInfo>, -} - -impl AbsoluteDescendants { - pub fn new() -> AbsoluteDescendants { - AbsoluteDescendants { - descendant_links: Vec::new(), - } - } - - pub fn len(&self) -> usize { - self.descendant_links.len() - } - - pub fn is_empty(&self) -> bool { - self.descendant_links.is_empty() - } - - pub fn push(&mut self, given_descendant: FlowRef) { - self.descendant_links.push(AbsoluteDescendantInfo { - flow: given_descendant, - has_reached_containing_block: false, - }); - } - - /// Push the given descendants on to the existing descendants. - /// - /// Ignore any static y offsets, because they are None before layout. - pub fn push_descendants(&mut self, given_descendants: AbsoluteDescendants) { - for elem in given_descendants.descendant_links { - self.descendant_links.push(elem); - } - } - - /// Return an iterator over the descendant flows. - pub fn iter(&mut self) -> AbsoluteDescendantIter { - AbsoluteDescendantIter { - iter: self.descendant_links.iter_mut(), - } - } - - /// Mark these descendants as having reached their containing block. - pub fn mark_as_having_reached_containing_block(&mut self) { - for descendant_info in self.descendant_links.iter_mut() { - descendant_info.has_reached_containing_block = true - } - } -} - -/// Information about each absolutely-positioned descendant of the given flow. -#[derive(Clone)] -pub struct AbsoluteDescendantInfo { - /// The absolute descendant flow in question. - flow: FlowRef, - - /// Whether the absolute descendant has reached its containing block. This exists so that we - /// can handle cases like the following: - /// - /// ```html - /// <div> - /// <span id=a style="position: absolute; ...">foo</span> - /// <span style="position: relative"> - /// <span id=b style="position: absolute; ...">bar</span> - /// </span> - /// </div> - /// ``` - /// - /// When we go to create the `InlineFlow` for the outer `div`, our absolute descendants will - /// be `a` and `b`. At this point, we need a way to distinguish between the two, because the - /// containing block for `a` will be different from the containing block for `b`. Specifically, - /// the latter's containing block is the inline flow itself, while the former's containing - /// block is going to be some parent of the outer `div`. Hence we need this flag as a way to - /// distinguish the two; it will be false for `a` and true for `b`. - has_reached_containing_block: bool, -} - -pub struct AbsoluteDescendantIter<'a> { - iter: IterMut<'a, AbsoluteDescendantInfo>, -} - -impl<'a> Iterator for AbsoluteDescendantIter<'a> { - type Item = &'a mut dyn Flow; - fn next(&mut self) -> Option<&'a mut dyn Flow> { - self.iter - .next() - .map(|info| FlowRef::deref_mut(&mut info.flow)) - } - - fn size_hint(&self) -> (usize, Option<usize>) { - self.iter.size_hint() - } -} - -/// Information needed to compute absolute (i.e. viewport-relative) flow positions (not to be -/// confused with absolutely-positioned flows) that is computed during block-size assignment. -#[derive(Clone, Copy)] -pub struct EarlyAbsolutePositionInfo { - /// The size of the containing block for relatively-positioned descendants. - pub relative_containing_block_size: LogicalSize<Au>, - - /// The writing mode for `relative_containing_block_size`. - pub relative_containing_block_mode: WritingMode, -} +pub trait Flow: HasBaseFlow + fmt::Debug + Sync + Send + 'static {} -impl EarlyAbsolutePositionInfo { - pub fn new(writing_mode: WritingMode) -> EarlyAbsolutePositionInfo { - // FIXME(pcwalton): The initial relative containing block-size should be equal to the size - // of the root layer. - EarlyAbsolutePositionInfo { - relative_containing_block_size: LogicalSize::zero(writing_mode), - relative_containing_block_mode: writing_mode, - } - } -} - -/// Information needed to compute absolute (i.e. viewport-relative) flow positions (not to be -/// confused with absolutely-positioned flows) that is computed during final position assignment. -#[derive(Clone, Copy, Serialize)] -pub struct LateAbsolutePositionInfo { - /// The position of the absolute containing block relative to the nearest ancestor stacking - /// context. If the absolute containing block establishes the stacking context for this flow, - /// and this flow is not itself absolutely-positioned, then this is (0, 0). - pub stacking_relative_position_of_absolute_containing_block: Point2D<Au>, -} - -impl LateAbsolutePositionInfo { - pub fn new() -> LateAbsolutePositionInfo { - LateAbsolutePositionInfo { - stacking_relative_position_of_absolute_containing_block: Point2D::zero(), - } - } -} - -#[derive(Clone, Copy, Debug)] -pub struct FragmentationContext { - pub available_block_size: Au, - pub this_fragment_is_empty: bool, -} - -/// Data common to all flows. pub struct BaseFlow { pub restyle_damage: RestyleDamage, - - /// The children of this flow. - pub children: FlowList, - - /// Intrinsic inline sizes for this flow. - pub intrinsic_inline_sizes: IntrinsicISizes, - - /// The upper left corner of the box representing this flow, relative to the box representing - /// its parent flow. - /// - /// For absolute flows, this represents the position with respect to its *containing block*. - /// - /// This does not include margins in the block flow direction, because those can collapse. So - /// for the block direction (usually vertical), this represents the *border box*. For the - /// inline direction (usually horizontal), this represents the *margin box*. - pub position: LogicalRect<Au>, - - /// The amount of overflow of this flow, relative to the containing block. Must include all the - /// pixels of all the display list items for correct invalidation. - pub overflow: Overflow, - - /// Data used during parallel traversals. - /// - /// TODO(pcwalton): Group with other transient data to save space. - pub parallel: FlowParallelInfo, - - /// The floats next to this flow. - pub floats: Floats, - - /// Metrics for floats in computed during the float metrics speculation phase. - pub speculated_float_placement_in: SpeculatedFloatPlacement, - - /// Metrics for floats out computed during the float metrics speculation phase. - pub speculated_float_placement_out: SpeculatedFloatPlacement, - - /// The collapsible margins for this flow, if any. - pub collapsible_margins: CollapsibleMargins, - - /// The position of this flow relative to the start of the nearest ancestor stacking context. - /// This is computed during the top-down pass of display list construction. - pub stacking_relative_position: Vector2D<Au>, - - /// Details about descendants with position 'absolute' or 'fixed' for which we are the - /// containing block. This is in tree order. This includes any direct children. - pub abs_descendants: AbsoluteDescendants, - - /// The inline-size of the block container of this flow. Used for computing percentage and - /// automatic values for `width`. - pub block_container_inline_size: Au, - - /// The writing mode of the block container of this flow. - /// - /// FIXME (mbrubeck): Combine this and block_container_inline_size and maybe - /// block_container_explicit_block_size into a struct, to guarantee they are set at the same - /// time? Or just store a link to the containing block flow. - pub block_container_writing_mode: WritingMode, - - /// The block-size of the block container of this flow, if it is an explicit size (does not - /// depend on content heights). Used for computing percentage values for `height`. - pub block_container_explicit_block_size: Option<Au>, - - /// Reference to the Containing Block, if this flow is absolutely positioned. - pub absolute_cb: ContainingBlockLink, - - /// Information needed to compute absolute (i.e. viewport-relative) flow positions (not to be - /// confused with absolutely-positioned flows) that is computed during block-size assignment. - pub early_absolute_position_info: EarlyAbsolutePositionInfo, - - /// Information needed to compute absolute (i.e. viewport-relative) flow positions (not to be - /// confused with absolutely-positioned flows) that is computed during final position - /// assignment. - pub late_absolute_position_info: LateAbsolutePositionInfo, - - /// The clipping rectangle for this flow and its descendants, in the coordinate system of the - /// nearest ancestor stacking context. If this flow itself represents a stacking context, then - /// this is in the flow's own coordinate system. - pub clip: Rect<Au>, - - /// The writing mode for this flow. - pub writing_mode: WritingMode, - - /// For debugging and profiling, the identifier of the thread that laid out this fragment. - pub thread_id: u8, - - /// Various flags for flows, tightly packed to save space. - pub flags: FlowFlags, - - /// The ID of the StackingContext that contains this flow. This is initialized - /// to 0, but it assigned during the collect_stacking_contexts phase of display - /// list construction. - pub stacking_context_id: StackingContextId, - - /// The indices of this Flow's ClipScrollNode. This is used to place the node's - /// display items into scrolling frames and clipping nodes. - pub clipping_and_scrolling: Option<ClippingAndScrolling>, -} - -impl fmt::Debug for BaseFlow { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let child_count = self.parallel.children_count.load(Ordering::SeqCst); - let child_count_string = if child_count > 0 { - format!("\nchildren={}", child_count) - } else { - "".to_owned() - }; - - let absolute_descendants_string = if self.abs_descendants.len() > 0 { - format!("\nabs-descendents={}", self.abs_descendants.len()) - } else { - "".to_owned() - }; - - let damage_string = if self.restyle_damage != RestyleDamage::empty() { - format!("\ndamage={:?}", self.restyle_damage) - } else { - "".to_owned() - }; - - write!( - f, - "\nsc={:?}\ - \npos={:?}{}{}\ - \nfloatspec-in={:?}\ - \nfloatspec-out={:?}\ - \noverflow={:?}{}{}{}", - self.stacking_context_id, - self.position, - if self.flags.contains(FlowFlags::FLOATS_LEFT) { - "FL" - } else { - "" - }, - if self.flags.contains(FlowFlags::FLOATS_RIGHT) { - "FR" - } else { - "" - }, - self.speculated_float_placement_in, - self.speculated_float_placement_out, - self.overflow, - child_count_string, - absolute_descendants_string, - damage_string - ) - } -} - -impl Serialize for BaseFlow { - fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { - let mut serializer = serializer.serialize_struct("base", 5)?; - serializer.serialize_field("id", &self.debug_id())?; - serializer.serialize_field( - "stacking_relative_position", - &self.stacking_relative_position, - )?; - serializer.serialize_field("intrinsic_inline_sizes", &self.intrinsic_inline_sizes)?; - serializer.serialize_field("position", &self.position)?; - serializer.serialize_field("children", &self.children)?; - serializer.end() - } -} - -/// Whether a base flow should be forced to be nonfloated. This can affect e.g. `TableFlow`, which -/// is never floated because the table wrapper flow is the floated one. -#[derive(Clone, PartialEq)] -pub enum ForceNonfloatedFlag { - /// The flow should be floated if the node has a `float` property. - FloatIfNecessary, - /// The flow should be forced to be nonfloated. - ForceNonfloated, -} - -impl BaseFlow { - #[inline] - pub fn new( - style: Option<&ComputedValues>, - writing_mode: WritingMode, - force_nonfloated: ForceNonfloatedFlag, - ) -> BaseFlow { - let mut flags = FlowFlags::empty(); - match style { - Some(style) => { - if style.can_be_fragmented() { - flags.insert(FlowFlags::CAN_BE_FRAGMENTED); - } - - match style.get_box().position { - Position::Absolute | Position::Fixed => { - flags.insert(FlowFlags::IS_ABSOLUTELY_POSITIONED); - - let logical_position = style.logical_position(); - if logical_position.inline_start == LengthPercentageOrAuto::Auto && - logical_position.inline_end == LengthPercentageOrAuto::Auto - { - flags.insert(FlowFlags::INLINE_POSITION_IS_STATIC); - } - if logical_position.block_start == LengthPercentageOrAuto::Auto && - logical_position.block_end == LengthPercentageOrAuto::Auto - { - flags.insert(FlowFlags::BLOCK_POSITION_IS_STATIC); - } - }, - _ => flags.insert( - FlowFlags::BLOCK_POSITION_IS_STATIC | FlowFlags::INLINE_POSITION_IS_STATIC, - ), - } - - if force_nonfloated == ForceNonfloatedFlag::FloatIfNecessary { - match style.get_box().float { - Float::None => {}, - Float::Left => flags.insert(FlowFlags::FLOATS_LEFT), - Float::Right => flags.insert(FlowFlags::FLOATS_RIGHT), - } - } - - match style.get_box().clear { - Clear::None => {}, - Clear::Left => flags.insert(FlowFlags::CLEARS_LEFT), - Clear::Right => flags.insert(FlowFlags::CLEARS_RIGHT), - Clear::Both => { - flags.insert(FlowFlags::CLEARS_LEFT); - flags.insert(FlowFlags::CLEARS_RIGHT); - }, - } - - if !style.get_counters().counter_reset.is_empty() || - !style.get_counters().counter_increment.is_empty() - { - flags.insert(FlowFlags::AFFECTS_COUNTERS) - } - }, - None => flags - .insert(FlowFlags::BLOCK_POSITION_IS_STATIC | FlowFlags::INLINE_POSITION_IS_STATIC), - } - - // New flows start out as fully damaged. - let mut damage = RestyleDamage::rebuild_and_reflow(); - damage.remove(ServoRestyleDamage::RECONSTRUCT_FLOW); - - BaseFlow { - restyle_damage: damage, - children: FlowList::new(), - intrinsic_inline_sizes: IntrinsicISizes::new(), - position: LogicalRect::zero(writing_mode), - overflow: Overflow::new(), - parallel: FlowParallelInfo::new(), - floats: Floats::new(writing_mode), - collapsible_margins: CollapsibleMargins::new(), - stacking_relative_position: Vector2D::zero(), - abs_descendants: AbsoluteDescendants::new(), - speculated_float_placement_in: SpeculatedFloatPlacement::zero(), - speculated_float_placement_out: SpeculatedFloatPlacement::zero(), - block_container_inline_size: Au(0), - block_container_writing_mode: writing_mode, - block_container_explicit_block_size: None, - absolute_cb: ContainingBlockLink::new(), - early_absolute_position_info: EarlyAbsolutePositionInfo::new(writing_mode), - late_absolute_position_info: LateAbsolutePositionInfo::new(), - clip: MaxRect::max_rect(), - flags: flags, - writing_mode: writing_mode, - thread_id: 0, - stacking_context_id: StackingContextId::root(), - clipping_and_scrolling: None, - } - } - - /// Update the 'flags' field when computed styles have changed. - /// - /// These flags are initially set during flow construction. They only need to be updated here - /// if they are based on properties that can change without triggering `RECONSTRUCT_FLOW`. - pub fn update_flags_if_needed(&mut self, style: &ComputedValues) { - // For absolutely-positioned flows, changes to top/bottom/left/right can cause these flags - // to get out of date: - if self - .restyle_damage - .contains(ServoRestyleDamage::REFLOW_OUT_OF_FLOW) - { - // Note: We don't need to check whether IS_ABSOLUTELY_POSITIONED has changed, because - // changes to the 'position' property trigger flow reconstruction. - if self.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) { - let logical_position = style.logical_position(); - self.flags.set( - FlowFlags::INLINE_POSITION_IS_STATIC, - logical_position.inline_start == LengthPercentageOrAuto::Auto && - logical_position.inline_end == LengthPercentageOrAuto::Auto, - ); - self.flags.set( - FlowFlags::BLOCK_POSITION_IS_STATIC, - logical_position.block_start == LengthPercentageOrAuto::Auto && - logical_position.block_end == LengthPercentageOrAuto::Auto, - ); - } - } - } - - /// Return a new BaseFlow like this one but with the given children list - pub fn clone_with_children(&self, children: FlowList) -> BaseFlow { - BaseFlow { - children: children, - restyle_damage: self.restyle_damage | - ServoRestyleDamage::REPAINT | - ServoRestyleDamage::REFLOW_OUT_OF_FLOW | - ServoRestyleDamage::REFLOW, - parallel: FlowParallelInfo::new(), - floats: self.floats.clone(), - abs_descendants: self.abs_descendants.clone(), - absolute_cb: self.absolute_cb.clone(), - clip: self.clip.clone(), - - ..*self - } - } - - /// Iterates over the children of this immutable flow. - pub fn child_iter(&self) -> FlowListIterator { - self.children.iter() - } - - pub fn child_iter_mut(&mut self) -> MutFlowListIterator { - self.children.iter_mut() - } - - pub fn debug_id(&self) -> usize { - let p = self as *const _; - p as usize - } - - pub fn collect_stacking_contexts_for_children( - &mut self, - state: &mut StackingContextCollectionState, - ) { - for kid in self.children.iter_mut() { - kid.collect_stacking_contexts(state); - } - } - - #[inline] - pub fn might_have_floats_in(&self) -> bool { - self.speculated_float_placement_in.left > Au(0) || - self.speculated_float_placement_in.right > Au(0) - } - - #[inline] - pub fn might_have_floats_out(&self) -> bool { - self.speculated_float_placement_out.left > Au(0) || - self.speculated_float_placement_out.right > Au(0) - } - - /// Compute the fragment position relative to the parent stacking context. If the fragment - /// itself establishes a stacking context, then the origin of its position will be (0, 0) - /// for the purposes of this computation. - pub fn stacking_relative_border_box_for_display_list(&self, fragment: &Fragment) -> Rect<Au> { - fragment.stacking_relative_border_box( - &self.stacking_relative_position, - &self - .early_absolute_position_info - .relative_containing_block_size, - self.early_absolute_position_info - .relative_containing_block_mode, - CoordinateSystem::Own, - ) - } -} - -impl<'a> ImmutableFlowUtils for &'a dyn Flow { - /// Returns true if this flow is a block flow or subclass thereof. - fn is_block_like(self) -> bool { - self.class().is_block_like() - } - - /// Returns true if this flow is a table row flow. - fn is_table_row(self) -> bool { - match self.class() { - FlowClass::TableRow => true, - _ => false, - } - } - - /// Returns true if this flow is a table cell flow. - fn is_table_cell(self) -> bool { - match self.class() { - FlowClass::TableCell => true, - _ => false, - } - } - - /// Returns true if this flow is a table colgroup flow. - fn is_table_colgroup(self) -> bool { - match self.class() { - FlowClass::TableColGroup => true, - _ => false, - } - } - - /// Returns true if this flow is a table flow. - fn is_table(self) -> bool { - match self.class() { - FlowClass::Table => true, - _ => false, - } - } - - /// Returns true if this flow is a table caption flow. - fn is_table_caption(self) -> bool { - match self.class() { - FlowClass::TableCaption => true, - _ => false, - } - } - - /// Returns true if this flow is a table rowgroup flow. - fn is_table_rowgroup(self) -> bool { - match self.class() { - FlowClass::TableRowGroup => true, - _ => false, - } - } - - /// Returns the number of children that this flow possesses. - fn child_count(self) -> usize { - self.base().children.len() - } - - /// Returns true if this flow is a block flow. - fn is_block_flow(self) -> bool { - match self.class() { - FlowClass::Block => true, - _ => false, - } - } - - /// Returns true if this flow is an inline flow. - fn is_inline_flow(self) -> bool { - match self.class() { - FlowClass::Inline => true, - _ => false, - } - } - - /// Dumps the flow tree for debugging. - fn print(self, title: String) { - let mut print_tree = PrintTree::new(title); - self.print_with_tree(&mut print_tree); - } - - /// Dumps the flow tree for debugging into the given PrintTree. - fn print_with_tree(self, print_tree: &mut PrintTree) { - print_tree.new_level(format!("{:?}", self)); - self.print_extra_flow_children(print_tree); - for kid in self.base().child_iter() { - kid.print_with_tree(print_tree); - } - print_tree.end_level(); - } - - fn floats_might_flow_through(self) -> bool { - if !self.base().might_have_floats_in() && !self.base().might_have_floats_out() { - return false; - } - if self.is_root() { - return false; - } - if !self.is_block_like() { - return true; - } - self.as_block().formatting_context_type() == FormattingContextType::None - } - - fn baseline_offset_of_last_line_box_in_flow(self) -> Option<Au> { - for kid in self.base().children.iter().rev() { - if kid.is_inline_flow() { - if let Some(baseline_offset) = kid.as_inline().baseline_offset_of_last_line() { - return Some(kid.base().position.start.b + baseline_offset); - } - } - if kid.is_block_like() && - !kid.base() - .flags - .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) - { - if let Some(baseline_offset) = kid.baseline_offset_of_last_line_box_in_flow() { - return Some(kid.base().position.start.b + baseline_offset); - } - } - } - None - } -} - -impl<'a> MutableFlowUtils for &'a mut dyn Flow { - /// Calls `repair_style` and `bubble_inline_sizes`. You should use this method instead of - /// calling them individually, since there is no reason not to perform both operations. - fn repair_style_and_bubble_inline_sizes(self, style: &crate::ServoArc<ComputedValues>) { - self.repair_style(style); - self.mut_base().update_flags_if_needed(style); - self.bubble_inline_sizes(); - } -} - -impl MutableOwnedFlowUtils for FlowRef { - /// Set absolute descendants for this flow. - /// - /// Set yourself as the Containing Block for all the absolute descendants. - /// - /// This is called during flow construction, so nothing else can be accessing the descendant - /// flows. This is enforced by the fact that we have a mutable `FlowRef`, which only flow - /// construction is allowed to possess. - fn set_absolute_descendants(&mut self, abs_descendants: AbsoluteDescendants) { - let this = self.clone(); - let base = FlowRef::deref_mut(self).mut_base(); - base.abs_descendants = abs_descendants; - for descendant_link in base.abs_descendants.descendant_links.iter_mut() { - debug_assert!(!descendant_link.has_reached_containing_block); - let descendant_base = FlowRef::deref_mut(&mut descendant_link.flow).mut_base(); - descendant_base.absolute_cb.set(this.clone()); - } - } - - /// Sets the flow as the containing block for all absolute descendants that have been marked - /// as having reached their containing block. This is needed in order to handle cases like: - /// - /// ```html - /// <div> - /// <span style="position: relative"> - /// <span style="position: absolute; ..."></span> - /// </span> - /// </div> - /// ``` - fn take_applicable_absolute_descendants( - &mut self, - absolute_descendants: &mut AbsoluteDescendants, - ) { - let mut applicable_absolute_descendants = AbsoluteDescendants::new(); - for absolute_descendant in absolute_descendants.descendant_links.iter() { - if absolute_descendant.has_reached_containing_block { - applicable_absolute_descendants.push(absolute_descendant.flow.clone()); - } - } - absolute_descendants - .descendant_links - .retain(|descendant| !descendant.has_reached_containing_block); - - let this = self.clone(); - let base = FlowRef::deref_mut(self).mut_base(); - base.abs_descendants = applicable_absolute_descendants; - for descendant_link in base.abs_descendants.iter() { - let descendant_base = descendant_link.mut_base(); - descendant_base.absolute_cb.set(this.clone()); - } - } -} - -/// A link to a flow's containing block. -/// -/// This cannot safely be a `Flow` pointer because this is a pointer *up* the tree, not *down* the -/// tree. A pointer up the tree is unsafe during layout because it can be used to access a node -/// with an immutable reference while that same node is being laid out, causing possible iterator -/// invalidation and use-after-free. -/// -/// FIXME(pcwalton): I think this would be better with a borrow flag instead of `unsafe`. -#[derive(Clone)] -pub struct ContainingBlockLink { - /// The pointer up to the containing block. - link: Option<WeakFlowRef>, -} - -impl ContainingBlockLink { - fn new() -> ContainingBlockLink { - ContainingBlockLink { link: None } - } - - fn set(&mut self, link: FlowRef) { - self.link = Some(FlowRef::downgrade(&link)) - } - - #[inline] - pub fn generated_containing_block_size(&self, for_flow: OpaqueFlow) -> LogicalSize<Au> { - match self.link { - None => panic!( - "Link to containing block not established; perhaps you forgot to call \ - `set_absolute_descendants`?" - ), - Some(ref link) => { - let flow = link.upgrade().unwrap(); - flow.generated_containing_block_size(for_flow) - }, - } - } - - #[inline] - pub fn explicit_block_containing_size( - &self, - shared_context: &SharedStyleContext, - ) -> Option<Au> { - match self.link { - None => panic!( - "Link to containing block not established; perhaps you forgot to call \ - `set_absolute_descendants`?" - ), - Some(ref link) => { - let flow = link.upgrade().unwrap(); - if flow.is_block_like() { - flow.as_block() - .explicit_block_containing_size(shared_context) - } else if flow.is_inline_flow() { - Some(flow.as_inline().minimum_line_metrics.space_above_baseline) - } else { - None - } - }, - } - } -} - -/// A wrapper for the pointer address of a flow. These pointer addresses may only be compared for -/// equality with other such pointer addresses, never dereferenced. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct OpaqueFlow(pub usize); - -impl OpaqueFlow { - pub fn from_flow(flow: &dyn Flow) -> OpaqueFlow { - let object_ptr: *const dyn Flow = flow; - let data_ptr = object_ptr as *const (); - OpaqueFlow(data_ptr as usize) - } } diff --git a/components/layout_2020/flow_list.rs b/components/layout_2020/flow_list.rs deleted file mode 100644 index a27db03920a..00000000000 --- a/components/layout_2020/flow_list.rs +++ /dev/null @@ -1,197 +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/. */ - -use crate::flow::{Flow, FlowClass}; -use crate::flow_ref::FlowRef; -use serde::ser::{Serialize, SerializeSeq, Serializer}; -use serde_json::{to_value, Map, Value}; -use std::collections::{linked_list, LinkedList}; -use std::ops::Deref; -use std::sync::Arc; - -/// This needs to be reworked now that we have dynamically-sized types in Rust. -/// Until then, it's just a wrapper around LinkedList. -/// -/// SECURITY-NOTE(pcwalton): It is very important that `FlowRef` values not leak directly to -/// layout. Layout code must only interact with `&Flow` or `&mut Flow` values. Otherwise, layout -/// could stash `FlowRef` values in random places unknown to the system and thereby cause data -/// races. Those data races can lead to memory safety problems, potentially including arbitrary -/// remote code execution! In general, do not add new methods to this file (e.g. new ways of -/// iterating over flows) unless you are *very* sure of what you are doing. -pub struct FlowList { - flows: LinkedList<FlowRef>, -} - -impl Serialize for FlowList { - fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { - let mut serializer = serializer.serialize_seq(Some(self.len()))?; - for f in self.iter() { - let mut flow_val = Map::new(); - flow_val.insert("class".to_owned(), to_value(f.class()).unwrap()); - let data = match f.class() { - FlowClass::Block => to_value(f.as_block()).unwrap(), - FlowClass::Inline => to_value(f.as_inline()).unwrap(), - FlowClass::Table => to_value(f.as_table()).unwrap(), - FlowClass::TableWrapper => to_value(f.as_table_wrapper()).unwrap(), - FlowClass::TableRowGroup => to_value(f.as_table_rowgroup()).unwrap(), - FlowClass::TableRow => to_value(f.as_table_row()).unwrap(), - FlowClass::TableCell => to_value(f.as_table_cell()).unwrap(), - FlowClass::Flex => to_value(f.as_flex()).unwrap(), - FlowClass::ListItem | - FlowClass::TableColGroup | - FlowClass::TableCaption | - FlowClass::Multicol | - FlowClass::MulticolColumn => { - Value::Null // Not implemented yet - }, - }; - flow_val.insert("data".to_owned(), data); - serializer.serialize_element(&flow_val)?; - } - serializer.end() - } -} - -pub struct MutFlowListIterator<'a> { - it: linked_list::IterMut<'a, FlowRef>, -} - -pub struct FlowListIterator<'a> { - it: linked_list::Iter<'a, FlowRef>, -} - -impl FlowList { - /// Add an element last in the list - /// - /// O(1) - pub fn push_back(&mut self, new_tail: FlowRef) { - self.flows.push_back(new_tail); - } - - pub fn push_back_arc(&mut self, new_head: Arc<dyn Flow>) { - self.flows.push_back(FlowRef::new(new_head)); - } - - pub fn push_front_arc(&mut self, new_head: Arc<dyn Flow>) { - self.flows.push_front(FlowRef::new(new_head)); - } - - pub fn pop_front_arc(&mut self) -> Option<Arc<dyn Flow>> { - self.flows.pop_front().map(FlowRef::into_arc) - } - - /// Create an empty list - #[inline] - pub fn new() -> FlowList { - FlowList { - flows: LinkedList::new(), - } - } - - /// Provide a forward iterator. - /// - /// SECURITY-NOTE(pcwalton): This does not hand out `FlowRef`s by design. Do not add a method - /// to do so! See the comment above in `FlowList`. - #[inline] - pub fn iter<'a>(&'a self) -> FlowListIterator { - FlowListIterator { - it: self.flows.iter(), - } - } - - /// Provide a forward iterator with mutable references - /// - /// SECURITY-NOTE(pcwalton): This does not hand out `FlowRef`s by design. Do not add a method - /// to do so! See the comment above in `FlowList`. - #[inline] - pub fn iter_mut(&mut self) -> MutFlowListIterator { - MutFlowListIterator { - it: self.flows.iter_mut(), - } - } - - /// Provides a caching random-access iterator that yields mutable references. This is - /// guaranteed to perform no more than O(n) pointer chases. - /// - /// SECURITY-NOTE(pcwalton): This does not hand out `FlowRef`s by design. Do not add a method - /// to do so! See the comment above in `FlowList`. - #[inline] - pub fn random_access_mut(&mut self) -> FlowListRandomAccessMut { - let length = self.flows.len(); - FlowListRandomAccessMut { - iterator: self.flows.iter_mut(), - cache: Vec::with_capacity(length), - } - } - - /// O(1) - #[inline] - pub fn len(&self) -> usize { - self.flows.len() - } - - #[inline] - pub fn split_off(&mut self, i: usize) -> Self { - FlowList { - flows: self.flows.split_off(i), - } - } -} - -impl<'a> DoubleEndedIterator for FlowListIterator<'a> { - fn next_back(&mut self) -> Option<&'a dyn Flow> { - self.it.next_back().map(Deref::deref) - } -} - -impl<'a> DoubleEndedIterator for MutFlowListIterator<'a> { - fn next_back(&mut self) -> Option<&'a mut dyn Flow> { - self.it.next_back().map(FlowRef::deref_mut) - } -} - -impl<'a> Iterator for FlowListIterator<'a> { - type Item = &'a dyn Flow; - #[inline] - fn next(&mut self) -> Option<&'a dyn Flow> { - self.it.next().map(Deref::deref) - } - - #[inline] - fn size_hint(&self) -> (usize, Option<usize>) { - self.it.size_hint() - } -} - -impl<'a> Iterator for MutFlowListIterator<'a> { - type Item = &'a mut dyn Flow; - #[inline] - fn next(&mut self) -> Option<&'a mut dyn Flow> { - self.it.next().map(FlowRef::deref_mut) - } - - #[inline] - fn size_hint(&self) -> (usize, Option<usize>) { - self.it.size_hint() - } -} - -/// A caching random-access iterator that yields mutable references. This is guaranteed to perform -/// no more than O(n) pointer chases. -pub struct FlowListRandomAccessMut<'a> { - iterator: linked_list::IterMut<'a, FlowRef>, - cache: Vec<FlowRef>, -} - -impl<'a> FlowListRandomAccessMut<'a> { - pub fn get<'b>(&'b mut self, index: usize) -> &'b mut dyn Flow { - while index >= self.cache.len() { - match self.iterator.next() { - None => panic!("Flow index out of range!"), - Some(next_flow) => self.cache.push((*next_flow).clone()), - } - } - FlowRef::deref_mut(&mut self.cache[index]) - } -} diff --git a/components/layout_2020/flow_ref.rs b/components/layout_2020/flow_ref.rs index f00740aa4ed..f13a562927c 100644 --- a/components/layout_2020/flow_ref.rs +++ b/components/layout_2020/flow_ref.rs @@ -2,15 +2,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -//! Reference-counted pointers to flows. -//! -//! Eventually, with dynamically sized types in Rust, much of this code will -//! be superfluous. This design is largely duplicating logic of Arc<T> and -//! Weak<T>; please see comments there for details. - use crate::flow::Flow; use std::ops::Deref; -use std::sync::{Arc, Weak}; +use std::sync::Arc; #[derive(Clone, Debug)] pub struct FlowRef(Arc<dyn Flow>); @@ -23,42 +17,14 @@ impl Deref for FlowRef { } impl FlowRef { - /// `FlowRef`s can only be made available to the traversal code. - /// See https://github.com/servo/servo/issues/14014 for more details. pub fn new(mut r: Arc<dyn Flow>) -> Self { - // This assertion checks that this `FlowRef` does not alias normal `Arc`s. - // If that happens, we're in trouble. assert!(Arc::get_mut(&mut r).is_some()); FlowRef(r) } - pub fn get_mut(this: &mut FlowRef) -> Option<&mut dyn Flow> { - Arc::get_mut(&mut this.0) - } - pub fn downgrade(this: &FlowRef) -> WeakFlowRef { - WeakFlowRef(Arc::downgrade(&this.0)) - } - pub fn into_arc(mut this: FlowRef) -> Arc<dyn Flow> { - // This assertion checks that this `FlowRef` does not alias normal `Arc`s. - // If that happens, we're in trouble. - assert!(FlowRef::get_mut(&mut this).is_some()); - this.0 - } - /// WARNING: This should only be used when there is no aliasing: - /// when the traversal ensures that no other threads accesses the same flow at the same time. - /// See https://github.com/servo/servo/issues/6503 - /// Use Arc::get_mut instead when possible (e.g. on an Arc that was just created). + #[allow(unsafe_code)] pub fn deref_mut(this: &mut FlowRef) -> &mut dyn Flow { let ptr: *const dyn Flow = &*this.0; unsafe { &mut *(ptr as *mut dyn Flow) } } } - -#[derive(Clone, Debug)] -pub struct WeakFlowRef(Weak<dyn Flow>); - -impl WeakFlowRef { - pub fn upgrade(&self) -> Option<FlowRef> { - self.0.upgrade().map(FlowRef) - } -} diff --git a/components/layout_2020/fragment.rs b/components/layout_2020/fragment.rs index 879e2af2f64..b48aa196785 100644 --- a/components/layout_2020/fragment.rs +++ b/components/layout_2020/fragment.rs @@ -4,662 +4,48 @@ //! The `Fragment` type, which represents the leaves of the layout tree. -use crate::context::{with_thread_local_font_context, LayoutContext}; -use crate::display_list::items::{ClipScrollNodeIndex, OpaqueNode, BLUR_INFLATION_FACTOR}; -use crate::display_list::ToLayout; -use crate::floats::ClearType; -use crate::flow::{GetBaseFlow, ImmutableFlowUtils}; -use crate::flow_ref::FlowRef; -use crate::inline::{InlineFragmentContext, InlineFragmentNodeFlags, InlineFragmentNodeInfo}; -use crate::inline::{InlineMetrics, LineMetrics}; -#[cfg(debug_assertions)] -use crate::layout_debug; -use crate::model::style_length; -use crate::model::{self, IntrinsicISizes, IntrinsicISizesContribution, MaybeAuto, SizeConstraint}; -use crate::text; -use crate::text::TextRunScanner; -use crate::wrapper::ThreadSafeLayoutNodeHelpers; +use crate::context::LayoutContext; +use crate::display_list::items::OpaqueNode; use crate::ServoArc; use app_units::Au; -use canvas_traits::canvas::{CanvasId, CanvasMsg}; -use euclid::{Point2D, Rect, Size2D, Vector2D}; -use gfx::text::glyph::ByteIndex; -use gfx::text::text_run::{TextRun, TextRunSlice}; -use gfx_traits::StackingContextId; -use ipc_channel::ipc::IpcSender; -use msg::constellation_msg::{BrowsingContextId, PipelineId}; -use net_traits::image::base::{Image, ImageMetadata}; -use net_traits::image_cache::{ImageOrMetadataAvailable, UsePlaceholder}; -use range::*; -use script_layout_interface::wrapper_traits::{ - PseudoElementType, ThreadSafeLayoutElement, ThreadSafeLayoutNode, -}; -use script_layout_interface::{HTMLCanvasData, HTMLCanvasDataSource, HTMLMediaData, SVGSVGData}; +use euclid::Rect; +use script_layout_interface::wrapper_traits::{PseudoElementType, ThreadSafeLayoutNode}; use serde::ser::{Serialize, SerializeStruct, Serializer}; -use servo_url::ServoUrl; -use std::borrow::ToOwned; -use std::cmp::{max, min, Ordering}; -use std::collections::LinkedList; -use std::sync::{Arc, Mutex}; -use std::{f32, fmt}; -use style::computed_values::border_collapse::T as BorderCollapse; -use style::computed_values::box_sizing::T as BoxSizing; -use style::computed_values::clear::T as Clear; -use style::computed_values::color::T as Color; -use style::computed_values::display::T as Display; -use style::computed_values::mix_blend_mode::T as MixBlendMode; -use style::computed_values::overflow_wrap::T as OverflowWrap; -use style::computed_values::overflow_x::T as StyleOverflow; -use style::computed_values::position::T as Position; -use style::computed_values::text_decoration_line::T as TextDecorationLine; -use style::computed_values::transform_style::T as TransformStyle; -use style::computed_values::white_space::T as WhiteSpace; -use style::computed_values::word_break::T as WordBreak; -use style::logical_geometry::{Direction, LogicalMargin, LogicalRect, LogicalSize, WritingMode}; +use style::logical_geometry::{LogicalMargin, LogicalRect}; use style::properties::ComputedValues; use style::selector_parser::RestyleDamage; use style::servo::restyle_damage::ServoRestyleDamage; -use style::str::char_is_whitespace; -use style::values::computed::counters::ContentItem; -use style::values::computed::{LengthPercentage, LengthPercentageOrAuto, Size, VerticalAlign}; -use style::values::generics::box_::{Perspective, VerticalAlignKeyword}; -use style::values::generics::transform; -use style::Zero; -use webrender_api; -use webrender_api::units::LayoutTransform; -// From gfxFontConstants.h in Firefox. -static FONT_SUBSCRIPT_OFFSET_RATIO: f32 = 0.20; -static FONT_SUPERSCRIPT_OFFSET_RATIO: f32 = 0.34; - -// https://drafts.csswg.org/css-images/#default-object-size -static DEFAULT_REPLACED_WIDTH: i32 = 300; -static DEFAULT_REPLACED_HEIGHT: i32 = 150; - -/// Fragments (`struct Fragment`) are the leaves of the layout tree. They cannot position -/// themselves. In general, fragments do not have a simple correspondence with CSS fragments in the -/// specification: -/// -/// * Several fragments may correspond to the same CSS box or DOM node. For example, a CSS text box -/// broken across two lines is represented by two fragments. -/// -/// * Some CSS fragments are not created at all, such as some anonymous block fragments induced by -/// inline fragments with block-level sibling fragments. In that case, Servo uses an `InlineFlow` -/// with `BlockFlow` siblings; the `InlineFlow` is block-level, but not a block container. It is -/// positioned as if it were a block fragment, but its children are positioned according to -/// inline flow. -/// -/// A `SpecificFragmentInfo::Generic` is an empty fragment that contributes only borders, margins, -/// padding, and backgrounds. It is analogous to a CSS nonreplaced content box. -/// -/// A fragment's type influences how its styles are interpreted during layout. For example, -/// replaced content such as images are resized differently from tables, text, or other content. -/// Different types of fragments may also contain custom data; for example, text fragments contain -/// text. -/// -/// Do not add fields to this structure unless they're really really mega necessary! Fragments get -/// moved around a lot and thus their size impacts performance of layout quite a bit. -/// -/// FIXME(#2260, pcwalton): This can be slimmed down some by (at least) moving `inline_context` -/// to be on `InlineFlow` only. #[derive(Clone)] pub struct Fragment { - /// An opaque reference to the DOM node that this `Fragment` originates from. pub node: OpaqueNode, - - /// The CSS style of this fragment. pub style: ServoArc<ComputedValues>, - - /// The CSS style of this fragment when it's selected - pub selected_style: ServoArc<ComputedValues>, - - /// The position of this fragment relative to its owning flow. The size includes padding and - /// border, but not margin. - /// - /// NB: This does not account for relative positioning. - /// NB: Collapsed borders are not included in this. pub border_box: LogicalRect<Au>, - - /// The sum of border and padding; i.e. the distance from the edge of the border box to the - /// content edge of the fragment. pub border_padding: LogicalMargin<Au>, - - /// The margin of the content box. pub margin: LogicalMargin<Au>, - - /// Info specific to the kind of fragment. Keep this enum small. pub specific: SpecificFragmentInfo, - - /// Holds the style context information for fragments that are part of an inline formatting - /// context. - pub inline_context: Option<InlineFragmentContext>, - - /// How damaged this fragment is since last reflow. pub restyle_damage: RestyleDamage, - - /// The pseudo-element that this fragment represents. pub pseudo: PseudoElementType, - - /// Various flags for this fragment. - pub flags: FragmentFlags, - - /// A debug ID that is consistent for the life of this fragment (via transform etc). - /// This ID should not be considered stable across multiple layouts or fragment - /// manipulations. - debug_id: DebugId, - - /// The ID of the StackingContext that contains this fragment. This is initialized - /// to 0, but it assigned during the collect_stacking_contexts phase of display - /// list construction. - pub stacking_context_id: StackingContextId, - - /// The indices of this Fragment's ClipScrollNode. If this fragment doesn't have a - /// `established_reference_frame` assigned, it will use the `clipping_and_scrolling` of the - /// parent block. - pub established_reference_frame: Option<ClipScrollNodeIndex>, } impl Serialize for Fragment { fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { let mut serializer = serializer.serialize_struct("fragment", 3)?; - serializer.serialize_field("id", &self.debug_id)?; serializer.serialize_field("border_box", &self.border_box)?; serializer.serialize_field("margin", &self.margin)?; serializer.end() } } -/// Info specific to the kind of fragment. -/// -/// Keep this enum small. As in, no more than one word. Or pcwalton will yell at you. #[derive(Clone)] pub enum SpecificFragmentInfo { Generic, - - /// A piece of generated content that cannot be resolved into `ScannedText` until the generated - /// content resolution phase (e.g. an ordered list item marker). - GeneratedContent(Box<GeneratedContentInfo>), - - Iframe(IframeFragmentInfo), - Image(Box<ImageFragmentInfo>), - Media(Box<MediaFragmentInfo>), - Canvas(Box<CanvasFragmentInfo>), - Svg(Box<SvgFragmentInfo>), - - /// A hypothetical box (see CSS 2.1 § 10.3.7) for an absolutely-positioned block that was - /// declared with `display: inline;`. - InlineAbsoluteHypothetical(InlineAbsoluteHypotheticalFragmentInfo), - - InlineBlock(InlineBlockFragmentInfo), - - /// An inline fragment that establishes an absolute containing block for its descendants (i.e. - /// a positioned inline fragment). - InlineAbsolute(InlineAbsoluteFragmentInfo), - - ScannedText(Box<ScannedTextFragmentInfo>), - Table, - TableCell, - TableColumn(TableColumnFragmentInfo), - TableRow, - TableWrapper, - Multicol, - MulticolColumn, - UnscannedText(Box<UnscannedTextFragmentInfo>), - - /// A container for a fragment that got truncated by text-overflow. - /// "Totally truncated fragments" are not rendered at all. - /// Text fragments may be partially truncated (in which case this renders like a text fragment). - /// Other fragments can only be totally truncated or not truncated at all. - TruncatedFragment(Box<TruncatedFragmentInfo>), } impl SpecificFragmentInfo { fn restyle_damage(&self) -> RestyleDamage { - let flow = match *self { - SpecificFragmentInfo::Canvas(_) | - SpecificFragmentInfo::GeneratedContent(_) | - SpecificFragmentInfo::Iframe(_) | - SpecificFragmentInfo::Image(_) | - SpecificFragmentInfo::Media(_) | - SpecificFragmentInfo::ScannedText(_) | - SpecificFragmentInfo::Svg(_) | - SpecificFragmentInfo::Table | - SpecificFragmentInfo::TableCell | - SpecificFragmentInfo::TableColumn(_) | - SpecificFragmentInfo::TableRow | - SpecificFragmentInfo::TableWrapper | - SpecificFragmentInfo::Multicol | - SpecificFragmentInfo::MulticolColumn | - SpecificFragmentInfo::UnscannedText(_) | - SpecificFragmentInfo::TruncatedFragment(_) | - SpecificFragmentInfo::Generic => return RestyleDamage::empty(), - SpecificFragmentInfo::InlineAbsoluteHypothetical(ref info) => &info.flow_ref, - SpecificFragmentInfo::InlineAbsolute(ref info) => &info.flow_ref, - SpecificFragmentInfo::InlineBlock(ref info) => &info.flow_ref, - }; - - flow.base().restyle_damage - } - - pub fn get_type(&self) -> &'static str { - match *self { - SpecificFragmentInfo::Canvas(_) => "SpecificFragmentInfo::Canvas", - SpecificFragmentInfo::Media(_) => "SpecificFragmentInfo::Media", - SpecificFragmentInfo::Generic => "SpecificFragmentInfo::Generic", - SpecificFragmentInfo::GeneratedContent(_) => "SpecificFragmentInfo::GeneratedContent", - SpecificFragmentInfo::Iframe(_) => "SpecificFragmentInfo::Iframe", - SpecificFragmentInfo::Image(_) => "SpecificFragmentInfo::Image", - SpecificFragmentInfo::InlineAbsolute(_) => "SpecificFragmentInfo::InlineAbsolute", - SpecificFragmentInfo::InlineAbsoluteHypothetical(_) => { - "SpecificFragmentInfo::InlineAbsoluteHypothetical" - }, - SpecificFragmentInfo::InlineBlock(_) => "SpecificFragmentInfo::InlineBlock", - SpecificFragmentInfo::ScannedText(_) => "SpecificFragmentInfo::ScannedText", - SpecificFragmentInfo::Svg(_) => "SpecificFragmentInfo::Svg", - SpecificFragmentInfo::Table => "SpecificFragmentInfo::Table", - SpecificFragmentInfo::TableCell => "SpecificFragmentInfo::TableCell", - SpecificFragmentInfo::TableColumn(_) => "SpecificFragmentInfo::TableColumn", - SpecificFragmentInfo::TableRow => "SpecificFragmentInfo::TableRow", - SpecificFragmentInfo::TableWrapper => "SpecificFragmentInfo::TableWrapper", - SpecificFragmentInfo::Multicol => "SpecificFragmentInfo::Multicol", - SpecificFragmentInfo::MulticolColumn => "SpecificFragmentInfo::MulticolColumn", - SpecificFragmentInfo::UnscannedText(_) => "SpecificFragmentInfo::UnscannedText", - SpecificFragmentInfo::TruncatedFragment(_) => "SpecificFragmentInfo::TruncatedFragment", - } - } -} - -impl fmt::Debug for SpecificFragmentInfo { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - SpecificFragmentInfo::ScannedText(ref info) => write!(f, "{:?}", info.text()), - SpecificFragmentInfo::UnscannedText(ref info) => write!(f, "{:?}", info.text), - _ => Ok(()), - } - } -} - -/// Information for generated content. -#[derive(Clone)] -pub enum GeneratedContentInfo { - ListItem, - ContentItem(ContentItem), - /// Placeholder for elements with generated content that did not generate any fragments. - Empty, -} - -/// A hypothetical box (see CSS 2.1 § 10.3.7) for an absolutely-positioned block that was declared -/// with `display: inline;`. -/// -/// FIXME(pcwalton): Stop leaking this `FlowRef` to layout; that is not memory safe because layout -/// can clone it. -#[derive(Clone)] -pub struct InlineAbsoluteHypotheticalFragmentInfo { - pub flow_ref: FlowRef, -} - -impl InlineAbsoluteHypotheticalFragmentInfo { - pub fn new(flow_ref: FlowRef) -> InlineAbsoluteHypotheticalFragmentInfo { - InlineAbsoluteHypotheticalFragmentInfo { flow_ref: flow_ref } - } -} - -/// A fragment that represents an inline-block element. -/// -/// FIXME(pcwalton): Stop leaking this `FlowRef` to layout; that is not memory safe because layout -/// can clone it. -#[derive(Clone)] -pub struct InlineBlockFragmentInfo { - pub flow_ref: FlowRef, -} - -impl InlineBlockFragmentInfo { - pub fn new(flow_ref: FlowRef) -> InlineBlockFragmentInfo { - InlineBlockFragmentInfo { flow_ref: flow_ref } - } -} - -/// An inline fragment that establishes an absolute containing block for its descendants (i.e. -/// a positioned inline fragment). -/// -/// FIXME(pcwalton): Stop leaking this `FlowRef` to layout; that is not memory safe because layout -/// can clone it. -#[derive(Clone)] -pub struct InlineAbsoluteFragmentInfo { - pub flow_ref: FlowRef, -} - -impl InlineAbsoluteFragmentInfo { - pub fn new(flow_ref: FlowRef) -> InlineAbsoluteFragmentInfo { - InlineAbsoluteFragmentInfo { flow_ref: flow_ref } - } -} - -#[derive(Clone)] -pub enum CanvasFragmentSource { - WebGL(webrender_api::ImageKey), - Image(Option<Arc<Mutex<IpcSender<CanvasMsg>>>>), -} - -#[derive(Clone)] -pub struct CanvasFragmentInfo { - pub source: CanvasFragmentSource, - pub dom_width: Au, - pub dom_height: Au, - pub canvas_id: CanvasId, -} - -impl CanvasFragmentInfo { - pub fn new(data: HTMLCanvasData) -> CanvasFragmentInfo { - let source = match data.source { - HTMLCanvasDataSource::WebGL(texture_id) => CanvasFragmentSource::WebGL(texture_id), - HTMLCanvasDataSource::Image(ipc_sender) => CanvasFragmentSource::Image( - ipc_sender.map(|renderer| Arc::new(Mutex::new(renderer))), - ), - }; - - CanvasFragmentInfo { - source: source, - dom_width: Au::from_px(data.width as i32), - dom_height: Au::from_px(data.height as i32), - canvas_id: data.canvas_id, - } - } -} - -#[derive(Clone)] -pub struct MediaFragmentInfo { - pub current_frame: Option<(webrender_api::ImageKey, i32, i32)>, -} - -impl MediaFragmentInfo { - pub fn new(data: HTMLMediaData) -> MediaFragmentInfo { - MediaFragmentInfo { - current_frame: data.current_frame, - } - } -} - -#[derive(Clone)] -pub struct SvgFragmentInfo { - pub dom_width: Au, - pub dom_height: Au, -} - -impl SvgFragmentInfo { - pub fn new(data: SVGSVGData) -> SvgFragmentInfo { - SvgFragmentInfo { - dom_width: Au::from_px(data.width as i32), - dom_height: Au::from_px(data.height as i32), - } - } -} - -/// A fragment that represents a replaced content image and its accompanying borders, shadows, etc. -#[derive(Clone)] -pub struct ImageFragmentInfo { - pub image: Option<Arc<Image>>, - pub metadata: Option<ImageMetadata>, -} - -enum ImageOrMetadata { - Image(Arc<Image>), - Metadata(ImageMetadata), -} - -impl ImageFragmentInfo { - /// Creates a new image fragment from the given URL and local image cache. - /// - /// FIXME(pcwalton): The fact that image fragments store the cache in the fragment makes little - /// sense to me. - pub fn new<N: ThreadSafeLayoutNode>( - url: Option<ServoUrl>, - density: Option<f64>, - node: &N, - layout_context: &LayoutContext, - ) -> ImageFragmentInfo { - // First use any image data present in the element... - let image_or_metadata = node - .image_data() - .and_then(|(image, metadata)| match (image, metadata) { - (Some(image), _) => Some(ImageOrMetadata::Image(image)), - (None, Some(metadata)) => Some(ImageOrMetadata::Metadata(metadata)), - _ => None, - }) - .or_else(|| { - url.and_then(|url| { - // Otherwise query the image cache for anything known about the associated source URL. - layout_context - .get_or_request_image_or_meta(node.opaque(), url, UsePlaceholder::Yes) - .map(|result| match result { - ImageOrMetadataAvailable::ImageAvailable(i, _) => { - ImageOrMetadata::Image(i) - }, - ImageOrMetadataAvailable::MetadataAvailable(m) => { - ImageOrMetadata::Metadata(m) - }, - }) - }) - }); - - let current_pixel_density = density.unwrap_or(1f64); - - let (image, metadata) = match image_or_metadata { - Some(ImageOrMetadata::Image(i)) => { - let height = (i.height as f64 / current_pixel_density) as u32; - let width = (i.width as f64 / current_pixel_density) as u32; - ( - Some(Arc::new(Image { - height: height, - width: width, - ..(*i).clone() - })), - Some(ImageMetadata { - height: height, - width: width, - }), - ) - }, - Some(ImageOrMetadata::Metadata(m)) => ( - None, - Some(ImageMetadata { - height: (m.height as f64 / current_pixel_density) as u32, - width: (m.width as f64 / current_pixel_density) as u32, - }), - ), - None => (None, None), - }; - - ImageFragmentInfo { - image: image, - metadata: metadata, - } - } -} - -/// A fragment that represents an inline frame (iframe). This stores the frame ID so that the -/// size of this iframe can be communicated via the constellation to the iframe's own layout thread. -#[derive(Clone)] -pub struct IframeFragmentInfo { - /// The frame ID of this iframe. None if there is no nested browsing context. - pub browsing_context_id: Option<BrowsingContextId>, - /// The pipelineID of this iframe. None if there is no nested browsing context. - pub pipeline_id: Option<PipelineId>, -} - -impl IframeFragmentInfo { - /// Creates the information specific to an iframe fragment. - pub fn new<N: ThreadSafeLayoutNode>(node: &N) -> IframeFragmentInfo { - let browsing_context_id = node.iframe_browsing_context_id(); - let pipeline_id = node.iframe_pipeline_id(); - IframeFragmentInfo { - browsing_context_id: browsing_context_id, - pipeline_id: pipeline_id, - } - } -} - -/// A scanned text fragment represents a single run of text with a distinct style. A `TextFragment` -/// may be split into two or more fragments across line breaks. Several `TextFragment`s may -/// correspond to a single DOM text node. Split text fragments are implemented by referring to -/// subsets of a single `TextRun` object. -#[derive(Clone)] -pub struct ScannedTextFragmentInfo { - /// The text run that this represents. - pub run: Arc<TextRun>, - - /// The intrinsic size of the text fragment. - pub content_size: LogicalSize<Au>, - - /// The byte offset of the insertion point, if any. - pub insertion_point: Option<ByteIndex>, - - /// The range within the above text run that this represents. - pub range: Range<ByteIndex>, - - /// The endpoint of the above range, including whitespace that was stripped out. This exists - /// so that we can restore the range to its original value (before line breaking occurred) when - /// performing incremental reflow. - pub range_end_including_stripped_whitespace: ByteIndex, - - pub flags: ScannedTextFlags, -} - -bitflags! { - pub struct ScannedTextFlags: u8 { - /// Whether a line break is required after this fragment if wrapping on newlines (e.g. if - /// `white-space: pre` is in effect). - const REQUIRES_LINE_BREAK_AFTERWARD_IF_WRAPPING_ON_NEWLINES = 0x01; - - /// Is this fragment selected? - const SELECTED = 0x02; - - /// Suppress line breaking between this and the previous fragment - /// - /// This handles cases like Foo<span>bar</span> - const SUPPRESS_LINE_BREAK_BEFORE = 0x04; - } -} - -impl ScannedTextFragmentInfo { - /// Creates the information specific to a scanned text fragment from a range and a text run. - pub fn new( - run: Arc<TextRun>, - range: Range<ByteIndex>, - content_size: LogicalSize<Au>, - insertion_point: Option<ByteIndex>, - flags: ScannedTextFlags, - ) -> ScannedTextFragmentInfo { - ScannedTextFragmentInfo { - run: run, - range: range, - insertion_point: insertion_point, - content_size: content_size, - range_end_including_stripped_whitespace: range.end(), - flags: flags, - } - } - - pub fn text(&self) -> &str { - &self.run.text[self.range.begin().to_usize()..self.range.end().to_usize()] + RestyleDamage::empty() } - - pub fn requires_line_break_afterward_if_wrapping_on_newlines(&self) -> bool { - self.flags - .contains(ScannedTextFlags::REQUIRES_LINE_BREAK_AFTERWARD_IF_WRAPPING_ON_NEWLINES) - } - - pub fn selected(&self) -> bool { - self.flags.contains(ScannedTextFlags::SELECTED) - } -} - -/// Describes how to split a fragment. This is used during line breaking as part of the return -/// value of `find_split_info_for_inline_size()`. -#[derive(Clone, Debug)] -pub struct SplitInfo { - // TODO(bjz): this should only need to be a single character index, but both values are - // currently needed for splitting in the `inline::try_append_*` functions. - pub range: Range<ByteIndex>, - pub inline_size: Au, -} - -impl SplitInfo { - fn new(range: Range<ByteIndex>, info: &ScannedTextFragmentInfo) -> SplitInfo { - let inline_size = info.run.advance_for_range(&range); - SplitInfo { - range: range, - inline_size: inline_size, - } - } -} - -/// Describes how to split a fragment into two. This contains up to two `SplitInfo`s. -pub struct SplitResult { - /// The part of the fragment that goes on the first line. - pub inline_start: Option<SplitInfo>, - /// The part of the fragment that goes on the second line. - pub inline_end: Option<SplitInfo>, - /// The text run which is being split. - pub text_run: Arc<TextRun>, -} - -/// Describes how a fragment should be truncated. -struct TruncationResult { - /// The part of the fragment remaining after truncation. - split: SplitInfo, - /// The text run which is being truncated. - text_run: Arc<TextRun>, -} - -/// Data for an unscanned text fragment. Unscanned text fragments are the results of flow -/// construction that have not yet had their inline-size determined. -#[derive(Clone)] -pub struct UnscannedTextFragmentInfo { - /// The text inside the fragment. - pub text: Box<str>, - - /// The selected text range. An empty range represents the insertion point. - pub selection: Option<Range<ByteIndex>>, -} - -impl UnscannedTextFragmentInfo { - /// Creates a new instance of `UnscannedTextFragmentInfo` from the given text. - #[inline] - pub fn new(text: Box<str>, selection: Option<Range<ByteIndex>>) -> UnscannedTextFragmentInfo { - UnscannedTextFragmentInfo { - text: text, - selection: selection, - } - } -} - -/// A fragment that represents a table column. -#[derive(Clone, Copy)] -pub struct TableColumnFragmentInfo { - /// the number of columns a <col> element should span - pub span: u32, -} - -impl TableColumnFragmentInfo { - /// Create the information specific to an table column fragment. - pub fn new<N: ThreadSafeLayoutNode>(node: &N) -> TableColumnFragmentInfo { - let element = node.as_element().unwrap(); - let span = element - .get_attr(&ns!(), &local_name!("span")) - .and_then(|string| string.parse().ok()) - .unwrap_or(0); - TableColumnFragmentInfo { span: span } - } -} - -/// A wrapper for fragments that have been truncated by the `text-overflow` property. -/// This may have an associated text node, or, if the fragment was completely truncated, -/// it may act as an invisible marker for incremental reflow. -#[derive(Clone)] -pub struct TruncatedFragmentInfo { - pub text_info: Option<ScannedTextFragmentInfo>, - pub full: Fragment, } impl Fragment { @@ -673,836 +59,34 @@ impl Fragment { let style = node.style(shared_context); let writing_mode = style.writing_mode; - let mut restyle_damage = node.restyle_damage(); + let mut restyle_damage = RestyleDamage::rebuild_and_reflow(); restyle_damage.remove(ServoRestyleDamage::RECONSTRUCT_FLOW); Fragment { node: node.opaque(), style: style, - selected_style: node.selected_style(), restyle_damage: restyle_damage, border_box: LogicalRect::zero(writing_mode), border_padding: LogicalMargin::zero(writing_mode), margin: LogicalMargin::zero(writing_mode), specific: specific, - inline_context: None, pseudo: node.get_pseudo_element_type(), - flags: FragmentFlags::empty(), - debug_id: DebugId::new(), - stacking_context_id: StackingContextId::root(), - established_reference_frame: None, } } - /// Constructs a new `Fragment` instance from an opaque node. - pub fn from_opaque_node_and_style( - node: OpaqueNode, - pseudo: PseudoElementType, - style: ServoArc<ComputedValues>, - selected_style: ServoArc<ComputedValues>, - mut restyle_damage: RestyleDamage, - specific: SpecificFragmentInfo, - ) -> Fragment { - let writing_mode = style.writing_mode; - - restyle_damage.remove(ServoRestyleDamage::RECONSTRUCT_FLOW); - - Fragment { - node: node, - style: style, - selected_style: selected_style, - restyle_damage: restyle_damage, - border_box: LogicalRect::zero(writing_mode), - border_padding: LogicalMargin::zero(writing_mode), - margin: LogicalMargin::zero(writing_mode), - specific: specific, - inline_context: None, - pseudo: pseudo, - flags: FragmentFlags::empty(), - debug_id: DebugId::new(), - stacking_context_id: StackingContextId::root(), - established_reference_frame: None, - } - } - - /// Creates an anonymous fragment just like this one but with the given style and fragment - /// type. For the new anonymous fragment, layout-related values (border box, etc.) are reset to - /// initial values. - pub fn create_similar_anonymous_fragment( - &self, - style: ServoArc<ComputedValues>, - specific: SpecificFragmentInfo, - ) -> Fragment { - let writing_mode = style.writing_mode; - Fragment { - node: self.node, - style: style, - selected_style: self.selected_style.clone(), - restyle_damage: self.restyle_damage, - border_box: LogicalRect::zero(writing_mode), - border_padding: LogicalMargin::zero(writing_mode), - margin: LogicalMargin::zero(writing_mode), - specific: specific, - inline_context: None, - pseudo: self.pseudo, - flags: FragmentFlags::empty(), - debug_id: DebugId::new(), - stacking_context_id: StackingContextId::root(), - established_reference_frame: None, - } - } - - /// Transforms this fragment into another fragment of the given type, with the given size, - /// preserving all the other data. - pub fn transform(&self, size: LogicalSize<Au>, info: SpecificFragmentInfo) -> Fragment { - let new_border_box = - LogicalRect::from_point_size(self.style.writing_mode, self.border_box.start, size); - - let mut restyle_damage = RestyleDamage::rebuild_and_reflow(); - restyle_damage.remove(ServoRestyleDamage::RECONSTRUCT_FLOW); - - Fragment { - node: self.node, - style: self.style.clone(), - selected_style: self.selected_style.clone(), - restyle_damage: restyle_damage, - border_box: new_border_box, - border_padding: self.border_padding, - margin: self.margin, - specific: info, - inline_context: self.inline_context.clone(), - pseudo: self.pseudo.clone(), - flags: FragmentFlags::empty(), - debug_id: self.debug_id.clone(), - stacking_context_id: StackingContextId::root(), - established_reference_frame: None, - } - } - - /// Transforms this fragment using the given `SplitInfo`, preserving all the other data. - /// - /// If this is the first half of a split, `first` is true - pub fn transform_with_split_info( - &self, - split: &SplitInfo, - text_run: Arc<TextRun>, - first: bool, - ) -> Fragment { - let size = LogicalSize::new( - self.style.writing_mode, - split.inline_size, - self.border_box.size.block, - ); - // Preserve the insertion point if it is in this fragment's range or it is at line end. - let (mut flags, insertion_point) = match self.specific { - SpecificFragmentInfo::ScannedText(ref info) => match info.insertion_point { - Some(index) if split.range.contains(index) => (info.flags, info.insertion_point), - Some(index) - if index == ByteIndex(text_run.text.chars().count() as isize - 1) && - index == split.range.end() => - { - (info.flags, info.insertion_point) - }, - _ => (info.flags, None), - }, - _ => (ScannedTextFlags::empty(), None), - }; - - if !first { - flags.set(ScannedTextFlags::SUPPRESS_LINE_BREAK_BEFORE, false); - } - - let info = Box::new(ScannedTextFragmentInfo::new( - text_run, - split.range, - size, - insertion_point, - flags, - )); - self.transform(size, SpecificFragmentInfo::ScannedText(info)) - } - - /// Transforms this fragment into an ellipsis fragment, preserving all the other data. - pub fn transform_into_ellipsis( - &self, - layout_context: &LayoutContext, - text_overflow_string: String, - ) -> Fragment { - let mut unscanned_ellipsis_fragments = LinkedList::new(); - let mut ellipsis_fragment = self.transform( - self.border_box.size, - SpecificFragmentInfo::UnscannedText(Box::new(UnscannedTextFragmentInfo::new( - text_overflow_string.into_boxed_str(), - None, - ))), - ); - unscanned_ellipsis_fragments.push_back(ellipsis_fragment); - let ellipsis_fragments = with_thread_local_font_context(layout_context, |font_context| { - TextRunScanner::new().scan_for_runs(font_context, unscanned_ellipsis_fragments) - }); - debug_assert_eq!(ellipsis_fragments.len(), 1); - ellipsis_fragment = ellipsis_fragments.fragments.into_iter().next().unwrap(); - ellipsis_fragment.flags |= FragmentFlags::IS_ELLIPSIS; - ellipsis_fragment - } - pub fn restyle_damage(&self) -> RestyleDamage { self.restyle_damage | self.specific.restyle_damage() } pub fn contains_node(&self, node_address: OpaqueNode) -> bool { - node_address == self.node || - self.inline_context - .as_ref() - .map_or(false, |ctx| ctx.contains_node(node_address)) - } - - /// Adds a style to the inline context for this fragment. If the inline context doesn't exist - /// yet, it will be created. - pub fn add_inline_context_style(&mut self, node_info: InlineFragmentNodeInfo) { - if self.inline_context.is_none() { - self.inline_context = Some(InlineFragmentContext::new()); - } - self.inline_context.as_mut().unwrap().nodes.push(node_info); - } - - /// Determines which quantities (border/padding/margin/specified) should be included in the - /// intrinsic inline size of this fragment. - fn quantities_included_in_intrinsic_inline_size( - &self, - ) -> QuantitiesIncludedInIntrinsicInlineSizes { - match self.specific { - SpecificFragmentInfo::Canvas(_) | - SpecificFragmentInfo::Media(_) | - SpecificFragmentInfo::Generic | - SpecificFragmentInfo::GeneratedContent(_) | - SpecificFragmentInfo::Iframe(_) | - SpecificFragmentInfo::Image(_) | - SpecificFragmentInfo::InlineAbsolute(_) | - SpecificFragmentInfo::Multicol | - SpecificFragmentInfo::Svg(_) => { - QuantitiesIncludedInIntrinsicInlineSizes::all() - } - SpecificFragmentInfo::Table => { - QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_SPECIFIED | - QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_PADDING | - QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_BORDER - } - SpecificFragmentInfo::TableCell => { - let base_quantities = QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_PADDING | - QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_SPECIFIED; - if self.style.get_inherited_table().border_collapse == - BorderCollapse::Separate { - base_quantities | QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_BORDER - } else { - base_quantities - } - } - SpecificFragmentInfo::TableWrapper => { - let base_quantities = QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_MARGINS | - QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_SPECIFIED; - if self.style.get_inherited_table().border_collapse == - BorderCollapse::Separate { - base_quantities | QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_BORDER - } else { - base_quantities - } - } - SpecificFragmentInfo::TableRow => { - let base_quantities = - QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_SPECIFIED; - if self.style.get_inherited_table().border_collapse == - BorderCollapse::Separate { - base_quantities | QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_BORDER - } else { - base_quantities - } - } - SpecificFragmentInfo::TruncatedFragment(_) | - SpecificFragmentInfo::ScannedText(_) | - SpecificFragmentInfo::TableColumn(_) | - SpecificFragmentInfo::UnscannedText(_) | - SpecificFragmentInfo::InlineAbsoluteHypothetical(_) | - SpecificFragmentInfo::InlineBlock(_) | - SpecificFragmentInfo::MulticolColumn => { - QuantitiesIncludedInIntrinsicInlineSizes::empty() - } - } - } - - /// Returns the portion of the intrinsic inline-size that consists of borders/padding and - /// margins, respectively. - /// - /// FIXME(#2261, pcwalton): This won't work well for inlines: is this OK? - pub fn surrounding_intrinsic_inline_size(&self) -> (Au, Au) { - let flags = self.quantities_included_in_intrinsic_inline_size(); - let style = self.style(); - - // FIXME(pcwalton): Percentages should be relative to any definite size per CSS-SIZING. - // This will likely need to be done by pushing down definite sizes during selector - // cascading. - let margin = if flags.contains( - QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_MARGINS, - ) { - let margin = style.logical_margin(); - (MaybeAuto::from_style(margin.inline_start, Au(0)).specified_or_zero() + - MaybeAuto::from_style(margin.inline_end, Au(0)).specified_or_zero()) - } else { - Au(0) - }; - - // FIXME(pcwalton): Percentages should be relative to any definite size per CSS-SIZING. - // This will likely need to be done by pushing down definite sizes during selector - // cascading. - let padding = if flags.contains( - QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_PADDING, - ) { - let padding = style.logical_padding(); - (padding.inline_start.to_used_value(Au(0)) + padding.inline_end.to_used_value(Au(0))) - } else { - Au(0) - }; - - let border = if flags.contains( - QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_BORDER, - ) { - self.border_width().inline_start_end() - } else { - Au(0) - }; - - (border + padding, margin) - } - - /// Uses the style only to estimate the intrinsic inline-sizes. These may be modified for text - /// or replaced elements. - pub fn style_specified_intrinsic_inline_size(&self) -> IntrinsicISizesContribution { - let flags = self.quantities_included_in_intrinsic_inline_size(); - let style = self.style(); - - // FIXME(#2261, pcwalton): This won't work well for inlines: is this OK? - let (border_padding, margin) = self.surrounding_intrinsic_inline_size(); - - let mut specified = Au(0); - if flags.contains( - QuantitiesIncludedInIntrinsicInlineSizes::INTRINSIC_INLINE_SIZE_INCLUDES_SPECIFIED, - ) { - specified = style - .content_inline_size() - .to_used_value(Au(0)) - .unwrap_or(Au(0)); - specified = max( - style - .min_inline_size() - .to_used_value(Au(0)) - .unwrap_or(Au(0)), - specified, - ); - if let Some(max) = style.max_inline_size().to_used_value(Au(0)) { - specified = min(specified, max) - } - - if self.style.get_position().box_sizing == BoxSizing::BorderBox { - specified = max(Au(0), specified - border_padding); - } - } - - IntrinsicISizesContribution { - content_intrinsic_sizes: IntrinsicISizes { - minimum_inline_size: specified, - preferred_inline_size: specified, - }, - surrounding_size: border_padding + margin, - } - } - - /// intrinsic width of this replaced element. - #[inline] - pub fn intrinsic_width(&self) -> Au { - match self.specific { - SpecificFragmentInfo::Image(ref info) => { - if let Some(ref data) = info.metadata { - Au::from_px(data.width as i32) - } else { - Au(0) - } - }, - SpecificFragmentInfo::Media(ref info) => { - if let Some((_, width, _)) = info.current_frame { - Au::from_px(width as i32) - } else { - Au(0) - } - }, - SpecificFragmentInfo::Canvas(ref info) => info.dom_width, - SpecificFragmentInfo::Svg(ref info) => info.dom_width, - // Note: Currently for replaced element with no intrinsic size, - // this function simply returns the default object size. As long as - // these elements do not have intrinsic aspect ratio this should be - // sufficient, but we may need to investigate if this is enough for - // use cases like SVG. - SpecificFragmentInfo::Iframe(_) => Au::from_px(DEFAULT_REPLACED_WIDTH), - _ => panic!("Trying to get intrinsic width on non-replaced element!"), - } - } - - /// intrinsic width of this replaced element. - #[inline] - pub fn intrinsic_height(&self) -> Au { - match self.specific { - SpecificFragmentInfo::Image(ref info) => { - if let Some(ref data) = info.metadata { - Au::from_px(data.height as i32) - } else { - Au(0) - } - }, - SpecificFragmentInfo::Media(ref info) => { - if let Some((_, _, height)) = info.current_frame { - Au::from_px(height as i32) - } else { - Au(0) - } - }, - SpecificFragmentInfo::Canvas(ref info) => info.dom_height, - SpecificFragmentInfo::Svg(ref info) => info.dom_height, - SpecificFragmentInfo::Iframe(_) => Au::from_px(DEFAULT_REPLACED_HEIGHT), - _ => panic!("Trying to get intrinsic height on non-replaced element!"), - } - } - - /// Whether this replace element has intrinsic aspect ratio. - pub fn has_intrinsic_ratio(&self) -> bool { - match self.specific { - SpecificFragmentInfo::Image(_) | - SpecificFragmentInfo::Canvas(_) | - SpecificFragmentInfo::Media(_) | - // TODO(stshine): According to the SVG spec, whether a SVG element has intrinsic - // aspect ratio is determined by the `preserveAspectRatio` attribute. Since for - // now SVG is far from implemented, we simply choose the default behavior that - // the intrinsic aspect ratio is preserved. - // https://svgwg.org/svg2-draft/coords.html#PreserveAspectRatioAttribute - SpecificFragmentInfo::Svg(_) => - self.intrinsic_width() != Au(0) && self.intrinsic_height() != Au(0), - _ => false - } - } - - /// CSS 2.1 § 10.3.2 & 10.6.2 Calculate the used width and height of a replaced element. - /// When a parameter is `None` it means the specified size in certain direction - /// is unconstrained. The inline containing size can also be `None` since this - /// method is also used for calculating intrinsic inline size contribution. - pub fn calculate_replaced_sizes( - &self, - containing_inline_size: Option<Au>, - containing_block_size: Option<Au>, - ) -> (Au, Au) { - let (intrinsic_inline_size, intrinsic_block_size) = if self.style.writing_mode.is_vertical() - { - (self.intrinsic_height(), self.intrinsic_width()) - } else { - (self.intrinsic_width(), self.intrinsic_height()) - }; - - // Make sure the size we used here is for content box since they may be - // transferred by the intrinsic aspect ratio. - let inline_size = style_length(self.style.content_inline_size(), containing_inline_size) - .map(|x| x - self.box_sizing_boundary(Direction::Inline)); - let block_size = style_length(self.style.content_block_size(), containing_block_size) - .map(|x| x - self.box_sizing_boundary(Direction::Block)); - let inline_constraint = self.size_constraint(containing_inline_size, Direction::Inline); - let block_constraint = self.size_constraint(containing_block_size, Direction::Block); - - // https://drafts.csswg.org/css-images-3/#default-sizing - match (inline_size, block_size) { - // If the specified size is a definite width and height, the concrete - // object size is given that width and height. - (MaybeAuto::Specified(inline_size), MaybeAuto::Specified(block_size)) => ( - inline_constraint.clamp(inline_size), - block_constraint.clamp(block_size), - ), - - // If the specified size is only a width or height (but not both) - // then the concrete object size is given that specified width or - // height. The other dimension is calculated as follows: - // - // If the object has an intrinsic aspect ratio, the missing dimension - // of the concrete object size is calculated using the intrinsic - // aspect ratio and the present dimension. - // - // Otherwise, if the missing dimension is present in the object’s intrinsic - // dimensions, the missing dimension is taken from the object’s intrinsic - // dimensions. Otherwise it is taken from the default object size. - (MaybeAuto::Specified(inline_size), MaybeAuto::Auto) => { - let inline_size = inline_constraint.clamp(inline_size); - let block_size = if self.has_intrinsic_ratio() { - // Note: We can not precompute the ratio and store it as a float, because - // doing so may result one pixel difference in calculation for certain - // images, thus make some tests fail. - Au::new( - (inline_size.0 as i64 * intrinsic_block_size.0 as i64 / - intrinsic_inline_size.0 as i64) as i32, - ) - } else { - intrinsic_block_size - }; - (inline_size, block_constraint.clamp(block_size)) - }, - (MaybeAuto::Auto, MaybeAuto::Specified(block_size)) => { - let block_size = block_constraint.clamp(block_size); - let inline_size = if self.has_intrinsic_ratio() { - Au::new( - (block_size.0 as i64 * intrinsic_inline_size.0 as i64 / - intrinsic_block_size.0 as i64) as i32, - ) - } else { - intrinsic_inline_size - }; - (inline_constraint.clamp(inline_size), block_size) - }, - // https://drafts.csswg.org/css2/visudet.html#min-max-widths - (MaybeAuto::Auto, MaybeAuto::Auto) => { - if self.has_intrinsic_ratio() { - // This approch follows the spirit of cover and contain constraint. - // https://drafts.csswg.org/css-images-3/#cover-contain - - // First, create two rectangles that keep aspect ratio while may be clamped - // by the contraints; - let first_isize = inline_constraint.clamp(intrinsic_inline_size); - let first_bsize = Au::new( - (first_isize.0 as i64 * intrinsic_block_size.0 as i64 / - intrinsic_inline_size.0 as i64) as i32, - ); - let second_bsize = block_constraint.clamp(intrinsic_block_size); - let second_isize = Au::new( - (second_bsize.0 as i64 * intrinsic_inline_size.0 as i64 / - intrinsic_block_size.0 as i64) as i32, - ); - let (inline_size, block_size) = match ( - first_isize.cmp(&intrinsic_inline_size), - second_isize.cmp(&intrinsic_inline_size), - ) { - (Ordering::Equal, Ordering::Equal) => (first_isize, first_bsize), - // When only one rectangle is clamped, use it; - (Ordering::Equal, _) => (second_isize, second_bsize), - (_, Ordering::Equal) => (first_isize, first_bsize), - // When both rectangles grow (smaller than min sizes), - // Choose the larger one; - (Ordering::Greater, Ordering::Greater) => { - if first_isize > second_isize { - (first_isize, first_bsize) - } else { - (second_isize, second_bsize) - } - }, - // When both rectangles shrink (larger than max sizes), - // Choose the smaller one; - (Ordering::Less, Ordering::Less) => { - if first_isize > second_isize { - (second_isize, second_bsize) - } else { - (first_isize, first_bsize) - } - }, - // It does not matter which we choose here, because both sizes - // will be clamped to constraint; - (Ordering::Less, Ordering::Greater) | - (Ordering::Greater, Ordering::Less) => (first_isize, first_bsize), - }; - // Clamp the result and we are done :-) - ( - inline_constraint.clamp(inline_size), - block_constraint.clamp(block_size), - ) - } else { - ( - inline_constraint.clamp(intrinsic_inline_size), - block_constraint.clamp(intrinsic_block_size), - ) - } - }, - } - } - - /// Return a size constraint that can be used the clamp size in given direction. - /// To take `box-sizing: border-box` into account, the `border_padding` field - /// must be initialized first. - /// - /// TODO(stshine): Maybe there is a more convenient way. - pub fn size_constraint( - &self, - containing_size: Option<Au>, - direction: Direction, - ) -> SizeConstraint { - let (style_min_size, style_max_size) = match direction { - Direction::Inline => (self.style.min_inline_size(), self.style.max_inline_size()), - Direction::Block => (self.style.min_block_size(), self.style.max_block_size()), - }; - - let border = if self.style().get_position().box_sizing == BoxSizing::BorderBox { - Some(self.border_padding.start_end(direction)) - } else { - None - }; - - SizeConstraint::new(containing_size, style_min_size, style_max_size, border) - } - - /// Returns a guess as to the distances from the margin edge of this fragment to its content - /// in the inline direction. This will generally be correct unless percentages are involved. - /// - /// This is used for the float placement speculation logic. - pub fn guess_inline_content_edge_offsets(&self) -> SpeculatedInlineContentEdgeOffsets { - let logical_margin = self.style.logical_margin(); - let logical_padding = self.style.logical_padding(); - let border_width = self.border_width(); - SpeculatedInlineContentEdgeOffsets { - start: MaybeAuto::from_style(logical_margin.inline_start, Au(0)).specified_or_zero() + - logical_padding.inline_start.to_used_value(Au(0)) + - border_width.inline_start, - end: MaybeAuto::from_style(logical_margin.inline_end, Au(0)).specified_or_zero() + - logical_padding.inline_end.to_used_value(Au(0)) + - border_width.inline_end, - } + node_address == self.node } /// Returns the sum of the inline-sizes of all the borders of this fragment. Note that this /// can be expensive to compute, so if possible use the `border_padding` field instead. #[inline] pub fn border_width(&self) -> LogicalMargin<Au> { - let style_border_width = self.style().logical_border_width(); - - // NOTE: We can have nodes with different writing mode inside - // the inline fragment context, so we need to overwrite the - // writing mode to compute the child logical sizes. - let writing_mode = self.style.writing_mode; - let context_border = match self.inline_context { - None => LogicalMargin::zero(writing_mode), - Some(ref inline_fragment_context) => inline_fragment_context.nodes.iter().fold( - style_border_width, - |accumulator, node| { - let mut this_border_width = - node.style.border_width_for_writing_mode(writing_mode); - if !node - .flags - .contains(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT) - { - this_border_width.inline_start = Au(0) - } - if !node - .flags - .contains(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT) - { - this_border_width.inline_end = Au(0) - } - accumulator + this_border_width - }, - ), - }; - style_border_width + context_border - } - - /// Returns the border width in given direction if this fragment has property - /// 'box-sizing: border-box'. The `border_padding` field must have been initialized. - pub fn box_sizing_boundary(&self, direction: Direction) -> Au { - match (self.style().get_position().box_sizing, direction) { - (BoxSizing::BorderBox, Direction::Inline) => self.border_padding.inline_start_end(), - (BoxSizing::BorderBox, Direction::Block) => self.border_padding.block_start_end(), - _ => Au(0), - } - } - - /// Computes the margins in the inline direction from the containing block inline-size and the - /// style. After this call, the inline direction of the `margin` field will be correct. - /// - /// Do not use this method if the inline direction margins are to be computed some other way - /// (for example, via constraint solving for blocks). - pub fn compute_inline_direction_margins(&mut self, containing_block_inline_size: Au) { - match self.specific { - SpecificFragmentInfo::Table | - SpecificFragmentInfo::TableCell | - SpecificFragmentInfo::TableRow | - SpecificFragmentInfo::TableColumn(_) | - SpecificFragmentInfo::InlineAbsoluteHypothetical(_) => { - self.margin.inline_start = Au(0); - self.margin.inline_end = Au(0); - return; - }, - _ => { - let margin = self.style().logical_margin(); - self.margin.inline_start = - MaybeAuto::from_style(margin.inline_start, containing_block_inline_size) - .specified_or_zero(); - self.margin.inline_end = - MaybeAuto::from_style(margin.inline_end, containing_block_inline_size) - .specified_or_zero(); - }, - } - - if let Some(ref inline_context) = self.inline_context { - for node in &inline_context.nodes { - let margin = node.style.logical_margin(); - let this_inline_start_margin = if !node - .flags - .contains(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT) - { - Au(0) - } else { - MaybeAuto::from_style(margin.inline_start, containing_block_inline_size) - .specified_or_zero() - }; - let this_inline_end_margin = if !node - .flags - .contains(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT) - { - Au(0) - } else { - MaybeAuto::from_style(margin.inline_end, containing_block_inline_size) - .specified_or_zero() - }; - - self.margin.inline_start += this_inline_start_margin; - self.margin.inline_end += this_inline_end_margin; - } - } - } - - /// Computes the margins in the block direction from the containing block inline-size and the - /// style. After this call, the block direction of the `margin` field will be correct. - /// - /// Do not use this method if the block direction margins are to be computed some other way - /// (for example, via constraint solving for absolutely-positioned flows). - pub fn compute_block_direction_margins(&mut self, containing_block_inline_size: Au) { - match self.specific { - SpecificFragmentInfo::Table | - SpecificFragmentInfo::TableCell | - SpecificFragmentInfo::TableRow | - SpecificFragmentInfo::TableColumn(_) => { - self.margin.block_start = Au(0); - self.margin.block_end = Au(0) - }, - _ => { - // NB: Percentages are relative to containing block inline-size (not block-size) - // per CSS 2.1. - let margin = self.style().logical_margin(); - self.margin.block_start = - MaybeAuto::from_style(margin.block_start, containing_block_inline_size) - .specified_or_zero(); - self.margin.block_end = - MaybeAuto::from_style(margin.block_end, containing_block_inline_size) - .specified_or_zero(); - }, - } - } - - /// Computes the border and padding in both inline and block directions from the containing - /// block inline-size and the style. After this call, the `border_padding` field will be - /// correct. - pub fn compute_border_and_padding(&mut self, containing_block_inline_size: Au) { - // Compute border. - let border = match self.style.get_inherited_table().border_collapse { - BorderCollapse::Separate => self.border_width(), - BorderCollapse::Collapse => LogicalMargin::zero(self.style.writing_mode), - }; - - // Compute padding from the fragment's style. - let padding_from_style = match self.specific { - SpecificFragmentInfo::TableColumn(_) | - SpecificFragmentInfo::TableRow | - SpecificFragmentInfo::TableWrapper => LogicalMargin::zero(self.style.writing_mode), - _ => model::padding_from_style( - self.style(), - containing_block_inline_size, - self.style().writing_mode, - ), - }; - - // Compute padding from the inline fragment context. - let padding_from_inline_fragment_context = match (&self.specific, &self.inline_context) { - (_, &None) | - (&SpecificFragmentInfo::TableColumn(_), _) | - (&SpecificFragmentInfo::TableRow, _) | - (&SpecificFragmentInfo::TableWrapper, _) => { - LogicalMargin::zero(self.style.writing_mode) - }, - (_, &Some(ref inline_fragment_context)) => { - let writing_mode = self.style.writing_mode; - let zero_padding = LogicalMargin::zero(writing_mode); - inline_fragment_context - .nodes - .iter() - .fold(zero_padding, |accumulator, node| { - let mut padding = - model::padding_from_style(&*node.style, Au(0), writing_mode); - if !node - .flags - .contains(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT) - { - padding.inline_start = Au(0) - } - if !node - .flags - .contains(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT) - { - padding.inline_end = Au(0) - } - accumulator + padding - }) - }, - }; - - self.border_padding = border + padding_from_style + padding_from_inline_fragment_context - } - - // Return offset from original position because of `position: relative`. - pub fn relative_position(&self, containing_block_size: &LogicalSize<Au>) -> LogicalSize<Au> { - fn from_style(style: &ComputedValues, container_size: &LogicalSize<Au>) -> LogicalSize<Au> { - let offsets = style.logical_position(); - let offset_i = if offsets.inline_start != LengthPercentageOrAuto::Auto { - MaybeAuto::from_style(offsets.inline_start, container_size.inline) - .specified_or_zero() - } else { - -MaybeAuto::from_style(offsets.inline_end, container_size.inline) - .specified_or_zero() - }; - let offset_b = if offsets.block_start != LengthPercentageOrAuto::Auto { - MaybeAuto::from_style(offsets.block_start, container_size.block).specified_or_zero() - } else { - -MaybeAuto::from_style(offsets.block_end, container_size.block).specified_or_zero() - }; - LogicalSize::new(style.writing_mode, offset_i, offset_b) - } - - // Go over the ancestor fragments and add all relative offsets (if any). - let mut rel_pos = if self.style().get_box().position == Position::Relative { - from_style(self.style(), containing_block_size) - } else { - LogicalSize::zero(self.style.writing_mode) - }; - - if let Some(ref inline_fragment_context) = self.inline_context { - for node in &inline_fragment_context.nodes { - if node.style.get_box().position == Position::Relative { - rel_pos = rel_pos + from_style(&*node.style, containing_block_size); - } - } - } - - rel_pos - } - - /// Always inline for SCCP. - /// - /// FIXME(pcwalton): Just replace with the clear type from the style module for speed? - #[inline(always)] - pub fn clear(&self) -> Option<ClearType> { - let style = self.style(); - match style.get_box().clear { - Clear::None => None, - Clear::Left => Some(ClearType::Left), - Clear::Right => Some(ClearType::Right), - Clear::Both => Some(ClearType::Both), - } + self.style().logical_border_width() } #[inline(always)] @@ -1510,1767 +94,8 @@ impl Fragment { &*self.style } - #[inline(always)] - pub fn selected_style(&self) -> &ComputedValues { - &*self.selected_style - } - - pub fn white_space(&self) -> WhiteSpace { - self.style().get_inherited_text().white_space - } - - pub fn color(&self) -> Color { - self.style().get_inherited_text().color - } - - /// Returns the text decoration line of this fragment, according to the style of the nearest ancestor - /// element. - /// - /// NB: This may not be the actual text decoration line, because of the override rules specified in - /// CSS 2.1 § 16.3.1. Unfortunately, computing this properly doesn't really fit into Servo's - /// model. Therefore, this is a best lower bound approximation, but the end result may actually - /// have the various decoration flags turned on afterward. - pub fn text_decoration_line(&self) -> TextDecorationLine { - self.style().get_text().text_decoration_line - } - - /// Returns the inline-start offset from margin edge to content edge. - /// - /// FIXME(#2262, pcwalton): I think this method is pretty bogus, because it won't work for - /// inlines. - pub fn inline_start_offset(&self) -> Au { - match self.specific { - SpecificFragmentInfo::TableWrapper => self.margin.inline_start, - SpecificFragmentInfo::Table | - SpecificFragmentInfo::TableCell | - SpecificFragmentInfo::TableRow => self.border_padding.inline_start, - SpecificFragmentInfo::TableColumn(_) => Au(0), - _ => self.margin.inline_start + self.border_padding.inline_start, - } - } - - /// If this is a Column fragment, get the col span - /// - /// Panics for non-column fragments - pub fn column_span(&self) -> u32 { - match self.specific { - SpecificFragmentInfo::TableColumn(col_fragment) => max(col_fragment.span, 1), - _ => panic!("non-table-column fragment inside table column?!"), - } - } - - /// Returns true if this element can be split. This is true for text fragments, unless - /// `white-space: pre` or `white-space: nowrap` is set. - pub fn can_split(&self) -> bool { - self.is_scanned_text_fragment() && self.white_space().allow_wrap() - } - - /// Returns true if and only if this fragment is a generated content fragment. - pub fn is_unscanned_generated_content(&self) -> bool { - match self.specific { - SpecificFragmentInfo::GeneratedContent(ref content) => match **content { - GeneratedContentInfo::Empty => false, - _ => true, - }, - _ => false, - } - } - - /// Returns true if and only if this is a scanned text fragment. - pub fn is_scanned_text_fragment(&self) -> bool { - match self.specific { - SpecificFragmentInfo::ScannedText(..) => true, - _ => false, - } - } - - pub fn suppress_line_break_before(&self) -> bool { - match self.specific { - SpecificFragmentInfo::ScannedText(ref st) => st - .flags - .contains(ScannedTextFlags::SUPPRESS_LINE_BREAK_BEFORE), - _ => false, - } - } - - /// Computes the intrinsic inline-sizes of this fragment. - pub fn compute_intrinsic_inline_sizes(&mut self) -> IntrinsicISizesContribution { - let mut result = self.style_specified_intrinsic_inline_size(); - match self.specific { - SpecificFragmentInfo::Generic | - SpecificFragmentInfo::GeneratedContent(_) | - SpecificFragmentInfo::Table | - SpecificFragmentInfo::TableCell | - SpecificFragmentInfo::TableColumn(_) | - SpecificFragmentInfo::TableRow | - SpecificFragmentInfo::TableWrapper | - SpecificFragmentInfo::Multicol | - SpecificFragmentInfo::MulticolColumn | - SpecificFragmentInfo::InlineAbsoluteHypothetical(_) => {}, - SpecificFragmentInfo::InlineBlock(ref info) => { - let block_flow = info.flow_ref.as_block(); - result.union_block(&block_flow.base.intrinsic_inline_sizes) - }, - SpecificFragmentInfo::InlineAbsolute(ref info) => { - let block_flow = info.flow_ref.as_block(); - result.union_block(&block_flow.base.intrinsic_inline_sizes) - }, - SpecificFragmentInfo::Image(_) | - SpecificFragmentInfo::Media(_) | - SpecificFragmentInfo::Canvas(_) | - SpecificFragmentInfo::Iframe(_) | - SpecificFragmentInfo::Svg(_) => { - let inline_size = match self.style.content_inline_size() { - Size::Auto => None, - Size::LengthPercentage(ref lp) => lp.maybe_to_used_value(None), - }; - - let mut inline_size = inline_size.unwrap_or_else(|| { - // We have to initialize the `border_padding` field first to make - // the size constraints work properly. - // TODO(stshine): Find a cleaner way to do this. - let padding = self.style.logical_padding(); - self.border_padding.inline_start = padding.inline_start.to_used_value(Au(0)); - self.border_padding.inline_end = padding.inline_end.to_used_value(Au(0)); - self.border_padding.block_start = padding.block_start.to_used_value(Au(0)); - self.border_padding.block_end = padding.block_end.to_used_value(Au(0)); - let border = self.border_width(); - self.border_padding.inline_start += border.inline_start; - self.border_padding.inline_end += border.inline_end; - self.border_padding.block_start += border.block_start; - self.border_padding.block_end += border.block_end; - let (result_inline, _) = self.calculate_replaced_sizes(None, None); - result_inline - }); - - let size_constraint = self.size_constraint(None, Direction::Inline); - inline_size = size_constraint.clamp(inline_size); - - result.union_block(&IntrinsicISizes { - minimum_inline_size: inline_size, - preferred_inline_size: inline_size, - }); - }, - - SpecificFragmentInfo::TruncatedFragment(ref t) if t.text_info.is_some() => { - let text_fragment_info = t.text_info.as_ref().unwrap(); - handle_text(text_fragment_info, self, &mut result) - }, - SpecificFragmentInfo::ScannedText(ref text_fragment_info) => { - handle_text(text_fragment_info, self, &mut result) - }, - - SpecificFragmentInfo::TruncatedFragment(_) => { - return IntrinsicISizesContribution::new() - }, - - SpecificFragmentInfo::UnscannedText(..) => { - panic!("Unscanned text fragments should have been scanned by now!") - }, - }; - - fn handle_text( - text_fragment_info: &ScannedTextFragmentInfo, - self_: &Fragment, - result: &mut IntrinsicISizesContribution, - ) { - let range = &text_fragment_info.range; - - // See http://dev.w3.org/csswg/css-sizing/#max-content-inline-size. - // TODO: Account for soft wrap opportunities. - let max_line_inline_size = text_fragment_info - .run - .metrics_for_range(range) - .advance_width; - - let min_line_inline_size = if self_.white_space().allow_wrap() { - text_fragment_info.run.min_width_for_range(range) - } else { - max_line_inline_size - }; - - result.union_block(&IntrinsicISizes { - minimum_inline_size: min_line_inline_size, - preferred_inline_size: max_line_inline_size, - }) - } - - // Take borders and padding for parent inline fragments into account. - let writing_mode = self.style.writing_mode; - if let Some(ref context) = self.inline_context { - for node in &context.nodes { - let mut border_width = node.style.logical_border_width(); - let mut padding = model::padding_from_style(&*node.style, Au(0), writing_mode); - let mut margin = model::specified_margin_from_style(&*node.style, writing_mode); - if !node - .flags - .contains(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT) - { - border_width.inline_start = Au(0); - padding.inline_start = Au(0); - margin.inline_start = Au(0); - } - if !node - .flags - .contains(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT) - { - border_width.inline_end = Au(0); - padding.inline_end = Au(0); - margin.inline_end = Au(0); - } - - result.surrounding_size = result.surrounding_size + - border_width.inline_start_end() + - padding.inline_start_end() + - margin.inline_start_end(); - } - } - - result - } - - /// Returns the narrowest inline-size that the first splittable part of this fragment could - /// possibly be split to. (In most cases, this returns the inline-size of the first word in - /// this fragment.) - pub fn minimum_splittable_inline_size(&self) -> Au { - match self.specific { - SpecificFragmentInfo::TruncatedFragment(ref t) if t.text_info.is_some() => { - let text = t.text_info.as_ref().unwrap(); - text.run.minimum_splittable_inline_size(&text.range) - }, - SpecificFragmentInfo::ScannedText(ref text) => { - text.run.minimum_splittable_inline_size(&text.range) - }, - _ => Au(0), - } - } - - /// Returns the dimensions of the content box. - /// - /// This is marked `#[inline]` because it is frequently called when only one or two of the - /// values are needed and that will save computation. - #[inline] - pub fn content_box(&self) -> LogicalRect<Au> { - self.border_box - self.border_padding - } - - /// Attempts to find the split positions of a text fragment so that its inline-size is no more - /// than `max_inline_size`. - /// - /// A return value of `None` indicates that the fragment could not be split. Otherwise the - /// information pertaining to the split is returned. The inline-start and inline-end split - /// information are both optional due to the possibility of them being whitespace. - pub fn calculate_split_position( - &self, - max_inline_size: Au, - starts_line: bool, - ) -> Option<SplitResult> { - let text_fragment_info = match self.specific { - SpecificFragmentInfo::ScannedText(ref text_fragment_info) => text_fragment_info, - _ => return None, - }; - - let mut flags = SplitOptions::empty(); - if starts_line { - flags.insert(SplitOptions::STARTS_LINE); - if self.style().get_inherited_text().overflow_wrap == OverflowWrap::BreakWord { - flags.insert(SplitOptions::RETRY_AT_CHARACTER_BOUNDARIES) - } - } - - match self.style().get_inherited_text().word_break { - WordBreak::Normal | WordBreak::KeepAll => { - // Break at normal word boundaries. keep-all forbids soft wrap opportunities. - let natural_word_breaking_strategy = text_fragment_info - .run - .natural_word_slices_in_range(&text_fragment_info.range); - self.calculate_split_position_using_breaking_strategy( - natural_word_breaking_strategy, - max_inline_size, - flags, - ) - }, - WordBreak::BreakAll => { - // Break at character boundaries. - let character_breaking_strategy = text_fragment_info - .run - .character_slices_in_range(&text_fragment_info.range); - flags.remove(SplitOptions::RETRY_AT_CHARACTER_BOUNDARIES); - self.calculate_split_position_using_breaking_strategy( - character_breaking_strategy, - max_inline_size, - flags, - ) - }, - } - } - - /// Does this fragment start on a glyph run boundary? - pub fn is_on_glyph_run_boundary(&self) -> bool { - let text_fragment_info = match self.specific { - SpecificFragmentInfo::ScannedText(ref text_fragment_info) => text_fragment_info, - _ => return true, - }; - text_fragment_info - .run - .on_glyph_run_boundary(text_fragment_info.range.begin()) - } - - /// Truncates this fragment to the given `max_inline_size`, using a character-based breaking - /// strategy. The resulting fragment will have `SpecificFragmentInfo::TruncatedFragment`, - /// preserving the original fragment for use in incremental reflow. - /// - /// This function will panic if self is already truncated. - pub fn truncate_to_inline_size(self, max_inline_size: Au) -> Fragment { - if let SpecificFragmentInfo::TruncatedFragment(_) = self.specific { - panic!("Cannot truncate an already truncated fragment"); - } - let info = self.calculate_truncate_to_inline_size(max_inline_size); - let (size, text_info) = match info { - Some(TruncationResult { - split: SplitInfo { inline_size, range }, - text_run, - }) => { - let size = LogicalSize::new( - self.style.writing_mode, - inline_size, - self.border_box.size.block, - ); - // Preserve the insertion point if it is in this fragment's range or it is at line end. - let (flags, insertion_point) = match self.specific { - SpecificFragmentInfo::ScannedText(ref info) => match info.insertion_point { - Some(index) if range.contains(index) => (info.flags, info.insertion_point), - Some(index) - if index == ByteIndex(text_run.text.chars().count() as isize - 1) && - index == range.end() => - { - (info.flags, info.insertion_point) - }, - _ => (info.flags, None), - }, - _ => (ScannedTextFlags::empty(), None), - }; - let text_info = - ScannedTextFragmentInfo::new(text_run, range, size, insertion_point, flags); - (size, Some(text_info)) - }, - None => (LogicalSize::zero(self.style.writing_mode), None), - }; - let mut result = self.transform(size, SpecificFragmentInfo::Generic); - result.specific = - SpecificFragmentInfo::TruncatedFragment(Box::new(TruncatedFragmentInfo { - text_info: text_info, - full: self, - })); - result - } - - /// Truncates this fragment to the given `max_inline_size`, using a character-based breaking - /// strategy. If no characters could fit, returns `None`. - fn calculate_truncate_to_inline_size(&self, max_inline_size: Au) -> Option<TruncationResult> { - let text_fragment_info = - if let SpecificFragmentInfo::ScannedText(ref text_fragment_info) = self.specific { - text_fragment_info - } else { - return None; - }; - - let character_breaking_strategy = text_fragment_info - .run - .character_slices_in_range(&text_fragment_info.range); - - let split_info = self.calculate_split_position_using_breaking_strategy( - character_breaking_strategy, - max_inline_size, - SplitOptions::empty(), - )?; - - let split = split_info.inline_start?; - Some(TruncationResult { - split: split, - text_run: split_info.text_run.clone(), - }) - } - - /// A helper method that uses the breaking strategy described by `slice_iterator` (at present, - /// either natural word breaking or character breaking) to split this fragment. - fn calculate_split_position_using_breaking_strategy<'a, I>( - &self, - slice_iterator: I, - max_inline_size: Au, - flags: SplitOptions, - ) -> Option<SplitResult> - where - I: Iterator<Item = TextRunSlice<'a>>, - { - let text_fragment_info = match self.specific { - SpecificFragmentInfo::ScannedText(ref text_fragment_info) => text_fragment_info, - _ => return None, - }; - - let mut remaining_inline_size = max_inline_size - self.border_padding.inline_start_end(); - let mut inline_start_range = Range::new(text_fragment_info.range.begin(), ByteIndex(0)); - let mut inline_end_range = None; - let mut overflowing = false; - - debug!( - "calculate_split_position_using_breaking_strategy: splitting text fragment \ - (strlen={}, range={:?}, max_inline_size={:?})", - text_fragment_info.run.text.len(), - text_fragment_info.range, - max_inline_size - ); - - for slice in slice_iterator { - debug!( - "calculate_split_position_using_breaking_strategy: considering slice \ - (offset={:?}, slice range={:?}, remaining_inline_size={:?})", - slice.offset, slice.range, remaining_inline_size - ); - - // Use the `remaining_inline_size` to find a split point if possible. If not, go around - // the loop again with the next slice. - let metrics = text_fragment_info - .run - .metrics_for_slice(slice.glyphs, &slice.range); - let advance = metrics.advance_width; - - // Have we found the split point? - if advance <= remaining_inline_size || slice.glyphs.is_whitespace() { - // Keep going; we haven't found the split point yet. - debug!("calculate_split_position_using_breaking_strategy: enlarging span"); - remaining_inline_size = remaining_inline_size - advance; - inline_start_range.extend_by(slice.range.length()); - continue; - } - - // The advance is more than the remaining inline-size, so split here. First, check to - // see if we're going to overflow the line. If so, perform a best-effort split. - let mut remaining_range = slice.text_run_range(); - let split_is_empty = inline_start_range.is_empty() && - !(self.requires_line_break_afterward_if_wrapping_on_newlines() && - !self.white_space().allow_wrap()); - if split_is_empty { - // We're going to overflow the line. - overflowing = true; - inline_start_range = slice.text_run_range(); - remaining_range = Range::new(slice.text_run_range().end(), ByteIndex(0)); - remaining_range.extend_to(text_fragment_info.range.end()); - } - - // Check to see if we need to create an inline-end chunk. - let slice_begin = remaining_range.begin(); - if slice_begin < text_fragment_info.range.end() { - // There still some things left over at the end of the line, so create the - // inline-end chunk. - let mut inline_end = remaining_range; - inline_end.extend_to(text_fragment_info.range.end()); - inline_end_range = Some(inline_end); - debug!( - "calculate_split_position: splitting remainder with inline-end range={:?}", - inline_end - ); - } - - // If we failed to find a suitable split point, we're on the verge of overflowing the - // line. - if split_is_empty || overflowing { - // If we've been instructed to retry at character boundaries (probably via - // `overflow-wrap: break-word`), do so. - if flags.contains(SplitOptions::RETRY_AT_CHARACTER_BOUNDARIES) { - let character_breaking_strategy = text_fragment_info - .run - .character_slices_in_range(&text_fragment_info.range); - let mut flags = flags; - flags.remove(SplitOptions::RETRY_AT_CHARACTER_BOUNDARIES); - return self.calculate_split_position_using_breaking_strategy( - character_breaking_strategy, - max_inline_size, - flags, - ); - } - - // We aren't at the start of the line, so don't overflow. Let inline layout wrap to - // the next line instead. - if !flags.contains(SplitOptions::STARTS_LINE) { - return None; - } - } - - break; - } - - let split_is_empty = inline_start_range.is_empty() && - !self.requires_line_break_afterward_if_wrapping_on_newlines(); - let inline_start = if !split_is_empty { - Some(SplitInfo::new(inline_start_range, &**text_fragment_info)) - } else { - None - }; - let inline_end = inline_end_range - .map(|inline_end_range| SplitInfo::new(inline_end_range, &**text_fragment_info)); - - Some(SplitResult { - inline_start: inline_start, - inline_end: inline_end, - text_run: text_fragment_info.run.clone(), - }) - } - - /// The opposite of `calculate_split_position_using_breaking_strategy`: merges this fragment - /// with the next one. - pub fn merge_with(&mut self, next_fragment: Fragment) { - match (&mut self.specific, &next_fragment.specific) { - ( - &mut SpecificFragmentInfo::ScannedText(ref mut this_info), - &SpecificFragmentInfo::ScannedText(ref other_info), - ) => { - debug_assert!(Arc::ptr_eq(&this_info.run, &other_info.run)); - this_info.range_end_including_stripped_whitespace = - other_info.range_end_including_stripped_whitespace; - if other_info.requires_line_break_afterward_if_wrapping_on_newlines() { - this_info.flags.insert( - ScannedTextFlags::REQUIRES_LINE_BREAK_AFTERWARD_IF_WRAPPING_ON_NEWLINES, - ); - } - if other_info.insertion_point.is_some() { - this_info.insertion_point = other_info.insertion_point; - } - self.border_padding.inline_end = next_fragment.border_padding.inline_end; - self.margin.inline_end = next_fragment.margin.inline_end; - }, - _ => panic!("Can only merge two scanned-text fragments!"), - } - self.reset_text_range_and_inline_size(); - self.meld_with_next_inline_fragment(&next_fragment); - } - - /// Restore any whitespace that was stripped from a text fragment, and recompute inline metrics - /// if necessary. - pub fn reset_text_range_and_inline_size(&mut self) { - if let SpecificFragmentInfo::ScannedText(ref mut info) = self.specific { - if info.run.extra_word_spacing != Au(0) { - Arc::make_mut(&mut info.run).extra_word_spacing = Au(0); - } - - // FIXME (mbrubeck): Do we need to restore leading too? - let range_end = info.range_end_including_stripped_whitespace; - if info.range.end() == range_end { - return; - } - info.range.extend_to(range_end); - info.content_size.inline = info.run.metrics_for_range(&info.range).advance_width; - self.border_box.size.inline = - info.content_size.inline + self.border_padding.inline_start_end(); - } - } - - /// Assigns replaced inline-size, padding, and margins for this fragment only if it is replaced - /// content per CSS 2.1 § 10.3.2. - pub fn assign_replaced_inline_size_if_necessary( - &mut self, - container_inline_size: Au, - container_block_size: Option<Au>, - ) { - match self.specific { - SpecificFragmentInfo::TruncatedFragment(ref t) if t.text_info.is_none() => return, - SpecificFragmentInfo::Generic | - SpecificFragmentInfo::GeneratedContent(_) | - SpecificFragmentInfo::Table | - SpecificFragmentInfo::TableCell | - SpecificFragmentInfo::TableRow | - SpecificFragmentInfo::TableWrapper | - SpecificFragmentInfo::Multicol | - SpecificFragmentInfo::MulticolColumn => return, - SpecificFragmentInfo::TableColumn(_) => { - panic!("Table column fragments do not have inline size") - }, - SpecificFragmentInfo::UnscannedText(_) => { - panic!("Unscanned text fragments should have been scanned by now!") - }, - SpecificFragmentInfo::Canvas(_) | - SpecificFragmentInfo::Image(_) | - SpecificFragmentInfo::Media(_) | - SpecificFragmentInfo::Iframe(_) | - SpecificFragmentInfo::InlineBlock(_) | - SpecificFragmentInfo::InlineAbsoluteHypothetical(_) | - SpecificFragmentInfo::InlineAbsolute(_) | - SpecificFragmentInfo::ScannedText(_) | - SpecificFragmentInfo::TruncatedFragment(_) | - SpecificFragmentInfo::Svg(_) => {}, - }; - - match self.specific { - // Inline blocks - SpecificFragmentInfo::InlineAbsoluteHypothetical(ref mut info) => { - let block_flow = FlowRef::deref_mut(&mut info.flow_ref).as_mut_block(); - block_flow.base.position.size.inline = - block_flow.base.intrinsic_inline_sizes.preferred_inline_size; - - // This is a hypothetical box, so it takes up no space. - self.border_box.size.inline = Au(0); - }, - SpecificFragmentInfo::InlineBlock(ref mut info) => { - let block_flow = FlowRef::deref_mut(&mut info.flow_ref).as_mut_block(); - self.border_box.size.inline = max( - block_flow.base.intrinsic_inline_sizes.minimum_inline_size, - block_flow.base.intrinsic_inline_sizes.preferred_inline_size, - ); - block_flow.base.block_container_inline_size = self.border_box.size.inline; - block_flow.base.block_container_writing_mode = self.style.writing_mode; - }, - SpecificFragmentInfo::InlineAbsolute(ref mut info) => { - let block_flow = FlowRef::deref_mut(&mut info.flow_ref).as_mut_block(); - self.border_box.size.inline = max( - block_flow.base.intrinsic_inline_sizes.minimum_inline_size, - block_flow.base.intrinsic_inline_sizes.preferred_inline_size, - ); - block_flow.base.block_container_inline_size = self.border_box.size.inline; - block_flow.base.block_container_writing_mode = self.style.writing_mode; - }, - - // Text - SpecificFragmentInfo::TruncatedFragment(ref t) if t.text_info.is_some() => { - let info = t.text_info.as_ref().unwrap(); - // Scanned text fragments will have already had their content inline-sizes assigned - // by this point. - self.border_box.size.inline = - info.content_size.inline + self.border_padding.inline_start_end(); - }, - SpecificFragmentInfo::ScannedText(ref info) => { - // Scanned text fragments will have already had their content inline-sizes assigned - // by this point. - self.border_box.size.inline = - info.content_size.inline + self.border_padding.inline_start_end(); - }, - - // Replaced elements - _ if self.is_replaced() => { - let (inline_size, block_size) = self - .calculate_replaced_sizes(Some(container_inline_size), container_block_size); - self.border_box.size.inline = inline_size + self.border_padding.inline_start_end(); - self.border_box.size.block = block_size + self.border_padding.block_start_end(); - }, - - ref unhandled @ _ => { - panic!("this case should have been handled above: {:?}", unhandled) - }, - } - } - - /// Assign block-size for this fragment if it is replaced content. The inline-size must have - /// been assigned first. - /// - /// Ideally, this should follow CSS 2.1 § 10.6.2. - pub fn assign_replaced_block_size_if_necessary(&mut self) { - match self.specific { - SpecificFragmentInfo::TruncatedFragment(ref t) if t.text_info.is_none() => return, - SpecificFragmentInfo::Generic | - SpecificFragmentInfo::GeneratedContent(_) | - SpecificFragmentInfo::Table | - SpecificFragmentInfo::TableCell | - SpecificFragmentInfo::TableRow | - SpecificFragmentInfo::TableWrapper | - SpecificFragmentInfo::Multicol | - SpecificFragmentInfo::MulticolColumn => return, - SpecificFragmentInfo::TableColumn(_) => { - panic!("Table column fragments do not have block size") - }, - SpecificFragmentInfo::UnscannedText(_) => { - panic!("Unscanned text fragments should have been scanned by now!") - }, - SpecificFragmentInfo::Canvas(_) | - SpecificFragmentInfo::Iframe(_) | - SpecificFragmentInfo::Image(_) | - SpecificFragmentInfo::Media(_) | - SpecificFragmentInfo::InlineBlock(_) | - SpecificFragmentInfo::InlineAbsoluteHypothetical(_) | - SpecificFragmentInfo::InlineAbsolute(_) | - SpecificFragmentInfo::ScannedText(_) | - SpecificFragmentInfo::TruncatedFragment(_) | - SpecificFragmentInfo::Svg(_) => {}, - } - - match self.specific { - // Text - SpecificFragmentInfo::TruncatedFragment(ref t) if t.text_info.is_some() => { - let info = t.text_info.as_ref().unwrap(); - // Scanned text fragments' content block-sizes are calculated by the text run - // scanner during flow construction. - self.border_box.size.block = - info.content_size.block + self.border_padding.block_start_end(); - }, - SpecificFragmentInfo::ScannedText(ref info) => { - // Scanned text fragments' content block-sizes are calculated by the text run - // scanner during flow construction. - self.border_box.size.block = - info.content_size.block + self.border_padding.block_start_end(); - }, - - // Inline blocks - SpecificFragmentInfo::InlineBlock(ref mut info) => { - // Not the primary fragment, so we do not take the noncontent size into account. - let block_flow = FlowRef::deref_mut(&mut info.flow_ref).as_block(); - self.border_box.size.block = block_flow.base.position.size.block + - block_flow.fragment.margin.block_start_end() - }, - SpecificFragmentInfo::InlineAbsoluteHypothetical(ref mut info) => { - // Not the primary fragment, so we do not take the noncontent size into account. - let block_flow = FlowRef::deref_mut(&mut info.flow_ref).as_block(); - self.border_box.size.block = block_flow.base.position.size.block; - }, - SpecificFragmentInfo::InlineAbsolute(ref mut info) => { - // Not the primary fragment, so we do not take the noncontent size into account. - let block_flow = FlowRef::deref_mut(&mut info.flow_ref).as_block(); - self.border_box.size.block = block_flow.base.position.size.block + - block_flow.fragment.margin.block_start_end() - }, - - // Replaced elements - _ if self.is_replaced() => {}, - - ref unhandled @ _ => panic!("should have been handled above: {:?}", unhandled), - } - } - - /// Returns true if this fragment is replaced content. - pub fn is_replaced(&self) -> bool { - match self.specific { - SpecificFragmentInfo::Iframe(_) | - SpecificFragmentInfo::Canvas(_) | - SpecificFragmentInfo::Image(_) | - SpecificFragmentInfo::Media(_) | - SpecificFragmentInfo::Svg(_) => true, - _ => false, - } - } - - /// Returns true if this fragment is replaced content or an inline-block or false otherwise. - pub fn is_replaced_or_inline_block(&self) -> bool { - match self.specific { - SpecificFragmentInfo::InlineAbsoluteHypothetical(_) | - SpecificFragmentInfo::InlineBlock(_) => true, - _ => self.is_replaced(), - } - } - - /// Calculates block-size above baseline, depth below baseline, and ascent for this fragment - /// when used in an inline formatting context. See CSS 2.1 § 10.8.1. - /// - /// This does not take `vertical-align` into account. For that, use `aligned_inline_metrics()`. - fn content_inline_metrics(&self, layout_context: &LayoutContext) -> InlineMetrics { - // CSS 2.1 § 10.8: "The height of each inline-level box in the line box is - // calculated. For replaced elements, inline-block elements, and inline-table - // elements, this is the height of their margin box." - // - // FIXME(pcwalton): We have to handle `Generic` and `GeneratedContent` here to avoid - // crashing in a couple of `css21_dev/html4/content-` WPTs, but I don't see how those two - // fragment types should end up inside inlines. (In the case of `GeneratedContent`, those - // fragment types should have been resolved by now…) - let inline_metrics = match self.specific { - SpecificFragmentInfo::Canvas(_) | - SpecificFragmentInfo::Iframe(_) | - SpecificFragmentInfo::Image(_) | - SpecificFragmentInfo::Media(_) | - SpecificFragmentInfo::Svg(_) | - SpecificFragmentInfo::Generic | - SpecificFragmentInfo::GeneratedContent(_) => { - let ascent = self.border_box.size.block + self.margin.block_end; - InlineMetrics { - space_above_baseline: ascent + self.margin.block_start, - space_below_baseline: Au(0), - ascent: ascent, - } - }, - SpecificFragmentInfo::TruncatedFragment(ref t) if t.text_info.is_some() => { - let info = t.text_info.as_ref().unwrap(); - inline_metrics_of_text(info, self, layout_context) - }, - SpecificFragmentInfo::ScannedText(ref info) => { - inline_metrics_of_text(info, self, layout_context) - }, - SpecificFragmentInfo::InlineBlock(ref info) => { - inline_metrics_of_block(&info.flow_ref, &*self.style) - }, - SpecificFragmentInfo::InlineAbsoluteHypothetical(ref info) => { - inline_metrics_of_block(&info.flow_ref, &*self.style) - }, - SpecificFragmentInfo::TruncatedFragment(..) | - SpecificFragmentInfo::InlineAbsolute(_) => InlineMetrics::new(Au(0), Au(0), Au(0)), - SpecificFragmentInfo::Table | - SpecificFragmentInfo::TableCell | - SpecificFragmentInfo::TableColumn(_) | - SpecificFragmentInfo::TableRow | - SpecificFragmentInfo::TableWrapper | - SpecificFragmentInfo::Multicol | - SpecificFragmentInfo::MulticolColumn | - SpecificFragmentInfo::UnscannedText(_) => { - unreachable!("Shouldn't see fragments of this type here!") - }, - }; - return inline_metrics; - - fn inline_metrics_of_text( - info: &ScannedTextFragmentInfo, - self_: &Fragment, - layout_context: &LayoutContext, - ) -> InlineMetrics { - // Fragments with no glyphs don't contribute any inline metrics. - // TODO: Filter out these fragments during flow construction? - if info.insertion_point.is_none() && info.content_size.inline == Au(0) { - return InlineMetrics::new(Au(0), Au(0), Au(0)); - } - // See CSS 2.1 § 10.8.1. - let font_metrics = with_thread_local_font_context(layout_context, |font_context| { - text::font_metrics_for_style(font_context, self_.style.clone_font()) - }); - let line_height = text::line_height_from_style(&*self_.style, &font_metrics); - InlineMetrics::from_font_metrics(&info.run.font_metrics, line_height) - } - - fn inline_metrics_of_block(flow: &FlowRef, style: &ComputedValues) -> InlineMetrics { - // CSS 2.1 § 10.8: "The height of each inline-level box in the line box is calculated. - // For replaced elements, inline-block elements, and inline-table elements, this is the - // height of their margin box." - // - // CSS 2.1 § 10.8.1: "The baseline of an 'inline-block' is the baseline of its last - // line box in the normal flow, unless it has either no in-flow line boxes or if its - // 'overflow' property has a computed value other than 'visible', in which case the - // baseline is the bottom margin edge." - // - // NB: We must use `block_flow.fragment.border_box.size.block` here instead of - // `block_flow.base.position.size.block` because sometimes the latter is late-computed - // and isn't up to date at this point. - let block_flow = flow.as_block(); - let start_margin = block_flow.fragment.margin.block_start; - let end_margin = block_flow.fragment.margin.block_end; - let border_box_block_size = block_flow.fragment.border_box.size.block; - - // -------- - // margin - // top -------- + + - // | | - // | | - // A ..pogo.. | + baseline_offset_of_last_line_box_in_flow() - // | - // -------- + border_box_block_size - // margin - // B -------- - // - // § 10.8.1 says that the baseline (and thus ascent, which is the - // distance from the baseline to the top) should be A if it has an - // in-flow line box and if overflow: visible, and B otherwise. - let ascent = match ( - flow.baseline_offset_of_last_line_box_in_flow(), - style.get_box().overflow_y, - ) { - // Case A - (Some(baseline_offset), StyleOverflow::Visible) => baseline_offset, - // Case B - _ => border_box_block_size + end_margin, - }; - - let space_below_baseline = border_box_block_size + end_margin - ascent; - let space_above_baseline = ascent + start_margin; - - InlineMetrics::new(space_above_baseline, space_below_baseline, ascent) - } - } - - /// Calculates the offset from the baseline that applies to this fragment due to - /// `vertical-align`. Positive values represent downward displacement. - /// - /// If `actual_line_metrics` is supplied, then these metrics are used to determine the - /// displacement of the fragment when `top` or `bottom` `vertical-align` values are - /// encountered. If this is not supplied, then `top` and `bottom` values are ignored. - fn vertical_alignment_offset( - &self, - layout_context: &LayoutContext, - content_inline_metrics: &InlineMetrics, - minimum_line_metrics: &LineMetrics, - actual_line_metrics: Option<&LineMetrics>, - ) -> Au { - let mut offset = Au(0); - for style in self.inline_styles() { - // If any of the inline styles say `top` or `bottom`, adjust the vertical align - // appropriately. - // - // FIXME(#5624, pcwalton): This passes our current reftests but isn't the right thing - // to do. - match style.get_box().vertical_align { - VerticalAlign::Keyword(kw) => match kw { - VerticalAlignKeyword::Baseline => {}, - VerticalAlignKeyword::Middle => { - let font_metrics = - with_thread_local_font_context(layout_context, |font_context| { - text::font_metrics_for_style(font_context, self.style.clone_font()) - }); - offset += (content_inline_metrics.ascent - - content_inline_metrics.space_below_baseline - - font_metrics.x_height) - .scale_by(0.5) - }, - VerticalAlignKeyword::Sub => { - offset += minimum_line_metrics - .space_needed() - .scale_by(FONT_SUBSCRIPT_OFFSET_RATIO) - }, - VerticalAlignKeyword::Super => { - offset -= minimum_line_metrics - .space_needed() - .scale_by(FONT_SUPERSCRIPT_OFFSET_RATIO) - }, - VerticalAlignKeyword::TextTop => { - offset = self.content_inline_metrics(layout_context).ascent - - minimum_line_metrics.space_above_baseline - }, - VerticalAlignKeyword::TextBottom => { - offset = minimum_line_metrics.space_below_baseline - - self.content_inline_metrics(layout_context) - .space_below_baseline - }, - VerticalAlignKeyword::Top => { - if let Some(actual_line_metrics) = actual_line_metrics { - offset = content_inline_metrics.ascent - - actual_line_metrics.space_above_baseline - } - }, - VerticalAlignKeyword::Bottom => { - if let Some(actual_line_metrics) = actual_line_metrics { - offset = actual_line_metrics.space_below_baseline - - content_inline_metrics.space_below_baseline - } - }, - }, - VerticalAlign::Length(ref lp) => { - offset -= lp.to_used_value(minimum_line_metrics.space_needed()); - }, - } - } - offset - } - - /// Calculates block-size above baseline, depth below baseline, and ascent for this fragment - /// when used in an inline formatting context, taking `vertical-align` (other than `top` or - /// `bottom`) into account. See CSS 2.1 § 10.8.1. - /// - /// If `actual_line_metrics` is supplied, then these metrics are used to determine the - /// displacement of the fragment when `top` or `bottom` `vertical-align` values are - /// encountered. If this is not supplied, then `top` and `bottom` values are ignored. - pub fn aligned_inline_metrics( - &self, - layout_context: &LayoutContext, - minimum_line_metrics: &LineMetrics, - actual_line_metrics: Option<&LineMetrics>, - ) -> InlineMetrics { - let content_inline_metrics = self.content_inline_metrics(layout_context); - let vertical_alignment_offset = self.vertical_alignment_offset( - layout_context, - &content_inline_metrics, - minimum_line_metrics, - actual_line_metrics, - ); - let mut space_above_baseline = match actual_line_metrics { - None => content_inline_metrics.space_above_baseline, - Some(actual_line_metrics) => actual_line_metrics.space_above_baseline, - }; - space_above_baseline = space_above_baseline - vertical_alignment_offset; - let space_below_baseline = - content_inline_metrics.space_below_baseline + vertical_alignment_offset; - let ascent = content_inline_metrics.ascent - vertical_alignment_offset; - InlineMetrics::new(space_above_baseline, space_below_baseline, ascent) - } - - /// Returns true if this fragment is a hypothetical box. See CSS 2.1 § 10.3.7. - pub fn is_hypothetical(&self) -> bool { - match self.specific { - SpecificFragmentInfo::InlineAbsoluteHypothetical(_) => true, - _ => false, - } - } - - /// Returns true if this fragment can merge with another immediately-following fragment or - /// false otherwise. - pub fn can_merge_with_fragment(&self, other: &Fragment) -> bool { - match (&self.specific, &other.specific) { - ( - &SpecificFragmentInfo::UnscannedText(ref first_unscanned_text), - &SpecificFragmentInfo::UnscannedText(_), - ) => { - // FIXME: Should probably use a whitelist of styles that can safely differ (#3165) - if self.style().get_font() != other.style().get_font() || - self.text_decoration_line() != other.text_decoration_line() || - self.white_space() != other.white_space() || - self.color() != other.color() - { - return false; - } - - if first_unscanned_text.text.ends_with('\n') { - return false; - } - - // If this node has any styles that have border/padding/margins on the following - // side, then we can't merge with the next fragment. - if let Some(ref inline_context) = self.inline_context { - for inline_context_node in inline_context.nodes.iter() { - if !inline_context_node - .flags - .contains(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT) - { - continue; - } - if inline_context_node.style.logical_margin().inline_end != - LengthPercentageOrAuto::zero() - { - return false; - } - if inline_context_node.style.logical_padding().inline_end != - LengthPercentage::zero() - { - return false; - } - if inline_context_node.style.logical_border_width().inline_end != Au(0) { - return false; - } - } - } - - // If the next fragment has any styles that have border/padding/margins on the - // preceding side, then it can't merge with us. - if let Some(ref inline_context) = other.inline_context { - for inline_context_node in inline_context.nodes.iter() { - if !inline_context_node - .flags - .contains(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT) - { - continue; - } - if inline_context_node.style.logical_margin().inline_start != - LengthPercentageOrAuto::zero() - { - return false; - } - if inline_context_node.style.logical_padding().inline_start != - LengthPercentage::zero() - { - return false; - } - if inline_context_node - .style - .logical_border_width() - .inline_start != - Au(0) - { - return false; - } - } - } - - true - }, - _ => false, - } - } - - /// Returns true if and only if this is the *primary fragment* for the fragment's style object - /// (conceptually, though style sharing makes this not really true, of course). The primary - /// fragment is the one that draws backgrounds, borders, etc., and takes borders, padding and - /// margins into account. Every style object has at most one primary fragment. - /// - /// At present, all fragments are primary fragments except for inline-block and table wrapper - /// fragments. Inline-block fragments are not primary fragments because the corresponding block - /// flow is the primary fragment, while table wrapper fragments are not primary fragments - /// because the corresponding table flow is the primary fragment. pub fn is_primary_fragment(&self) -> bool { - match self.specific { - SpecificFragmentInfo::InlineBlock(_) | - SpecificFragmentInfo::InlineAbsoluteHypothetical(_) | - SpecificFragmentInfo::InlineAbsolute(_) | - SpecificFragmentInfo::MulticolColumn | - SpecificFragmentInfo::TableWrapper => false, - SpecificFragmentInfo::Canvas(_) | - SpecificFragmentInfo::Generic | - SpecificFragmentInfo::GeneratedContent(_) | - SpecificFragmentInfo::Iframe(_) | - SpecificFragmentInfo::Image(_) | - SpecificFragmentInfo::Media(_) | - SpecificFragmentInfo::ScannedText(_) | - SpecificFragmentInfo::Svg(_) | - SpecificFragmentInfo::Table | - SpecificFragmentInfo::TableCell | - SpecificFragmentInfo::TableColumn(_) | - SpecificFragmentInfo::TableRow | - SpecificFragmentInfo::TruncatedFragment(_) | - SpecificFragmentInfo::Multicol | - SpecificFragmentInfo::UnscannedText(_) => true, - } - } - - /// Determines the inline sizes of inline-block fragments. These cannot be fully computed until - /// inline size assignment has run for the child flow: thus it is computed "late", during - /// block size assignment. - pub fn update_late_computed_replaced_inline_size_if_necessary(&mut self) { - if let SpecificFragmentInfo::InlineBlock(ref mut inline_block_info) = self.specific { - let block_flow = FlowRef::deref_mut(&mut inline_block_info.flow_ref).as_block(); - self.border_box.size.inline = block_flow.fragment.margin_box_inline_size(); - } - } - - pub fn update_late_computed_inline_position_if_necessary(&mut self) { - if let SpecificFragmentInfo::InlineAbsoluteHypothetical(ref mut info) = self.specific { - let position = self.border_box.start.i; - FlowRef::deref_mut(&mut info.flow_ref) - .update_late_computed_inline_position_if_necessary(position) - } - } - - pub fn update_late_computed_block_position_if_necessary(&mut self) { - if let SpecificFragmentInfo::InlineAbsoluteHypothetical(ref mut info) = self.specific { - let position = self.border_box.start.b; - FlowRef::deref_mut(&mut info.flow_ref) - .update_late_computed_block_position_if_necessary(position) - } - } - - pub fn repair_style(&mut self, new_style: &ServoArc<ComputedValues>) { - self.style = (*new_style).clone() - } - - /// Given the stacking-context-relative position of the containing flow, returns the border box - /// of this fragment relative to the parent stacking context. This takes `position: relative` - /// into account. - /// - /// If `coordinate_system` is `Parent`, this returns the border box in the parent stacking - /// context's coordinate system. Otherwise, if `coordinate_system` is `Own` and this fragment - /// establishes a stacking context itself, this returns a border box anchored at (0, 0). (If - /// this fragment does not establish a stacking context, then it always belongs to its parent - /// stacking context and thus `coordinate_system` is ignored.) - /// - /// This is the method you should use for display list construction as well as - /// `getBoundingClientRect()` and so forth. - pub fn stacking_relative_border_box( - &self, - stacking_relative_flow_origin: &Vector2D<Au>, - relative_containing_block_size: &LogicalSize<Au>, - relative_containing_block_mode: WritingMode, - coordinate_system: CoordinateSystem, - ) -> Rect<Au> { - let container_size = - relative_containing_block_size.to_physical(relative_containing_block_mode); - let border_box = self - .border_box - .to_physical(self.style.writing_mode, container_size); - if coordinate_system == CoordinateSystem::Own && self.establishes_stacking_context() { - return Rect::new(Point2D::zero(), border_box.size); - } - - // FIXME(pcwalton): This can double-count relative position sometimes for inlines (e.g. - // `<div style="position:relative">x</div>`, because the `position:relative` trickles down - // to the inline flow. Possibly we should extend the notion of "primary fragment" to fix - // this. - let relative_position = self.relative_position(relative_containing_block_size); - border_box - .translate_by_size(&relative_position.to_physical(self.style.writing_mode)) - .translate(&stacking_relative_flow_origin) - } - - /// Given the stacking-context-relative border box, returns the stacking-context-relative - /// content box. - pub fn stacking_relative_content_box( - &self, - stacking_relative_border_box: Rect<Au>, - ) -> Rect<Au> { - let border_padding = self.border_padding.to_physical(self.style.writing_mode); - Rect::new( - Point2D::new( - stacking_relative_border_box.origin.x + border_padding.left, - stacking_relative_border_box.origin.y + border_padding.top, - ), - Size2D::new( - stacking_relative_border_box.size.width - border_padding.horizontal(), - stacking_relative_border_box.size.height - border_padding.vertical(), - ), - ) - } - - /// Returns true if this fragment may establish a reference frame. - pub fn can_establish_reference_frame(&self) -> bool { - !self.style().get_box().transform.0.is_empty() || - self.style().get_box().perspective != Perspective::None - } - - /// Returns true if this fragment has a filter, transform, or perspective property set. - pub fn has_filter_transform_or_perspective(&self) -> bool { - !self.style().get_box().transform.0.is_empty() || - !self.style().get_effects().filter.0.is_empty() || - self.style().get_box().perspective != Perspective::None - } - - /// Returns true if this fragment establishes a new stacking context and false otherwise. - pub fn establishes_stacking_context(&self) -> bool { - // Text fragments shouldn't create stacking contexts. - match self.specific { - SpecificFragmentInfo::TruncatedFragment(_) | - SpecificFragmentInfo::ScannedText(_) | - SpecificFragmentInfo::UnscannedText(_) => return false, - _ => {}, - } - - if self.style().get_effects().opacity != 1.0 { - return true; - } - - if self.style().get_effects().mix_blend_mode != MixBlendMode::Normal { - return true; - } - - if self.has_filter_transform_or_perspective() { - return true; - } - - if self.style().get_box().transform_style == TransformStyle::Preserve3d || - self.style().overrides_transform_style() - { - return true; - } - - // Fixed position and sticky position always create stacking contexts. - if self.style().get_box().position == Position::Fixed || - self.style().get_box().position == Position::Sticky - { - return true; - } - - // Statically positioned fragments don't establish stacking contexts if the previous - // conditions are not fulfilled. Furthermore, z-index doesn't apply to statically - // positioned fragments. - if self.style().get_box().position == Position::Static { - return false; - } - - // For absolutely and relatively positioned fragments we only establish a stacking - // context if there is a z-index set. - // See https://www.w3.org/TR/CSS2/visuren.html#z-index - !self.style().get_position().z_index.is_auto() - } - - // Get the effective z-index of this fragment. Z-indices only apply to positioned element - // per CSS 2 9.9.1 (http://www.w3.org/TR/CSS2/visuren.html#z-index), so this value may differ - // from the value specified in the style. - pub fn effective_z_index(&self) -> i32 { - match self.style().get_box().position { - Position::Static => {}, - _ => return self.style().get_position().z_index.integer_or(0), - } - - if !self.style().get_box().transform.0.is_empty() { - return self.style().get_position().z_index.integer_or(0); - } - - match self.style().get_box().display { - Display::Flex => self.style().get_position().z_index.integer_or(0), - _ => 0, - } - } - - /// Computes the overflow rect of this fragment relative to the start of the flow. - pub fn compute_overflow( - &self, - flow_size: &Size2D<Au>, - relative_containing_block_size: &LogicalSize<Au>, - ) -> Overflow { - let mut border_box = self - .border_box - .to_physical(self.style.writing_mode, *flow_size); - - // Relative position can cause us to draw outside our border box. - // - // FIXME(pcwalton): I'm not a fan of the way this makes us crawl though so many styles all - // the time. Can't we handle relative positioning by just adjusting `border_box`? - let relative_position = self.relative_position(relative_containing_block_size); - border_box = - border_box.translate_by_size(&relative_position.to_physical(self.style.writing_mode)); - let mut overflow = Overflow::from_rect(&border_box); - - // Box shadows cause us to draw outside our border box. - for box_shadow in &*self.style().get_effects().box_shadow.0 { - let offset = Vector2D::new( - Au::from(box_shadow.base.horizontal), - Au::from(box_shadow.base.vertical), - ); - let inflation = Au::from(box_shadow.spread) + - Au::from(box_shadow.base.blur) * BLUR_INFLATION_FACTOR; - overflow.paint = overflow - .paint - .union(&border_box.translate(&offset).inflate(inflation, inflation)) - } - - // Outlines cause us to draw outside our border box. - let outline_width = Au::from(self.style.get_outline().outline_width); - if outline_width != Au(0) { - overflow.paint = overflow - .paint - .union(&border_box.inflate(outline_width, outline_width)) - } - - // Include the overflow of the block flow, if any. - match self.specific { - SpecificFragmentInfo::InlineBlock(ref info) => { - let block_flow = info.flow_ref.as_block(); - overflow.union(&block_flow.base().overflow); - }, - SpecificFragmentInfo::InlineAbsolute(ref info) => { - let block_flow = info.flow_ref.as_block(); - overflow.union(&block_flow.base().overflow); - }, - _ => (), - } - - // FIXME(pcwalton): Sometimes excessively fancy glyphs can make us draw outside our border - // box too. - overflow - } - - pub fn requires_line_break_afterward_if_wrapping_on_newlines(&self) -> bool { - match self.specific { - SpecificFragmentInfo::TruncatedFragment(ref t) if t.text_info.is_some() => { - let text = t.text_info.as_ref().unwrap(); - text.requires_line_break_afterward_if_wrapping_on_newlines() - }, - SpecificFragmentInfo::ScannedText(ref text) => { - text.requires_line_break_afterward_if_wrapping_on_newlines() - }, - _ => false, - } - } - - pub fn strip_leading_whitespace_if_necessary(&mut self) -> WhitespaceStrippingResult { - if self.white_space().preserve_spaces() { - return WhitespaceStrippingResult::RetainFragment; - } - - return match self.specific { - SpecificFragmentInfo::TruncatedFragment(ref mut t) if t.text_info.is_some() => { - let scanned_text_fragment_info = t.text_info.as_mut().unwrap(); - scanned_text(scanned_text_fragment_info, &mut self.border_box) - }, - SpecificFragmentInfo::ScannedText(ref mut scanned_text_fragment_info) => { - scanned_text(scanned_text_fragment_info, &mut self.border_box) - }, - SpecificFragmentInfo::UnscannedText(ref mut unscanned_text_fragment_info) => { - let mut new_text_string = String::new(); - let mut modified = false; - for (i, character) in unscanned_text_fragment_info.text.char_indices() { - if gfx::text::util::is_bidi_control(character) { - new_text_string.push(character); - continue; - } - if char_is_whitespace(character) { - modified = true; - continue; - } - // Finished processing leading control chars and whitespace. - if modified { - new_text_string.push_str(&unscanned_text_fragment_info.text[i..]); - } - break; - } - if modified { - unscanned_text_fragment_info.text = new_text_string.into_boxed_str(); - } - - WhitespaceStrippingResult::from_unscanned_text_fragment_info( - &unscanned_text_fragment_info, - ) - }, - _ => WhitespaceStrippingResult::RetainFragment, - }; - - fn scanned_text( - scanned_text_fragment_info: &mut ScannedTextFragmentInfo, - border_box: &mut LogicalRect<Au>, - ) -> WhitespaceStrippingResult { - let leading_whitespace_byte_count = scanned_text_fragment_info - .text() - .find(|c| !char_is_whitespace(c)) - .unwrap_or(scanned_text_fragment_info.text().len()); - - let whitespace_len = ByteIndex(leading_whitespace_byte_count as isize); - let whitespace_range = - Range::new(scanned_text_fragment_info.range.begin(), whitespace_len); - let text_bounds = scanned_text_fragment_info - .run - .metrics_for_range(&whitespace_range) - .bounding_box; - border_box.size.inline = border_box.size.inline - text_bounds.size.width; - scanned_text_fragment_info.content_size.inline = - scanned_text_fragment_info.content_size.inline - text_bounds.size.width; - - scanned_text_fragment_info - .range - .adjust_by(whitespace_len, -whitespace_len); - - WhitespaceStrippingResult::RetainFragment - } - } - - /// Returns true if the entire fragment was stripped. - pub fn strip_trailing_whitespace_if_necessary(&mut self) -> WhitespaceStrippingResult { - if self.white_space().preserve_spaces() { - return WhitespaceStrippingResult::RetainFragment; - } - - return match self.specific { - SpecificFragmentInfo::TruncatedFragment(ref mut t) if t.text_info.is_some() => { - let scanned_text_fragment_info = t.text_info.as_mut().unwrap(); - scanned_text(scanned_text_fragment_info, &mut self.border_box) - }, - SpecificFragmentInfo::ScannedText(ref mut scanned_text_fragment_info) => { - scanned_text(scanned_text_fragment_info, &mut self.border_box) - }, - SpecificFragmentInfo::UnscannedText(ref mut unscanned_text_fragment_info) => { - let mut trailing_bidi_control_characters_to_retain = Vec::new(); - let (mut modified, mut last_character_index) = (true, 0); - for (i, character) in unscanned_text_fragment_info.text.char_indices().rev() { - if gfx::text::util::is_bidi_control(character) { - trailing_bidi_control_characters_to_retain.push(character); - continue; - } - if char_is_whitespace(character) { - modified = true; - continue; - } - last_character_index = i + character.len_utf8(); - break; - } - if modified { - let mut text = unscanned_text_fragment_info.text.to_string(); - text.truncate(last_character_index); - for character in trailing_bidi_control_characters_to_retain.iter().rev() { - text.push(*character); - } - unscanned_text_fragment_info.text = text.into_boxed_str(); - } - - WhitespaceStrippingResult::from_unscanned_text_fragment_info( - &unscanned_text_fragment_info, - ) - }, - _ => WhitespaceStrippingResult::RetainFragment, - }; - - fn scanned_text( - scanned_text_fragment_info: &mut ScannedTextFragmentInfo, - border_box: &mut LogicalRect<Au>, - ) -> WhitespaceStrippingResult { - let mut trailing_whitespace_start_byte = 0; - for (i, c) in scanned_text_fragment_info.text().char_indices().rev() { - if !char_is_whitespace(c) { - trailing_whitespace_start_byte = i + c.len_utf8(); - break; - } - } - let whitespace_start = ByteIndex(trailing_whitespace_start_byte as isize); - let whitespace_len = scanned_text_fragment_info.range.length() - whitespace_start; - let mut whitespace_range = Range::new(whitespace_start, whitespace_len); - whitespace_range.shift_by(scanned_text_fragment_info.range.begin()); - - let text_bounds = scanned_text_fragment_info - .run - .metrics_for_range(&whitespace_range) - .bounding_box; - border_box.size.inline -= text_bounds.size.width; - scanned_text_fragment_info.content_size.inline -= text_bounds.size.width; - - scanned_text_fragment_info.range.extend_by(-whitespace_len); - WhitespaceStrippingResult::RetainFragment - } - } - - pub fn inline_styles(&self) -> InlineStyleIterator { - InlineStyleIterator::new(self) - } - - /// Returns the inline-size of this fragment's margin box. - pub fn margin_box_inline_size(&self) -> Au { - self.border_box.size.inline + self.margin.inline_start_end() - } - - /// Returns true if this node *or any of the nodes within its inline fragment context* have - /// non-`static` `position`. - pub fn is_positioned(&self) -> bool { - if self.style.get_box().position != Position::Static { - return true; - } - if let Some(ref inline_context) = self.inline_context { - for node in inline_context.nodes.iter() { - if node.style.get_box().position != Position::Static { - return true; - } - } - } - false - } - - /// Returns true if this node is absolutely positioned. - pub fn is_absolutely_positioned(&self) -> bool { - self.style.get_box().position == Position::Absolute - } - - pub fn is_inline_absolute(&self) -> bool { - match self.specific { - SpecificFragmentInfo::InlineAbsolute(..) => true, - _ => false, - } - } - - pub fn meld_with_next_inline_fragment(&mut self, next_fragment: &Fragment) { - if let Some(ref mut inline_context_of_this_fragment) = self.inline_context { - if let Some(ref inline_context_of_next_fragment) = next_fragment.inline_context { - for ( - inline_context_node_from_this_fragment, - inline_context_node_from_next_fragment, - ) in inline_context_of_this_fragment - .nodes - .iter_mut() - .rev() - .zip(inline_context_of_next_fragment.nodes.iter().rev()) - { - if !inline_context_node_from_next_fragment - .flags - .contains(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT) - { - continue; - } - if inline_context_node_from_next_fragment.address != - inline_context_node_from_this_fragment.address - { - continue; - } - inline_context_node_from_this_fragment - .flags - .insert(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT); - } - } - } - } - - pub fn meld_with_prev_inline_fragment(&mut self, prev_fragment: &Fragment) { - if let Some(ref mut inline_context_of_this_fragment) = self.inline_context { - if let Some(ref inline_context_of_prev_fragment) = prev_fragment.inline_context { - for ( - inline_context_node_from_prev_fragment, - inline_context_node_from_this_fragment, - ) in inline_context_of_prev_fragment - .nodes - .iter() - .rev() - .zip(inline_context_of_this_fragment.nodes.iter_mut().rev()) - { - if !inline_context_node_from_prev_fragment - .flags - .contains(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT) - { - continue; - } - if inline_context_node_from_prev_fragment.address != - inline_context_node_from_this_fragment.address - { - continue; - } - inline_context_node_from_this_fragment - .flags - .insert(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT); - } - } - } - } - - /// Returns true if any of the inline styles associated with this fragment have - /// `vertical-align` set to `top` or `bottom`. - pub fn is_vertically_aligned_to_top_or_bottom(&self) -> bool { - fn is_top_or_bottom(v: &VerticalAlign) -> bool { - match *v { - VerticalAlign::Keyword(VerticalAlignKeyword::Top) | - VerticalAlign::Keyword(VerticalAlignKeyword::Bottom) => true, - _ => false, - } - } - - if is_top_or_bottom(&self.style.get_box().vertical_align) { - return true; - } - - if let Some(ref inline_context) = self.inline_context { - for node in &inline_context.nodes { - if is_top_or_bottom(&node.style.get_box().vertical_align) { - return true; - } - } - } - false - } - - pub fn is_text_or_replaced(&self) -> bool { - match self.specific { - SpecificFragmentInfo::Generic | - SpecificFragmentInfo::InlineAbsolute(_) | - SpecificFragmentInfo::InlineAbsoluteHypothetical(_) | - SpecificFragmentInfo::InlineBlock(_) | - SpecificFragmentInfo::Multicol | - SpecificFragmentInfo::MulticolColumn | - SpecificFragmentInfo::Table | - SpecificFragmentInfo::TableCell | - SpecificFragmentInfo::TableColumn(_) | - SpecificFragmentInfo::TableRow | - SpecificFragmentInfo::TableWrapper => false, - SpecificFragmentInfo::Canvas(_) | - SpecificFragmentInfo::GeneratedContent(_) | - SpecificFragmentInfo::Iframe(_) | - SpecificFragmentInfo::Image(_) | - SpecificFragmentInfo::Media(_) | - SpecificFragmentInfo::ScannedText(_) | - SpecificFragmentInfo::TruncatedFragment(_) | - SpecificFragmentInfo::Svg(_) | - SpecificFragmentInfo::UnscannedText(_) => true, - } - } - - /// Returns the 4D matrix representing this fragment's transform. - pub fn transform_matrix( - &self, - stacking_relative_border_box: &Rect<Au>, - ) -> Option<LayoutTransform> { - let list = &self.style.get_box().transform; - let transform = LayoutTransform::from_untyped( - &list - .to_transform_3d_matrix(Some(stacking_relative_border_box)) - .ok()? - .0, - ); - - let transform_origin = &self.style.get_box().transform_origin; - let transform_origin_x = transform_origin - .horizontal - .to_used_value(stacking_relative_border_box.size.width) - .to_f32_px(); - let transform_origin_y = transform_origin - .vertical - .to_used_value(stacking_relative_border_box.size.height) - .to_f32_px(); - let transform_origin_z = transform_origin.depth.px(); - - let pre_transform = LayoutTransform::create_translation( - transform_origin_x, - transform_origin_y, - transform_origin_z, - ); - let post_transform = LayoutTransform::create_translation( - -transform_origin_x, - -transform_origin_y, - -transform_origin_z, - ); - - Some(pre_transform.pre_mul(&transform).pre_mul(&post_transform)) - } - - /// Returns the 4D matrix representing this fragment's perspective. - pub fn perspective_matrix( - &self, - stacking_relative_border_box: &Rect<Au>, - ) -> Option<LayoutTransform> { - match self.style().get_box().perspective { - Perspective::Length(length) => { - let perspective_origin = self.style().get_box().perspective_origin; - let perspective_origin = Point2D::new( - perspective_origin - .horizontal - .to_used_value(stacking_relative_border_box.size.width), - perspective_origin - .vertical - .to_used_value(stacking_relative_border_box.size.height), - ) - .to_layout(); - - let pre_transform = LayoutTransform::create_translation( - perspective_origin.x, - perspective_origin.y, - 0.0, - ); - let post_transform = LayoutTransform::create_translation( - -perspective_origin.x, - -perspective_origin.y, - 0.0, - ); - - let perspective_matrix = LayoutTransform::from_untyped( - &transform::create_perspective_matrix(length.px()), - ); - - Some( - pre_transform - .pre_mul(&perspective_matrix) - .pre_mul(&post_transform), - ) - }, - Perspective::None => None, - } - } -} - -impl fmt::Debug for Fragment { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let border_padding_string = if !self.border_padding.is_zero() { - format!("\nborder_padding={:?}", self.border_padding) - } else { - "".to_owned() - }; - - let margin_string = if !self.margin.is_zero() { - format!("\nmargin={:?}", self.margin) - } else { - "".to_owned() - }; - - let damage_string = if self.restyle_damage != RestyleDamage::empty() { - format!("\ndamage={:?}", self.restyle_damage) - } else { - "".to_owned() - }; - - write!( - f, - "\n{}({}) [{:?}]\nborder_box={:?}{}{}{}", - self.specific.get_type(), - self.debug_id, - self.specific, - self.border_box, - border_padding_string, - margin_string, - damage_string - ) - } -} - -bitflags! { - struct QuantitiesIncludedInIntrinsicInlineSizes: u8 { - const INTRINSIC_INLINE_SIZE_INCLUDES_MARGINS = 0x01; - const INTRINSIC_INLINE_SIZE_INCLUDES_PADDING = 0x02; - const INTRINSIC_INLINE_SIZE_INCLUDES_BORDER = 0x04; - const INTRINSIC_INLINE_SIZE_INCLUDES_SPECIFIED = 0x08; - } -} - -bitflags! { - // Various flags we can use when splitting fragments. See - // `calculate_split_position_using_breaking_strategy()`. - struct SplitOptions: u8 { - #[doc = "True if this is the first fragment on the line."] - const STARTS_LINE = 0x01; - #[doc = "True if we should attempt to split at character boundaries if this split fails. \ - This is used to implement `overflow-wrap: break-word`."] - const RETRY_AT_CHARACTER_BOUNDARIES = 0x02; + true } } @@ -3283,173 +108,3 @@ pub trait FragmentBorderBoxIterator { /// we skip the operation for this fragment, but continue processing siblings. fn should_process(&mut self, fragment: &Fragment) -> bool; } - -/// The coordinate system used in `stacking_relative_border_box()`. See the documentation of that -/// method for details. -#[derive(Clone, Debug, PartialEq)] -pub enum CoordinateSystem { - /// The border box returned is relative to the fragment's parent stacking context. - Parent, - /// The border box returned is relative to the fragment's own stacking context, if applicable. - Own, -} - -pub struct InlineStyleIterator<'a> { - fragment: &'a Fragment, - inline_style_index: usize, - primary_style_yielded: bool, -} - -impl<'a> Iterator for InlineStyleIterator<'a> { - type Item = &'a ComputedValues; - - fn next(&mut self) -> Option<&'a ComputedValues> { - if !self.primary_style_yielded { - self.primary_style_yielded = true; - return Some(&*self.fragment.style); - } - let inline_context = self.fragment.inline_context.as_ref()?; - let inline_style_index = self.inline_style_index; - if inline_style_index == inline_context.nodes.len() { - return None; - } - self.inline_style_index += 1; - Some(&*inline_context.nodes[inline_style_index].style) - } -} - -impl<'a> InlineStyleIterator<'a> { - fn new(fragment: &Fragment) -> InlineStyleIterator { - InlineStyleIterator { - fragment: fragment, - inline_style_index: 0, - primary_style_yielded: false, - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum WhitespaceStrippingResult { - RetainFragment, - FragmentContainedOnlyBidiControlCharacters, - FragmentContainedOnlyWhitespace, -} - -impl WhitespaceStrippingResult { - fn from_unscanned_text_fragment_info( - info: &UnscannedTextFragmentInfo, - ) -> WhitespaceStrippingResult { - if info.text.is_empty() { - WhitespaceStrippingResult::FragmentContainedOnlyWhitespace - } else if info.text.chars().all(gfx::text::util::is_bidi_control) { - WhitespaceStrippingResult::FragmentContainedOnlyBidiControlCharacters - } else { - WhitespaceStrippingResult::RetainFragment - } - } -} - -/// The overflow area. We need two different notions of overflow: paint overflow and scrollable -/// overflow. -#[derive(Clone, Copy, Debug)] -pub struct Overflow { - pub scroll: Rect<Au>, - pub paint: Rect<Au>, -} - -impl Overflow { - pub fn new() -> Overflow { - Overflow { - scroll: Rect::zero(), - paint: Rect::zero(), - } - } - - pub fn from_rect(border_box: &Rect<Au>) -> Overflow { - Overflow { - scroll: *border_box, - paint: *border_box, - } - } - - pub fn union(&mut self, other: &Overflow) { - self.scroll = self.scroll.union(&other.scroll); - self.paint = self.paint.union(&other.paint); - } - - pub fn translate(&mut self, by: &Vector2D<Au>) { - self.scroll = self.scroll.translate(by); - self.paint = self.paint.translate(by); - } -} - -bitflags! { - pub struct FragmentFlags: u8 { - // TODO(stshine): find a better name since these flags can also be used for grid item. - /// Whether this fragment represents a child in a row flex container. - const IS_INLINE_FLEX_ITEM = 0b0000_0001; - /// Whether this fragment represents a child in a column flex container. - const IS_BLOCK_FLEX_ITEM = 0b0000_0010; - /// Whether this fragment represents the generated text from a text-overflow clip. - const IS_ELLIPSIS = 0b0000_0100; - } -} - -/// Specified distances from the margin edge of a block to its content in the inline direction. -/// These are returned by `guess_inline_content_edge_offsets()` and are used in the float placement -/// speculation logic. -#[derive(Clone, Copy, Debug)] -pub struct SpeculatedInlineContentEdgeOffsets { - pub start: Au, - pub end: Au, -} - -#[cfg(not(debug_assertions))] -#[derive(Clone)] -struct DebugId; - -#[cfg(debug_assertions)] -#[derive(Clone)] -struct DebugId(u16); - -#[cfg(not(debug_assertions))] -impl DebugId { - pub fn new() -> DebugId { - DebugId - } -} - -#[cfg(debug_assertions)] -impl DebugId { - pub fn new() -> DebugId { - DebugId(layout_debug::generate_unique_debug_id()) - } -} - -#[cfg(not(debug_assertions))] -impl fmt::Display for DebugId { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:p}", &self) - } -} - -#[cfg(debug_assertions)] -impl fmt::Display for DebugId { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -#[cfg(not(debug_assertions))] -impl Serialize for DebugId { - fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { - serializer.serialize_str(&format!("{:p}", &self)) - } -} - -#[cfg(debug_assertions)] -impl Serialize for DebugId { - fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { - serializer.serialize_u16(self.0) - } -} diff --git a/components/layout_2020/generated_content.rs b/components/layout_2020/generated_content.rs deleted file mode 100644 index e1ebc570a62..00000000000 --- a/components/layout_2020/generated_content.rs +++ /dev/null @@ -1,651 +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/. */ - -//! The generated content assignment phase. -//! -//! This phase handles CSS counters, quotes, and ordered lists per CSS § 12.3-12.5. It cannot be -//! done in parallel and is therefore a sequential pass that runs on as little of the flow tree -//! as possible. - -use crate::context::{with_thread_local_font_context, LayoutContext}; -use crate::display_list::items::OpaqueNode; -use crate::flow::{Flow, FlowFlags, GetBaseFlow, ImmutableFlowUtils}; -use crate::fragment::{ - Fragment, GeneratedContentInfo, SpecificFragmentInfo, UnscannedTextFragmentInfo, -}; -use crate::text::TextRunScanner; -use crate::traversal::InorderFlowTraversal; -use script_layout_interface::wrapper_traits::PseudoElementType; -use smallvec::SmallVec; -use std::collections::{HashMap, LinkedList}; -use style::computed_values::display::T as Display; -use style::computed_values::list_style_type::T as ListStyleType; -use style::properties::ComputedValues; -use style::selector_parser::RestyleDamage; -use style::servo::restyle_damage::ServoRestyleDamage; -use style::values::generics::counters::ContentItem; - -// Decimal styles per CSS-COUNTER-STYLES § 6.1: -static DECIMAL: [char; 10] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; -// TODO(pcwalton): `decimal-leading-zero` -static ARABIC_INDIC: [char; 10] = ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩']; -// TODO(pcwalton): `armenian`, `upper-armenian`, `lower-armenian` -static BENGALI: [char; 10] = [ - '০', '১', '২', '৩', '৪', '৫', '৬', '৭', '৮', '৯', -]; -static CAMBODIAN: [char; 10] = [ - '០', '១', '២', '៣', '៤', '៥', '៦', '៧', '៨', '៩', -]; -// TODO(pcwalton): Suffix for CJK decimal. -static CJK_DECIMAL: [char; 10] = [ - '〇', '一', '二', '三', '四', '五', '六', '七', '八', '九', -]; -static DEVANAGARI: [char; 10] = [ - '०', '१', '२', '३', '४', '५', '६', '७', '८', '९', -]; -// TODO(pcwalton): `georgian` -static GUJARATI: [char; 10] = [ - '૦', '૧', '૨', '૩', '૪', '૫', '૬', '૭', '૮', '૯', -]; -static GURMUKHI: [char; 10] = [ - '੦', '੧', '੨', '੩', '੪', '੫', '੬', '੭', '੮', '੯', -]; -// TODO(pcwalton): `hebrew` -static KANNADA: [char; 10] = [ - '೦', '೧', '೨', '೩', '೪', '೫', '೬', '೭', '೮', '೯', -]; -static LAO: [char; 10] = [ - '໐', '໑', '໒', '໓', '໔', '໕', '໖', '໗', '໘', '໙', -]; -static MALAYALAM: [char; 10] = [ - '൦', '൧', '൨', '൩', '൪', '൫', '൬', '൭', '൮', '൯', -]; -static MONGOLIAN: [char; 10] = [ - '᠐', '᠑', '᠒', '᠓', '᠔', '᠕', '᠖', '᠗', '᠘', '᠙', -]; -static MYANMAR: [char; 10] = [ - '၀', '၁', '၂', '၃', '၄', '၅', '၆', '၇', '၈', '၉', -]; -static ORIYA: [char; 10] = [ - '୦', '୧', '୨', '୩', '୪', '୫', '୬', '୭', '୮', '୯', -]; -static PERSIAN: [char; 10] = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']; -// TODO(pcwalton): `lower-roman`, `upper-roman` -static TELUGU: [char; 10] = [ - '౦', '౧', '౨', '౩', '౪', '౫', '౬', '౭', '౮', '౯', -]; -static THAI: [char; 10] = [ - '๐', '๑', '๒', '๓', '๔', '๕', '๖', '๗', '๘', '๙', -]; -static TIBETAN: [char; 10] = [ - '༠', '༡', '༢', '༣', '༤', '༥', '༦', '༧', '༨', '༩', -]; - -// Alphabetic styles per CSS-COUNTER-STYLES § 6.2: -static LOWER_ALPHA: [char; 26] = [ - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', - 't', 'u', 'v', 'w', 'x', 'y', 'z', -]; -static UPPER_ALPHA: [char; 26] = [ - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', - 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', -]; -static CJK_EARTHLY_BRANCH: [char; 12] = [ - '子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥', -]; -static CJK_HEAVENLY_STEM: [char; 10] = [ - '甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸', -]; -static LOWER_GREEK: [char; 24] = [ - 'α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ', 'ι', 'κ', 'λ', 'μ', 'ν', 'ξ', 'ο', 'π', - 'ρ', 'σ', 'τ', 'υ', 'φ', 'χ', 'ψ', 'ω', -]; -static HIRAGANA: [char; 48] = [ - 'あ', 'い', 'う', 'え', 'お', 'か', 'き', 'く', 'け', 'こ', 'さ', 'し', 'す', - 'せ', 'そ', 'た', 'ち', 'つ', 'て', 'と', 'な', 'に', 'ぬ', 'ね', 'の', 'は', - 'ひ', 'ふ', 'へ', 'ほ', 'ま', 'み', 'む', 'め', 'も', 'や', 'ゆ', 'よ', 'ら', - 'り', 'る', 'れ', 'ろ', 'わ', 'ゐ', 'ゑ', 'を', 'ん', -]; -static HIRAGANA_IROHA: [char; 47] = [ - 'い', 'ろ', 'は', 'に', 'ほ', 'へ', 'と', 'ち', 'り', 'ぬ', 'る', 'を', 'わ', - 'か', 'よ', 'た', 'れ', 'そ', 'つ', 'ね', 'な', 'ら', 'む', 'う', 'ゐ', 'の', - 'お', 'く', 'や', 'ま', 'け', 'ふ', 'こ', 'え', 'て', 'あ', 'さ', 'き', 'ゆ', - 'め', 'み', 'し', 'ゑ', 'ひ', 'も', 'せ', 'す', -]; -static KATAKANA: [char; 48] = [ - 'ア', 'イ', 'ウ', 'エ', 'オ', 'カ', 'キ', 'ク', 'ケ', 'コ', 'サ', 'シ', 'ス', - 'セ', 'ソ', 'タ', 'チ', 'ツ', 'テ', 'ト', 'ナ', 'ニ', 'ヌ', 'ネ', 'ノ', 'ハ', - 'ヒ', 'フ', 'ヘ', 'ホ', 'マ', 'ミ', 'ム', 'メ', 'モ', 'ヤ', 'ユ', 'ヨ', 'ラ', - 'リ', 'ル', 'レ', 'ロ', 'ワ', 'ヰ', 'ヱ', 'ヲ', 'ン', -]; -static KATAKANA_IROHA: [char; 47] = [ - 'イ', 'ロ', 'ハ', 'ニ', 'ホ', 'ヘ', 'ト', 'チ', 'リ', 'ヌ', 'ル', 'ヲ', 'ワ', - 'カ', 'ヨ', 'タ', 'レ', 'ソ', 'ツ', 'ネ', 'ナ', 'ラ', 'ム', 'ウ', 'ヰ', 'ノ', - 'オ', 'ク', 'ヤ', 'マ', 'ケ', 'フ', 'コ', 'エ', 'テ', 'ア', 'サ', 'キ', 'ユ', - 'メ', 'ミ', 'シ', 'ヱ', 'ヒ', 'モ', 'セ', 'ス', -]; - -/// The generated content resolution traversal. -pub struct ResolveGeneratedContent<'a> { - /// The layout context. - layout_context: &'a LayoutContext<'a>, - /// The counter representing an ordered list item. - list_item: Counter, - /// Named CSS counters. - counters: HashMap<String, Counter>, - /// The level of quote nesting. - quote: u32, -} - -impl<'a> ResolveGeneratedContent<'a> { - /// Creates a new generated content resolution traversal. - pub fn new(layout_context: &'a LayoutContext) -> ResolveGeneratedContent<'a> { - ResolveGeneratedContent { - layout_context: layout_context, - list_item: Counter::new(), - counters: HashMap::new(), - quote: 0, - } - } -} - -impl<'a> InorderFlowTraversal for ResolveGeneratedContent<'a> { - #[inline] - fn process(&mut self, flow: &mut dyn Flow, level: u32) { - let mut mutator = ResolveGeneratedContentFragmentMutator { - traversal: self, - level: level, - is_block: flow.is_block_like(), - incremented: false, - }; - flow.mutate_fragments(&mut |fragment| mutator.mutate_fragment(fragment)) - } - - #[inline] - fn should_process_subtree(&mut self, flow: &mut dyn Flow) -> bool { - flow.base() - .restyle_damage - .intersects(ServoRestyleDamage::RESOLVE_GENERATED_CONTENT) || - flow.base().flags.intersects( - FlowFlags::AFFECTS_COUNTERS | FlowFlags::HAS_COUNTER_AFFECTING_CHILDREN, - ) - } -} - -/// The object that mutates the generated content fragments. -struct ResolveGeneratedContentFragmentMutator<'a, 'b: 'a> { - /// The traversal. - traversal: &'a mut ResolveGeneratedContent<'b>, - /// The level we're at in the flow tree. - level: u32, - /// Whether this flow is a block flow. - is_block: bool, - /// Whether we've incremented the counter yet. - incremented: bool, -} - -impl<'a, 'b> ResolveGeneratedContentFragmentMutator<'a, 'b> { - fn mutate_fragment(&mut self, fragment: &mut Fragment) { - // We only reset and/or increment counters once per flow. This avoids double-incrementing - // counters on list items (once for the main fragment and once for the marker). - if !self.incremented { - self.reset_and_increment_counters_as_necessary(fragment); - } - - let mut list_style_type = fragment.style().get_list().list_style_type; - if fragment.style().get_box().display != Display::ListItem { - list_style_type = ListStyleType::None - } - - let mut new_info = None; - { - let info = - if let SpecificFragmentInfo::GeneratedContent(ref mut info) = fragment.specific { - info - } else { - return; - }; - - match **info { - GeneratedContentInfo::ListItem => { - new_info = self.traversal.list_item.render( - self.traversal.layout_context, - fragment.node, - fragment.pseudo.clone(), - fragment.style.clone(), - list_style_type, - RenderingMode::Suffix(".\u{00a0}"), - ) - }, - GeneratedContentInfo::Empty | - GeneratedContentInfo::ContentItem(ContentItem::String(_)) => { - // Nothing to do here. - }, - GeneratedContentInfo::ContentItem(ContentItem::Counter( - ref counter_name, - counter_style, - )) => { - let temporary_counter = Counter::new(); - let counter = self - .traversal - .counters - .get(&*counter_name.0) - .unwrap_or(&temporary_counter); - new_info = counter.render( - self.traversal.layout_context, - fragment.node, - fragment.pseudo.clone(), - fragment.style.clone(), - counter_style, - RenderingMode::Plain, - ) - }, - GeneratedContentInfo::ContentItem(ContentItem::Counters( - ref counter_name, - ref separator, - counter_style, - )) => { - let temporary_counter = Counter::new(); - let counter = self - .traversal - .counters - .get(&*counter_name.0) - .unwrap_or(&temporary_counter); - new_info = counter.render( - self.traversal.layout_context, - fragment.node, - fragment.pseudo, - fragment.style.clone(), - counter_style, - RenderingMode::All(&separator), - ); - }, - GeneratedContentInfo::ContentItem(ContentItem::OpenQuote) => { - new_info = render_text( - self.traversal.layout_context, - fragment.node, - fragment.pseudo, - fragment.style.clone(), - self.quote(&*fragment.style, false), - ); - self.traversal.quote += 1 - }, - GeneratedContentInfo::ContentItem(ContentItem::CloseQuote) => { - if self.traversal.quote >= 1 { - self.traversal.quote -= 1 - } - - new_info = render_text( - self.traversal.layout_context, - fragment.node, - fragment.pseudo, - fragment.style.clone(), - self.quote(&*fragment.style, true), - ); - }, - GeneratedContentInfo::ContentItem(ContentItem::NoOpenQuote) => { - self.traversal.quote += 1 - }, - GeneratedContentInfo::ContentItem(ContentItem::NoCloseQuote) => { - if self.traversal.quote >= 1 { - self.traversal.quote -= 1 - } - }, - GeneratedContentInfo::ContentItem(ContentItem::Url(..)) => { - unreachable!("Servo doesn't parse content: url(..) yet") - }, - } - }; - - fragment.specific = match new_info { - Some(new_info) => new_info, - // If the fragment did not generate any content, replace it with a no-op placeholder - // so that it isn't processed again on the next layout. FIXME (mbrubeck): When - // processing an inline flow, this traversal should be allowed to insert or remove - // fragments. Then we can just remove these fragments rather than adding placeholders. - None => SpecificFragmentInfo::GeneratedContent(Box::new(GeneratedContentInfo::Empty)), - }; - } - - fn reset_and_increment_counters_as_necessary(&mut self, fragment: &mut Fragment) { - let mut list_style_type = fragment.style().get_list().list_style_type; - if !self.is_block || fragment.style().get_box().display != Display::ListItem { - list_style_type = ListStyleType::None - } - - match list_style_type { - ListStyleType::Disc | - ListStyleType::None | - ListStyleType::Circle | - ListStyleType::Square | - ListStyleType::DisclosureOpen | - ListStyleType::DisclosureClosed => {}, - _ => self.traversal.list_item.increment(self.level, 1), - } - - // Truncate down counters. - for (_, counter) in &mut self.traversal.counters { - counter.truncate_to_level(self.level); - } - self.traversal.list_item.truncate_to_level(self.level); - - for pair in &*fragment.style().get_counters().counter_reset { - let counter_name = &*pair.name.0; - if let Some(ref mut counter) = self.traversal.counters.get_mut(counter_name) { - counter.reset(self.level, pair.value); - continue; - } - - let mut counter = Counter::new(); - counter.reset(self.level, pair.value); - self.traversal - .counters - .insert(counter_name.to_owned(), counter); - } - - for pair in &*fragment.style().get_counters().counter_increment { - let counter_name = &*pair.name.0; - if let Some(ref mut counter) = self.traversal.counters.get_mut(counter_name) { - counter.increment(self.level, pair.value); - continue; - } - - let mut counter = Counter::new(); - counter.increment(self.level, pair.value); - self.traversal - .counters - .insert(counter_name.to_owned(), counter); - } - - self.incremented = true - } - - fn quote(&self, style: &ComputedValues, close: bool) -> String { - let quotes = &style.get_list().quotes; - if quotes.0.is_empty() { - return String::new(); - } - let pair = if self.traversal.quote as usize >= quotes.0.len() { - quotes.0.last().unwrap() - } else { - "es.0[self.traversal.quote as usize] - }; - if close { - pair.closing.to_string() - } else { - pair.opening.to_string() - } - } -} - -/// A counter per CSS 2.1 § 12.4. -struct Counter { - /// The values at each level. - values: Vec<CounterValue>, -} - -impl Counter { - fn new() -> Counter { - Counter { values: Vec::new() } - } - - fn reset(&mut self, level: u32, value: i32) { - // Do we have an instance of the counter at this level? If so, just mutate it. - if let Some(ref mut existing_value) = self.values.last_mut() { - if level == existing_value.level { - existing_value.value = value; - return; - } - } - - // Otherwise, push a new instance of the counter. - self.values.push(CounterValue { - level: level, - value: value, - }) - } - - fn truncate_to_level(&mut self, level: u32) { - if let Some(position) = self.values.iter().position(|value| value.level > level) { - self.values.truncate(position) - } - } - - fn increment(&mut self, level: u32, amount: i32) { - if let Some(ref mut value) = self.values.last_mut() { - value.value += amount; - return; - } - - self.values.push(CounterValue { - level: level, - value: amount, - }) - } - - fn render( - &self, - layout_context: &LayoutContext, - node: OpaqueNode, - pseudo: PseudoElementType, - style: crate::ServoArc<ComputedValues>, - list_style_type: ListStyleType, - mode: RenderingMode, - ) -> Option<SpecificFragmentInfo> { - let mut string = String::new(); - match mode { - RenderingMode::Plain => { - let value = match self.values.last() { - Some(ref value) => value.value, - None => 0, - }; - push_representation(value, list_style_type, &mut string) - }, - RenderingMode::Suffix(suffix) => { - let value = match self.values.last() { - Some(ref value) => value.value, - None => 0, - }; - push_representation(value, list_style_type, &mut string); - string.push_str(suffix) - }, - RenderingMode::All(separator) => { - let mut first = true; - for value in &self.values { - if !first { - string.push_str(separator) - } - first = false; - push_representation(value.value, list_style_type, &mut string) - } - }, - } - - if string.is_empty() { - None - } else { - render_text(layout_context, node, pseudo, style, string) - } - } -} - -/// How a counter value is to be rendered. -enum RenderingMode<'a> { - /// The innermost counter value is rendered with no extra decoration. - Plain, - /// The innermost counter value is rendered with the given string suffix. - Suffix(&'a str), - /// All values of the counter are rendered with the given separator string between them. - All(&'a str), -} - -/// The value of a counter at a given level. -struct CounterValue { - /// The level of the flow tree that this corresponds to. - level: u32, - /// The value of the counter at this level. - value: i32, -} - -/// Creates fragment info for a literal string. -fn render_text( - layout_context: &LayoutContext, - node: OpaqueNode, - pseudo: PseudoElementType, - style: crate::ServoArc<ComputedValues>, - string: String, -) -> Option<SpecificFragmentInfo> { - let mut fragments = LinkedList::new(); - let info = SpecificFragmentInfo::UnscannedText(Box::new(UnscannedTextFragmentInfo::new( - string.into_boxed_str(), - None, - ))); - fragments.push_back(Fragment::from_opaque_node_and_style( - node, - pseudo, - style.clone(), - style, - RestyleDamage::rebuild_and_reflow(), - info, - )); - // FIXME(pcwalton): This should properly handle multiple marker fragments. This could happen - // due to text run splitting. - let fragments = with_thread_local_font_context(layout_context, |font_context| { - TextRunScanner::new().scan_for_runs(font_context, fragments) - }); - if fragments.is_empty() { - None - } else { - Some(fragments.fragments.into_iter().next().unwrap().specific) - } -} - -/// Appends string that represents the value rendered using the system appropriate for the given -/// `list-style-type` onto the given string. -fn push_representation(value: i32, list_style_type: ListStyleType, accumulator: &mut String) { - match list_style_type { - ListStyleType::None => {}, - ListStyleType::Disc | - ListStyleType::Circle | - ListStyleType::Square | - ListStyleType::DisclosureOpen | - ListStyleType::DisclosureClosed => accumulator.push(static_representation(list_style_type)), - ListStyleType::Decimal => push_numeric_representation(value, &DECIMAL, accumulator), - ListStyleType::ArabicIndic => { - push_numeric_representation(value, &ARABIC_INDIC, accumulator) - }, - ListStyleType::Bengali => push_numeric_representation(value, &BENGALI, accumulator), - ListStyleType::Cambodian | ListStyleType::Khmer => { - push_numeric_representation(value, &CAMBODIAN, accumulator) - }, - ListStyleType::CjkDecimal => push_numeric_representation(value, &CJK_DECIMAL, accumulator), - ListStyleType::Devanagari => push_numeric_representation(value, &DEVANAGARI, accumulator), - ListStyleType::Gujarati => push_numeric_representation(value, &GUJARATI, accumulator), - ListStyleType::Gurmukhi => push_numeric_representation(value, &GURMUKHI, accumulator), - ListStyleType::Kannada => push_numeric_representation(value, &KANNADA, accumulator), - ListStyleType::Lao => push_numeric_representation(value, &LAO, accumulator), - ListStyleType::Malayalam => push_numeric_representation(value, &MALAYALAM, accumulator), - ListStyleType::Mongolian => push_numeric_representation(value, &MONGOLIAN, accumulator), - ListStyleType::Myanmar => push_numeric_representation(value, &MYANMAR, accumulator), - ListStyleType::Oriya => push_numeric_representation(value, &ORIYA, accumulator), - ListStyleType::Persian => push_numeric_representation(value, &PERSIAN, accumulator), - ListStyleType::Telugu => push_numeric_representation(value, &TELUGU, accumulator), - ListStyleType::Thai => push_numeric_representation(value, &THAI, accumulator), - ListStyleType::Tibetan => push_numeric_representation(value, &TIBETAN, accumulator), - ListStyleType::LowerAlpha => { - push_alphabetic_representation(value, &LOWER_ALPHA, accumulator) - }, - ListStyleType::UpperAlpha => { - push_alphabetic_representation(value, &UPPER_ALPHA, accumulator) - }, - ListStyleType::CjkEarthlyBranch => { - push_alphabetic_representation(value, &CJK_EARTHLY_BRANCH, accumulator) - }, - ListStyleType::CjkHeavenlyStem => { - push_alphabetic_representation(value, &CJK_HEAVENLY_STEM, accumulator) - }, - ListStyleType::LowerGreek => { - push_alphabetic_representation(value, &LOWER_GREEK, accumulator) - }, - ListStyleType::Hiragana => push_alphabetic_representation(value, &HIRAGANA, accumulator), - ListStyleType::HiraganaIroha => { - push_alphabetic_representation(value, &HIRAGANA_IROHA, accumulator) - }, - ListStyleType::Katakana => push_alphabetic_representation(value, &KATAKANA, accumulator), - ListStyleType::KatakanaIroha => { - push_alphabetic_representation(value, &KATAKANA_IROHA, accumulator) - }, - } -} - -/// Returns the static character that represents the value rendered using the given list-style, if -/// possible. -pub fn static_representation(list_style_type: ListStyleType) -> char { - match list_style_type { - ListStyleType::Disc => '•', - ListStyleType::Circle => '◦', - ListStyleType::Square => '▪', - ListStyleType::DisclosureOpen => '▾', - ListStyleType::DisclosureClosed => '‣', - _ => panic!("No static representation for this list-style-type!"), - } -} - -/// Pushes the string that represents the value rendered using the given *alphabetic system* onto -/// the accumulator per CSS-COUNTER-STYLES § 3.1.4. -fn push_alphabetic_representation(value: i32, system: &[char], accumulator: &mut String) { - let mut abs_value = handle_negative_value(value, accumulator); - - let mut string: SmallVec<[char; 8]> = SmallVec::new(); - while abs_value != 0 { - // Step 1. - abs_value = abs_value - 1; - // Step 2. - string.push(system[abs_value % system.len()]); - // Step 3. - abs_value = abs_value / system.len(); - } - - accumulator.extend(string.iter().cloned().rev()) -} - -/// Pushes the string that represents the value rendered using the given *numeric system* onto the -/// accumulator per CSS-COUNTER-STYLES § 3.1.5. -fn push_numeric_representation(value: i32, system: &[char], accumulator: &mut String) { - let mut abs_value = handle_negative_value(value, accumulator); - - // Step 1. - if abs_value == 0 { - accumulator.push(system[0]); - return; - } - - // Step 2. - let mut string: SmallVec<[char; 8]> = SmallVec::new(); - while abs_value != 0 { - // Step 2.1. - string.push(system[abs_value % system.len()]); - // Step 2.2. - abs_value = abs_value / system.len(); - } - - // Step 3. - accumulator.extend(string.iter().cloned().rev()) -} - -/// If the system uses a negative sign, handle negative values per CSS-COUNTER-STYLES § 2. -/// -/// Returns the absolute value of the counter. -fn handle_negative_value(value: i32, accumulator: &mut String) -> usize { - // 3. If the counter value is negative and the counter style uses a negative sign, instead - // generate an initial representation using the absolute value of the counter value. - if value < 0 { - // TODO: Support different negative signs using the 'negative' descriptor. - // https://drafts.csswg.org/date/2015-07-16/css-counter-styles/#counter-style-negative - accumulator.push('-'); - value.abs() as usize - } else { - value as usize - } -} diff --git a/components/layout_2020/incremental.rs b/components/layout_2020/incremental.rs deleted file mode 100644 index 4e313f088bb..00000000000 --- a/components/layout_2020/incremental.rs +++ /dev/null @@ -1,101 +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/. */ - -use crate::flow::{Flow, FlowFlags, GetBaseFlow}; -use style::computed_values::float::T as Float; -use style::selector_parser::RestyleDamage; -use style::servo::restyle_damage::ServoRestyleDamage; - -/// Used in a flow traversal to indicate whether this re-layout should be incremental or not. -#[derive(Clone, Copy, PartialEq)] -pub enum RelayoutMode { - Incremental, - Force, -} - -bitflags! { - pub struct SpecialRestyleDamage: u8 { - #[doc = "If this flag is set, we need to reflow the entire document. This is more or less a \ - temporary hack to deal with cases that we don't handle incrementally yet."] - const REFLOW_ENTIRE_DOCUMENT = 0x01; - } -} - -impl dyn Flow { - pub fn compute_layout_damage(&mut self) -> SpecialRestyleDamage { - let mut special_damage = SpecialRestyleDamage::empty(); - let is_absolutely_positioned = self - .base() - .flags - .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED); - - // In addition to damage, we use this phase to compute whether nodes affect CSS counters. - let mut has_counter_affecting_children = false; - - { - let self_base = self.mut_base(); - // Take a snapshot of the parent damage before updating it with damage from children. - let parent_damage = self_base.restyle_damage; - - for kid in self_base.children.iter_mut() { - let child_is_absolutely_positioned = kid - .base() - .flags - .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED); - kid.mut_base().restyle_damage.insert( - parent_damage - .damage_for_child(is_absolutely_positioned, child_is_absolutely_positioned), - ); - { - let kid: &mut dyn Flow = kid; - special_damage.insert(kid.compute_layout_damage()); - } - self_base.restyle_damage.insert( - kid.base() - .restyle_damage - .damage_for_parent(child_is_absolutely_positioned), - ); - - has_counter_affecting_children = has_counter_affecting_children || - kid.base().flags.intersects( - FlowFlags::AFFECTS_COUNTERS | FlowFlags::HAS_COUNTER_AFFECTING_CHILDREN, - ); - } - } - - let self_base = self.mut_base(); - if self_base.flags.float_kind() != Float::None && - self_base - .restyle_damage - .intersects(ServoRestyleDamage::REFLOW) - { - special_damage.insert(SpecialRestyleDamage::REFLOW_ENTIRE_DOCUMENT); - } - - if has_counter_affecting_children { - self_base - .flags - .insert(FlowFlags::HAS_COUNTER_AFFECTING_CHILDREN) - } else { - self_base - .flags - .remove(FlowFlags::HAS_COUNTER_AFFECTING_CHILDREN) - } - - special_damage - } - - pub fn reflow_entire_document(&mut self) { - let self_base = self.mut_base(); - self_base - .restyle_damage - .insert(RestyleDamage::rebuild_and_reflow()); - self_base - .restyle_damage - .remove(ServoRestyleDamage::RECONSTRUCT_FLOW); - for kid in self_base.children.iter_mut() { - kid.reflow_entire_document(); - } - } -} diff --git a/components/layout_2020/inline.rs b/components/layout_2020/inline.rs deleted file mode 100644 index f4dacefc5ef..00000000000 --- a/components/layout_2020/inline.rs +++ /dev/null @@ -1,2220 +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/. */ - -use crate::block::AbsoluteAssignBSizesTraversal; -use crate::context::{LayoutContext, LayoutFontContext}; -use crate::display_list::items::{DisplayListSection, OpaqueNode}; -use crate::display_list::{ - BorderPaintingMode, DisplayListBuildState, StackingContextCollectionState, -}; -use crate::floats::{FloatKind, Floats, PlacementInfo}; -use crate::flow::{BaseFlow, Flow, FlowClass, ForceNonfloatedFlag}; -use crate::flow::{EarlyAbsolutePositionInfo, FlowFlags, GetBaseFlow, OpaqueFlow}; -use crate::flow_ref::FlowRef; -use crate::fragment::FragmentFlags; -use crate::fragment::SpecificFragmentInfo; -use crate::fragment::{CoordinateSystem, Fragment, FragmentBorderBoxIterator, Overflow}; -use crate::layout_debug; -use crate::model::IntrinsicISizesContribution; -use crate::text; -use crate::traversal::PreorderFlowTraversal; -use crate::ServoArc; -use app_units::{Au, MIN_AU}; -use euclid::{Point2D, Rect, Size2D}; -use gfx::font::FontMetrics; -use gfx_traits::print_tree::PrintTree; -use range::{Range, RangeIndex}; -use script_layout_interface::wrapper_traits::PseudoElementType; -use servo_geometry::MaxRect; -use std::cmp::max; -use std::collections::VecDeque; -use std::sync::Arc; -use std::{fmt, i32, isize, mem}; -use style::computed_values::display::T as Display; -use style::computed_values::overflow_x::T as StyleOverflow; -use style::computed_values::position::T as Position; -use style::computed_values::text_align::T as TextAlign; -use style::computed_values::text_justify::T as TextJustify; -use style::computed_values::white_space::T as WhiteSpace; -use style::logical_geometry::{LogicalRect, LogicalSize, WritingMode}; -use style::properties::ComputedValues; -use style::servo::restyle_damage::ServoRestyleDamage; -use style::values::computed::box_::VerticalAlign; -use style::values::generics::box_::VerticalAlignKeyword; -use style::values::specified::text::TextOverflowSide; -use unicode_bidi as bidi; - -/// `Line`s are represented as offsets into the child list, rather than -/// as an object that "owns" fragments. Choosing a different set of line -/// breaks requires a new list of offsets, and possibly some splitting and -/// merging of TextFragments. -/// -/// A similar list will keep track of the mapping between CSS fragments and -/// the corresponding fragments in the inline flow. -/// -/// After line breaks are determined, render fragments in the inline flow may -/// overlap visually. For example, in the case of nested inline CSS fragments, -/// outer inlines must be at least as large as the inner inlines, for -/// purposes of drawing noninherited things like backgrounds, borders, -/// outlines. -/// -/// N.B. roc has an alternative design where the list instead consists of -/// things like "start outer fragment, text, start inner fragment, text, end inner -/// fragment, text, end outer fragment, text". This seems a little complicated to -/// serve as the starting point, but the current design doesn't make it -/// hard to try out that alternative. -/// -/// Line fragments also contain some metadata used during line breaking. The -/// green zone is the area that the line can expand to before it collides -/// with a float or a horizontal wall of the containing block. The block-start -/// inline-start corner of the green zone is the same as that of the line, but -/// the green zone can be taller and wider than the line itself. -#[derive(Clone, Debug, Serialize)] -pub struct Line { - /// A range of line indices that describe line breaks. - /// - /// For example, consider the following HTML and rendered element with - /// linebreaks: - /// - /// ~~~html - /// <span>I <span>like truffles, <img></span> yes I do.</span> - /// ~~~ - /// - /// ~~~text - /// +------------+ - /// | I like | - /// | truffles, | - /// | +----+ | - /// | | | | - /// | +----+ yes | - /// | I do. | - /// +------------+ - /// ~~~ - /// - /// The ranges that describe these lines would be: - /// - /// | [0, 2) | [2, 3) | [3, 5) | [5, 6) | - /// |----------|-------------|-------------|----------| - /// | 'I like' | 'truffles,' | '<img> yes' | 'I do.' | - pub range: Range<FragmentIndex>, - - /// The bidirectional embedding level runs for this line, in visual order. - /// - /// Can be set to `None` if the line is 100% left-to-right. - pub visual_runs: Option<Vec<(Range<FragmentIndex>, bidi::Level)>>, - - /// The bounds are the exact position and extents of the line with respect - /// to the parent box. - /// - /// For example, for the HTML below... - /// - /// ~~~html - /// <div><span>I like truffles, <img></span></div> - /// ~~~ - /// - /// ...the bounds would be: - /// - /// ~~~text - /// +-----------------------------------------------------------+ - /// | ^ | - /// | | | - /// | origin.y | - /// | | | - /// | v | - /// |< - origin.x ->+ - - - - - - - - +---------+---- | - /// | | | | ^ | - /// | | | <img> | size.block | - /// | I like truffles, | | v | - /// | + - - - - - - - - +---------+---- | - /// | | | | - /// | |<------ size.inline ------>| | - /// | | - /// | | - /// +-----------------------------------------------------------+ - /// ~~~ - pub bounds: LogicalRect<Au>, - - /// The green zone is the greatest extent from which a line can extend to - /// before it collides with a float. - /// - /// ~~~text - /// +-----------------------+ - /// |::::::::::::::::: | - /// |:::::::::::::::::FFFFFF| - /// |============:::::FFFFFF| - /// |:::::::::::::::::FFFFFF| - /// |:::::::::::::::::FFFFFF| - /// |::::::::::::::::: | - /// | FFFFFFFFF | - /// | FFFFFFFFF | - /// | FFFFFFFFF | - /// | | - /// +-----------------------+ - /// - /// === line - /// ::: green zone - /// FFF float - /// ~~~ - pub green_zone: LogicalSize<Au>, - - /// The minimum metrics for this line, as specified by the style. - pub minimum_metrics: LineMetrics, - - /// The actual metrics for this line. - pub metrics: LineMetrics, -} - -impl Line { - fn new(writing_mode: WritingMode, minimum_metrics: &LineMetrics) -> Line { - Line { - range: Range::empty(), - visual_runs: None, - bounds: LogicalRect::zero(writing_mode), - green_zone: LogicalSize::zero(writing_mode), - minimum_metrics: *minimum_metrics, - metrics: *minimum_metrics, - } - } - - /// Returns the new metrics that this line would have if `new_fragment` were added to it. - /// - /// FIXME(pcwalton): this assumes that the tallest fragment in the line determines the line - /// block-size. This might not be the case with some weird text fonts. - fn new_metrics_for_fragment( - &self, - new_fragment: &Fragment, - layout_context: &LayoutContext, - ) -> LineMetrics { - if !new_fragment.is_vertically_aligned_to_top_or_bottom() { - let fragment_inline_metrics = - new_fragment.aligned_inline_metrics(layout_context, &self.minimum_metrics, None); - self.metrics - .new_metrics_for_fragment(&fragment_inline_metrics) - } else { - self.metrics - } - } - - /// Returns the new block size that this line would have if `new_fragment` were added to it. - /// `new_inline_metrics` represents the new inline metrics that this line would have; it can - /// be computed with `new_inline_metrics()`. - fn new_block_size_for_fragment( - &self, - new_fragment: &Fragment, - new_line_metrics: &LineMetrics, - layout_context: &LayoutContext, - ) -> Au { - let new_block_size = if new_fragment.is_vertically_aligned_to_top_or_bottom() { - max( - new_fragment - .aligned_inline_metrics(layout_context, &self.minimum_metrics, None) - .space_needed(), - self.minimum_metrics.space_needed(), - ) - } else { - new_line_metrics.space_needed() - }; - max(self.bounds.size.block, new_block_size) - } -} - -int_range_index! { - #[derive(Serialize)] - #[doc = "The index of a fragment in a flattened vector of DOM elements."] - struct FragmentIndex(isize) -} - -/// Arranges fragments into lines, splitting them up as necessary. -struct LineBreaker { - /// The floats we need to flow around. - floats: Floats, - /// The resulting fragment list for the flow, consisting of possibly-broken fragments. - new_fragments: Vec<Fragment>, - /// The next fragment or fragments that we need to work on. - work_list: VecDeque<Fragment>, - /// The line we're currently working on. - pending_line: Line, - /// The lines we've already committed. - lines: Vec<Line>, - /// The index of the last known good line breaking opportunity. The opportunity will either - /// be inside this fragment (if it is splittable) or immediately prior to it. - last_known_line_breaking_opportunity: Option<FragmentIndex>, - /// The current position in the block direction. - cur_b: Au, - /// The computed value of the indentation for the first line (`text-indent`, CSS 2.1 § 16.1). - first_line_indentation: Au, - /// The minimum metrics for each line, as specified by the line height and font style. - minimum_metrics: LineMetrics, -} - -impl LineBreaker { - /// Creates a new `LineBreaker` with a set of floats and the indentation of the first line. - fn new( - float_context: Floats, - first_line_indentation: Au, - minimum_line_metrics: &LineMetrics, - ) -> LineBreaker { - LineBreaker { - new_fragments: Vec::new(), - work_list: VecDeque::new(), - pending_line: Line::new(float_context.writing_mode, minimum_line_metrics), - floats: float_context, - lines: Vec::new(), - cur_b: Au(0), - last_known_line_breaking_opportunity: None, - first_line_indentation: first_line_indentation, - minimum_metrics: *minimum_line_metrics, - } - } - - /// Resets the `LineBreaker` to the initial state it had after a call to `new`. - fn reset_scanner(&mut self) { - self.lines = Vec::new(); - self.new_fragments = Vec::new(); - self.cur_b = Au(0); - self.reset_line(); - } - - /// Reinitializes the pending line to blank data. - fn reset_line(&mut self) { - self.last_known_line_breaking_opportunity = None; - // https://github.com/rust-lang/rust/issues/49282 - self.pending_line = Line::new(self.floats.writing_mode, &self.minimum_metrics); - } - - /// Reflows fragments for the given inline flow. - fn scan_for_lines(&mut self, flow: &mut InlineFlow, layout_context: &LayoutContext) { - self.reset_scanner(); - - // Create our fragment iterator. - debug!( - "LineBreaker: scanning for lines, {} fragments", - flow.fragments.len() - ); - let mut old_fragments = mem::replace(&mut flow.fragments, InlineFragments::new()); - let old_fragment_iter = old_fragments.fragments.into_iter(); - - // TODO(pcwalton): This would likely be better as a list of dirty line - // indices. That way we could resynchronize if we discover during reflow - // that all subsequent fragments must have the same position as they had - // in the previous reflow. I don't know how common this case really is - // in practice, but it's probably worth handling. - self.lines = Vec::new(); - - // Do the reflow. - self.reflow_fragments(old_fragment_iter, flow, layout_context); - - // Perform unicode bidirectional layout. - let para_level = flow.base.writing_mode.to_bidi_level(); - - // The text within a fragment is at a single bidi embedding level - // (because we split fragments on level run boundaries during flow - // construction), so we can build a level array with just one entry per - // fragment. - let levels: Vec<bidi::Level> = self - .new_fragments - .iter() - .map(|fragment| match fragment.specific { - SpecificFragmentInfo::ScannedText(ref info) => info.run.bidi_level, - _ => para_level, - }) - .collect(); - - let mut lines = mem::replace(&mut self.lines, Vec::new()); - - // If everything is LTR, don't bother with reordering. - if bidi::level::has_rtl(&levels) { - // Compute and store the visual ordering of the fragments within the - // line. - for line in &mut lines { - let range = line.range.begin().to_usize()..line.range.end().to_usize(); - // FIXME: Update to use BidiInfo::visual_runs, as this algorithm needs access to - // the original text and original BidiClass of its characters. - #[allow(deprecated)] - let runs = bidi::deprecated::visual_runs(range, &levels); - line.visual_runs = Some( - runs.iter() - .map(|run| { - let start = FragmentIndex(run.start as isize); - let len = FragmentIndex(run.len() as isize); - (Range::new(start, len), levels[run.start]) - }) - .collect(), - ); - } - } - - // Place the fragments back into the flow. - old_fragments.fragments = mem::replace(&mut self.new_fragments, vec![]); - flow.fragments = old_fragments; - flow.lines = lines; - } - - /// Reflows the given fragments, which have been plucked out of the inline flow. - fn reflow_fragments<'a, I>( - &mut self, - mut old_fragment_iter: I, - flow: &'a InlineFlow, - layout_context: &LayoutContext, - ) where - I: Iterator<Item = Fragment>, - { - loop { - // Acquire the next fragment to lay out from the work list or fragment list, as - // appropriate. - let fragment = match self.next_unbroken_fragment(&mut old_fragment_iter) { - None => break, - Some(fragment) => fragment, - }; - - // Do not reflow truncated fragments. Reflow the original fragment only. - let fragment = if fragment.flags.contains(FragmentFlags::IS_ELLIPSIS) { - continue; - } else if let SpecificFragmentInfo::TruncatedFragment(info) = fragment.specific { - info.full - } else { - fragment - }; - - // Try to append the fragment. - self.reflow_fragment(fragment, flow, layout_context); - } - - if !self.pending_line_is_empty() { - debug!( - "LineBreaker: partially full line {} at end of scanning; committing it", - self.lines.len() - ); - self.flush_current_line() - } - } - - /// Acquires a new fragment to lay out from the work list or fragment list as appropriate. - /// Note that you probably don't want to call this method directly in order to be incremental- - /// reflow-safe; try `next_unbroken_fragment` instead. - fn next_fragment<I>(&mut self, old_fragment_iter: &mut I) -> Option<Fragment> - where - I: Iterator<Item = Fragment>, - { - self.work_list - .pop_front() - .or_else(|| old_fragment_iter.next()) - } - - /// Acquires a new fragment to lay out from the work list or fragment list, - /// merging it with any subsequent fragments as appropriate. In effect, what - /// this method does is to return the next fragment to lay out, undoing line - /// break operations that any previous reflows may have performed. You - /// probably want to be using this method instead of `next_fragment`. - fn next_unbroken_fragment<I>(&mut self, old_fragment_iter: &mut I) -> Option<Fragment> - where - I: Iterator<Item = Fragment>, - { - let mut result = self.next_fragment(old_fragment_iter)?; - - loop { - let candidate = match self.next_fragment(old_fragment_iter) { - None => return Some(result), - Some(fragment) => fragment, - }; - - let need_to_merge = match (&mut result.specific, &candidate.specific) { - ( - &mut SpecificFragmentInfo::ScannedText(ref mut result_info), - &SpecificFragmentInfo::ScannedText(ref candidate_info), - ) => { - result.margin.inline_end == Au(0) && - candidate.margin.inline_start == Au(0) && - result.border_padding.inline_end == Au(0) && - candidate.border_padding.inline_start == Au(0) && - result_info.selected() == candidate_info.selected() && - Arc::ptr_eq(&result_info.run, &candidate_info.run) && - inline_contexts_are_equal( - &result.inline_context, - &candidate.inline_context, - ) - }, - _ => false, - }; - - if need_to_merge { - result.merge_with(candidate); - continue; - } - - self.work_list.push_front(candidate); - return Some(result); - } - } - - /// Commits a line to the list. - fn flush_current_line(&mut self) { - debug!( - "LineBreaker: flushing line {}: {:?}", - self.lines.len(), - self.pending_line - ); - self.strip_trailing_whitespace_from_pending_line_if_necessary(); - self.lines.push(self.pending_line.clone()); - self.cur_b = self.pending_line.bounds.start.b + self.pending_line.bounds.size.block; - self.reset_line(); - } - - /// Removes trailing whitespace from the pending line if necessary. This is done right before - /// flushing it. - fn strip_trailing_whitespace_from_pending_line_if_necessary(&mut self) { - if self.pending_line.range.is_empty() { - return; - } - let last_fragment_index = self.pending_line.range.end() - FragmentIndex(1); - let fragment = &mut self.new_fragments[last_fragment_index.get() as usize]; - - let old_fragment_inline_size = fragment.border_box.size.inline; - - fragment.strip_trailing_whitespace_if_necessary(); - - self.pending_line.bounds.size.inline += - fragment.border_box.size.inline - old_fragment_inline_size; - } - - /// Computes the position of a line that has only the provided fragment. Returns the bounding - /// rect of the line's green zone (whose origin coincides with the line's origin) and the - /// actual inline-size of the first fragment after splitting. - fn initial_line_placement( - &self, - flow: &InlineFlow, - first_fragment: &Fragment, - ceiling: Au, - ) -> (LogicalRect<Au>, Au) { - debug!( - "LineBreaker: trying to place first fragment of line {}; fragment size: {:?}, \ - splittable: {}", - self.lines.len(), - first_fragment.border_box.size, - first_fragment.can_split() - ); - - // Initially, pretend a splittable fragment has zero inline-size. We will move it later if - // it has nonzero inline-size and that causes problems. - let placement_inline_size = if first_fragment.can_split() { - first_fragment.minimum_splittable_inline_size() - } else { - first_fragment.margin_box_inline_size() + self.indentation_for_pending_fragment() - }; - - // Try to place the fragment between floats. - let line_bounds = self.floats.place_between_floats(&PlacementInfo { - size: LogicalSize::new( - self.floats.writing_mode, - placement_inline_size, - first_fragment.border_box.size.block, - ), - ceiling: ceiling, - max_inline_size: flow.base.position.size.inline, - kind: FloatKind::Left, - }); - - let fragment_margin_box_inline_size = first_fragment.margin_box_inline_size(); - - // Simple case: if the fragment fits, then we can stop here. - if line_bounds.size.inline > fragment_margin_box_inline_size { - debug!("LineBreaker: fragment fits on line {}", self.lines.len()); - return (line_bounds, fragment_margin_box_inline_size); - } - - // If not, but we can't split the fragment, then we'll place the line here and it will - // overflow. - if !first_fragment.can_split() { - debug!("LineBreaker: line doesn't fit, but is unsplittable"); - } - - (line_bounds, fragment_margin_box_inline_size) - } - - /// Performs float collision avoidance. This is called when adding a fragment is going to - /// increase the block-size, and because of that we will collide with some floats. - /// - /// We have two options here: - /// 1) Move the entire line so that it doesn't collide any more. - /// 2) Break the line and put the new fragment on the next line. - /// - /// The problem with option 1 is that we might move the line and then wind up breaking anyway, - /// which violates the standard. But option 2 is going to look weird sometimes. - /// - /// So we'll try to move the line whenever we can, but break if we have to. - /// - /// Returns false if and only if we should break the line. - fn avoid_floats( - &mut self, - flow: &InlineFlow, - in_fragment: Fragment, - new_block_size: Au, - ) -> bool { - debug!("LineBreaker: entering float collision avoider!"); - - // First predict where the next line is going to be. - let (next_line, first_fragment_inline_size) = - self.initial_line_placement(flow, &in_fragment, self.pending_line.bounds.start.b); - let next_green_zone = next_line.size; - - let new_inline_size = self.pending_line.bounds.size.inline + first_fragment_inline_size; - - // Now, see if everything can fit at the new location. - if next_green_zone.inline >= new_inline_size && next_green_zone.block >= new_block_size { - debug!( - "LineBreaker: case=adding fragment collides vertically with floats: moving \ - line" - ); - - self.pending_line.bounds.start = next_line.start; - self.pending_line.green_zone = next_green_zone; - - debug_assert!( - !self.pending_line_is_empty(), - "Non-terminating line breaking" - ); - self.work_list.push_front(in_fragment); - return true; - } - - debug!("LineBreaker: case=adding fragment collides vertically with floats: breaking line"); - self.work_list.push_front(in_fragment); - false - } - - /// Tries to append the given fragment to the line, splitting it if necessary. Commits the - /// current line if needed. - fn reflow_fragment( - &mut self, - mut fragment: Fragment, - flow: &InlineFlow, - layout_context: &LayoutContext, - ) { - // Undo any whitespace stripping from previous reflows. - fragment.reset_text_range_and_inline_size(); - // Determine initial placement for the fragment if we need to. - // - // Also, determine whether we can legally break the line before, or - // inside, this fragment. - let fragment_is_line_break_opportunity = if self.pending_line_is_empty() { - fragment.strip_leading_whitespace_if_necessary(); - let (line_bounds, _) = self.initial_line_placement(flow, &fragment, self.cur_b); - self.pending_line.bounds.start = line_bounds.start; - self.pending_line.green_zone = line_bounds.size; - false - } else { - // In case of Foo<span style="...">bar</span>, the line breaker will - // set the "suppress line break before" flag for the second fragment. - // - // In case of Foo<span>bar</span> the second fragment ("bar") will - // start _within_ a glyph run, so we also avoid breaking there - // - // is_on_glyph_run_boundary does a binary search, but this is ok - // because the result will be cached and reused in - // `calculate_split_position` later - if fragment.suppress_line_break_before() || !fragment.is_on_glyph_run_boundary() { - false - } else { - fragment.white_space().allow_wrap() - } - }; - - debug!( - "LineBreaker: trying to append to line {} \ - (fragment size: {:?}, green zone: {:?}): {:?}", - self.lines.len(), - fragment.border_box.size, - self.pending_line.green_zone, - fragment - ); - - // NB: At this point, if `green_zone.inline < - // self.pending_line.bounds.size.inline` or `green_zone.block < - // self.pending_line.bounds.size.block`, then we committed a line that - // overlaps with floats. - let green_zone = self.pending_line.green_zone; - let new_line_metrics = self - .pending_line - .new_metrics_for_fragment(&fragment, layout_context); - let new_block_size = self.pending_line.new_block_size_for_fragment( - &fragment, - &new_line_metrics, - layout_context, - ); - if new_block_size > green_zone.block { - // Uh-oh. Float collision imminent. Enter the float collision avoider! - if !self.avoid_floats(flow, fragment, new_block_size) { - self.flush_current_line(); - } - return; - } - - // Record the last known good line break opportunity if this is one. - if fragment_is_line_break_opportunity { - self.last_known_line_breaking_opportunity = Some(self.pending_line.range.end()) - } - - // If we must flush the line after finishing this fragment due to `white-space: pre`, - // detect that. - let line_flush_mode = if fragment.white_space().preserve_newlines() { - if fragment.requires_line_break_afterward_if_wrapping_on_newlines() { - LineFlushMode::Flush - } else { - LineFlushMode::No - } - } else { - LineFlushMode::No - }; - - // If we're not going to overflow the green zone vertically, we might still do so - // horizontally. We'll try to place the whole fragment on this line and break somewhere if - // it doesn't fit. - let indentation = self.indentation_for_pending_fragment(); - let new_inline_size = - self.pending_line.bounds.size.inline + fragment.margin_box_inline_size() + indentation; - if new_inline_size <= green_zone.inline { - debug!("LineBreaker: fragment fits without splitting"); - self.push_fragment_to_line(layout_context, fragment, line_flush_mode); - return; - } - - // If the wrapping mode prevents us from splitting, then back up and split at the last - // known good split point. - if !fragment.white_space().allow_wrap() { - debug!( - "LineBreaker: fragment can't split; falling back to last known good split point" - ); - self.split_line_at_last_known_good_position(layout_context, fragment, line_flush_mode); - return; - } - - // Split it up! - let available_inline_size = - green_zone.inline - self.pending_line.bounds.size.inline - indentation; - let inline_start_fragment; - let inline_end_fragment; - let split_result = match fragment - .calculate_split_position(available_inline_size, self.pending_line_is_empty()) - { - None => { - // We failed to split. Defer to the next line if we're allowed to; otherwise, - // rewind to the last line breaking opportunity. - if fragment_is_line_break_opportunity { - debug!("LineBreaker: fragment was unsplittable; deferring to next line"); - self.work_list.push_front(fragment); - self.flush_current_line(); - } else { - self.split_line_at_last_known_good_position( - layout_context, - fragment, - LineFlushMode::No, - ); - } - return; - }, - Some(split_result) => split_result, - }; - - inline_start_fragment = split_result - .inline_start - .as_ref() - .map(|x| fragment.transform_with_split_info(x, split_result.text_run.clone(), true)); - inline_end_fragment = split_result - .inline_end - .as_ref() - .map(|x| fragment.transform_with_split_info(x, split_result.text_run.clone(), false)); - - // Push the first fragment onto the line we're working on and start off the next line with - // the second fragment. If there's no second fragment, the next line will start off empty. - match (inline_start_fragment, inline_end_fragment) { - (Some(mut inline_start_fragment), Some(mut inline_end_fragment)) => { - inline_start_fragment.border_padding.inline_end = Au(0); - if let Some(ref mut inline_context) = inline_start_fragment.inline_context { - for node in &mut inline_context.nodes { - node.flags - .remove(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT); - } - } - inline_start_fragment.border_box.size.inline += - inline_start_fragment.border_padding.inline_start; - - inline_end_fragment.border_padding.inline_start = Au(0); - if let Some(ref mut inline_context) = inline_end_fragment.inline_context { - for node in &mut inline_context.nodes { - node.flags - .remove(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT); - } - } - inline_end_fragment.border_box.size.inline += - inline_end_fragment.border_padding.inline_end; - - self.push_fragment_to_line( - layout_context, - inline_start_fragment, - LineFlushMode::Flush, - ); - self.work_list.push_front(inline_end_fragment) - }, - (Some(fragment), None) => { - self.push_fragment_to_line(layout_context, fragment, line_flush_mode); - }, - (None, Some(fragment)) => { - // Yes, this can happen! - self.flush_current_line(); - self.work_list.push_front(fragment) - }, - (None, None) => {}, - } - } - - /// Pushes a fragment to the current line unconditionally, possibly truncating it and placing - /// an ellipsis based on the value of `text-overflow`. If `flush_line` is `Flush`, then flushes - /// the line afterward; - fn push_fragment_to_line( - &mut self, - layout_context: &LayoutContext, - fragment: Fragment, - line_flush_mode: LineFlushMode, - ) { - let indentation = self.indentation_for_pending_fragment(); - if self.pending_line_is_empty() { - debug_assert!(self.new_fragments.len() <= (isize::MAX as usize)); - self.pending_line.range.reset( - FragmentIndex(self.new_fragments.len() as isize), - FragmentIndex(0), - ); - } - - // Determine if an ellipsis will be necessary to account for `text-overflow`. - let available_inline_size = self.pending_line.green_zone.inline - - self.pending_line.bounds.size.inline - - indentation; - - let ellipsis = match ( - &fragment.style().get_text().text_overflow.second, - fragment.style().get_box().overflow_x, - ) { - (&TextOverflowSide::Clip, _) | (_, StyleOverflow::Visible) => None, - (&TextOverflowSide::Ellipsis, _) => { - if fragment.margin_box_inline_size() > available_inline_size { - Some("…".to_string()) - } else { - None - } - }, - (&TextOverflowSide::String(ref string), _) => { - if fragment.margin_box_inline_size() > available_inline_size { - Some(string.to_string()) - } else { - None - } - }, - }; - - if let Some(string) = ellipsis { - let ellipsis = fragment.transform_into_ellipsis(layout_context, string); - let truncated = fragment - .truncate_to_inline_size(available_inline_size - ellipsis.margin_box_inline_size()); - self.push_fragment_to_line_ignoring_text_overflow(truncated, layout_context); - self.push_fragment_to_line_ignoring_text_overflow(ellipsis, layout_context); - } else { - self.push_fragment_to_line_ignoring_text_overflow(fragment, layout_context); - } - - if line_flush_mode == LineFlushMode::Flush { - self.flush_current_line() - } - } - - /// Pushes a fragment to the current line unconditionally, without placing an ellipsis in the - /// case of `text-overflow: ellipsis`. - fn push_fragment_to_line_ignoring_text_overflow( - &mut self, - fragment: Fragment, - layout_context: &LayoutContext, - ) { - let indentation = self.indentation_for_pending_fragment(); - self.pending_line.range.extend_by(FragmentIndex(1)); - - if !fragment.is_inline_absolute() && !fragment.is_hypothetical() { - self.pending_line.bounds.size.inline = self.pending_line.bounds.size.inline + - fragment.margin_box_inline_size() + - indentation; - self.pending_line.metrics = self - .pending_line - .new_metrics_for_fragment(&fragment, layout_context); - self.pending_line.bounds.size.block = self.pending_line.new_block_size_for_fragment( - &fragment, - &self.pending_line.metrics, - layout_context, - ); - } - - self.new_fragments.push(fragment); - } - - fn split_line_at_last_known_good_position( - &mut self, - layout_context: &LayoutContext, - cur_fragment: Fragment, - line_flush_mode: LineFlushMode, - ) { - let last_known_line_breaking_opportunity = match self.last_known_line_breaking_opportunity { - None => { - // No line breaking opportunity exists at all for this line. Overflow. - self.push_fragment_to_line(layout_context, cur_fragment, line_flush_mode); - return; - }, - Some(last_known_line_breaking_opportunity) => last_known_line_breaking_opportunity, - }; - - self.work_list.push_front(cur_fragment); - for fragment_index in - (last_known_line_breaking_opportunity.get()..self.pending_line.range.end().get()).rev() - { - debug_assert_eq!(fragment_index, (self.new_fragments.len() as isize) - 1); - self.work_list.push_front(self.new_fragments.pop().unwrap()); - } - - // FIXME(pcwalton): This should actually attempt to split the last fragment if - // possible to do so, to handle cases like: - // - // (available width) - // +-------------+ - // The alphabet - // (<em>abcdefghijklmnopqrstuvwxyz</em>) - // - // Here, the last known-good split point is inside the fragment containing - // "The alphabet (", which has already been committed by the time we get to this - // point. Unfortunately, the existing splitting API (`calculate_split_position`) - // has no concept of "split right before the last non-whitespace position". We'll - // need to add that feature to the API to handle this case correctly. - self.pending_line - .range - .extend_to(last_known_line_breaking_opportunity); - self.flush_current_line(); - } - - /// Returns the indentation that needs to be applied before the fragment we're reflowing. - fn indentation_for_pending_fragment(&self) -> Au { - if self.pending_line_is_empty() && self.lines.is_empty() { - self.first_line_indentation - } else { - Au(0) - } - } - - /// Returns true if the pending line is empty and false otherwise. - fn pending_line_is_empty(&self) -> bool { - self.pending_line.range.length() == FragmentIndex(0) - } -} - -/// Represents a list of inline fragments, including element ranges. -#[derive(Clone, Serialize)] -pub struct InlineFragments { - /// The fragments themselves. - pub fragments: Vec<Fragment>, -} - -impl InlineFragments { - /// Creates an empty set of inline fragments. - pub fn new() -> InlineFragments { - InlineFragments { fragments: vec![] } - } - - /// Returns the number of inline fragments. - pub fn len(&self) -> usize { - self.fragments.len() - } - - /// Returns true if this list contains no fragments and false if it contains at least one - /// fragment. - pub fn is_empty(&self) -> bool { - self.fragments.is_empty() - } - - /// A convenience function to return the fragment at a given index. - pub fn get(&self, index: usize) -> &Fragment { - &self.fragments[index] - } - - /// A convenience function to return a mutable reference to the fragment at a given index. - pub fn get_mut(&mut self, index: usize) -> &mut Fragment { - &mut self.fragments[index] - } -} - -#[allow(unsafe_code)] -unsafe impl crate::flow::HasBaseFlow for InlineFlow {} - -/// Flows for inline layout. -#[derive(Serialize)] -#[repr(C)] -pub struct InlineFlow { - /// Data common to all flows. - pub base: BaseFlow, - - /// A vector of all inline fragments. Several fragments may correspond to one node/element. - pub fragments: InlineFragments, - - /// A vector of ranges into fragments that represents line positions. These ranges are disjoint - /// and are the result of inline layout. This also includes some metadata used for positioning - /// lines. - pub lines: Vec<Line>, - - /// The minimum metrics for each line, as specified by the line height and font style. - pub minimum_line_metrics: LineMetrics, - - /// The amount of indentation to use on the first line. This is determined by our block parent - /// (because percentages are relative to the containing block, and we aren't in a position to - /// compute things relative to our parent's containing block). - pub first_line_indentation: Au, -} - -impl InlineFlow { - pub fn from_fragments(fragments: InlineFragments, writing_mode: WritingMode) -> InlineFlow { - let mut flow = InlineFlow { - base: BaseFlow::new(None, writing_mode, ForceNonfloatedFlag::ForceNonfloated), - fragments: fragments, - lines: Vec::new(), - minimum_line_metrics: LineMetrics::new(Au(0), Au(0)), - first_line_indentation: Au(0), - }; - - if flow - .fragments - .fragments - .iter() - .any(Fragment::is_unscanned_generated_content) - { - flow.base - .restyle_damage - .insert(ServoRestyleDamage::RESOLVE_GENERATED_CONTENT); - } - - flow - } - - /// Sets fragment positions in the inline direction based on alignment for one line. This - /// performs text justification if mandated by the style. - fn set_inline_fragment_positions( - fragments: &mut InlineFragments, - line: &Line, - line_align: TextAlign, - indentation: Au, - is_last_line: bool, - ) { - // Figure out how much inline-size we have. - let slack_inline_size = max(Au(0), line.green_zone.inline - line.bounds.size.inline); - - // Compute the value we're going to use for `text-justify`. - if fragments.fragments.is_empty() { - return; - } - let text_justify = fragments.fragments[0] - .style() - .get_inherited_text() - .text_justify; - - // Translate `left` and `right` to logical directions. - let is_ltr = fragments.fragments[0].style().writing_mode.is_bidi_ltr(); - let line_align = match (line_align, is_ltr) { - (TextAlign::Left, true) | - (TextAlign::ServoLeft, true) | - (TextAlign::Right, false) | - (TextAlign::ServoRight, false) => TextAlign::Start, - (TextAlign::Left, false) | - (TextAlign::ServoLeft, false) | - (TextAlign::Right, true) | - (TextAlign::ServoRight, true) => TextAlign::End, - _ => line_align, - }; - - // Set the fragment inline positions based on that alignment, and justify the text if - // necessary. - let mut inline_start_position_for_fragment = line.bounds.start.i + indentation; - match line_align { - TextAlign::Justify if !is_last_line && text_justify != TextJustify::None => { - InlineFlow::justify_inline_fragments(fragments, line, slack_inline_size) - }, - TextAlign::Justify | TextAlign::Start => {}, - TextAlign::Center | TextAlign::ServoCenter => { - inline_start_position_for_fragment = - inline_start_position_for_fragment + slack_inline_size.scale_by(0.5) - }, - TextAlign::End => { - inline_start_position_for_fragment = - inline_start_position_for_fragment + slack_inline_size - }, - TextAlign::Left | TextAlign::ServoLeft | TextAlign::Right | TextAlign::ServoRight => { - unreachable!() - }, - } - - // Lay out the fragments in visual order. - let run_count = match line.visual_runs { - Some(ref runs) => runs.len(), - None => 1, - }; - for run_idx in 0..run_count { - let (range, level) = match line.visual_runs { - Some(ref runs) if is_ltr => runs[run_idx], - Some(ref runs) => runs[run_count - run_idx - 1], // reverse order for RTL runs - None => (line.range, bidi::Level::ltr()), - }; - - struct MaybeReverse<I> { - iter: I, - reverse: bool, - } - - impl<I: DoubleEndedIterator> Iterator for MaybeReverse<I> { - type Item = I::Item; - - fn next(&mut self) -> Option<I::Item> { - if self.reverse { - self.iter.next_back() - } else { - self.iter.next() - } - } - - fn size_hint(&self) -> (usize, Option<usize>) { - self.iter.size_hint() - } - } - - // If the bidi embedding direction is opposite the layout direction, lay out this - // run in reverse order. - let fragment_indices = MaybeReverse { - iter: range.begin().get()..range.end().get(), - reverse: level.is_ltr() != is_ltr, - }; - - for fragment_index in fragment_indices { - let fragment = fragments.get_mut(fragment_index as usize); - inline_start_position_for_fragment = - inline_start_position_for_fragment + fragment.margin.inline_start; - - let border_start = if fragment.style.writing_mode.is_bidi_ltr() == is_ltr { - inline_start_position_for_fragment - } else { - line.green_zone.inline - - inline_start_position_for_fragment - - fragment.margin.inline_end - - fragment.border_box.size.inline - }; - fragment.border_box = LogicalRect::new( - fragment.style.writing_mode, - border_start, - fragment.border_box.start.b, - fragment.border_box.size.inline, - fragment.border_box.size.block, - ); - fragment.update_late_computed_inline_position_if_necessary(); - - if !fragment.is_inline_absolute() { - inline_start_position_for_fragment = inline_start_position_for_fragment + - fragment.border_box.size.inline + - fragment.margin.inline_end; - } - } - } - } - - /// Justifies the given set of inline fragments, distributing the `slack_inline_size` among all - /// of them according to the value of `text-justify`. - fn justify_inline_fragments( - fragments: &mut InlineFragments, - line: &Line, - slack_inline_size: Au, - ) { - // Fast path. - if slack_inline_size == Au(0) { - return; - } - - // First, calculate the number of expansion opportunities (spaces, normally). - let mut expansion_opportunities = 0; - for fragment_index in line.range.each_index() { - let fragment = fragments.get(fragment_index.to_usize()); - let scanned_text_fragment_info = match fragment.specific { - SpecificFragmentInfo::ScannedText(ref info) if !info.range.is_empty() => info, - _ => continue, - }; - let fragment_range = scanned_text_fragment_info.range; - - for slice in scanned_text_fragment_info - .run - .character_slices_in_range(&fragment_range) - { - expansion_opportunities += slice.glyphs.space_count_in_range(&slice.range) - } - } - - if expansion_opportunities == 0 { - return; - } - - // Then distribute all the space across the expansion opportunities. - let space_per_expansion_opportunity = slack_inline_size / expansion_opportunities as i32; - for fragment_index in line.range.each_index() { - let fragment = fragments.get_mut(fragment_index.to_usize()); - let scanned_text_fragment_info = match fragment.specific { - SpecificFragmentInfo::ScannedText(ref mut info) if !info.range.is_empty() => info, - _ => continue, - }; - let fragment_range = scanned_text_fragment_info.range; - let run = Arc::make_mut(&mut scanned_text_fragment_info.run); - run.extra_word_spacing = space_per_expansion_opportunity; - - // Recompute the fragment's border box size. - let new_inline_size = run.advance_for_range(&fragment_range); - let new_size = LogicalSize::new( - fragment.style.writing_mode, - new_inline_size, - fragment.border_box.size.block, - ); - fragment.border_box = LogicalRect::from_point_size( - fragment.style.writing_mode, - fragment.border_box.start, - new_size, - ); - } - } - - /// Sets final fragment positions in the block direction for one line. - fn set_block_fragment_positions( - fragments: &mut InlineFragments, - line: &Line, - minimum_line_metrics: &LineMetrics, - layout_context: &LayoutContext, - ) { - for fragment_index in line.range.each_index() { - let fragment = fragments.get_mut(fragment_index.to_usize()); - let line_metrics = LineMetrics::for_line_and_fragment(line, fragment, layout_context); - let inline_metrics = fragment.aligned_inline_metrics( - layout_context, - minimum_line_metrics, - Some(&line_metrics), - ); - - // Align the top of the fragment's border box with its ascent above the baseline. - fragment.border_box.start.b = - line.bounds.start.b + line_metrics.space_above_baseline - inline_metrics.ascent; - - // CSS 2.1 § 10.8: "The height of each inline-level box in the line box is - // calculated. For replaced elements, inline-block elements, and inline-table - // elements, this is the height of their margin box; for inline boxes, this is their - // 'line-height'." - // - // CSS 2.1 § 10.8.1: "Although margins, borders, and padding of non-replaced elements - // do not enter into the line box calculation, they are still rendered around inline - // boxes." - // - // Effectively, if the fragment is a non-replaced element (excluding inline-block), we - // need to align its ascent above the baseline with the top of the *content box*, not - // the border box. Since the code above has already aligned it to the border box, we - // simply need to adjust it in this case. - if !fragment.is_replaced_or_inline_block() { - fragment.border_box.start.b -= fragment.border_padding.block_start - } - - fragment.update_late_computed_block_position_if_necessary(); - } - } - - /// Computes the minimum metrics for each line. This is done during flow construction. - /// - /// `style` is the style of the block. - pub fn minimum_line_metrics( - &self, - font_context: &mut LayoutFontContext, - style: &ComputedValues, - ) -> LineMetrics { - InlineFlow::minimum_line_metrics_for_fragments( - &self.fragments.fragments, - font_context, - style, - ) - } - - /// Computes the minimum line metrics for the given fragments. This is typically done during - /// flow construction. - /// - /// `style` is the style of the block that these fragments belong to. - pub fn minimum_line_metrics_for_fragments( - fragments: &[Fragment], - font_context: &mut LayoutFontContext, - style: &ComputedValues, - ) -> LineMetrics { - // As a special case, if this flow contains only hypothetical fragments, then the entire - // flow is hypothetical and takes up no space. See CSS 2.1 § 10.3.7. - if fragments.iter().all(Fragment::is_hypothetical) { - return LineMetrics::new(Au(0), Au(0)); - } - - let font_style = style.clone_font(); - let font_metrics = text::font_metrics_for_style(font_context, font_style); - let line_height = text::line_height_from_style(style, &font_metrics); - let inline_metrics = if fragments.iter().any(Fragment::is_text_or_replaced) { - InlineMetrics::from_font_metrics(&font_metrics, line_height) - } else { - InlineMetrics::new(Au(0), Au(0), Au(0)) - }; - - let mut line_metrics = LineMetrics::new(Au(0), MIN_AU); - let mut largest_block_size_for_top_fragments = Au(0); - let mut largest_block_size_for_bottom_fragments = Au(0); - - // We use `VerticalAlign::baseline()` here because `vertical-align` must - // not apply to the inside of inline blocks. - update_line_metrics_for_fragment( - &mut line_metrics, - &inline_metrics, - style.get_box().display, - VerticalAlign::baseline(), - &mut largest_block_size_for_top_fragments, - &mut largest_block_size_for_bottom_fragments, - ); - - // According to CSS 2.1 § 10.8, `line-height` of any inline element specifies the minimal - // height of line boxes within the element. - for inline_context in fragments - .iter() - .filter_map(|fragment| fragment.inline_context.as_ref()) - { - for node in &inline_context.nodes { - let font_style = node.style.clone_font(); - let font_metrics = text::font_metrics_for_style(font_context, font_style); - let line_height = text::line_height_from_style(&*node.style, &font_metrics); - let inline_metrics = InlineMetrics::from_font_metrics(&font_metrics, line_height); - - update_line_metrics_for_fragment( - &mut line_metrics, - &inline_metrics, - node.style.get_box().display, - node.style.get_box().vertical_align, - &mut largest_block_size_for_top_fragments, - &mut largest_block_size_for_bottom_fragments, - ); - } - } - - line_metrics.space_above_baseline = max( - line_metrics.space_above_baseline, - largest_block_size_for_bottom_fragments - max(line_metrics.space_below_baseline, Au(0)), - ); - line_metrics.space_below_baseline = max( - line_metrics.space_below_baseline, - largest_block_size_for_top_fragments - line_metrics.space_above_baseline, - ); - - return line_metrics; - - fn update_line_metrics_for_fragment( - line_metrics: &mut LineMetrics, - inline_metrics: &InlineMetrics, - display_value: Display, - vertical_align_value: VerticalAlign, - largest_block_size_for_top_fragments: &mut Au, - largest_block_size_for_bottom_fragments: &mut Au, - ) { - // FIXME(emilio): This should probably be handled. - let vertical_align_value = match vertical_align_value { - VerticalAlign::Keyword(kw) => kw, - VerticalAlign::Length(..) => { - *line_metrics = line_metrics.new_metrics_for_fragment(inline_metrics); - return; - }, - }; - - match (display_value, vertical_align_value) { - (Display::Inline, VerticalAlignKeyword::Top) | - (Display::Block, VerticalAlignKeyword::Top) | - (Display::InlineFlex, VerticalAlignKeyword::Top) | - (Display::InlineBlock, VerticalAlignKeyword::Top) - if inline_metrics.space_above_baseline >= Au(0) => - { - *largest_block_size_for_top_fragments = max( - *largest_block_size_for_top_fragments, - inline_metrics.space_above_baseline + inline_metrics.space_below_baseline, - ) - }, - (Display::Inline, VerticalAlignKeyword::Bottom) | - (Display::Block, VerticalAlignKeyword::Bottom) | - (Display::InlineFlex, VerticalAlignKeyword::Bottom) | - (Display::InlineBlock, VerticalAlignKeyword::Bottom) - if inline_metrics.space_below_baseline >= Au(0) => - { - *largest_block_size_for_bottom_fragments = max( - *largest_block_size_for_bottom_fragments, - inline_metrics.space_above_baseline + inline_metrics.space_below_baseline, - ) - }, - _ => *line_metrics = line_metrics.new_metrics_for_fragment(inline_metrics), - } - } - } - - fn update_restyle_damage(&mut self) { - let mut damage = self.base.restyle_damage; - - for frag in &self.fragments.fragments { - damage.insert(frag.restyle_damage()); - } - - self.base.restyle_damage = damage; - } - - fn containing_block_range_for_flow_surrounding_fragment_at_index( - &self, - fragment_index: FragmentIndex, - ) -> Range<FragmentIndex> { - let mut start_index = fragment_index; - while start_index > FragmentIndex(0) && - self.fragments.fragments[(start_index - FragmentIndex(1)).get() as usize] - .is_positioned() - { - start_index = start_index - FragmentIndex(1) - } - - let mut end_index = fragment_index + FragmentIndex(1); - while end_index < FragmentIndex(self.fragments.fragments.len() as isize) && - self.fragments.fragments[end_index.get() as usize].is_positioned() - { - end_index = end_index + FragmentIndex(1) - } - - Range::new(start_index, end_index - start_index) - } - - fn containing_block_range_for_flow(&self, opaque_flow: OpaqueFlow) -> Range<FragmentIndex> { - match self - .fragments - .fragments - .iter() - .position(|fragment| match fragment.specific { - SpecificFragmentInfo::InlineAbsolute(ref inline_absolute) => { - OpaqueFlow::from_flow(&*inline_absolute.flow_ref) == opaque_flow - }, - SpecificFragmentInfo::InlineAbsoluteHypothetical( - ref inline_absolute_hypothetical, - ) => OpaqueFlow::from_flow(&*inline_absolute_hypothetical.flow_ref) == opaque_flow, - _ => false, - }) { - Some(index) => { - let index = FragmentIndex(index as isize); - self.containing_block_range_for_flow_surrounding_fragment_at_index(index) - }, - None => { - // FIXME(pcwalton): This is quite wrong. We should only return the range - // surrounding the inline fragments that constitute the containing block. But this - // suffices to get Google looking right. - Range::new( - FragmentIndex(0), - FragmentIndex(self.fragments.fragments.len() as isize), - ) - }, - } - } - - pub fn baseline_offset_of_last_line(&self) -> Option<Au> { - self.last_line_containing_real_fragments().map(|line| { - line.bounds.start.b + line.bounds.size.block - line.metrics.space_below_baseline - }) - } - - // Returns the last line that doesn't consist entirely of hypothetical boxes. - fn last_line_containing_real_fragments(&self) -> Option<&Line> { - for line in self.lines.iter().rev() { - if (line.range.begin().get()..line.range.end().get()) - .any(|index| !self.fragments.fragments[index as usize].is_hypothetical()) - { - return Some(line); - } - } - None - } - - fn build_display_list_for_inline_fragment_at_index( - &mut self, - state: &mut DisplayListBuildState, - index: usize, - ) { - let fragment = self.fragments.fragments.get_mut(index).unwrap(); - let stacking_relative_border_box = self - .base - .stacking_relative_border_box_for_display_list(fragment); - fragment.build_display_list( - state, - stacking_relative_border_box, - BorderPaintingMode::Separate, - DisplayListSection::Content, - self.base.clip, - None, - ); - } -} - -impl Flow for InlineFlow { - fn class(&self) -> FlowClass { - FlowClass::Inline - } - - fn as_inline(&self) -> &InlineFlow { - self - } - - fn as_mut_inline(&mut self) -> &mut InlineFlow { - self - } - - fn bubble_inline_sizes(&mut self) { - self.update_restyle_damage(); - - let _scope = layout_debug_scope!("inline::bubble_inline_sizes {:x}", self.base.debug_id()); - - let writing_mode = self.base.writing_mode; - for kid in self.base.child_iter_mut() { - kid.mut_base().floats = Floats::new(writing_mode); - } - - self.base - .flags - .remove(FlowFlags::CONTAINS_TEXT_OR_REPLACED_FRAGMENTS); - - let mut intrinsic_sizes_for_flow = IntrinsicISizesContribution::new(); - let mut intrinsic_sizes_for_inline_run = IntrinsicISizesContribution::new(); - let mut intrinsic_sizes_for_nonbroken_run = IntrinsicISizesContribution::new(); - for fragment in &mut self.fragments.fragments { - let intrinsic_sizes_for_fragment = fragment.compute_intrinsic_inline_sizes().finish(); - match fragment.style.get_inherited_text().white_space { - WhiteSpace::Nowrap => intrinsic_sizes_for_nonbroken_run - .union_nonbreaking_inline(&intrinsic_sizes_for_fragment), - WhiteSpace::Pre => { - intrinsic_sizes_for_nonbroken_run - .union_nonbreaking_inline(&intrinsic_sizes_for_fragment); - - // Flush the intrinsic sizes we've been gathering up in order to handle the - // line break, if necessary. - if fragment.requires_line_break_afterward_if_wrapping_on_newlines() { - intrinsic_sizes_for_inline_run - .union_inline(&intrinsic_sizes_for_nonbroken_run.finish()); - intrinsic_sizes_for_nonbroken_run = IntrinsicISizesContribution::new(); - intrinsic_sizes_for_flow - .union_block(&intrinsic_sizes_for_inline_run.finish()); - intrinsic_sizes_for_inline_run = IntrinsicISizesContribution::new(); - } - }, - WhiteSpace::PreWrap | WhiteSpace::PreLine => { - // Flush the intrinsic sizes we were gathering up for the nonbroken run, if - // necessary. - intrinsic_sizes_for_inline_run - .union_inline(&intrinsic_sizes_for_nonbroken_run.finish()); - intrinsic_sizes_for_nonbroken_run = IntrinsicISizesContribution::new(); - - intrinsic_sizes_for_nonbroken_run.union_inline(&intrinsic_sizes_for_fragment); - - // Flush the intrinsic sizes we've been gathering up in order to handle the - // line break, if necessary. - if fragment.requires_line_break_afterward_if_wrapping_on_newlines() { - intrinsic_sizes_for_inline_run - .union_inline(&intrinsic_sizes_for_nonbroken_run.finish()); - intrinsic_sizes_for_nonbroken_run = IntrinsicISizesContribution::new(); - intrinsic_sizes_for_flow - .union_block(&intrinsic_sizes_for_inline_run.finish()); - intrinsic_sizes_for_inline_run = IntrinsicISizesContribution::new(); - } - }, - WhiteSpace::Normal => { - // Flush the intrinsic sizes we were gathering up for the nonbroken run, if - // necessary. - intrinsic_sizes_for_inline_run - .union_inline(&intrinsic_sizes_for_nonbroken_run.finish()); - intrinsic_sizes_for_nonbroken_run = IntrinsicISizesContribution::new(); - - intrinsic_sizes_for_nonbroken_run.union_inline(&intrinsic_sizes_for_fragment); - }, - } - - fragment - .restyle_damage - .remove(ServoRestyleDamage::BUBBLE_ISIZES); - - if fragment.is_text_or_replaced() { - self.base - .flags - .insert(FlowFlags::CONTAINS_TEXT_OR_REPLACED_FRAGMENTS); - } - } - - // Flush any remaining nonbroken-run and inline-run intrinsic sizes. - intrinsic_sizes_for_inline_run.union_inline(&intrinsic_sizes_for_nonbroken_run.finish()); - intrinsic_sizes_for_flow.union_block(&intrinsic_sizes_for_inline_run.finish()); - - // Finish up the computation. - self.base.intrinsic_inline_sizes = intrinsic_sizes_for_flow.finish() - } - - /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. - /// When called on this context, the context has had its inline-size set by the parent context. - fn assign_inline_sizes(&mut self, _: &LayoutContext) { - let _scope = layout_debug_scope!("inline::assign_inline_sizes {:x}", self.base.debug_id()); - - // Initialize content fragment inline-sizes if they haven't been initialized already. - // - // TODO: Combine this with `LineBreaker`'s walk in the fragment list, or put this into - // `Fragment`. - - debug!( - "InlineFlow::assign_inline_sizes: floats in: {:?}", - self.base.floats - ); - - let inline_size = self.base.block_container_inline_size; - let container_mode = self.base.block_container_writing_mode; - let container_block_size = self.base.block_container_explicit_block_size; - self.base.position.size.inline = inline_size; - - { - let this = &mut *self; - for fragment in this.fragments.fragments.iter_mut() { - fragment.compute_border_and_padding(inline_size); - fragment.compute_block_direction_margins(inline_size); - fragment.compute_inline_direction_margins(inline_size); - fragment - .assign_replaced_inline_size_if_necessary(inline_size, container_block_size); - } - } - - // If there are any inline-block kids, propagate explicit block and inline - // sizes down to them. - let block_container_explicit_block_size = self.base.block_container_explicit_block_size; - for kid in self.base.child_iter_mut() { - let kid_base = kid.mut_base(); - - kid_base.block_container_inline_size = inline_size; - kid_base.block_container_writing_mode = container_mode; - kid_base.block_container_explicit_block_size = block_container_explicit_block_size; - } - } - - /// Calculate and set the block-size of this flow. See CSS 2.1 § 10.6.1. - /// Note that we do not need to do in-order traversal because the children - /// are always block formatting context. - fn assign_block_size(&mut self, layout_context: &LayoutContext) { - let _scope = layout_debug_scope!("inline::assign_block_size {:x}", self.base.debug_id()); - - // Divide the fragments into lines. - // - // TODO(pcwalton, #226): Get the CSS `line-height` property from the - // style of the containing block to determine the minimum line block - // size. - // - // TODO(pcwalton, #226): Get the CSS `line-height` property from each - // non-replaced inline element to determine its block-size for computing - // the line's own block-size. - // - // TODO(pcwalton): Cache the line scanner? - debug!( - "assign_block_size_inline: floats in: {:?}", - self.base.floats - ); - - // Assign the block-size and late-computed inline-sizes for the inline fragments. - for fragment in &mut self.fragments.fragments { - fragment.update_late_computed_replaced_inline_size_if_necessary(); - fragment.assign_replaced_block_size_if_necessary(); - } - - // Reset our state, so that we handle incremental reflow correctly. - // - // TODO(pcwalton): Do something smarter, like Gecko and WebKit? - self.lines.clear(); - - // Determine how much indentation the first line wants. - let mut indentation = if self.fragments.is_empty() { - Au(0) - } else { - self.first_line_indentation - }; - - // Perform line breaking. - let mut scanner = LineBreaker::new( - self.base.floats.clone(), - indentation, - &self.minimum_line_metrics, - ); - scanner.scan_for_lines(self, layout_context); - - // Now, go through each line and lay out the fragments inside. - let line_count = self.lines.len(); - for (line_index, line) in self.lines.iter_mut().enumerate() { - // Lay out fragments in the inline direction, and justify them if - // necessary. - InlineFlow::set_inline_fragment_positions( - &mut self.fragments, - line, - self.base.flags.text_align(), - indentation, - line_index + 1 == line_count, - ); - - // Compute the final positions in the block direction of each fragment. - InlineFlow::set_block_fragment_positions( - &mut self.fragments, - line, - &self.minimum_line_metrics, - layout_context, - ); - - // This is used to set the block-start position of the next line in - // the next iteration of the loop. We're no longer on the first - // line, so set indentation to zero. - indentation = Au(0) - } - - if self.is_absolute_containing_block() { - // Assign block-sizes for all flows in this absolute flow tree. - // This is preorder because the block-size of an absolute flow may depend on - // the block-size of its containing block, which may also be an absolute flow. - let assign_abs_b_sizes = AbsoluteAssignBSizesTraversal(layout_context.shared_context()); - assign_abs_b_sizes.traverse_absolute_flows(&mut *self); - } - - self.base.position.size.block = match self.last_line_containing_real_fragments() { - Some(last_line) => last_line.bounds.start.b + last_line.bounds.size.block, - None => Au(0), - }; - - self.base.floats = scanner.floats; - let writing_mode = self.base.floats.writing_mode; - self.base.floats.translate(LogicalSize::new( - writing_mode, - Au(0), - -self.base.position.size.block, - )); - - let containing_block_size = - LogicalSize::new(writing_mode, Au(0), self.base.position.size.block); - self.mutate_fragments(&mut |f: &mut Fragment| match f.specific { - SpecificFragmentInfo::InlineBlock(ref mut info) => { - let block = FlowRef::deref_mut(&mut info.flow_ref); - block.mut_base().early_absolute_position_info = EarlyAbsolutePositionInfo { - relative_containing_block_size: containing_block_size, - relative_containing_block_mode: writing_mode, - }; - }, - SpecificFragmentInfo::InlineAbsolute(ref mut info) => { - let block = FlowRef::deref_mut(&mut info.flow_ref); - block.mut_base().early_absolute_position_info = EarlyAbsolutePositionInfo { - relative_containing_block_size: containing_block_size, - relative_containing_block_mode: writing_mode, - }; - }, - _ => (), - }); - - self.base - .restyle_damage - .remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW); - for fragment in &mut self.fragments.fragments { - fragment - .restyle_damage - .remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW); - } - } - - fn compute_stacking_relative_position(&mut self, _: &LayoutContext) { - // First, gather up the positions of all the containing blocks (if any). - // - // FIXME(pcwalton): This will get the absolute containing blocks inside `...` wrong in the - // case of something like: - // - // <span style="position: relative"> - // Foo - // <span style="display: inline-block">...</span> - // </span> - let mut containing_block_positions = Vec::new(); - let container_size = Size2D::new(self.base.block_container_inline_size, Au(0)); - for (fragment_index, fragment) in self.fragments.fragments.iter().enumerate() { - match fragment.specific { - SpecificFragmentInfo::InlineAbsolute(_) => { - let containing_block_range = self - .containing_block_range_for_flow_surrounding_fragment_at_index( - FragmentIndex(fragment_index as isize), - ); - let first_fragment_index = containing_block_range.begin().get() as usize; - debug_assert!(first_fragment_index < self.fragments.fragments.len()); - let first_fragment = &self.fragments.fragments[first_fragment_index]; - let padding_box_origin = (first_fragment.border_box - - first_fragment.style.logical_border_width()) - .start; - containing_block_positions.push( - padding_box_origin.to_physical(self.base.writing_mode, container_size), - ); - }, - SpecificFragmentInfo::InlineBlock(_) if fragment.is_positioned() => { - let containing_block_range = self - .containing_block_range_for_flow_surrounding_fragment_at_index( - FragmentIndex(fragment_index as isize), - ); - let first_fragment_index = containing_block_range.begin().get() as usize; - debug_assert!(first_fragment_index < self.fragments.fragments.len()); - let first_fragment = &self.fragments.fragments[first_fragment_index]; - let padding_box_origin = (first_fragment.border_box - - first_fragment.style.logical_border_width()) - .start; - containing_block_positions.push( - padding_box_origin.to_physical(self.base.writing_mode, container_size), - ); - }, - _ => {}, - } - } - - // Then compute the positions of all of our fragments. - let mut containing_block_positions = containing_block_positions.iter(); - for fragment in &mut self.fragments.fragments { - let stacking_relative_border_box = fragment.stacking_relative_border_box( - &self.base.stacking_relative_position, - &self - .base - .early_absolute_position_info - .relative_containing_block_size, - self.base - .early_absolute_position_info - .relative_containing_block_mode, - CoordinateSystem::Parent, - ); - let stacking_relative_content_box = - fragment.stacking_relative_content_box(stacking_relative_border_box); - - let is_positioned = fragment.is_positioned(); - match fragment.specific { - SpecificFragmentInfo::InlineBlock(ref mut info) => { - let flow = FlowRef::deref_mut(&mut info.flow_ref); - let block_flow = flow.as_mut_block(); - block_flow.base.late_absolute_position_info = - self.base.late_absolute_position_info; - - let stacking_relative_position = self.base.stacking_relative_position; - if is_positioned { - let padding_box_origin = containing_block_positions.next().unwrap(); - block_flow - .base - .late_absolute_position_info - .stacking_relative_position_of_absolute_containing_block = - *padding_box_origin + stacking_relative_position; - } - - block_flow.base.stacking_relative_position = - stacking_relative_content_box.origin.to_vector(); - - // Write the clip in our coordinate system into the child flow. (The kid will - // fix it up to be in its own coordinate system if necessary.) - block_flow.base.clip = self.base.clip.clone() - }, - SpecificFragmentInfo::InlineAbsoluteHypothetical(ref mut info) => { - let flow = FlowRef::deref_mut(&mut info.flow_ref); - let block_flow = flow.as_mut_block(); - block_flow.base.late_absolute_position_info = - self.base.late_absolute_position_info; - - block_flow.base.stacking_relative_position = - stacking_relative_border_box.origin.to_vector(); - - // As above, this is in our coordinate system for now. - block_flow.base.clip = self.base.clip.clone() - }, - SpecificFragmentInfo::InlineAbsolute(ref mut info) => { - let flow = FlowRef::deref_mut(&mut info.flow_ref); - let block_flow = flow.as_mut_block(); - block_flow.base.late_absolute_position_info = - self.base.late_absolute_position_info; - - let stacking_relative_position = self.base.stacking_relative_position; - let padding_box_origin = containing_block_positions.next().unwrap(); - block_flow - .base - .late_absolute_position_info - .stacking_relative_position_of_absolute_containing_block = - *padding_box_origin + stacking_relative_position; - - block_flow.base.stacking_relative_position = - stacking_relative_border_box.origin.to_vector(); - - // As above, this is in our coordinate system for now. - block_flow.base.clip = self.base.clip.clone() - }, - _ => {}, - } - } - } - - fn update_late_computed_inline_position_if_necessary(&mut self, _: Au) {} - - fn update_late_computed_block_position_if_necessary(&mut self, _: Au) {} - - fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) { - self.base.stacking_context_id = state.current_stacking_context_id; - self.base.clipping_and_scrolling = Some(state.current_clipping_and_scrolling); - self.base.clip = state - .clip_stack - .last() - .cloned() - .unwrap_or_else(Rect::max_rect); - - let previous_cb_clipping_and_scrolling = state.containing_block_clipping_and_scrolling; - - for fragment in self.fragments.fragments.iter_mut() { - state.containing_block_clipping_and_scrolling = previous_cb_clipping_and_scrolling; - - let abspos_containing_block = fragment.style.get_box().position != Position::Static; - if abspos_containing_block { - state.containing_block_clipping_and_scrolling = - state.current_clipping_and_scrolling; - } - - // We clear this here, but it might be set again if we create a stacking context for - // this fragment. - fragment.established_reference_frame = None; - - if !fragment.collect_stacking_contexts_for_blocklike_fragment(state) { - if !fragment.establishes_stacking_context() { - fragment.stacking_context_id = state.current_stacking_context_id; - } else { - fragment.create_stacking_context_for_inline_block(&self.base, state); - } - } - - // Reset the containing block clipping and scrolling before each loop iteration, - // so we don't pollute subsequent fragments. - state.containing_block_clipping_and_scrolling = previous_cb_clipping_and_scrolling; - } - } - - fn build_display_list(&mut self, state: &mut DisplayListBuildState) { - debug!( - "Flow: building display list for {} inline fragments", - self.fragments.len() - ); - - // We iterate using an index here, because we want to avoid doing a doing - // a double-borrow of self (one mutable for the method call and one immutable - // for the self.fragments.fragment iterator itself). - for index in 0..self.fragments.fragments.len() { - let (establishes_stacking_context, stacking_context_id) = { - let fragment = self.fragments.fragments.get(index).unwrap(); - ( - self.base.stacking_context_id != fragment.stacking_context_id, - fragment.stacking_context_id, - ) - }; - - let parent_stacking_context_id = state.current_stacking_context_id; - if establishes_stacking_context { - state.current_stacking_context_id = stacking_context_id; - } - - self.build_display_list_for_inline_fragment_at_index(state, index); - - if establishes_stacking_context { - state.current_stacking_context_id = parent_stacking_context_id - } - } - - if !self.fragments.fragments.is_empty() { - self.base - .build_display_items_for_debugging_tint(state, self.fragments.fragments[0].node); - } - } - - fn repair_style(&mut self, _: &ServoArc<ComputedValues>) {} - - fn compute_overflow(&self) -> Overflow { - let mut overflow = Overflow::new(); - let flow_size = self.base.position.size.to_physical(self.base.writing_mode); - let relative_containing_block_size = &self - .base - .early_absolute_position_info - .relative_containing_block_size; - for fragment in &self.fragments.fragments { - overflow.union(&fragment.compute_overflow(&flow_size, &relative_containing_block_size)) - } - overflow - } - - fn iterate_through_fragment_border_boxes( - &self, - iterator: &mut dyn FragmentBorderBoxIterator, - level: i32, - stacking_context_position: &Point2D<Au>, - ) { - // FIXME(#2795): Get the real container size. - for fragment in &self.fragments.fragments { - if !iterator.should_process(fragment) { - continue; - } - - let stacking_relative_position = &self.base.stacking_relative_position; - let relative_containing_block_size = &self - .base - .early_absolute_position_info - .relative_containing_block_size; - let relative_containing_block_mode = self - .base - .early_absolute_position_info - .relative_containing_block_mode; - iterator.process( - fragment, - level, - &fragment - .stacking_relative_border_box( - stacking_relative_position, - relative_containing_block_size, - relative_containing_block_mode, - CoordinateSystem::Own, - ) - .translate(&stacking_context_position.to_vector()), - ) - } - } - - fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) { - for fragment in &mut self.fragments.fragments { - (*mutator)(fragment) - } - } - - fn contains_positioned_fragments(&self) -> bool { - self.fragments - .fragments - .iter() - .any(|fragment| fragment.is_positioned()) - } - - fn contains_relatively_positioned_fragments(&self) -> bool { - self.fragments - .fragments - .iter() - .any(|fragment| fragment.style.get_box().position == Position::Relative) - } - - fn generated_containing_block_size(&self, for_flow: OpaqueFlow) -> LogicalSize<Au> { - let mut containing_block_size = LogicalSize::new(self.base.writing_mode, Au(0), Au(0)); - for index in self.containing_block_range_for_flow(for_flow).each_index() { - let fragment = &self.fragments.fragments[index.get() as usize]; - if fragment.is_absolutely_positioned() { - continue; - } - containing_block_size.inline = - containing_block_size.inline + fragment.border_box.size.inline; - containing_block_size.block = - max(containing_block_size.block, fragment.border_box.size.block); - } - containing_block_size - } - - fn print_extra_flow_children(&self, print_tree: &mut PrintTree) { - for fragment in &self.fragments.fragments { - print_tree.add_item(format!("{:?}", fragment)); - } - } -} - -impl fmt::Debug for InlineFlow { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "{:?}({:x}) {:?}", - self.class(), - self.base.debug_id(), - self.base() - ) - } -} - -#[derive(Clone)] -pub struct InlineFragmentNodeInfo { - pub address: OpaqueNode, - pub style: ServoArc<ComputedValues>, - pub selected_style: ServoArc<ComputedValues>, - pub pseudo: PseudoElementType, - pub flags: InlineFragmentNodeFlags, -} - -bitflags! { - pub struct InlineFragmentNodeFlags: u8 { - const FIRST_FRAGMENT_OF_ELEMENT = 0x01; - const LAST_FRAGMENT_OF_ELEMENT = 0x02; - } -} - -impl fmt::Debug for InlineFragmentNodeInfo { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}", self.flags.bits()) - } -} - -#[derive(Clone)] -pub struct InlineFragmentContext { - /// The list of nodes that this fragment will be inheriting styles from, - /// from the most deeply-nested node out. - pub nodes: Vec<InlineFragmentNodeInfo>, -} - -impl InlineFragmentContext { - pub fn new() -> InlineFragmentContext { - InlineFragmentContext { nodes: vec![] } - } - - #[inline] - pub fn contains_node(&self, node_address: OpaqueNode) -> bool { - self.nodes - .iter() - .position(|node| node.address == node_address) - .is_some() - } - - fn ptr_eq(&self, other: &InlineFragmentContext) -> bool { - if self.nodes.len() != other.nodes.len() { - return false; - } - for (this_node, other_node) in self.nodes.iter().zip(&other.nodes) { - if this_node.address != other_node.address { - return false; - } - } - true - } -} - -fn inline_contexts_are_equal( - inline_context_a: &Option<InlineFragmentContext>, - inline_context_b: &Option<InlineFragmentContext>, -) -> bool { - match (inline_context_a, inline_context_b) { - (&Some(ref inline_context_a), &Some(ref inline_context_b)) => { - inline_context_a.ptr_eq(inline_context_b) - }, - (&None, &None) => true, - (&Some(_), &None) | (&None, &Some(_)) => false, - } -} - -/// Ascent and space needed above and below the baseline for a fragment. See CSS 2.1 § 10.8.1. -/// -/// Descent is not included in this structure because it can be computed from the fragment's -/// border/content box and the ascent. -#[derive(Clone, Copy, Debug, Serialize)] -pub struct InlineMetrics { - /// The amount of space above the baseline needed for this fragment. - pub space_above_baseline: Au, - /// The amount of space below the baseline needed for this fragment. - pub space_below_baseline: Au, - /// The distance from the baseline to the top of this fragment. This can differ from - /// `block_size_above_baseline` if the fragment needs some empty space above it due to - /// line-height, etc. - pub ascent: Au, -} - -impl InlineMetrics { - /// Creates a new set of inline metrics. - pub fn new(space_above_baseline: Au, space_below_baseline: Au, ascent: Au) -> InlineMetrics { - InlineMetrics { - space_above_baseline: space_above_baseline, - space_below_baseline: space_below_baseline, - ascent: ascent, - } - } - - /// Calculates inline metrics from font metrics and line block-size per CSS 2.1 § 10.8.1. - #[inline] - pub fn from_font_metrics(font_metrics: &FontMetrics, line_height: Au) -> InlineMetrics { - let leading = line_height - (font_metrics.ascent + font_metrics.descent); - - // Calculating the half leading here and then using leading - half_leading - // below ensure that we don't introduce any rounding accuracy issues here. - // The invariant is that the resulting total line height must exactly - // equal the requested line_height. - let half_leading = leading.scale_by(0.5); - InlineMetrics { - space_above_baseline: font_metrics.ascent + half_leading, - space_below_baseline: font_metrics.descent + leading - half_leading, - ascent: font_metrics.ascent, - } - } - - /// Returns the sum of the space needed above and below the baseline. - fn space_needed(&self) -> Au { - self.space_above_baseline + self.space_below_baseline - } -} - -#[derive(Clone, Copy, PartialEq)] -enum LineFlushMode { - No, - Flush, -} - -#[derive(Clone, Copy, Debug, Serialize)] -pub struct LineMetrics { - pub space_above_baseline: Au, - pub space_below_baseline: Au, -} - -impl LineMetrics { - pub fn new(space_above_baseline: Au, space_below_baseline: Au) -> LineMetrics { - LineMetrics { - space_above_baseline: space_above_baseline, - space_below_baseline: space_below_baseline, - } - } - - /// Returns the line metrics that result from combining the line that these metrics represent - /// with a fragment with the given metrics. - fn new_metrics_for_fragment(&self, fragment_inline_metrics: &InlineMetrics) -> LineMetrics { - LineMetrics { - space_above_baseline: max( - self.space_above_baseline, - fragment_inline_metrics.space_above_baseline, - ), - space_below_baseline: max( - self.space_below_baseline, - fragment_inline_metrics.space_below_baseline, - ), - } - } - - fn for_line_and_fragment( - line: &Line, - fragment: &Fragment, - layout_context: &LayoutContext, - ) -> LineMetrics { - if !fragment.is_hypothetical() { - let space_above_baseline = line.metrics.space_above_baseline; - return LineMetrics { - space_above_baseline: space_above_baseline, - space_below_baseline: line.bounds.size.block - space_above_baseline, - }; - } - - let hypothetical_line_metrics = line.new_metrics_for_fragment(fragment, layout_context); - let hypothetical_block_size = - line.new_block_size_for_fragment(fragment, &hypothetical_line_metrics, layout_context); - let hypothetical_space_above_baseline = hypothetical_line_metrics.space_above_baseline; - LineMetrics { - space_above_baseline: hypothetical_space_above_baseline, - space_below_baseline: hypothetical_block_size - hypothetical_space_above_baseline, - } - } - - /// Returns the sum of the space needed above and below the baseline. - pub fn space_needed(&self) -> Au { - self.space_above_baseline + self.space_below_baseline - } -} diff --git a/components/layout_2020/layout_debug.rs b/components/layout_2020/layout_debug.rs deleted file mode 100644 index d3af6b4d8d5..00000000000 --- a/components/layout_2020/layout_debug.rs +++ /dev/null @@ -1,124 +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/. */ - -//! Supports writing a trace file created during each layout scope -//! that can be viewed by an external tool to make layout debugging easier. - -use crate::flow::GetBaseFlow; -use crate::flow_ref::FlowRef; -use serde_json::{to_string, to_value, Value}; -use std::borrow::ToOwned; -use std::cell::RefCell; -use std::fs::File; -use std::io::Write; -#[cfg(debug_assertions)] -use std::sync::atomic::{AtomicUsize, Ordering}; - -thread_local!(static STATE_KEY: RefCell<Option<State>> = RefCell::new(None)); - -#[cfg(debug_assertions)] -static DEBUG_ID_COUNTER: AtomicUsize = AtomicUsize::new(0); - -pub struct Scope; - -#[macro_export] -macro_rules! layout_debug_scope( - ($($arg:tt)*) => ( - if cfg!(debug_assertions) { - layout_debug::Scope::new(format!($($arg)*)) - } else { - layout_debug::Scope - } - ) -); - -#[derive(Serialize)] -struct ScopeData { - name: String, - pre: Value, - post: Value, - children: Vec<Box<ScopeData>>, -} - -impl ScopeData { - fn new(name: String, pre: Value) -> ScopeData { - ScopeData { - name: name, - pre: pre, - post: Value::Null, - children: vec![], - } - } -} - -struct State { - flow_root: FlowRef, - scope_stack: Vec<Box<ScopeData>>, -} - -/// A layout debugging scope. The entire state of the flow tree -/// will be output at the beginning and end of this scope. -impl Scope { - pub fn new(name: String) -> Scope { - STATE_KEY.with(|ref r| { - if let Some(ref mut state) = *r.borrow_mut() { - let flow_trace = to_value(&state.flow_root.base()).unwrap(); - let data = Box::new(ScopeData::new(name.clone(), flow_trace)); - state.scope_stack.push(data); - } - }); - Scope - } -} - -#[cfg(debug_assertions)] -impl Drop for Scope { - fn drop(&mut self) { - STATE_KEY.with(|ref r| { - if let Some(ref mut state) = *r.borrow_mut() { - let mut current_scope = state.scope_stack.pop().unwrap(); - current_scope.post = to_value(&state.flow_root.base()).unwrap(); - let previous_scope = state.scope_stack.last_mut().unwrap(); - previous_scope.children.push(current_scope); - } - }); - } -} - -/// Generate a unique ID. This is used for items such as Fragment -/// which are often reallocated but represent essentially the -/// same data. -#[cfg(debug_assertions)] -pub fn generate_unique_debug_id() -> u16 { - DEBUG_ID_COUNTER.fetch_add(1, Ordering::SeqCst) as u16 -} - -/// Begin a layout debug trace. If this has not been called, -/// creating debug scopes has no effect. -pub fn begin_trace(flow_root: FlowRef) { - assert!(STATE_KEY.with(|ref r| r.borrow().is_none())); - - STATE_KEY.with(|ref r| { - let flow_trace = to_value(&flow_root.base()).unwrap(); - let state = State { - scope_stack: vec![Box::new(ScopeData::new("root".to_owned(), flow_trace))], - flow_root: flow_root.clone(), - }; - *r.borrow_mut() = Some(state); - }); -} - -/// End the debug layout trace. This will write the layout -/// trace to disk in the current directory. The output -/// file can then be viewed with an external tool. -pub fn end_trace(generation: u32) { - let mut thread_state = STATE_KEY.with(|ref r| r.borrow_mut().take().unwrap()); - assert_eq!(thread_state.scope_stack.len(), 1); - let mut root_scope = thread_state.scope_stack.pop().unwrap(); - root_scope.post = to_value(&thread_state.flow_root.base()).unwrap(); - - let result = to_string(&root_scope).unwrap(); - let mut file = File::create(format!("layout_trace-{}.json", generation)).unwrap(); - file.write_all(result.as_bytes()).unwrap(); -} diff --git a/components/layout_2020/lib.rs b/components/layout_2020/lib.rs index f8afdc79190..98a2d5c618d 100644 --- a/components/layout_2020/lib.rs +++ b/components/layout_2020/lib.rs @@ -5,58 +5,21 @@ #![deny(unsafe_code)] #[macro_use] -extern crate bitflags; -#[macro_use] -extern crate html5ever; -#[macro_use] -extern crate log; -#[macro_use] -extern crate range; -#[macro_use] extern crate serde; -#[macro_use] -pub mod layout_debug; - -pub mod animation; -mod block; -pub mod construct; pub mod context; pub mod data; pub mod display_list; -mod flex; -mod floats; pub mod flow; -mod flow_list; pub mod flow_ref; mod fragment; -mod generated_content; -pub mod incremental; -mod inline; -mod linked_list; -mod list_item; -mod model; -mod multicol; pub mod opaque_node; -pub mod parallel; -mod persistent_list; pub mod query; -pub mod sequential; -mod table; -mod table_caption; -mod table_cell; -mod table_colgroup; -mod table_row; -mod table_rowgroup; -mod table_wrapper; -mod text; pub mod traversal; pub mod wrapper; // For unit tests: -pub use self::data::LayoutData; pub use crate::fragment::Fragment; -pub use crate::fragment::SpecificFragmentInfo; // We can't use servo_arc for everything in layout, because the Flow stuff uses // weak references. diff --git a/components/layout_2020/linked_list.rs b/components/layout_2020/linked_list.rs deleted file mode 100644 index 09e605a3d31..00000000000 --- a/components/layout_2020/linked_list.rs +++ /dev/null @@ -1,21 +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/. */ - -//! Utility functions for doubly-linked lists. - -use std::collections::LinkedList; -use std::mem; - -/// Splits the head off a list in O(1) time, and returns the head. -pub fn split_off_head<T>(list: &mut LinkedList<T>) -> LinkedList<T> { - let tail = list.split_off(1); - mem::replace(list, tail) -} - -/// Prepends the items in the other list to this one, leaving the other list empty. -#[inline] -pub fn prepend_from<T>(this: &mut LinkedList<T>, other: &mut LinkedList<T>) { - other.append(this); - mem::swap(this, other); -} diff --git a/components/layout_2020/list_item.rs b/components/layout_2020/list_item.rs deleted file mode 100644 index 3416ff444b3..00000000000 --- a/components/layout_2020/list_item.rs +++ /dev/null @@ -1,323 +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 elements with a CSS `display` property of `list-item`. These elements consist of a -//! block and an extra inline fragment for the marker. - -use crate::block::BlockFlow; -use crate::context::{with_thread_local_font_context, LayoutContext}; -use crate::display_list::items::DisplayListSection; -use crate::display_list::{ - BorderPaintingMode, DisplayListBuildState, StackingContextCollectionState, -}; -use crate::floats::FloatKind; -use crate::flow::{Flow, FlowClass, OpaqueFlow}; -use crate::fragment::Overflow; -use crate::fragment::{ - CoordinateSystem, Fragment, FragmentBorderBoxIterator, GeneratedContentInfo, -}; -use crate::generated_content; -use crate::inline::InlineFlow; -use app_units::Au; -use euclid::Point2D; -use style::computed_values::list_style_type::T as ListStyleType; -use style::computed_values::position::T as Position; -use style::logical_geometry::LogicalSize; -use style::properties::ComputedValues; -use style::servo::restyle_damage::ServoRestyleDamage; - -#[allow(unsafe_code)] -unsafe impl crate::flow::HasBaseFlow for ListItemFlow {} - -/// A block with the CSS `display` property equal to `list-item`. -#[derive(Debug)] -#[repr(C)] -pub struct ListItemFlow { - /// Data common to all block flows. - pub block_flow: BlockFlow, - /// The marker, if outside. (Markers that are inside are instead just fragments on the interior - /// `InlineFlow`.) - pub marker_fragments: Vec<Fragment>, -} - -impl ListItemFlow { - pub fn from_fragments_and_flotation( - main_fragment: Fragment, - marker_fragments: Vec<Fragment>, - flotation: Option<FloatKind>, - ) -> ListItemFlow { - let mut this = ListItemFlow { - block_flow: BlockFlow::from_fragment_and_float_kind(main_fragment, flotation), - marker_fragments: marker_fragments, - }; - - if let Some(ref marker) = this.marker_fragments.first() { - match marker.style().get_list().list_style_type { - ListStyleType::Disc | - ListStyleType::None | - ListStyleType::Circle | - ListStyleType::Square | - ListStyleType::DisclosureOpen | - ListStyleType::DisclosureClosed => {}, - _ => this - .block_flow - .base - .restyle_damage - .insert(ServoRestyleDamage::RESOLVE_GENERATED_CONTENT), - } - } - - this - } - - /// Assign inline size and position for the marker. This is done during the `assign_block_size` - /// traversal because floats will impact the marker position. Therefore we need to have already - /// called `assign_block_size` on the list item's block flow, in order to know which floats - /// impact the position. - /// - /// Per CSS 2.1 § 12.5.1, the marker position is not precisely specified, but it must be on the - /// left side of the content (for ltr direction). However, flowing the marker around floats - /// matches the rendering of Gecko and Blink. - fn assign_marker_inline_sizes(&mut self, layout_context: &LayoutContext) { - let base = &self.block_flow.base; - let available_rect = base.floats.available_rect( - -base.position.size.block, - base.position.size.block, - base.block_container_inline_size, - ); - let mut marker_inline_start = available_rect - .unwrap_or(self.block_flow.fragment.border_box) - .start - .i; - - for marker in self.marker_fragments.iter_mut().rev() { - let container_block_size = self - .block_flow - .explicit_block_containing_size(layout_context.shared_context()); - marker.assign_replaced_inline_size_if_necessary( - base.block_container_inline_size, - container_block_size, - ); - - // Do this now. There's no need to do this in bubble-widths, since markers do not - // contribute to the inline size of this flow. - let intrinsic_inline_sizes = marker.compute_intrinsic_inline_sizes(); - - marker.border_box.size.inline = intrinsic_inline_sizes - .content_intrinsic_sizes - .preferred_inline_size; - marker_inline_start = marker_inline_start - marker.border_box.size.inline; - marker.border_box.start.i = marker_inline_start; - } - } - - fn assign_marker_block_sizes(&mut self, layout_context: &LayoutContext) { - // FIXME(pcwalton): Do this during flow construction, like `InlineFlow` does? - let marker_line_metrics = with_thread_local_font_context(layout_context, |font_context| { - InlineFlow::minimum_line_metrics_for_fragments( - &self.marker_fragments, - font_context, - &*self.block_flow.fragment.style, - ) - }); - - for marker in &mut self.marker_fragments { - marker.assign_replaced_block_size_if_necessary(); - let marker_inline_metrics = marker.aligned_inline_metrics( - layout_context, - &marker_line_metrics, - Some(&marker_line_metrics), - ); - marker.border_box.start.b = - marker_line_metrics.space_above_baseline - marker_inline_metrics.ascent; - } - } -} - -impl Flow for ListItemFlow { - fn class(&self) -> FlowClass { - FlowClass::ListItem - } - - fn as_mut_block(&mut self) -> &mut BlockFlow { - &mut self.block_flow - } - - fn as_block(&self) -> &BlockFlow { - &self.block_flow - } - - fn bubble_inline_sizes(&mut self) { - // The marker contributes no intrinsic inline-size, so… - self.block_flow.bubble_inline_sizes() - } - - fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) { - self.block_flow.assign_inline_sizes(layout_context); - } - - fn assign_block_size(&mut self, layout_context: &LayoutContext) { - self.block_flow.assign_block_size(layout_context); - self.assign_marker_inline_sizes(layout_context); - self.assign_marker_block_sizes(layout_context); - } - - fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) { - self.block_flow - .compute_stacking_relative_position(layout_context) - } - - fn place_float_if_applicable<'a>(&mut self) { - self.block_flow.place_float_if_applicable() - } - - fn contains_roots_of_absolute_flow_tree(&self) -> bool { - self.block_flow.contains_roots_of_absolute_flow_tree() - } - - fn is_absolute_containing_block(&self) -> bool { - self.block_flow.is_absolute_containing_block() - } - - fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) { - self.block_flow - .update_late_computed_inline_position_if_necessary(inline_position) - } - - fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) { - self.block_flow - .update_late_computed_block_position_if_necessary(block_position) - } - - fn build_display_list(&mut self, state: &mut DisplayListBuildState) { - // Draw the marker, if applicable. - for marker in &mut self.marker_fragments { - let stacking_relative_border_box = self - .block_flow - .base - .stacking_relative_border_box_for_display_list(marker); - marker.build_display_list( - state, - stacking_relative_border_box, - BorderPaintingMode::Separate, - DisplayListSection::Content, - self.block_flow.base.clip, - None, - ); - } - - // Draw the rest of the block. - self.block_flow - .build_display_list_for_block(state, BorderPaintingMode::Separate) - } - - fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) { - self.block_flow.collect_stacking_contexts(state); - } - - fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>) { - self.block_flow.repair_style(new_style) - } - - fn compute_overflow(&self) -> Overflow { - let mut overflow = self.block_flow.compute_overflow(); - let flow_size = self - .block_flow - .base - .position - .size - .to_physical(self.block_flow.base.writing_mode); - let relative_containing_block_size = &self - .block_flow - .base - .early_absolute_position_info - .relative_containing_block_size; - - for fragment in &self.marker_fragments { - overflow.union(&fragment.compute_overflow(&flow_size, &relative_containing_block_size)) - } - overflow - } - - fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> { - self.block_flow.generated_containing_block_size(flow) - } - - /// The 'position' property of this flow. - fn positioning(&self) -> Position { - self.block_flow.positioning() - } - - fn iterate_through_fragment_border_boxes( - &self, - iterator: &mut dyn FragmentBorderBoxIterator, - level: i32, - stacking_context_position: &Point2D<Au>, - ) { - self.block_flow.iterate_through_fragment_border_boxes( - iterator, - level, - stacking_context_position, - ); - - for marker in &self.marker_fragments { - if iterator.should_process(marker) { - iterator.process( - marker, - level, - &marker - .stacking_relative_border_box( - &self.block_flow.base.stacking_relative_position, - &self - .block_flow - .base - .early_absolute_position_info - .relative_containing_block_size, - self.block_flow - .base - .early_absolute_position_info - .relative_containing_block_mode, - CoordinateSystem::Own, - ) - .translate(&stacking_context_position.to_vector()), - ); - } - } - } - - fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) { - self.block_flow.mutate_fragments(mutator); - - for marker in &mut self.marker_fragments { - (*mutator)(marker) - } - } -} - -/// The kind of content that `list-style-type` results in. -pub enum ListStyleTypeContent { - None, - StaticText(char), - GeneratedContent(Box<GeneratedContentInfo>), -} - -impl ListStyleTypeContent { - /// Returns the content to be used for the given value of the `list-style-type` property. - pub fn from_list_style_type(list_style_type: ListStyleType) -> ListStyleTypeContent { - // Just to keep things simple, use a nonbreaking space (Unicode 0xa0) to provide the marker - // separation. - match list_style_type { - ListStyleType::None => ListStyleTypeContent::None, - ListStyleType::Disc | - ListStyleType::Circle | - ListStyleType::Square | - ListStyleType::DisclosureOpen | - ListStyleType::DisclosureClosed => { - let text = generated_content::static_representation(list_style_type); - ListStyleTypeContent::StaticText(text) - }, - _ => ListStyleTypeContent::GeneratedContent(Box::new(GeneratedContentInfo::ListItem)), - } - } -} diff --git a/components/layout_2020/model.rs b/components/layout_2020/model.rs deleted file mode 100644 index 17c3b625af3..00000000000 --- a/components/layout_2020/model.rs +++ /dev/null @@ -1,609 +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/. */ - -//! Borders, padding, and margins. - -use crate::fragment::Fragment; -use app_units::Au; -use euclid::SideOffsets2D; -use std::cmp::{max, min}; -use std::fmt; -use style::logical_geometry::{LogicalMargin, WritingMode}; -use style::properties::ComputedValues; -use style::values::computed::MaxSize; -use style::values::computed::{LengthPercentageOrAuto, Size}; - -/// A collapsible margin. See CSS 2.1 § 8.3.1. -#[derive(Clone, Copy, Debug)] -pub struct AdjoiningMargins { - /// The value of the greatest positive margin. - pub most_positive: Au, - - /// The actual value (not the absolute value) of the negative margin with the largest absolute - /// value. Since this is not the absolute value, this is always zero or negative. - pub most_negative: Au, -} - -impl AdjoiningMargins { - pub fn new() -> AdjoiningMargins { - AdjoiningMargins { - most_positive: Au(0), - most_negative: Au(0), - } - } - - pub fn from_margin(margin_value: Au) -> AdjoiningMargins { - if margin_value >= Au(0) { - AdjoiningMargins { - most_positive: margin_value, - most_negative: Au(0), - } - } else { - AdjoiningMargins { - most_positive: Au(0), - most_negative: margin_value, - } - } - } - - pub fn union(&mut self, other: AdjoiningMargins) { - self.most_positive = max(self.most_positive, other.most_positive); - self.most_negative = min(self.most_negative, other.most_negative) - } - - pub fn collapse(&self) -> Au { - self.most_positive + self.most_negative - } -} - -/// Represents the block-start and block-end margins of a flow with collapsible margins. See CSS 2.1 § 8.3.1. -#[derive(Clone, Copy, Debug)] -pub enum CollapsibleMargins { - /// Margins may not collapse with this flow. - None(Au, Au), - - /// Both the block-start and block-end margins (specified here in that order) may collapse, but the - /// margins do not collapse through this flow. - Collapse(AdjoiningMargins, AdjoiningMargins), - - /// Margins collapse *through* this flow. This means, essentially, that the flow doesn’t - /// have any border, padding, or out-of-flow (floating or positioned) content - CollapseThrough(AdjoiningMargins), -} - -impl CollapsibleMargins { - pub fn new() -> CollapsibleMargins { - CollapsibleMargins::None(Au(0), Au(0)) - } - - /// Returns the amount of margin that should be applied in a noncollapsible context. This is - /// currently used to apply block-start margin for hypothetical boxes, since we do not collapse - /// margins of hypothetical boxes. - pub fn block_start_margin_for_noncollapsible_context(&self) -> Au { - match *self { - CollapsibleMargins::None(block_start, _) => block_start, - CollapsibleMargins::Collapse(ref block_start, _) | - CollapsibleMargins::CollapseThrough(ref block_start) => block_start.collapse(), - } - } - - pub fn block_end_margin_for_noncollapsible_context(&self) -> Au { - match *self { - CollapsibleMargins::None(_, block_end) => block_end, - CollapsibleMargins::Collapse(_, ref block_end) | - CollapsibleMargins::CollapseThrough(ref block_end) => block_end.collapse(), - } - } -} - -enum FinalMarginState { - MarginsCollapseThrough, - BottomMarginCollapses, -} - -pub struct MarginCollapseInfo { - pub state: MarginCollapseState, - pub block_start_margin: AdjoiningMargins, - pub margin_in: AdjoiningMargins, -} - -impl MarginCollapseInfo { - pub fn initialize_block_start_margin( - fragment: &Fragment, - can_collapse_block_start_margin_with_kids: bool, - ) -> MarginCollapseInfo { - MarginCollapseInfo { - state: if can_collapse_block_start_margin_with_kids { - MarginCollapseState::AccumulatingCollapsibleTopMargin - } else { - MarginCollapseState::AccumulatingMarginIn - }, - block_start_margin: AdjoiningMargins::from_margin(fragment.margin.block_start), - margin_in: AdjoiningMargins::new(), - } - } - - pub fn finish_and_compute_collapsible_margins( - mut self, - fragment: &Fragment, - containing_block_size: Option<Au>, - can_collapse_block_end_margin_with_kids: bool, - mut may_collapse_through: bool, - ) -> (CollapsibleMargins, Au) { - let state = match self.state { - MarginCollapseState::AccumulatingCollapsibleTopMargin => { - may_collapse_through = may_collapse_through && - match fragment.style().content_block_size() { - Size::Auto => true, - Size::LengthPercentage(ref lp) => { - lp.is_definitely_zero() || - lp.maybe_to_used_value(containing_block_size).is_none() - }, - }; - - if may_collapse_through { - if fragment.style.min_block_size().is_auto() || - fragment.style().min_block_size().is_definitely_zero() - { - FinalMarginState::MarginsCollapseThrough - } else { - // If the fragment has non-zero min-block-size, margins may not - // collapse through it. - FinalMarginState::BottomMarginCollapses - } - } else { - // If the fragment has an explicitly specified block-size, margins may not - // collapse through it. - FinalMarginState::BottomMarginCollapses - } - }, - MarginCollapseState::AccumulatingMarginIn => FinalMarginState::BottomMarginCollapses, - }; - - // Different logic is needed here depending on whether this flow can collapse its block-end - // margin with its children. - let block_end_margin = fragment.margin.block_end; - if !can_collapse_block_end_margin_with_kids { - match state { - FinalMarginState::MarginsCollapseThrough => { - let advance = self.block_start_margin.collapse(); - self.margin_in - .union(AdjoiningMargins::from_margin(block_end_margin)); - ( - CollapsibleMargins::Collapse(self.block_start_margin, self.margin_in), - advance, - ) - }, - FinalMarginState::BottomMarginCollapses => { - let advance = self.margin_in.collapse(); - self.margin_in - .union(AdjoiningMargins::from_margin(block_end_margin)); - ( - CollapsibleMargins::Collapse(self.block_start_margin, self.margin_in), - advance, - ) - }, - } - } else { - match state { - FinalMarginState::MarginsCollapseThrough => { - self.block_start_margin - .union(AdjoiningMargins::from_margin(block_end_margin)); - ( - CollapsibleMargins::CollapseThrough(self.block_start_margin), - Au(0), - ) - }, - FinalMarginState::BottomMarginCollapses => { - self.margin_in - .union(AdjoiningMargins::from_margin(block_end_margin)); - ( - CollapsibleMargins::Collapse(self.block_start_margin, self.margin_in), - Au(0), - ) - }, - } - } - } - - pub fn current_float_ceiling(&mut self) -> Au { - match self.state { - MarginCollapseState::AccumulatingCollapsibleTopMargin => { - // We do not include the top margin in the float ceiling, because the float flow - // needs to be positioned relative to our *border box*, not our margin box. See - // `tests/ref/float_under_top_margin_a.html`. - Au(0) - }, - MarginCollapseState::AccumulatingMarginIn => self.margin_in.collapse(), - } - } - - /// Adds the child's potentially collapsible block-start margin to the current margin state and - /// advances the Y offset by the appropriate amount to handle that margin. Returns the amount - /// that should be added to the Y offset during block layout. - pub fn advance_block_start_margin( - &mut self, - child_collapsible_margins: &CollapsibleMargins, - can_collapse_block_start_margin: bool, - ) -> Au { - if !can_collapse_block_start_margin { - self.state = MarginCollapseState::AccumulatingMarginIn - } - - match (self.state, *child_collapsible_margins) { - ( - MarginCollapseState::AccumulatingCollapsibleTopMargin, - CollapsibleMargins::None(block_start, _), - ) => { - self.state = MarginCollapseState::AccumulatingMarginIn; - block_start - }, - ( - MarginCollapseState::AccumulatingCollapsibleTopMargin, - CollapsibleMargins::Collapse(block_start, _), - ) => { - self.block_start_margin.union(block_start); - self.state = MarginCollapseState::AccumulatingMarginIn; - Au(0) - }, - ( - MarginCollapseState::AccumulatingMarginIn, - CollapsibleMargins::None(block_start, _), - ) => { - let previous_margin_value = self.margin_in.collapse(); - self.margin_in = AdjoiningMargins::new(); - previous_margin_value + block_start - }, - ( - MarginCollapseState::AccumulatingMarginIn, - CollapsibleMargins::Collapse(block_start, _), - ) => { - self.margin_in.union(block_start); - let margin_value = self.margin_in.collapse(); - self.margin_in = AdjoiningMargins::new(); - margin_value - }, - (_, CollapsibleMargins::CollapseThrough(_)) => { - // For now, we ignore this; this will be handled by `advance_block_end_margin` - // below. - Au(0) - }, - } - } - - /// Adds the child's potentially collapsible block-end margin to the current margin state and - /// advances the Y offset by the appropriate amount to handle that margin. Returns the amount - /// that should be added to the Y offset during block layout. - pub fn advance_block_end_margin( - &mut self, - child_collapsible_margins: &CollapsibleMargins, - ) -> Au { - match (self.state, *child_collapsible_margins) { - ( - MarginCollapseState::AccumulatingCollapsibleTopMargin, - CollapsibleMargins::None(..), - ) | - ( - MarginCollapseState::AccumulatingCollapsibleTopMargin, - CollapsibleMargins::Collapse(..), - ) => { - // Can't happen because the state will have been replaced with - // `MarginCollapseState::AccumulatingMarginIn` above. - panic!("should not be accumulating collapsible block_start margins anymore!") - }, - ( - MarginCollapseState::AccumulatingCollapsibleTopMargin, - CollapsibleMargins::CollapseThrough(margin), - ) => { - self.block_start_margin.union(margin); - Au(0) - }, - (MarginCollapseState::AccumulatingMarginIn, CollapsibleMargins::None(_, block_end)) => { - assert_eq!(self.margin_in.most_positive, Au(0)); - assert_eq!(self.margin_in.most_negative, Au(0)); - block_end - }, - ( - MarginCollapseState::AccumulatingMarginIn, - CollapsibleMargins::Collapse(_, block_end), - ) | - ( - MarginCollapseState::AccumulatingMarginIn, - CollapsibleMargins::CollapseThrough(block_end), - ) => { - self.margin_in.union(block_end); - Au(0) - }, - } - } -} - -#[derive(Clone, Copy, Debug)] -pub enum MarginCollapseState { - /// We are accumulating margin on the logical top of this flow. - AccumulatingCollapsibleTopMargin, - /// We are accumulating margin between two blocks. - AccumulatingMarginIn, -} - -/// Intrinsic inline-sizes, which consist of minimum and preferred. -#[derive(Clone, Copy, Serialize)] -pub struct IntrinsicISizes { - /// The *minimum inline-size* of the content. - pub minimum_inline_size: Au, - /// The *preferred inline-size* of the content. - pub preferred_inline_size: Au, -} - -impl fmt::Debug for IntrinsicISizes { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "min={:?}, pref={:?}", - self.minimum_inline_size, self.preferred_inline_size - ) - } -} - -impl IntrinsicISizes { - pub fn new() -> IntrinsicISizes { - IntrinsicISizes { - minimum_inline_size: Au(0), - preferred_inline_size: Au(0), - } - } -} - -/// The temporary result of the computation of intrinsic inline-sizes. -#[derive(Debug)] -pub struct IntrinsicISizesContribution { - /// Intrinsic sizes for the content only (not counting borders, padding, or margins). - pub content_intrinsic_sizes: IntrinsicISizes, - /// The inline size of borders and padding, as well as margins if appropriate. - pub surrounding_size: Au, -} - -impl IntrinsicISizesContribution { - /// Creates and initializes an inline size computation with all sizes set to zero. - pub fn new() -> IntrinsicISizesContribution { - IntrinsicISizesContribution { - content_intrinsic_sizes: IntrinsicISizes::new(), - surrounding_size: Au(0), - } - } - - /// Adds the content intrinsic sizes and the surrounding size together to yield the final - /// intrinsic size computation. - pub fn finish(self) -> IntrinsicISizes { - IntrinsicISizes { - minimum_inline_size: self.content_intrinsic_sizes.minimum_inline_size + - self.surrounding_size, - preferred_inline_size: self.content_intrinsic_sizes.preferred_inline_size + - self.surrounding_size, - } - } - - /// Updates the computation so that the minimum is the maximum of the current minimum and the - /// given minimum and the preferred is the sum of the current preferred and the given - /// preferred. This is used when laying out fragments in the inline direction. - /// - /// FIXME(pcwalton): This is incorrect when the inline fragment contains forced line breaks - /// (e.g. `<br>` or `white-space: pre`). - pub fn union_inline(&mut self, sizes: &IntrinsicISizes) { - self.content_intrinsic_sizes.minimum_inline_size = max( - self.content_intrinsic_sizes.minimum_inline_size, - sizes.minimum_inline_size, - ); - self.content_intrinsic_sizes.preferred_inline_size = - self.content_intrinsic_sizes.preferred_inline_size + sizes.preferred_inline_size - } - - /// Updates the computation so that the minimum is the sum of the current minimum and the - /// given minimum and the preferred is the sum of the current preferred and the given - /// preferred. This is used when laying out fragments in the inline direction when - /// `white-space` is `pre` or `nowrap`. - pub fn union_nonbreaking_inline(&mut self, sizes: &IntrinsicISizes) { - self.content_intrinsic_sizes.minimum_inline_size = - self.content_intrinsic_sizes.minimum_inline_size + sizes.minimum_inline_size; - self.content_intrinsic_sizes.preferred_inline_size = - self.content_intrinsic_sizes.preferred_inline_size + sizes.preferred_inline_size - } - - /// Updates the computation so that the minimum is the maximum of the current minimum and the - /// given minimum and the preferred is the maximum of the current preferred and the given - /// preferred. This can be useful when laying out fragments in the block direction (but note - /// that it does not take floats into account, so `BlockFlow` does not use it). - /// - /// This is used when contributing the intrinsic sizes for individual fragments. - pub fn union_block(&mut self, sizes: &IntrinsicISizes) { - self.content_intrinsic_sizes.minimum_inline_size = max( - self.content_intrinsic_sizes.minimum_inline_size, - sizes.minimum_inline_size, - ); - self.content_intrinsic_sizes.preferred_inline_size = max( - self.content_intrinsic_sizes.preferred_inline_size, - sizes.preferred_inline_size, - ) - } -} - -/// Useful helper data type when computing values for blocks and positioned elements. -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum MaybeAuto { - Auto, - Specified(Au), -} - -impl MaybeAuto { - #[inline] - pub fn from_style(length: LengthPercentageOrAuto, containing_length: Au) -> MaybeAuto { - match length { - LengthPercentageOrAuto::Auto => MaybeAuto::Auto, - LengthPercentageOrAuto::LengthPercentage(ref lp) => { - MaybeAuto::Specified(lp.to_used_value(containing_length)) - }, - } - } - - #[inline] - pub fn from_option(au: Option<Au>) -> MaybeAuto { - match au { - Some(l) => MaybeAuto::Specified(l), - _ => MaybeAuto::Auto, - } - } - - #[inline] - pub fn to_option(&self) -> Option<Au> { - match *self { - MaybeAuto::Specified(value) => Some(value), - MaybeAuto::Auto => None, - } - } - - #[inline] - pub fn specified_or_default(&self, default: Au) -> Au { - match *self { - MaybeAuto::Auto => default, - MaybeAuto::Specified(value) => value, - } - } - - #[inline] - pub fn specified_or_zero(&self) -> Au { - self.specified_or_default(Au::new(0)) - } - - #[inline] - pub fn is_auto(&self) -> bool { - match *self { - MaybeAuto::Auto => true, - MaybeAuto::Specified(..) => false, - } - } - - #[inline] - pub fn map<F>(&self, mapper: F) -> MaybeAuto - where - F: FnOnce(Au) -> Au, - { - match *self { - MaybeAuto::Auto => MaybeAuto::Auto, - MaybeAuto::Specified(value) => MaybeAuto::Specified(mapper(value)), - } - } -} - -/// Receive an optional container size and return used value for width or height. -/// -/// `style_length`: content size as given in the CSS. -pub fn style_length(style_length: Size, container_size: Option<Au>) -> MaybeAuto { - match style_length { - Size::Auto => MaybeAuto::Auto, - Size::LengthPercentage(ref lp) => { - MaybeAuto::from_option(lp.0.maybe_to_used_value(container_size)) - }, - } -} - -#[inline] -pub fn padding_from_style( - style: &ComputedValues, - containing_block_inline_size: Au, - writing_mode: WritingMode, -) -> LogicalMargin<Au> { - let padding_style = style.get_padding(); - LogicalMargin::from_physical( - writing_mode, - SideOffsets2D::new( - padding_style - .padding_top - .to_used_value(containing_block_inline_size), - padding_style - .padding_right - .to_used_value(containing_block_inline_size), - padding_style - .padding_bottom - .to_used_value(containing_block_inline_size), - padding_style - .padding_left - .to_used_value(containing_block_inline_size), - ), - ) -} - -/// Returns the explicitly-specified margin lengths from the given style. Percentage and auto -/// margins are returned as zero. -/// -/// This is used when calculating intrinsic inline sizes. -#[inline] -pub fn specified_margin_from_style( - style: &ComputedValues, - writing_mode: WritingMode, -) -> LogicalMargin<Au> { - let margin_style = style.get_margin(); - LogicalMargin::from_physical( - writing_mode, - SideOffsets2D::new( - MaybeAuto::from_style(margin_style.margin_top, Au(0)).specified_or_zero(), - MaybeAuto::from_style(margin_style.margin_right, Au(0)).specified_or_zero(), - MaybeAuto::from_style(margin_style.margin_bottom, Au(0)).specified_or_zero(), - MaybeAuto::from_style(margin_style.margin_left, Au(0)).specified_or_zero(), - ), - ) -} - -/// A min-size and max-size constraint. The constructor has a optional `border` -/// parameter, and when it is present the constraint will be subtracted. This is -/// used to adjust the constraint for `box-sizing: border-box`, and when you do so -/// make sure the size you want to clamp is intended to be used for content box. -#[derive(Clone, Copy, Debug, Serialize)] -pub struct SizeConstraint { - min_size: Au, - max_size: Option<Au>, -} - -impl SizeConstraint { - /// Create a `SizeConstraint` for an axis. - pub fn new( - container_size: Option<Au>, - min_size: Size, - max_size: MaxSize, - border: Option<Au>, - ) -> SizeConstraint { - let mut min_size = match min_size { - Size::Auto => Au(0), - Size::LengthPercentage(ref lp) => { - lp.maybe_to_used_value(container_size).unwrap_or(Au(0)) - }, - }; - - let mut max_size = match max_size { - MaxSize::None => None, - MaxSize::LengthPercentage(ref lp) => lp.maybe_to_used_value(container_size), - }; - - // Make sure max size is not smaller than min size. - max_size = max_size.map(|x| max(x, min_size)); - - if let Some(border) = border { - min_size = max(min_size - border, Au(0)); - max_size = max_size.map(|x| max(x - border, Au(0))); - } - - SizeConstraint { min_size, max_size } - } - - /// Clamp the given size by the given min size and max size constraint. - pub fn clamp(&self, other: Au) -> Au { - if other < self.min_size { - self.min_size - } else { - match self.max_size { - Some(max_size) if max_size < other => max_size, - _ => other, - } - } - } -} diff --git a/components/layout_2020/multicol.rs b/components/layout_2020/multicol.rs deleted file mode 100644 index d49e5f1d79f..00000000000 --- a/components/layout_2020/multicol.rs +++ /dev/null @@ -1,384 +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 Multi-column layout http://dev.w3.org/csswg/css-multicol/ - -use crate::block::BlockFlow; -use crate::context::LayoutContext; -use crate::display_list::{DisplayListBuildState, StackingContextCollectionState}; -use crate::floats::FloatKind; -use crate::flow::{Flow, FlowClass, FragmentationContext, GetBaseFlow, OpaqueFlow}; -use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow}; -use crate::ServoArc; -use app_units::Au; -use euclid::{Point2D, Vector2D}; -use gfx_traits::print_tree::PrintTree; -use std::cmp::{max, min}; -use std::fmt; -use std::sync::Arc; -use style::logical_geometry::LogicalSize; -use style::properties::ComputedValues; -use style::values::computed::length::{ - MaxSize, NonNegativeLengthOrAuto, NonNegativeLengthPercentageOrNormal, Size, -}; -use style::values::generics::column::ColumnCount; - -#[allow(unsafe_code)] -unsafe impl crate::flow::HasBaseFlow for MulticolFlow {} - -#[repr(C)] -pub struct MulticolFlow { - pub block_flow: BlockFlow, - - /// Length between the inline-start edge of a column and that of the next. - /// That is, the used column-width + used column-gap. - pub column_pitch: Au, -} - -#[allow(unsafe_code)] -unsafe impl crate::flow::HasBaseFlow for MulticolColumnFlow {} - -#[repr(C)] -pub struct MulticolColumnFlow { - pub block_flow: BlockFlow, -} - -impl MulticolFlow { - pub fn from_fragment(fragment: Fragment, float_kind: Option<FloatKind>) -> MulticolFlow { - MulticolFlow { - block_flow: BlockFlow::from_fragment_and_float_kind(fragment, float_kind), - column_pitch: Au(0), - } - } -} - -impl MulticolColumnFlow { - pub fn from_fragment(fragment: Fragment) -> MulticolColumnFlow { - MulticolColumnFlow { - block_flow: BlockFlow::from_fragment(fragment), - } - } -} - -impl Flow for MulticolFlow { - fn class(&self) -> FlowClass { - FlowClass::Multicol - } - - fn as_mut_block(&mut self) -> &mut BlockFlow { - &mut self.block_flow - } - - fn as_block(&self) -> &BlockFlow { - &self.block_flow - } - - fn bubble_inline_sizes(&mut self) { - // FIXME(SimonSapin) http://dev.w3.org/csswg/css-sizing/#multicol-intrinsic - self.block_flow.bubble_inline_sizes(); - } - - fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) { - debug!( - "assign_inline_sizes({}): assigning inline_size for flow", - "multicol" - ); - let shared_context = layout_context.shared_context(); - self.block_flow.compute_inline_sizes(shared_context); - - // Move in from the inline-start border edge. - let inline_start_content_edge = self.block_flow.fragment.border_box.start.i + - self.block_flow.fragment.border_padding.inline_start; - - // Distance from the inline-end margin edge to the inline-end content edge. - let inline_end_content_edge = self.block_flow.fragment.margin.inline_end + - self.block_flow.fragment.border_padding.inline_end; - - self.block_flow.assign_inline_sizes(layout_context); - let padding_and_borders = self.block_flow.fragment.border_padding.inline_start_end(); - let content_inline_size = - self.block_flow.fragment.border_box.size.inline - padding_and_borders; - let column_width; - { - let style = &self.block_flow.fragment.style; - let column_gap = match style.get_position().column_gap { - NonNegativeLengthPercentageOrNormal::LengthPercentage(len) => { - len.0.to_pixel_length(content_inline_size).into() - }, - NonNegativeLengthPercentageOrNormal::Normal => { - self.block_flow.fragment.style.get_font().font_size.size() - }, - }; - - let column_style = style.get_column(); - let mut column_count; - if let NonNegativeLengthOrAuto::LengthPercentage(column_width) = - column_style.column_width - { - let column_width = Au::from(column_width); - column_count = max( - 1, - (content_inline_size + column_gap).0 / (column_width + column_gap).0, - ); - if let ColumnCount::Integer(specified_column_count) = column_style.column_count { - column_count = min(column_count, specified_column_count.0 as i32); - } - } else { - column_count = match column_style.column_count { - ColumnCount::Integer(n) => n.0, - _ => unreachable!(), - } - } - column_width = max( - Au(0), - (content_inline_size + column_gap) / column_count - column_gap, - ); - self.column_pitch = column_width + column_gap; - } - - self.block_flow.fragment.border_box.size.inline = content_inline_size + padding_and_borders; - - self.block_flow.propagate_assigned_inline_size_to_children( - shared_context, - inline_start_content_edge, - inline_end_content_edge, - column_width, - |_, _, _, _, _, _| {}, - ); - } - - fn assign_block_size(&mut self, ctx: &LayoutContext) { - debug!("assign_block_size: assigning block_size for multicol"); - - let fragmentation_context = Some(FragmentationContext { - this_fragment_is_empty: true, - available_block_size: { - let style = &self.block_flow.fragment.style; - let size = match style.content_block_size() { - Size::Auto => None, - Size::LengthPercentage(ref lp) => lp.maybe_to_used_value(None), - }; - let size = size.or_else(|| match style.max_block_size() { - MaxSize::None => None, - MaxSize::LengthPercentage(ref lp) => lp.maybe_to_used_value(None), - }); - - size.unwrap_or_else(|| { - // FIXME: do column balancing instead - // FIXME: (until column balancing) substract margins/borders/padding - LogicalSize::from_physical( - self.block_flow.base.writing_mode, - ctx.shared_context().viewport_size(), - ) - .block - }) - }, - }); - - // Before layout, everything is in a single "column" - assert_eq!(self.block_flow.base.children.len(), 1); - let mut column = self.block_flow.base.children.pop_front_arc().unwrap(); - - // Pretend there is no children for this: - self.block_flow.assign_block_size(ctx); - - loop { - let remaining = Arc::get_mut(&mut column) - .unwrap() - .fragment(ctx, fragmentation_context); - self.block_flow.base.children.push_back_arc(column); - column = match remaining { - Some(remaining) => remaining, - None => break, - }; - } - } - - fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) { - self.block_flow - .compute_stacking_relative_position(layout_context); - let pitch = LogicalSize::new(self.block_flow.base.writing_mode, self.column_pitch, Au(0)); - let pitch = pitch.to_physical(self.block_flow.base.writing_mode); - for (i, child) in self.block_flow.base.children.iter_mut().enumerate() { - let point = &mut child.mut_base().stacking_relative_position; - *point = *point + Vector2D::new(pitch.width * i as i32, pitch.height * i as i32); - } - } - - fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) { - self.block_flow - .update_late_computed_inline_position_if_necessary(inline_position) - } - - fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) { - self.block_flow - .update_late_computed_block_position_if_necessary(block_position) - } - - fn build_display_list(&mut self, state: &mut DisplayListBuildState) { - debug!("build_display_list_multicol"); - self.block_flow.build_display_list(state); - } - - fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) { - self.block_flow.collect_stacking_contexts(state); - } - - fn repair_style(&mut self, new_style: &ServoArc<ComputedValues>) { - self.block_flow.repair_style(new_style) - } - - fn compute_overflow(&self) -> Overflow { - self.block_flow.compute_overflow() - } - - fn contains_roots_of_absolute_flow_tree(&self) -> bool { - self.block_flow.contains_roots_of_absolute_flow_tree() - } - - fn is_absolute_containing_block(&self) -> bool { - self.block_flow.is_absolute_containing_block() - } - - fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> { - self.block_flow.generated_containing_block_size(flow) - } - - fn iterate_through_fragment_border_boxes( - &self, - iterator: &mut dyn FragmentBorderBoxIterator, - level: i32, - stacking_context_position: &Point2D<Au>, - ) { - self.block_flow.iterate_through_fragment_border_boxes( - iterator, - level, - stacking_context_position, - ); - } - - fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) { - self.block_flow.mutate_fragments(mutator); - } - - fn print_extra_flow_children(&self, print_tree: &mut PrintTree) { - self.block_flow.print_extra_flow_children(print_tree); - } -} - -impl Flow for MulticolColumnFlow { - fn class(&self) -> FlowClass { - FlowClass::MulticolColumn - } - - fn as_mut_block(&mut self) -> &mut BlockFlow { - &mut self.block_flow - } - - fn as_block(&self) -> &BlockFlow { - &self.block_flow - } - - fn bubble_inline_sizes(&mut self) { - self.block_flow.bubble_inline_sizes(); - } - - fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) { - debug!( - "assign_inline_sizes({}): assigning inline_size for flow", - "multicol column" - ); - self.block_flow.assign_inline_sizes(layout_context); - } - - fn assign_block_size(&mut self, ctx: &LayoutContext) { - debug!("assign_block_size: assigning block_size for multicol column"); - self.block_flow.assign_block_size(ctx); - } - - fn fragment( - &mut self, - layout_context: &LayoutContext, - fragmentation_context: Option<FragmentationContext>, - ) -> Option<Arc<dyn Flow>> { - Flow::fragment(&mut self.block_flow, layout_context, fragmentation_context) - } - - fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) { - self.block_flow - .compute_stacking_relative_position(layout_context) - } - - fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) { - self.block_flow - .update_late_computed_inline_position_if_necessary(inline_position) - } - - fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) { - self.block_flow - .update_late_computed_block_position_if_necessary(block_position) - } - - fn build_display_list(&mut self, state: &mut DisplayListBuildState) { - debug!("build_display_list_multicol column"); - self.block_flow.build_display_list(state); - } - - fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) { - self.block_flow.collect_stacking_contexts(state); - } - - fn repair_style(&mut self, new_style: &ServoArc<ComputedValues>) { - self.block_flow.repair_style(new_style) - } - - fn compute_overflow(&self) -> Overflow { - self.block_flow.compute_overflow() - } - - fn contains_roots_of_absolute_flow_tree(&self) -> bool { - self.block_flow.contains_roots_of_absolute_flow_tree() - } - - fn is_absolute_containing_block(&self) -> bool { - self.block_flow.is_absolute_containing_block() - } - - fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> { - self.block_flow.generated_containing_block_size(flow) - } - - fn iterate_through_fragment_border_boxes( - &self, - iterator: &mut dyn FragmentBorderBoxIterator, - level: i32, - stacking_context_position: &Point2D<Au>, - ) { - self.block_flow.iterate_through_fragment_border_boxes( - iterator, - level, - stacking_context_position, - ); - } - - fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) { - self.block_flow.mutate_fragments(mutator); - } - - fn print_extra_flow_children(&self, print_tree: &mut PrintTree) { - self.block_flow.print_extra_flow_children(print_tree); - } -} - -impl fmt::Debug for MulticolFlow { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "MulticolFlow: {:?}", self.block_flow) - } -} - -impl fmt::Debug for MulticolColumnFlow { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "MulticolColumnFlow: {:?}", self.block_flow) - } -} diff --git a/components/layout_2020/opaque_node.rs b/components/layout_2020/opaque_node.rs index b48820052e9..3d56ef21dae 100644 --- a/components/layout_2020/opaque_node.rs +++ b/components/layout_2020/opaque_node.rs @@ -7,8 +7,6 @@ use libc::c_void; use script_traits::UntrustedNodeAddress; pub trait OpaqueNodeMethods { - /// Converts this node to an `UntrustedNodeAddress`. An `UntrustedNodeAddress` is just the type - /// of node that script expects to receive in a hit test. fn to_untrusted_node_address(&self) -> UntrustedNodeAddress; } diff --git a/components/layout_2020/parallel.rs b/components/layout_2020/parallel.rs deleted file mode 100644 index 4ad10b3db11..00000000000 --- a/components/layout_2020/parallel.rs +++ /dev/null @@ -1,232 +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/. */ - -//! Implements parallel traversals over the DOM and flow trees. -//! -//! This code is highly unsafe. Keep this file small and easy to audit. - -#![allow(unsafe_code)] - -use crate::block::BlockFlow; -use crate::context::LayoutContext; -use crate::flow::{Flow, GetBaseFlow}; -use crate::flow_ref::FlowRef; -use crate::traversal::{AssignBSizes, AssignISizes, BubbleISizes}; -use crate::traversal::{PostorderFlowTraversal, PreorderFlowTraversal}; -use profile_traits::time::{self, profile, TimerMetadata}; -use servo_config::opts; -use smallvec::SmallVec; -use std::mem; -use std::ptr; -use std::sync::atomic::{AtomicIsize, Ordering}; - -/// Traversal chunk size. -const CHUNK_SIZE: usize = 16; - -pub type FlowList = SmallVec<[UnsafeFlow; CHUNK_SIZE]>; - -/// Vtable + pointer representation of a Flow trait object. -#[derive(Clone, Copy, Eq, PartialEq)] -pub struct UnsafeFlow(*const dyn Flow); - -unsafe impl Sync for UnsafeFlow {} -unsafe impl Send for UnsafeFlow {} - -fn null_unsafe_flow() -> UnsafeFlow { - UnsafeFlow(ptr::null::<BlockFlow>()) -} - -pub fn mut_owned_flow_to_unsafe_flow(flow: *mut FlowRef) -> UnsafeFlow { - unsafe { UnsafeFlow(&**flow) } -} - -/// Information that we need stored in each flow. -pub struct FlowParallelInfo { - /// The number of children that still need work done. - pub children_count: AtomicIsize, - /// The address of the parent flow. - pub parent: UnsafeFlow, -} - -impl FlowParallelInfo { - pub fn new() -> FlowParallelInfo { - FlowParallelInfo { - children_count: AtomicIsize::new(0), - parent: null_unsafe_flow(), - } - } -} - -/// Process current flow and potentially traverse its ancestors. -/// -/// If we are the last child that finished processing, recursively process -/// our parent. Else, stop. Also, stop at the root. -/// -/// Thus, if we start with all the leaves of a tree, we end up traversing -/// the whole tree bottom-up because each parent will be processed exactly -/// once (by the last child that finishes processing). -/// -/// The only communication between siblings is that they both -/// fetch-and-subtract the parent's children count. -fn bottom_up_flow(mut unsafe_flow: UnsafeFlow, assign_bsize_traversal: &AssignBSizes) { - loop { - // Get a real flow. - let flow: &mut dyn Flow = unsafe { mem::transmute(unsafe_flow) }; - - // Perform the appropriate traversal. - if assign_bsize_traversal.should_process(flow) { - assign_bsize_traversal.process(flow); - } - - let base = flow.mut_base(); - - // Reset the count of children for the next layout traversal. - base.parallel - .children_count - .store(base.children.len() as isize, Ordering::Relaxed); - - // Possibly enqueue the parent. - let unsafe_parent = base.parallel.parent; - if unsafe_parent == null_unsafe_flow() { - // We're done! - break; - } - - // No, we're not at the root yet. Then are we the last child - // of our parent to finish processing? If so, we can continue - // on with our parent; otherwise, we've gotta wait. - let parent: &mut dyn Flow = unsafe { &mut *(unsafe_parent.0 as *mut dyn Flow) }; - let parent_base = parent.mut_base(); - if parent_base - .parallel - .children_count - .fetch_sub(1, Ordering::Relaxed) == - 1 - { - // We were the last child of our parent. Reflow our parent. - unsafe_flow = unsafe_parent - } else { - // Stop. - break; - } - } -} - -fn top_down_flow<'scope>( - unsafe_flows: &[UnsafeFlow], - pool: &'scope rayon::ThreadPool, - scope: &rayon::ScopeFifo<'scope>, - assign_isize_traversal: &'scope AssignISizes, - assign_bsize_traversal: &'scope AssignBSizes, -) { - let mut discovered_child_flows = FlowList::new(); - - for unsafe_flow in unsafe_flows { - let mut had_children = false; - unsafe { - // Get a real flow. - let flow: &mut dyn Flow = mem::transmute(*unsafe_flow); - flow.mut_base().thread_id = pool.current_thread_index().unwrap() as u8; - - if assign_isize_traversal.should_process(flow) { - // Perform the appropriate traversal. - assign_isize_traversal.process(flow); - } - - // Possibly enqueue the children. - for kid in flow.mut_base().child_iter_mut() { - had_children = true; - discovered_child_flows.push(UnsafeFlow(kid)); - } - } - - // If there were no more children, start assigning block-sizes. - if !had_children { - bottom_up_flow(*unsafe_flow, &assign_bsize_traversal) - } - } - - if discovered_child_flows.is_empty() { - return; - } - - if discovered_child_flows.len() <= CHUNK_SIZE { - // We can handle all the children in this work unit. - top_down_flow( - &discovered_child_flows, - pool, - scope, - &assign_isize_traversal, - &assign_bsize_traversal, - ); - } else { - // Spawn a new work unit for each chunk after the first. - let mut chunks = discovered_child_flows.chunks(CHUNK_SIZE); - let first_chunk = chunks.next(); - for chunk in chunks { - let nodes = chunk.iter().cloned().collect::<FlowList>(); - scope.spawn_fifo(move |scope| { - top_down_flow( - &nodes, - pool, - scope, - &assign_isize_traversal, - &assign_bsize_traversal, - ); - }); - } - if let Some(chunk) = first_chunk { - top_down_flow( - chunk, - pool, - scope, - &assign_isize_traversal, - &assign_bsize_traversal, - ); - } - } -} - -/// Run the main layout passes in parallel. -pub fn reflow( - root: &mut dyn Flow, - profiler_metadata: Option<TimerMetadata>, - time_profiler_chan: time::ProfilerChan, - context: &LayoutContext, - queue: &rayon::ThreadPool, -) { - if opts::get().bubble_inline_sizes_separately { - let bubble_inline_sizes = BubbleISizes { - layout_context: &context, - }; - bubble_inline_sizes.traverse(root); - } - - let assign_isize_traversal = &AssignISizes { - layout_context: &context, - }; - let assign_bsize_traversal = &AssignBSizes { - layout_context: &context, - }; - let nodes = [UnsafeFlow(root)]; - - queue.install(move || { - rayon::scope_fifo(move |scope| { - profile( - time::ProfilerCategory::LayoutParallelWarmup, - profiler_metadata, - time_profiler_chan, - move || { - top_down_flow( - &nodes, - queue, - scope, - assign_isize_traversal, - assign_bsize_traversal, - ); - }, - ); - }); - }); -} diff --git a/components/layout_2020/persistent_list.rs b/components/layout_2020/persistent_list.rs deleted file mode 100644 index 16bbc319ea0..00000000000 --- a/components/layout_2020/persistent_list.rs +++ /dev/null @@ -1,101 +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/. */ - -//! A persistent, thread-safe singly-linked list. - -use std::sync::Arc; - -pub struct PersistentList<T> { - head: PersistentListLink<T>, - length: usize, -} - -struct PersistentListEntry<T> { - value: T, - next: PersistentListLink<T>, -} - -type PersistentListLink<T> = Option<Arc<PersistentListEntry<T>>>; - -impl<T> PersistentList<T> -where - T: Send + Sync, -{ - #[inline] - pub fn new() -> PersistentList<T> { - PersistentList { - head: None, - length: 0, - } - } - - #[inline] - pub fn len(&self) -> usize { - self.length - } - - #[inline] - pub fn front(&self) -> Option<&T> { - self.head.as_ref().map(|head| &head.value) - } - - #[inline] - pub fn prepend_elem(&self, value: T) -> PersistentList<T> { - PersistentList { - head: Some(Arc::new(PersistentListEntry { - value: value, - next: self.head.clone(), - })), - length: self.length + 1, - } - } - - #[inline] - pub fn iter(&self) -> PersistentListIterator<T> { - // This could clone (and would not need the lifetime if it did), but then it would incur - // atomic operations on every call to `.next()`. Bad. - PersistentListIterator { - entry: self.head.as_ref().map(|head| &**head), - } - } -} - -impl<T> Clone for PersistentList<T> -where - T: Send + Sync, -{ - fn clone(&self) -> PersistentList<T> { - // This establishes the persistent nature of this list: we can clone a list by just cloning - // its head. - PersistentList { - head: self.head.clone(), - length: self.length, - } - } -} - -pub struct PersistentListIterator<'a, T> -where - T: Send + Sync, -{ - entry: Option<&'a PersistentListEntry<T>>, -} - -impl<'a, T> Iterator for PersistentListIterator<'a, T> -where - T: Send + Sync + 'static, -{ - type Item = &'a T; - - #[inline] - fn next(&mut self) -> Option<&'a T> { - let entry = self.entry?; - let value = &entry.value; - self.entry = match entry.next { - None => None, - Some(ref entry) => Some(&**entry), - }; - Some(value) - } -} diff --git a/components/layout_2020/query.rs b/components/layout_2020/query.rs index 789056753d5..2dfb2eebaa0 100644 --- a/components/layout_2020/query.rs +++ b/components/layout_2020/query.rs @@ -4,16 +4,11 @@ //! Utilities for querying the layout, as needed by the layout thread. -use crate::construct::ConstructionResult; use crate::context::LayoutContext; use crate::display_list::items::{DisplayList, OpaqueNode, ScrollOffsetMap}; use crate::display_list::IndexableText; -use crate::flow::{Flow, GetBaseFlow}; -use crate::fragment::{Fragment, FragmentBorderBoxIterator, SpecificFragmentInfo}; -use crate::inline::InlineFragmentNodeFlags; +use crate::fragment::{Fragment, FragmentBorderBoxIterator}; use crate::opaque_node::OpaqueNodeMethods; -use crate::sequential; -use crate::wrapper::LayoutNodeLayoutData; use app_units::Au; use euclid::{Point2D, Rect, Size2D, Vector2D}; use ipc_channel::ipc::IpcSender; @@ -30,7 +25,6 @@ use script_layout_interface::{LayoutElementType, LayoutNodeType}; use script_traits::LayoutMsg as ConstellationMsg; use script_traits::UntrustedNodeAddress; use std::cmp::{max, min}; -use std::ops::Deref; use std::sync::{Arc, Mutex}; use style::computed_values::display::T as Display; use style::computed_values::position::T as Position; @@ -366,24 +360,16 @@ impl FragmentBorderBoxIterator for MarginRetrievingFragmentBorderBoxIterator { pub fn process_content_box_request( requested_node: OpaqueNode, - layout_root: &mut dyn Flow, ) -> Option<Rect<Au>> { - // FIXME(pcwalton): This has not been updated to handle the stacking context relative - // stuff. So the position is wrong in most cases. - let mut iterator = UnioningFragmentBorderBoxIterator::new(requested_node); - sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, &mut iterator); - iterator.rect + UnioningFragmentBorderBoxIterator::new(requested_node).rect } pub fn process_content_boxes_request( requested_node: OpaqueNode, - layout_root: &mut dyn Flow, ) -> Vec<Rect<Au>> { // FIXME(pcwalton): This has not been updated to handle the stacking context relative // stuff. So the position is wrong in most cases. - let mut iterator = CollectingFragmentBorderBoxIterator::new(requested_node); - sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, &mut iterator); - iterator.rects + CollectingFragmentBorderBoxIterator::new(requested_node).rects } struct FragmentLocatingFragmentIterator { @@ -581,45 +567,6 @@ impl FragmentBorderBoxIterator for ParentOffsetBorderBoxIterator { if fragment.style.get_box().position == Position::Fixed { self.parent_nodes.clear(); } - } else if let Some(node) = fragment.inline_context.as_ref().and_then(|inline_context| { - inline_context - .nodes - .iter() - .find(|node| node.address == self.node_address) - }) { - // TODO: Handle cases where the `offsetParent` is an inline - // element. This will likely be impossible until - // https://github.com/servo/servo/issues/13982 is fixed. - - // Found a fragment in the flow tree whose inline context - // contains the DOM node we're looking for, i.e. the node - // is inline and contains this fragment. - match self.node_offset_box { - Some(NodeOffsetBoxInfo { - ref mut rectangle, .. - }) => { - *rectangle = rectangle.union(border_box); - }, - None => { - // https://github.com/servo/servo/issues/13982 will - // cause this assertion to fail sometimes, so it's - // commented out for now. - /*assert!(node.flags.contains(FIRST_FRAGMENT_OF_ELEMENT), - "First fragment of inline node found wasn't its first fragment!");*/ - - self.node_offset_box = Some(NodeOffsetBoxInfo { - offset: border_box.origin, - rectangle: *border_box, - }); - }, - } - - if node - .flags - .contains(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT) - { - self.has_processed_node = true; - } } else if self.node_offset_box.is_none() { // TODO(gw): Is there a less fragile way of checking whether this // fragment is the body element, rather than just checking that @@ -636,8 +583,6 @@ impl FragmentBorderBoxIterator for ParentOffsetBorderBoxIterator { // 2) Is static position *and* is a table or table cell // 3) Is not static position (true, _, _) | - (false, Position::Static, &SpecificFragmentInfo::Table) | - (false, Position::Static, &SpecificFragmentInfo::TableCell) | (false, Position::Sticky, _) | (false, Position::Absolute, _) | (false, Position::Relative, _) | @@ -671,11 +616,8 @@ impl FragmentBorderBoxIterator for ParentOffsetBorderBoxIterator { pub fn process_node_geometry_request( requested_node: OpaqueNode, - layout_root: &mut dyn Flow, ) -> Rect<i32> { - let mut iterator = FragmentLocatingFragmentIterator::new(requested_node); - sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, &mut iterator); - iterator.client_rect + FragmentLocatingFragmentIterator::new(requested_node).client_rect } pub fn process_node_scroll_id_request<N: LayoutNode>( @@ -689,10 +631,8 @@ pub fn process_node_scroll_id_request<N: LayoutNode>( /// https://drafts.csswg.org/cssom-view/#scrolling-area pub fn process_node_scroll_area_request( requested_node: OpaqueNode, - layout_root: &mut dyn Flow, ) -> Rect<i32> { - let mut iterator = UnioningFragmentScrollAreaIterator::new(requested_node); - sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, &mut iterator); + let iterator = UnioningFragmentScrollAreaIterator::new(requested_node); match iterator.overflow_direction { OverflowDirection::RightAndDown => { let right = max( @@ -742,7 +682,6 @@ pub fn process_resolved_style_request<'a, N>( node: N, pseudo: &Option<PseudoElement>, property: &PropertyId, - layout_root: &mut dyn Flow, ) -> String where N: LayoutNode, @@ -755,7 +694,7 @@ where // We call process_resolved_style_request after performing a whole-document // traversal, so in the common case, the element is styled. if element.get_data().is_some() { - return process_resolved_style_request_internal(node, pseudo, property, layout_root); + return process_resolved_style_request_internal(node, pseudo, property); } // In a display: none subtree. No pseudo-element exists. @@ -791,7 +730,6 @@ fn process_resolved_style_request_internal<'a, N>( requested_node: N, pseudo: &Option<PseudoElement>, property: &PropertyId, - layout_root: &mut dyn Flow, ) -> String where N: LayoutNode, @@ -842,24 +780,11 @@ where let applies = true; fn used_value_for_position_property<N: LayoutNode>( - layout_el: <N::ConcreteThreadSafeLayoutNode as ThreadSafeLayoutNode>::ConcreteThreadSafeLayoutElement, - layout_root: &mut dyn Flow, + _layout_el: <N::ConcreteThreadSafeLayoutNode as ThreadSafeLayoutNode>::ConcreteThreadSafeLayoutElement, requested_node: N, longhand_id: LonghandId, ) -> String { - let maybe_data = layout_el.borrow_layout_data(); - let position = maybe_data.map_or(Point2D::zero(), |data| { - match (*data).flow_construction_result { - ConstructionResult::Flow(ref flow_ref, _) => flow_ref - .deref() - .base() - .stacking_relative_position - .to_point(), - // TODO(dzbarsky) search parents until we find node with a flow ref. - // https://github.com/servo/servo/issues/8307 - _ => Point2D::zero(), - } - }); + let position = Point2D::zero(); let property = match longhand_id { LonghandId::Bottom => PositionProperty::Bottom, LonghandId::Top => PositionProperty::Top, @@ -869,12 +794,11 @@ where LonghandId::Height => PositionProperty::Height, _ => unreachable!(), }; - let mut iterator = PositionRetrievingFragmentBorderBoxIterator::new( + let iterator = PositionRetrievingFragmentBorderBoxIterator::new( requested_node.opaque(), property, position, ); - sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, &mut iterator); iterator .result .map(|r| r.to_css_string()) @@ -904,13 +828,12 @@ where LonghandId::PaddingRight => (MarginPadding::Padding, Side::Right), _ => unreachable!(), }; - let mut iterator = MarginRetrievingFragmentBorderBoxIterator::new( + let iterator = MarginRetrievingFragmentBorderBoxIterator::new( requested_node.opaque(), side, margin_padding, style.writing_mode, ); - sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, &mut iterator); iterator .result .map(|r| r.to_css_string()) @@ -920,12 +843,12 @@ where LonghandId::Bottom | LonghandId::Top | LonghandId::Right | LonghandId::Left if applies && positioned && style.get_box().display != Display::None => { - used_value_for_position_property(layout_el, layout_root, requested_node, longhand_id) + used_value_for_position_property(layout_el, requested_node, longhand_id) }, LonghandId::Width | LonghandId::Height if applies && style.get_box().display != Display::None => { - used_value_for_position_property(layout_el, layout_root, requested_node, longhand_id) + used_value_for_position_property(layout_el, requested_node, longhand_id) }, // FIXME: implement used value computation for line-height _ => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)), @@ -934,10 +857,8 @@ where pub fn process_offset_parent_query( requested_node: OpaqueNode, - layout_root: &mut dyn Flow, ) -> OffsetParentResponse { - let mut iterator = ParentOffsetBorderBoxIterator::new(requested_node); - sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, &mut iterator); + let iterator = ParentOffsetBorderBoxIterator::new(requested_node); let node_offset_box = iterator.node_offset_box; let parent_info = iterator diff --git a/components/layout_2020/sequential.rs b/components/layout_2020/sequential.rs deleted file mode 100644 index 16dd3b8d4da..00000000000 --- a/components/layout_2020/sequential.rs +++ /dev/null @@ -1,193 +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/. */ - -//! Implements sequential traversals over the DOM and flow trees. - -use crate::context::LayoutContext; -use crate::display_list::items::{self, CommonDisplayItem, DisplayItem, DisplayListSection}; -use crate::display_list::{DisplayListBuildState, StackingContextCollectionState}; -use crate::floats::SpeculatedFloatPlacement; -use crate::flow::{Flow, FlowFlags, GetBaseFlow, ImmutableFlowUtils}; -use crate::fragment::{CoordinateSystem, FragmentBorderBoxIterator}; -use crate::generated_content::ResolveGeneratedContent; -use crate::incremental::RelayoutMode; -use crate::traversal::{AssignBSizes, AssignISizes, BubbleISizes, BuildDisplayList}; -use crate::traversal::{InorderFlowTraversal, PostorderFlowTraversal, PreorderFlowTraversal}; -use app_units::Au; -use euclid::{Point2D, Rect, Size2D, Vector2D}; -use servo_config::opts; -use style::servo::restyle_damage::ServoRestyleDamage; -use webrender_api::units::LayoutPoint; - -pub fn resolve_generated_content(root: &mut dyn Flow, layout_context: &LayoutContext) { - ResolveGeneratedContent::new(&layout_context).traverse(root, 0); -} - -/// Run the main layout passes sequentially. -pub fn reflow(root: &mut dyn Flow, layout_context: &LayoutContext, relayout_mode: RelayoutMode) { - fn doit( - flow: &mut dyn Flow, - assign_inline_sizes: AssignISizes, - assign_block_sizes: AssignBSizes, - relayout_mode: RelayoutMode, - ) { - // Force reflow children during this traversal. This is needed when we failed - // the float speculation of a block formatting context and need to fix it. - if relayout_mode == RelayoutMode::Force { - flow.mut_base() - .restyle_damage - .insert(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW); - } - - if assign_inline_sizes.should_process(flow) { - assign_inline_sizes.process(flow); - } - - for kid in flow.mut_base().child_iter_mut() { - doit(kid, assign_inline_sizes, assign_block_sizes, relayout_mode); - } - - if assign_block_sizes.should_process(flow) { - assign_block_sizes.process(flow); - } - } - - if opts::get().bubble_inline_sizes_separately { - let bubble_inline_sizes = BubbleISizes { - layout_context: &layout_context, - }; - bubble_inline_sizes.traverse(root); - } - - let assign_inline_sizes = AssignISizes { - layout_context: &layout_context, - }; - let assign_block_sizes = AssignBSizes { - layout_context: &layout_context, - }; - - doit(root, assign_inline_sizes, assign_block_sizes, relayout_mode); -} - -pub fn build_display_list_for_subtree<'a>( - flow_root: &mut dyn Flow, - layout_context: &'a LayoutContext, - background_color: webrender_api::ColorF, - client_size: Size2D<Au>, -) -> DisplayListBuildState<'a> { - let mut state = StackingContextCollectionState::new(layout_context.id); - flow_root.collect_stacking_contexts(&mut state); - - let mut state = DisplayListBuildState::new(layout_context, state); - - // Create a base rectangle for the page background based on the root - // background color. - let base = state.create_base_display_item( - Rect::new(Point2D::new(Au::new(0), Au::new(0)), client_size), - flow_root.as_block().fragment.node, - None, - DisplayListSection::BackgroundAndBorders, - ); - state.add_display_item(DisplayItem::Rectangle(CommonDisplayItem::new( - base, - webrender_api::RectangleDisplayItem { - color: background_color, - common: items::empty_common_item_properties(), - }, - ))); - - let mut build_display_list = BuildDisplayList { state: state }; - build_display_list.traverse(flow_root); - build_display_list.state -} - -pub fn iterate_through_flow_tree_fragment_border_boxes( - root: &mut dyn Flow, - iterator: &mut dyn FragmentBorderBoxIterator, -) { - fn doit( - flow: &mut dyn Flow, - level: i32, - iterator: &mut dyn FragmentBorderBoxIterator, - stacking_context_position: &Point2D<Au>, - ) { - flow.iterate_through_fragment_border_boxes(iterator, level, stacking_context_position); - - for kid in flow.mut_base().child_iter_mut() { - let mut stacking_context_position = *stacking_context_position; - if kid.is_block_flow() && kid.as_block().fragment.establishes_stacking_context() { - stacking_context_position = - Point2D::new(kid.as_block().fragment.margin.inline_start, Au(0)) + - kid.base().stacking_relative_position + - stacking_context_position.to_vector(); - let relative_position = kid - .as_block() - .stacking_relative_border_box(CoordinateSystem::Own); - if let Some(matrix) = kid.as_block().fragment.transform_matrix(&relative_position) { - let transform_matrix = matrix.transform_point2d(&LayoutPoint::zero()).unwrap(); - stacking_context_position = stacking_context_position + - Vector2D::new( - Au::from_f32_px(transform_matrix.x), - Au::from_f32_px(transform_matrix.y), - ) - } - } - doit(kid, level + 1, iterator, &stacking_context_position); - } - } - - doit(root, 0, iterator, &Point2D::zero()); -} - -pub fn store_overflow(layout_context: &LayoutContext, flow: &mut dyn Flow) { - if !flow - .base() - .restyle_damage - .contains(ServoRestyleDamage::STORE_OVERFLOW) - { - return; - } - - for kid in flow.mut_base().child_iter_mut() { - store_overflow(layout_context, kid); - } - - flow.store_overflow(layout_context); - - flow.mut_base() - .restyle_damage - .remove(ServoRestyleDamage::STORE_OVERFLOW); -} - -/// Guesses how much inline size will be taken up by floats on the left and right sides of the -/// given flow. This is needed to speculatively calculate the inline sizes of block formatting -/// contexts. The speculation typically succeeds, but if it doesn't we have to lay it out again. -pub fn guess_float_placement(flow: &mut dyn Flow) { - if !flow - .base() - .restyle_damage - .intersects(ServoRestyleDamage::REFLOW) - { - return; - } - - let mut floats_in = SpeculatedFloatPlacement::compute_floats_in_for_first_child(flow); - for kid in flow.mut_base().child_iter_mut() { - if kid - .base() - .flags - .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) - { - // Do not propagate floats in or out, but do propogate between kids. - guess_float_placement(kid); - } else { - floats_in.compute_floats_in(kid); - kid.mut_base().speculated_float_placement_in = floats_in; - guess_float_placement(kid); - floats_in = kid.base().speculated_float_placement_out; - } - } - floats_in.compute_floats_out(flow); - flow.mut_base().speculated_float_placement_out = floats_in -} diff --git a/components/layout_2020/table.rs b/components/layout_2020/table.rs deleted file mode 100644 index 9f6219f0980..00000000000 --- a/components/layout_2020/table.rs +++ /dev/null @@ -1,1378 +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 table formatting contexts. - -use crate::block::{BlockFlow, CandidateBSizeIterator, ISizeAndMarginsComputer}; -use crate::block::{ISizeConstraintInput, ISizeConstraintSolution}; -use crate::context::LayoutContext; -use crate::display_list::{ - BorderPaintingMode, DisplayListBuildState, StackingContextCollectionFlags, - StackingContextCollectionState, -}; -use crate::flow::{ - BaseFlow, EarlyAbsolutePositionInfo, Flow, FlowClass, GetBaseFlow, ImmutableFlowUtils, - OpaqueFlow, -}; -use crate::flow_list::{FlowListIterator, MutFlowListIterator}; -use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow}; -use crate::layout_debug; -use crate::model::{IntrinsicISizes, IntrinsicISizesContribution, MaybeAuto}; -use crate::table_cell::TableCellFlow; -use crate::table_row::{self, CellIntrinsicInlineSize, CollapsedBorder, CollapsedBorderProvenance}; -use crate::table_row::{TableRowFlow, TableRowSizeData}; -use crate::table_wrapper::TableLayout; -use app_units::Au; -use euclid::Point2D; -use gfx_traits::print_tree::PrintTree; -use std::{cmp, fmt}; -use style::computed_values::{border_collapse, border_spacing, table_layout}; -use style::context::SharedStyleContext; -use style::logical_geometry::LogicalSize; -use style::properties::style_structs::Background; -use style::properties::ComputedValues; -use style::servo::restyle_damage::ServoRestyleDamage; -use style::values::computed::Size; -use style::values::CSSFloat; - -#[allow(unsafe_code)] -unsafe impl crate::flow::HasBaseFlow for TableFlow {} - -/// A table flow corresponded to the table's internal table fragment under a table wrapper flow. -/// The properties `position`, `float`, and `margin-*` are used on the table wrapper fragment, -/// not table fragment per CSS 2.1 § 10.5. -#[derive(Serialize)] -#[repr(C)] -pub struct TableFlow { - pub block_flow: BlockFlow, - - /// Information about the intrinsic inline-sizes of each column, computed bottom-up during - /// intrinsic inline-size bubbling. - pub column_intrinsic_inline_sizes: Vec<ColumnIntrinsicInlineSize>, - - /// Information about the actual inline sizes of each column, computed top-down during actual - /// inline-size bubbling. - pub column_computed_inline_sizes: Vec<ColumnComputedInlineSize>, - - /// The final width of the borders in the inline direction for each cell, computed by the - /// entire table and pushed down into each row during inline size computation. - pub collapsed_inline_direction_border_widths_for_table: Vec<Au>, - - /// The final width of the borders in the block direction for each cell, computed by the - /// entire table and pushed down into each row during inline size computation. - pub collapsed_block_direction_border_widths_for_table: Vec<Au>, - - /// Table-layout property - pub table_layout: TableLayout, -} - -impl TableFlow { - pub fn from_fragment(fragment: Fragment) -> TableFlow { - let mut block_flow = BlockFlow::from_fragment(fragment); - let table_layout = - if block_flow.fragment().style().get_table().table_layout == table_layout::T::Fixed { - TableLayout::Fixed - } else { - TableLayout::Auto - }; - TableFlow { - block_flow: block_flow, - column_intrinsic_inline_sizes: Vec::new(), - column_computed_inline_sizes: Vec::new(), - collapsed_inline_direction_border_widths_for_table: Vec::new(), - collapsed_block_direction_border_widths_for_table: Vec::new(), - table_layout: table_layout, - } - } - - /// Update the corresponding value of `self_inline_sizes` if a value of `kid_inline_sizes` has - /// a larger value than one of `self_inline_sizes`. Returns the minimum and preferred inline - /// sizes. - fn update_automatic_column_inline_sizes( - parent_inline_sizes: &mut Vec<ColumnIntrinsicInlineSize>, - child_cell_inline_sizes: &[CellIntrinsicInlineSize], - surrounding_size: Au, - ) -> IntrinsicISizes { - let mut total_inline_sizes = IntrinsicISizes { - minimum_inline_size: surrounding_size, - preferred_inline_size: surrounding_size, - }; - let mut column_index = 0; - let mut incoming_rowspan = vec![]; - - for child_cell_inline_size in child_cell_inline_sizes { - // Skip any column occupied by a cell from a previous row. - while column_index < incoming_rowspan.len() && incoming_rowspan[column_index] != 1 { - if incoming_rowspan[column_index] > 1 { - incoming_rowspan[column_index] -= 1; - } - column_index += 1; - } - for _ in 0..child_cell_inline_size.column_span { - if column_index < parent_inline_sizes.len() { - // We already have some intrinsic size information for this column. Merge it in - // according to the rules specified in INTRINSIC § 4. - let parent_sizes = &mut parent_inline_sizes[column_index]; - if child_cell_inline_size.column_span > 1 { - // TODO(pcwalton): Perform the recursive algorithm specified in INTRINSIC § - // 4. For now we make this column contribute no width. - } else { - let column_size = &child_cell_inline_size.column_size; - *parent_sizes = ColumnIntrinsicInlineSize { - minimum_length: cmp::max( - parent_sizes.minimum_length, - column_size.minimum_length, - ), - percentage: parent_sizes.greatest_percentage(column_size), - preferred: cmp::max(parent_sizes.preferred, column_size.preferred), - constrained: parent_sizes.constrained || column_size.constrained, - } - } - } else { - // We discovered a new column. Initialize its data. - debug_assert_eq!(column_index, parent_inline_sizes.len()); - if child_cell_inline_size.column_span > 1 { - // TODO(pcwalton): Perform the recursive algorithm specified in INTRINSIC § - // 4. For now we make this column contribute no width. - parent_inline_sizes.push(ColumnIntrinsicInlineSize::new()) - } else { - parent_inline_sizes.push(child_cell_inline_size.column_size) - } - } - - total_inline_sizes.minimum_inline_size += - parent_inline_sizes[column_index].minimum_length; - total_inline_sizes.preferred_inline_size += - parent_inline_sizes[column_index].preferred; - - // If this cell spans later rows, record its rowspan. - if child_cell_inline_size.row_span > 1 { - if incoming_rowspan.len() < column_index + 1 { - incoming_rowspan.resize(column_index + 1, 0); - } - incoming_rowspan[column_index] = child_cell_inline_size.row_span; - } - - column_index += 1 - } - } - - total_inline_sizes - } - - /// Updates the minimum and preferred inline-size calculation for a single row. This is - /// factored out into a separate function because we process children of rowgroups too. - fn update_column_inline_sizes_for_row( - row: &TableRowFlow, - column_inline_sizes: &mut Vec<ColumnIntrinsicInlineSize>, - computation: &mut IntrinsicISizesContribution, - first_row: bool, - table_layout: TableLayout, - surrounding_inline_size: Au, - ) { - // Read column inline-sizes from the table-row, and assign inline-size=0 for the columns - // not defined in the column group. - // - // FIXME: Need to read inline-sizes from either table-header-group OR the first table-row. - match table_layout { - TableLayout::Fixed => { - // Fixed table layout only looks at the first row. - // - // FIXME(pcwalton): This is really inefficient. We should stop after the first row! - if first_row { - for cell_inline_size in &row.cell_intrinsic_inline_sizes { - column_inline_sizes.push(cell_inline_size.column_size); - } - } - }, - TableLayout::Auto => { - computation.union_block(&TableFlow::update_automatic_column_inline_sizes( - column_inline_sizes, - &row.cell_intrinsic_inline_sizes, - surrounding_inline_size, - )) - }, - } - } - - /// Returns the effective spacing per cell, taking the value of `border-collapse` into account. - pub fn spacing(&self) -> border_spacing::T { - let style = self.block_flow.fragment.style(); - match style.get_inherited_table().border_collapse { - border_collapse::T::Separate => style.get_inherited_table().border_spacing, - border_collapse::T::Collapse => border_spacing::T::zero(), - } - } - - pub fn total_horizontal_spacing(&self) -> Au { - let num_columns = self.column_intrinsic_inline_sizes.len(); - if num_columns == 0 { - return Au(0); - } - self.spacing().horizontal() * (num_columns as i32 + 1) - } - - fn column_styles(&self) -> Vec<ColumnStyle> { - let mut styles = vec![]; - for group in self - .block_flow - .base - .child_iter() - .filter(|kid| kid.is_table_colgroup()) - { - // XXXManishearth these as_foo methods should return options - // so that we can filter_map - let group = group.as_table_colgroup(); - let colgroup_style = group.fragment.as_ref().map(|f| f.style()); - - // The colgroup's span attribute is only relevant when - // it has no children - // https://html.spec.whatwg.org/multipage/#forming-a-table - if group.cols.is_empty() { - let span = group - .fragment - .as_ref() - .map(|f| f.column_span()) - .unwrap_or(1); - styles.push(ColumnStyle { - span, - colgroup_style, - col_style: None, - }); - } else { - for col in &group.cols { - // XXXManishearth Arc-cloning colgroup_style is suboptimal - styles.push(ColumnStyle { - span: col.column_span(), - colgroup_style: colgroup_style, - col_style: Some(col.style()), - }) - } - } - } - styles - } -} - -impl Flow for TableFlow { - fn class(&self) -> FlowClass { - FlowClass::Table - } - - fn as_mut_table(&mut self) -> &mut TableFlow { - self - } - - fn as_table(&self) -> &TableFlow { - self - } - - fn as_mut_block(&mut self) -> &mut BlockFlow { - &mut self.block_flow - } - - fn as_block(&self) -> &BlockFlow { - &self.block_flow - } - - fn mark_as_root(&mut self) { - self.block_flow.mark_as_root(); - } - - /// The specified column inline-sizes are set from column group and the first row for the fixed - /// table layout calculation. - /// The maximum min/pref inline-sizes of each column are set from the rows for the automatic - /// table layout calculation. - fn bubble_inline_sizes(&mut self) { - let _scope = layout_debug_scope!( - "table::bubble_inline_sizes {:x}", - self.block_flow.base.debug_id() - ); - - // Get column inline sizes from colgroups - for kid in self - .block_flow - .base - .child_iter_mut() - .filter(|kid| kid.is_table_colgroup()) - { - for specified_inline_size in &kid.as_mut_table_colgroup().inline_sizes { - self.column_intrinsic_inline_sizes - .push(ColumnIntrinsicInlineSize { - minimum_length: match *specified_inline_size { - Size::Auto => Au(0), - Size::LengthPercentage(ref lp) => { - lp.maybe_to_used_value(None).unwrap_or(Au(0)) - }, - }, - percentage: match *specified_inline_size { - Size::Auto => 0.0, - Size::LengthPercentage(ref lp) => { - lp.0.as_percentage().map_or(0.0, |p| p.0) - }, - }, - preferred: Au(0), - constrained: false, - }) - } - } - - self.collapsed_inline_direction_border_widths_for_table = Vec::new(); - self.collapsed_block_direction_border_widths_for_table = vec![Au(0)]; - - let collapsing_borders = self - .block_flow - .fragment - .style - .get_inherited_table() - .border_collapse == - border_collapse::T::Collapse; - let table_inline_collapsed_borders = if collapsing_borders { - Some(TableInlineCollapsedBorders { - start: CollapsedBorder::inline_start( - &*self.block_flow.fragment.style, - CollapsedBorderProvenance::FromTable, - ), - end: CollapsedBorder::inline_end( - &*self.block_flow.fragment.style, - CollapsedBorderProvenance::FromTable, - ), - }) - } else { - None - }; - - let mut computation = IntrinsicISizesContribution::new(); - let mut previous_collapsed_block_end_borders = - PreviousBlockCollapsedBorders::FromTable(CollapsedBorder::block_start( - &*self.block_flow.fragment.style, - CollapsedBorderProvenance::FromTable, - )); - let mut first_row = true; - let (border_padding, _) = self.block_flow.fragment.surrounding_intrinsic_inline_size(); - - { - let mut iterator = TableRowIterator::new(&mut self.block_flow.base).peekable(); - while let Some(row) = iterator.next() { - TableFlow::update_column_inline_sizes_for_row( - row, - &mut self.column_intrinsic_inline_sizes, - &mut computation, - first_row, - self.table_layout, - border_padding, - ); - if collapsing_borders { - let next_index_and_sibling = iterator.peek(); - let next_collapsed_borders_in_block_direction = match next_index_and_sibling { - Some(next_sibling) => NextBlockCollapsedBorders::FromNextRow( - &next_sibling - .as_table_row() - .preliminary_collapsed_borders - .block_start, - ), - None => NextBlockCollapsedBorders::FromTable(CollapsedBorder::block_end( - &*self.block_flow.fragment.style, - CollapsedBorderProvenance::FromTable, - )), - }; - perform_border_collapse_for_row( - row, - table_inline_collapsed_borders.as_ref().unwrap(), - previous_collapsed_block_end_borders, - next_collapsed_borders_in_block_direction, - &mut self.collapsed_inline_direction_border_widths_for_table, - &mut self.collapsed_block_direction_border_widths_for_table, - ); - previous_collapsed_block_end_borders = - PreviousBlockCollapsedBorders::FromPreviousRow( - row.final_collapsed_borders.block_end.clone(), - ); - } - first_row = false - } - } - - let total_horizontal_spacing = self.total_horizontal_spacing(); - let mut style_specified_intrinsic_inline_size = self - .block_flow - .fragment - .style_specified_intrinsic_inline_size() - .finish(); - style_specified_intrinsic_inline_size.minimum_inline_size -= total_horizontal_spacing; - style_specified_intrinsic_inline_size.preferred_inline_size -= total_horizontal_spacing; - computation.union_block(&style_specified_intrinsic_inline_size); - computation.surrounding_size += total_horizontal_spacing; - - self.block_flow.base.intrinsic_inline_sizes = computation.finish() - } - - /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. - /// When called on this context, the context has had its inline-size set by the parent context. - fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) { - let _scope = layout_debug_scope!( - "table::assign_inline_sizes {:x}", - self.block_flow.base.debug_id() - ); - debug!( - "assign_inline_sizes({}): assigning inline_size for flow", - "table" - ); - - let shared_context = layout_context.shared_context(); - // The position was set to the containing block by the flow's parent. - // FIXME: The code for distributing column widths should really be placed under table_wrapper.rs. - let containing_block_inline_size = self.block_flow.base.block_container_inline_size; - - let mut constrained_column_inline_sizes_indices = vec![]; - let mut unspecified_inline_sizes_indices = vec![]; - for (idx, column_inline_size) in self.column_intrinsic_inline_sizes.iter().enumerate() { - if column_inline_size.constrained { - constrained_column_inline_sizes_indices.push(idx); - } else if column_inline_size.percentage == 0.0 { - unspecified_inline_sizes_indices.push(idx); - } - } - - let inline_size_computer = InternalTable; - inline_size_computer.compute_used_inline_size( - &mut self.block_flow, - shared_context, - containing_block_inline_size, - ); - - let inline_start_content_edge = self.block_flow.fragment.border_padding.inline_start; - let inline_end_content_edge = self.block_flow.fragment.border_padding.inline_end; - let padding_and_borders = self.block_flow.fragment.border_padding.inline_start_end(); - let spacing_per_cell = self.spacing(); - let total_horizontal_spacing = self.total_horizontal_spacing(); - let content_inline_size = self.block_flow.fragment.border_box.size.inline - - padding_and_borders - - total_horizontal_spacing; - let mut remaining_inline_size = content_inline_size; - - match self.table_layout { - TableLayout::Fixed => { - self.column_computed_inline_sizes.clear(); - - // https://drafts.csswg.org/css2/tables.html#fixed-table-layout - for column_inline_size in &self.column_intrinsic_inline_sizes { - if column_inline_size.constrained { - self.column_computed_inline_sizes - .push(ColumnComputedInlineSize { - size: column_inline_size.minimum_length, - }); - remaining_inline_size -= column_inline_size.minimum_length; - } else if column_inline_size.percentage != 0.0 { - let size = remaining_inline_size.scale_by(column_inline_size.percentage); - self.column_computed_inline_sizes - .push(ColumnComputedInlineSize { size: size }); - remaining_inline_size -= size; - } else { - // Set the size to 0 now, distribute the remaining widths later - self.column_computed_inline_sizes - .push(ColumnComputedInlineSize { size: Au(0) }); - } - } - - // Distribute remaining content inline size - if unspecified_inline_sizes_indices.len() > 0 { - for &index in &unspecified_inline_sizes_indices { - self.column_computed_inline_sizes[index].size = remaining_inline_size - .scale_by(1.0 / unspecified_inline_sizes_indices.len() as f32); - } - } else { - let total_minimum_size = self - .column_intrinsic_inline_sizes - .iter() - .filter(|size| size.constrained) - .map(|size| size.minimum_length.0 as f32) - .sum::<f32>(); - - for &index in &constrained_column_inline_sizes_indices { - let inline_size = self.column_computed_inline_sizes[index].size.0; - self.column_computed_inline_sizes[index].size += - remaining_inline_size.scale_by(inline_size as f32 / total_minimum_size); - } - } - }, - _ => { - // The table wrapper already computed the inline-sizes and propagated them down - // to us. - }, - } - - let column_computed_inline_sizes = &self.column_computed_inline_sizes; - let collapsed_inline_direction_border_widths_for_table = - &self.collapsed_inline_direction_border_widths_for_table; - let mut collapsed_block_direction_border_widths_for_table = self - .collapsed_block_direction_border_widths_for_table - .iter() - .peekable(); - let mut incoming_rowspan = vec![]; - self.block_flow.propagate_assigned_inline_size_to_children( - shared_context, - inline_start_content_edge, - inline_end_content_edge, - content_inline_size, - |child_flow, - _child_index, - _content_inline_size, - writing_mode, - _inline_start_margin_edge, - _inline_end_margin_edge| { - table_row::propagate_column_inline_sizes_to_child( - child_flow, - writing_mode, - column_computed_inline_sizes, - &spacing_per_cell, - &mut incoming_rowspan, - ); - if child_flow.is_table_row() { - let child_table_row = child_flow.as_mut_table_row(); - child_table_row.populate_collapsed_border_spacing( - collapsed_inline_direction_border_widths_for_table, - &mut collapsed_block_direction_border_widths_for_table, - ); - } else if child_flow.is_table_rowgroup() { - let child_table_rowgroup = child_flow.as_mut_table_rowgroup(); - child_table_rowgroup.populate_collapsed_border_spacing( - collapsed_inline_direction_border_widths_for_table, - &mut collapsed_block_direction_border_widths_for_table, - ); - } - }, - ); - } - - fn assign_block_size(&mut self, lc: &LayoutContext) { - debug!("assign_block_size: assigning block_size for table"); - let vertical_spacing = self.spacing().vertical(); - self.block_flow - .assign_block_size_for_table_like_flow(vertical_spacing, lc) - } - - fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) { - self.block_flow - .compute_stacking_relative_position(layout_context) - } - - fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> { - self.block_flow.generated_containing_block_size(flow) - } - - fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) { - self.block_flow - .update_late_computed_inline_position_if_necessary(inline_position) - } - - fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) { - self.block_flow - .update_late_computed_block_position_if_necessary(block_position) - } - - fn build_display_list(&mut self, state: &mut DisplayListBuildState) { - let border_painting_mode = match self - .block_flow - .fragment - .style - .get_inherited_table() - .border_collapse - { - border_collapse::T::Separate => BorderPaintingMode::Separate, - border_collapse::T::Collapse => BorderPaintingMode::Hidden, - }; - - self.block_flow - .build_display_list_for_block(state, border_painting_mode); - - let iter = TableCellStyleIterator::new(&self); - for style in iter { - style.build_display_list(state) - } - } - - fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) { - // Stacking contexts are collected by the table wrapper. - self.block_flow.collect_stacking_contexts_for_block( - state, - StackingContextCollectionFlags::NEVER_CREATES_STACKING_CONTEXT, - ); - } - - fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>) { - self.block_flow.repair_style(new_style) - } - - fn compute_overflow(&self) -> Overflow { - self.block_flow.compute_overflow() - } - - fn iterate_through_fragment_border_boxes( - &self, - iterator: &mut dyn FragmentBorderBoxIterator, - level: i32, - stacking_context_position: &Point2D<Au>, - ) { - self.block_flow.iterate_through_fragment_border_boxes( - iterator, - level, - stacking_context_position, - ) - } - - fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) { - self.block_flow.mutate_fragments(mutator) - } - - fn print_extra_flow_children(&self, print_tree: &mut PrintTree) { - self.block_flow.print_extra_flow_children(print_tree); - } -} - -#[derive(Debug)] -struct ColumnStyle<'table> { - span: u32, - colgroup_style: Option<&'table ComputedValues>, - col_style: Option<&'table ComputedValues>, -} - -impl fmt::Debug for TableFlow { - /// Outputs a debugging string describing this table flow. - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "TableFlow: {:?}", self.block_flow) - } -} - -/// Table, TableRowGroup, TableRow, TableCell types. -/// Their inline-sizes are calculated in the same way and do not have margins. -pub struct InternalTable; - -impl ISizeAndMarginsComputer for InternalTable { - /// Compute the used value of inline-size, taking care of min-inline-size and max-inline-size. - /// - /// CSS Section 10.4: Minimum and Maximum inline-sizes - fn compute_used_inline_size( - &self, - block: &mut BlockFlow, - shared_context: &SharedStyleContext, - parent_flow_inline_size: Au, - ) { - let mut input = self.compute_inline_size_constraint_inputs( - block, - parent_flow_inline_size, - shared_context, - ); - - // Tables are always at least as wide as their minimum inline size. - let minimum_inline_size = block.base.intrinsic_inline_sizes.minimum_inline_size - - block.fragment.border_padding.inline_start_end(); - input.available_inline_size = cmp::max(input.available_inline_size, minimum_inline_size); - - let solution = self.solve_inline_size_constraints(block, &input); - self.set_inline_size_constraint_solutions(block, solution); - } - - /// Solve the inline-size and margins constraints for this block flow. - fn solve_inline_size_constraints( - &self, - _: &mut BlockFlow, - input: &ISizeConstraintInput, - ) -> ISizeConstraintSolution { - ISizeConstraintSolution::new(input.available_inline_size, Au(0), Au(0)) - } -} - -/// Information about the intrinsic inline sizes of columns within a table. -/// -/// During table inline-size bubbling, we might need to store both a percentage constraint and a -/// specific width constraint. For instance, one cell might say that it wants to be 100 pixels wide -/// in the inline direction and another cell might say that it wants to take up 20% of the inline- -/// size of the table. Now because we bubble up these constraints during the bubble-inline-sizes -/// phase of layout, we don't know yet how wide the table is ultimately going to be in the inline -/// direction. As we need to pick the maximum width of all cells for a column (in this case, the -/// maximum of 100 pixels and 20% of the table), the preceding constraint means that we must -/// potentially store both a specified width *and* a specified percentage, so that the inline-size -/// assignment phase of layout will know which one to pick. -#[derive(Clone, Copy, Debug, Serialize)] -pub struct ColumnIntrinsicInlineSize { - /// The preferred intrinsic inline size. - pub preferred: Au, - /// The largest specified size of this column as a length. - pub minimum_length: Au, - /// The largest specified size of this column as a percentage (`width` property). - pub percentage: CSSFloat, - /// Whether the column inline size is *constrained* per INTRINSIC § 4.1. - pub constrained: bool, -} - -impl ColumnIntrinsicInlineSize { - /// Returns a newly-initialized `ColumnIntrinsicInlineSize` with all fields blank. - pub fn new() -> ColumnIntrinsicInlineSize { - ColumnIntrinsicInlineSize { - preferred: Au(0), - minimum_length: Au(0), - percentage: 0.0, - constrained: false, - } - } - - /// Returns the higher of the two percentages specified in `self` and `other`. - pub fn greatest_percentage(&self, other: &ColumnIntrinsicInlineSize) -> CSSFloat { - if self.percentage > other.percentage { - self.percentage - } else { - other.percentage - } - } -} - -/// The actual inline size for each column. -/// -/// TODO(pcwalton): There will probably be some `border-collapse`-related info in here too -/// eventually. -#[derive(Clone, Copy, Debug, Serialize)] -pub struct ColumnComputedInlineSize { - /// The computed size of this inline column. - pub size: Au, -} - -pub trait VecExt<T> { - fn push_or_set(&mut self, index: usize, value: T) -> &mut T; - fn get_mut_or_push(&mut self, index: usize, zero: T) -> &mut T; -} - -impl<T> VecExt<T> for Vec<T> { - fn push_or_set(&mut self, index: usize, value: T) -> &mut T { - if index < self.len() { - self[index] = value - } else { - debug_assert_eq!(index, self.len()); - self.push(value) - } - &mut self[index] - } - - fn get_mut_or_push(&mut self, index: usize, zero: T) -> &mut T { - if index >= self.len() { - debug_assert_eq!(index, self.len()); - self.push(zero) - } - &mut self[index] - } -} - -/// Updates the border styles in the block direction for a single row. This function should -/// only be called if border collapsing is on. It is factored out into a separate function -/// because we process children of rowgroups too. -fn perform_border_collapse_for_row( - child_table_row: &mut TableRowFlow, - table_inline_borders: &TableInlineCollapsedBorders, - previous_block_borders: PreviousBlockCollapsedBorders, - next_block_borders: NextBlockCollapsedBorders, - inline_spacing: &mut Vec<Au>, - block_spacing: &mut Vec<Au>, -) { - // TODO mbrubeck: Take rowspan and colspan into account. - let number_of_borders_inline_direction = - child_table_row.preliminary_collapsed_borders.inline.len(); - // Compute interior inline borders. - for (i, this_inline_border) in child_table_row - .preliminary_collapsed_borders - .inline - .iter_mut() - .enumerate() - { - child_table_row - .final_collapsed_borders - .inline - .push_or_set(i, *this_inline_border); - if i == 0 { - child_table_row.final_collapsed_borders.inline[i].combine(&table_inline_borders.start); - } else if i + 1 == number_of_borders_inline_direction { - child_table_row.final_collapsed_borders.inline[i].combine(&table_inline_borders.end); - } - - let inline_spacing = inline_spacing.get_mut_or_push(i, Au(0)); - *inline_spacing = cmp::max( - *inline_spacing, - child_table_row.final_collapsed_borders.inline[i].width, - ) - } - - // Compute block-start borders. - let block_start_borders = &mut child_table_row.final_collapsed_borders.block_start; - *block_start_borders = child_table_row - .preliminary_collapsed_borders - .block_start - .clone(); - for (i, this_border) in block_start_borders.iter_mut().enumerate() { - match previous_block_borders { - PreviousBlockCollapsedBorders::FromPreviousRow(ref previous_block_borders) => { - if previous_block_borders.len() > i { - this_border.combine(&previous_block_borders[i]); - } - }, - PreviousBlockCollapsedBorders::FromTable(table_border) => { - this_border.combine(&table_border); - }, - } - } - - // Compute block-end borders. - let next_block = &mut child_table_row.final_collapsed_borders.block_end; - block_spacing.push(Au(0)); - let block_spacing = block_spacing.last_mut().unwrap(); - for (i, this_block_border) in child_table_row - .preliminary_collapsed_borders - .block_end - .iter() - .enumerate() - { - let next_block = next_block.push_or_set(i, *this_block_border); - match next_block_borders { - NextBlockCollapsedBorders::FromNextRow(next_block_borders) => { - if next_block_borders.len() > i { - next_block.combine(&next_block_borders[i]) - } - }, - NextBlockCollapsedBorders::FromTable(ref next_block_borders) => { - next_block.combine(next_block_borders); - }, - } - *block_spacing = cmp::max(*block_spacing, next_block.width) - } -} - -/// Encapsulates functionality shared among all table-like flows: for now, tables and table -/// rowgroups. -pub trait TableLikeFlow { - /// Lays out the rows of a table. - fn assign_block_size_for_table_like_flow( - &mut self, - block_direction_spacing: Au, - layout_context: &LayoutContext, - ); -} - -impl TableLikeFlow for BlockFlow { - fn assign_block_size_for_table_like_flow( - &mut self, - block_direction_spacing: Au, - layout_context: &LayoutContext, - ) { - debug_assert!( - self.fragment.style.get_inherited_table().border_collapse == - border_collapse::T::Separate || - block_direction_spacing == Au(0) - ); - - fn border_spacing_for_row( - fragment: &Fragment, - row: &TableRowFlow, - block_direction_spacing: Au, - ) -> Au { - match fragment.style.get_inherited_table().border_collapse { - border_collapse::T::Separate => block_direction_spacing, - border_collapse::T::Collapse => row.collapsed_border_spacing.block_start, - } - } - - if self - .base - .restyle_damage - .contains(ServoRestyleDamage::REFLOW) - { - let mut sizes = vec![Default::default()]; - // The amount of border spacing up to and including this row, - // but not including the spacing beneath it - let mut cumulative_border_spacing = Au(0); - let mut incoming_rowspan_data = vec![]; - let mut rowgroup_id = 0; - let mut first = true; - - // First pass: Compute block-direction border spacings - // XXXManishearth this can be done in tandem with the second pass, - // provided we never hit any rowspan cases - for kid in self.base.child_iter_mut() { - if kid.is_table_row() { - // skip the first row, it is accounted for - if first { - first = false; - continue; - } - cumulative_border_spacing += border_spacing_for_row( - &self.fragment, - kid.as_table_row(), - block_direction_spacing, - ); - sizes.push(TableRowSizeData { - // we haven't calculated sizes yet - size: Au(0), - cumulative_border_spacing, - rowgroup_id, - }); - } else if kid.is_table_rowgroup() && !first { - rowgroup_id += 1; - } - } - - // Second pass: Compute row block sizes - // [expensive: iterates over cells] - let mut i = 0; - for kid in self.base.child_iter_mut() { - if kid.is_table_row() { - let size = kid.as_mut_table_row().compute_block_size_table_row_base( - layout_context, - &mut incoming_rowspan_data, - &sizes, - i, - ); - sizes[i].size = size; - i += 1; - } - } - - // Our current border-box position. - let block_start_border_padding = self.fragment.border_padding.block_start; - let mut current_block_offset = block_start_border_padding; - let mut has_rows = false; - - // Third pass: Assign block sizes and positions to rows, cells, and other children - // [expensive: iterates over cells] - // At this point, `current_block_offset` is at the content edge of our box. Now iterate - // over children. - let mut i = 0; - for kid in self.base.child_iter_mut() { - if kid.is_table_row() { - has_rows = true; - let row = kid.as_mut_table_row(); - row.assign_block_size_to_self_and_children(&sizes, i); - row.mut_base().restyle_damage.remove( - ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW, - ); - current_block_offset = current_block_offset + - border_spacing_for_row(&self.fragment, row, block_direction_spacing); - i += 1; - } - - // At this point, `current_block_offset` is at the border edge of the child. - kid.mut_base().position.start.b = current_block_offset; - - // Move past the child's border box. Do not use the `translate_including_floats` - // function here because the child has already translated floats past its border - // box. - let kid_base = kid.mut_base(); - current_block_offset = current_block_offset + kid_base.position.size.block; - } - - // Compute any explicitly-specified block size. - // Can't use `for` because we assign to - // `candidate_block_size_iterator.candidate_value`. - let mut block_size = current_block_offset - block_start_border_padding; - let mut candidate_block_size_iterator = CandidateBSizeIterator::new( - &self.fragment, - self.base.block_container_explicit_block_size, - ); - while let Some(candidate_block_size) = candidate_block_size_iterator.next() { - candidate_block_size_iterator.candidate_value = match candidate_block_size { - MaybeAuto::Auto => block_size, - MaybeAuto::Specified(value) => value, - }; - } - - // Adjust `current_block_offset` as necessary to account for the explicitly-specified - // block-size. - block_size = candidate_block_size_iterator.candidate_value; - let delta = block_size - (current_block_offset - block_start_border_padding); - current_block_offset = current_block_offset + delta; - - // Take border, padding, and spacing into account. - let block_end_offset = self.fragment.border_padding.block_end + - if has_rows { - block_direction_spacing - } else { - Au(0) - }; - current_block_offset = current_block_offset + block_end_offset; - - // Now that `current_block_offset` is at the block-end of the border box, compute the - // final border box position. - self.fragment.border_box.size.block = current_block_offset; - self.fragment.border_box.start.b = Au(0); - self.base.position.size.block = current_block_offset; - - // Fourth pass: Assign absolute position info - // Write in the size of the relative containing block for children. (This information - // is also needed to handle RTL.) - for kid in self.base.child_iter_mut() { - kid.mut_base().early_absolute_position_info = EarlyAbsolutePositionInfo { - relative_containing_block_size: self.fragment.content_box().size, - relative_containing_block_mode: self.fragment.style().writing_mode, - }; - } - } - - self.base - .restyle_damage - .remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW); - } -} - -/// Inline collapsed borders for the table itself. -#[derive(Debug)] -struct TableInlineCollapsedBorders { - /// The table border at the start of the inline direction. - start: CollapsedBorder, - /// The table border at the end of the inline direction. - end: CollapsedBorder, -} - -enum PreviousBlockCollapsedBorders { - FromPreviousRow(Vec<CollapsedBorder>), - FromTable(CollapsedBorder), -} - -enum NextBlockCollapsedBorders<'a> { - FromNextRow(&'a [CollapsedBorder]), - FromTable(CollapsedBorder), -} - -/// Iterator over all the rows of a table, which also -/// provides the Fragment for rowgroups if any -struct TableRowAndGroupIterator<'a> { - kids: FlowListIterator<'a>, - group: Option<(&'a Fragment, FlowListIterator<'a>)>, -} - -impl<'a> TableRowAndGroupIterator<'a> { - fn new(base: &'a BaseFlow) -> Self { - TableRowAndGroupIterator { - kids: base.child_iter(), - group: None, - } - } -} - -impl<'a> Iterator for TableRowAndGroupIterator<'a> { - type Item = (Option<&'a Fragment>, &'a TableRowFlow); - #[inline] - fn next(&mut self) -> Option<Self::Item> { - // If we're inside a rowgroup, iterate through the rowgroup's children. - if let Some(ref mut group) = self.group { - if let Some(grandkid) = group.1.next() { - return Some((Some(group.0), grandkid.as_table_row())); - } - } - // Otherwise, iterate through the table's children. - self.group = None; - match self.kids.next() { - Some(kid) => { - if kid.is_table_rowgroup() { - let rowgroup = kid.as_table_rowgroup(); - let iter = rowgroup.block_flow.base.child_iter(); - self.group = Some((&rowgroup.block_flow.fragment, iter)); - self.next() - } else if kid.is_table_row() { - Some((None, kid.as_table_row())) - } else { - self.next() // Skip children that are not rows or rowgroups - } - }, - None => None, - } - } -} - -/// Iterator over all the rows of a table, which also -/// provides the Fragment for rowgroups if any -struct MutTableRowAndGroupIterator<'a> { - kids: MutFlowListIterator<'a>, - group: Option<(&'a Fragment, MutFlowListIterator<'a>)>, -} - -impl<'a> MutTableRowAndGroupIterator<'a> { - fn new(base: &'a mut BaseFlow) -> Self { - MutTableRowAndGroupIterator { - kids: base.child_iter_mut(), - group: None, - } - } -} - -impl<'a> Iterator for MutTableRowAndGroupIterator<'a> { - type Item = (Option<&'a Fragment>, &'a mut TableRowFlow); - #[inline] - fn next(&mut self) -> Option<Self::Item> { - // If we're inside a rowgroup, iterate through the rowgroup's children. - if let Some(ref mut group) = self.group { - if let Some(grandkid) = group.1.next() { - return Some((Some(group.0), grandkid.as_mut_table_row())); - } - } - // Otherwise, iterate through the table's children. - self.group = None; - match self.kids.next() { - Some(kid) => { - if kid.is_table_rowgroup() { - let rowgroup = kid.as_mut_table_rowgroup(); - let iter = rowgroup.block_flow.base.child_iter_mut(); - self.group = Some((&rowgroup.block_flow.fragment, iter)); - self.next() - } else if kid.is_table_row() { - Some((None, kid.as_mut_table_row())) - } else { - self.next() // Skip children that are not rows or rowgroups - } - }, - None => None, - } - } -} - -/// Iterator over all the rows of a table -struct TableRowIterator<'a>(MutTableRowAndGroupIterator<'a>); - -impl<'a> TableRowIterator<'a> { - fn new(base: &'a mut BaseFlow) -> Self { - TableRowIterator(MutTableRowAndGroupIterator::new(base)) - } -} - -impl<'a> Iterator for TableRowIterator<'a> { - type Item = &'a mut TableRowFlow; - #[inline] - fn next(&mut self) -> Option<Self::Item> { - self.0.next().map(|n| n.1) - } -} - -/// An iterator over table cells, yielding all relevant style objects -/// for each cell -/// -/// Used for correctly handling table layers from -/// https://drafts.csswg.org/css2/tables.html#table-layers -struct TableCellStyleIterator<'table> { - column_styles: Vec<ColumnStyle<'table>>, - row_iterator: TableRowAndGroupIterator<'table>, - row_info: Option<TableCellStyleIteratorRowInfo<'table>>, - column_index: TableCellColumnIndexData, -} - -struct TableCellStyleIteratorRowInfo<'table> { - row: &'table TableRowFlow, - rowgroup: Option<&'table Fragment>, - cell_iterator: FlowListIterator<'table>, -} - -impl<'table> TableCellStyleIterator<'table> { - fn new(table: &'table TableFlow) -> Self { - let column_styles = table.column_styles(); - let mut row_iterator = TableRowAndGroupIterator::new(&table.block_flow.base); - let row_info = if let Some((group, row)) = row_iterator.next() { - Some(TableCellStyleIteratorRowInfo { - row: &row, - rowgroup: group, - cell_iterator: row.block_flow.base.child_iter(), - }) - } else { - None - }; - TableCellStyleIterator { - column_styles, - row_iterator, - row_info, - column_index: Default::default(), - } - } -} - -struct TableCellStyleInfo<'table> { - cell: &'table TableCellFlow, - colgroup_style: Option<&'table ComputedValues>, - col_style: Option<&'table ComputedValues>, - rowgroup_style: Option<&'table ComputedValues>, - row_style: &'table ComputedValues, -} - -struct TableCellColumnIndexData { - /// Which column this is in the table - pub absolute: u32, - /// The index of the current column in column_styles - /// (i.e. which <col> element it is) - pub relative: u32, - /// In case of multispan <col>s, where we are in the - /// span of the current <col> element - pub relative_offset: u32, -} - -impl Default for TableCellColumnIndexData { - fn default() -> Self { - TableCellColumnIndexData { - absolute: 0, - relative: 0, - relative_offset: 0, - } - } -} - -impl TableCellColumnIndexData { - /// Moves forward by `amount` columns, updating the various indices used - /// - /// This totally ignores rowspan -- if colspan and rowspan clash, - /// they just overlap, so we ignore it. - fn advance(&mut self, amount: u32, column_styles: &[ColumnStyle]) { - self.absolute += amount; - self.relative_offset += amount; - if let Some(mut current_col) = column_styles.get(self.relative as usize) { - while self.relative_offset >= current_col.span { - // move to the next column - self.relative += 1; - self.relative_offset -= current_col.span; - if let Some(column_style) = column_styles.get(self.relative as usize) { - current_col = column_style; - } else { - // we ran out of column_styles, - // so we don't need to update the indices - break; - } - } - } - } -} - -impl<'table> Iterator for TableCellStyleIterator<'table> { - type Item = TableCellStyleInfo<'table>; - #[inline] - fn next(&mut self) -> Option<Self::Item> { - // FIXME We do this awkward .take() followed by shoving it back in - // because without NLL the row_info borrow lasts too long - if let Some(mut row_info) = self.row_info.take() { - if let Some(rowspan) = row_info - .row - .incoming_rowspan - .get(self.column_index.absolute as usize) - { - // we are not allowed to use this column as a starting point. Try the next one. - if *rowspan > 1 { - self.column_index.advance(1, &self.column_styles); - // put row_info back in - self.row_info = Some(row_info); - // try again - return self.next(); - } - } - if let Some(cell) = row_info.cell_iterator.next() { - let rowgroup_style = row_info.rowgroup.map(|r| r.style()); - let row_style = row_info.row.block_flow.fragment.style(); - let cell = cell.as_table_cell(); - let (col_style, colgroup_style) = if let Some(column_style) = - self.column_styles.get(self.column_index.relative as usize) - { - let styles = ( - column_style.col_style.clone(), - column_style.colgroup_style.clone(), - ); - self.column_index - .advance(cell.column_span, &self.column_styles); - - styles - } else { - (None, None) - }; - // put row_info back in - self.row_info = Some(row_info); - return Some(TableCellStyleInfo { - cell, - colgroup_style, - col_style, - rowgroup_style, - row_style, - }); - } else { - // next row - if let Some((group, row)) = self.row_iterator.next() { - self.row_info = Some(TableCellStyleIteratorRowInfo { - row: &row, - rowgroup: group, - cell_iterator: row.block_flow.base.child_iter(), - }); - self.column_index = Default::default(); - self.next() - } else { - // out of rows - // row_info stays None - None - } - } - } else { - // empty table - None - } - } -} - -impl<'table> TableCellStyleInfo<'table> { - fn build_display_list(&self, mut state: &mut DisplayListBuildState) { - use style::computed_values::visibility::T as Visibility; - - if !self.cell.visible || - self.cell - .block_flow - .fragment - .style() - .get_inherited_box() - .visibility != - Visibility::Visible - { - return; - } - let border_painting_mode = match self - .cell - .block_flow - .fragment - .style - .get_inherited_table() - .border_collapse - { - border_collapse::T::Separate => BorderPaintingMode::Separate, - border_collapse::T::Collapse => { - BorderPaintingMode::Collapse(&self.cell.collapsed_borders) - }, - }; - { - let cell_flow = &self.cell.block_flow; - let initial = ComputedValues::initial_values(); - - let build_dl = |sty: &ComputedValues, state: &mut &mut DisplayListBuildState| { - let background = sty.get_background(); - // Don't redraw backgrounds that we've already drawn - if background as *const Background == initial.get_background() as *const _ { - return; - } - let background_color = sty.resolve_color(background.background_color); - cell_flow.build_display_list_for_background_if_applicable_with_background( - state, - background, - background_color, - ); - }; - - if let Some(ref sty) = self.colgroup_style { - build_dl(&sty, &mut state); - } - if let Some(ref sty) = self.col_style { - build_dl(&sty, &mut state); - } - if let Some(ref sty) = self.rowgroup_style { - build_dl(sty, &mut state); - } - build_dl(self.row_style, &mut state); - } - // the restyle damage will be set in TableCellFlow::build_display_list() - self.cell - .block_flow - .build_display_list_for_block_no_damage(state, border_painting_mode) - } -} diff --git a/components/layout_2020/table_caption.rs b/components/layout_2020/table_caption.rs deleted file mode 100644 index 3d44190f986..00000000000 --- a/components/layout_2020/table_caption.rs +++ /dev/null @@ -1,139 +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 table formatting contexts. - -use crate::block::BlockFlow; -use crate::context::LayoutContext; -use crate::display_list::{ - DisplayListBuildState, StackingContextCollectionFlags, StackingContextCollectionState, -}; -use crate::flow::{Flow, FlowClass, OpaqueFlow}; -use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow}; -use app_units::Au; -use euclid::Point2D; -use gfx_traits::print_tree::PrintTree; -use std::fmt; -use style::logical_geometry::LogicalSize; -use style::properties::ComputedValues; - -#[allow(unsafe_code)] -unsafe impl crate::flow::HasBaseFlow for TableCaptionFlow {} - -/// A table formatting context. -#[repr(C)] -pub struct TableCaptionFlow { - pub block_flow: BlockFlow, -} - -impl TableCaptionFlow { - pub fn from_fragment(fragment: Fragment) -> TableCaptionFlow { - TableCaptionFlow { - block_flow: BlockFlow::from_fragment(fragment), - } - } -} - -impl Flow for TableCaptionFlow { - fn class(&self) -> FlowClass { - FlowClass::TableCaption - } - - fn as_mut_block(&mut self) -> &mut BlockFlow { - &mut self.block_flow - } - - fn as_block(&self) -> &BlockFlow { - &self.block_flow - } - - fn bubble_inline_sizes(&mut self) { - self.block_flow.bubble_inline_sizes(); - } - - fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) { - debug!( - "assign_inline_sizes({}): assigning inline_size for flow", - "table_caption" - ); - self.block_flow.assign_inline_sizes(layout_context); - } - - fn assign_block_size(&mut self, layout_context: &LayoutContext) { - debug!("assign_block_size: assigning block_size for table_caption"); - self.block_flow.assign_block_size(layout_context); - } - - fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) { - self.block_flow - .compute_stacking_relative_position(layout_context) - } - - fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) { - self.block_flow - .update_late_computed_inline_position_if_necessary(inline_position) - } - - fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) { - self.block_flow - .update_late_computed_block_position_if_necessary(block_position) - } - - fn build_display_list(&mut self, state: &mut DisplayListBuildState) { - debug!("build_display_list_table_caption: same process as block flow"); - self.block_flow.build_display_list(state); - } - - fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) { - self.block_flow - .collect_stacking_contexts_for_block(state, StackingContextCollectionFlags::empty()); - } - - fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>) { - self.block_flow.repair_style(new_style) - } - - fn compute_overflow(&self) -> Overflow { - self.block_flow.compute_overflow() - } - - fn contains_roots_of_absolute_flow_tree(&self) -> bool { - self.block_flow.contains_roots_of_absolute_flow_tree() - } - - fn is_absolute_containing_block(&self) -> bool { - self.block_flow.is_absolute_containing_block() - } - - fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> { - self.block_flow.generated_containing_block_size(flow) - } - - fn iterate_through_fragment_border_boxes( - &self, - iterator: &mut dyn FragmentBorderBoxIterator, - level: i32, - stacking_context_position: &Point2D<Au>, - ) { - self.block_flow.iterate_through_fragment_border_boxes( - iterator, - level, - stacking_context_position, - ) - } - - fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) { - self.block_flow.mutate_fragments(mutator) - } - - fn print_extra_flow_children(&self, print_tree: &mut PrintTree) { - self.block_flow.print_extra_flow_children(print_tree); - } -} - -impl fmt::Debug for TableCaptionFlow { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "TableCaptionFlow: {:?}", self.block_flow) - } -} diff --git a/components/layout_2020/table_cell.rs b/components/layout_2020/table_cell.rs deleted file mode 100644 index ec79f9f4540..00000000000 --- a/components/layout_2020/table_cell.rs +++ /dev/null @@ -1,506 +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 table formatting contexts. - -use crate::block::{BlockFlow, ISizeAndMarginsComputer, MarginsMayCollapseFlag}; -use crate::context::LayoutContext; -use crate::display_list::{ - DisplayListBuildState, StackingContextCollectionFlags, StackingContextCollectionState, -}; -use crate::flow::{Flow, FlowClass, FlowFlags, GetBaseFlow, OpaqueFlow}; -use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow}; -use crate::layout_debug; -use crate::table::InternalTable; -use crate::table_row::{CollapsedBorder, CollapsedBorderProvenance}; -use app_units::Au; -use euclid::{Point2D, Rect, SideOffsets2D, Size2D}; -use gfx_traits::print_tree::PrintTree; -use script_layout_interface::wrapper_traits::ThreadSafeLayoutNode; -use std::fmt; -use style::logical_geometry::{LogicalMargin, LogicalRect, LogicalSize, WritingMode}; -use style::properties::ComputedValues; -use style::values::computed::length::Size; -use style::values::computed::Color; -use style::values::generics::box_::{VerticalAlign, VerticalAlignKeyword}; -use style::values::specified::BorderStyle; - -#[allow(unsafe_code)] -unsafe impl crate::flow::HasBaseFlow for TableCellFlow {} - -/// A table formatting context. -#[derive(Serialize)] -#[repr(C)] -pub struct TableCellFlow { - /// Data common to all block flows. - pub block_flow: BlockFlow, - - /// Border collapse information for the cell. - pub collapsed_borders: CollapsedBordersForCell, - - /// The column span of this cell. - pub column_span: u32, - - /// The rows spanned by this cell. - pub row_span: u32, - - /// Whether this cell is visible. If false, the value of `empty-cells` means that we must not - /// display this cell. - pub visible: bool, -} - -impl TableCellFlow { - pub fn from_fragment(fragment: Fragment) -> TableCellFlow { - TableCellFlow { - block_flow: BlockFlow::from_fragment(fragment), - collapsed_borders: CollapsedBordersForCell::new(), - column_span: 1, - row_span: 1, - visible: true, - } - } - - pub fn from_node_fragment_and_visibility_flag<N: ThreadSafeLayoutNode>( - node: &N, - fragment: Fragment, - visible: bool, - ) -> TableCellFlow { - TableCellFlow { - block_flow: BlockFlow::from_fragment(fragment), - collapsed_borders: CollapsedBordersForCell::new(), - column_span: node.get_colspan(), - row_span: node.get_rowspan(), - visible: visible, - } - } - - pub fn fragment(&mut self) -> &Fragment { - &self.block_flow.fragment - } - - pub fn mut_fragment(&mut self) -> &mut Fragment { - &mut self.block_flow.fragment - } - - /// Assign block-size for table-cell flow. - /// - /// inline(always) because this is only ever called by in-order or non-in-order top-level - /// methods. - #[inline(always)] - fn assign_block_size_table_cell_base(&mut self, layout_context: &LayoutContext) { - let remaining = self.block_flow.assign_block_size_block_base( - layout_context, - None, - MarginsMayCollapseFlag::MarginsMayNotCollapse, - ); - debug_assert!(remaining.is_none()); - } - - /// Position this cell's children according to vertical-align. - pub fn valign_children(&mut self) { - // Note to the reader: this code has been tested with negative margins. - // We end up with a "end" that's before the "start," but the math still works out. - let mut extents = None; - for kid in self.base().children.iter() { - let kid_base = kid.base(); - if kid_base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) { - continue; - } - let start = kid_base.position.start.b - - kid_base - .collapsible_margins - .block_start_margin_for_noncollapsible_context(); - let end = kid_base.position.start.b + - kid_base.position.size.block + - kid_base - .collapsible_margins - .block_end_margin_for_noncollapsible_context(); - match extents { - Some((ref mut first_start, ref mut last_end)) => { - if start < *first_start { - *first_start = start - } - if end > *last_end { - *last_end = end - } - }, - None => extents = Some((start, end)), - } - } - let (first_start, last_end) = match extents { - Some(extents) => extents, - None => return, - }; - - let kids_size = last_end - first_start; - let self_size = self.base().position.size.block - - self.block_flow.fragment.border_padding.block_start_end(); - let kids_self_gap = self_size - kids_size; - - // This offset should also account for VerticalAlign::baseline. - // Need max cell ascent from the first row of this cell. - let offset = match self.block_flow.fragment.style().get_box().vertical_align { - VerticalAlign::Keyword(VerticalAlignKeyword::Middle) => kids_self_gap / 2, - VerticalAlign::Keyword(VerticalAlignKeyword::Bottom) => kids_self_gap, - _ => Au(0), - }; - if offset == Au(0) { - return; - } - - for kid in self.mut_base().children.iter_mut() { - let kid_base = kid.mut_base(); - if !kid_base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) { - kid_base.position.start.b += offset - } - } - } - - // Total block size of child - // - // Call after block size calculation - pub fn total_block_size(&mut self) -> Au { - // TODO: Percentage block-size - let specified = self - .fragment() - .style() - .content_block_size() - .to_used_value(Au(0)) - .unwrap_or(Au(0)); - specified + self.fragment().border_padding.block_start_end() - } -} - -impl Flow for TableCellFlow { - fn class(&self) -> FlowClass { - FlowClass::TableCell - } - - fn as_mut_table_cell(&mut self) -> &mut TableCellFlow { - self - } - - fn as_table_cell(&self) -> &TableCellFlow { - self - } - - fn as_mut_block(&mut self) -> &mut BlockFlow { - &mut self.block_flow - } - - fn as_block(&self) -> &BlockFlow { - &self.block_flow - } - - /// Minimum/preferred inline-sizes set by this function are used in automatic table layout - /// calculation. - fn bubble_inline_sizes(&mut self) { - let _scope = layout_debug_scope!( - "table_cell::bubble_inline_sizes {:x}", - self.block_flow.base.debug_id() - ); - - self.block_flow.bubble_inline_sizes_for_block(true); - let specified_inline_size = match self.block_flow.fragment.style().content_inline_size() { - Size::Auto => Au(0), - Size::LengthPercentage(ref lp) => lp.to_used_value(Au(0)), - }; - - if self - .block_flow - .base - .intrinsic_inline_sizes - .minimum_inline_size < - specified_inline_size - { - self.block_flow - .base - .intrinsic_inline_sizes - .minimum_inline_size = specified_inline_size - } - if self - .block_flow - .base - .intrinsic_inline_sizes - .preferred_inline_size < - self.block_flow - .base - .intrinsic_inline_sizes - .minimum_inline_size - { - self.block_flow - .base - .intrinsic_inline_sizes - .preferred_inline_size = self - .block_flow - .base - .intrinsic_inline_sizes - .minimum_inline_size; - } - } - - /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. - /// When called on this context, the context has had its inline-size set by the parent table - /// row. - fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) { - let _scope = layout_debug_scope!( - "table_cell::assign_inline_sizes {:x}", - self.block_flow.base.debug_id() - ); - debug!( - "assign_inline_sizes({}): assigning inline_size for flow", - "table_cell" - ); - - let shared_context = layout_context.shared_context(); - // The position was set to the column inline-size by the parent flow, table row flow. - let containing_block_inline_size = self.block_flow.base.block_container_inline_size; - - let inline_size_computer = InternalTable; - inline_size_computer.compute_used_inline_size( - &mut self.block_flow, - shared_context, - containing_block_inline_size, - ); - - let inline_start_content_edge = self.block_flow.fragment.border_box.start.i + - self.block_flow.fragment.border_padding.inline_start; - let inline_end_content_edge = self.block_flow.base.block_container_inline_size - - self.block_flow.fragment.border_padding.inline_start_end() - - self.block_flow.fragment.border_box.size.inline; - let padding_and_borders = self.block_flow.fragment.border_padding.inline_start_end(); - let content_inline_size = - self.block_flow.fragment.border_box.size.inline - padding_and_borders; - - self.block_flow.propagate_assigned_inline_size_to_children( - shared_context, - inline_start_content_edge, - inline_end_content_edge, - content_inline_size, - |_, _, _, _, _, _| {}, - ); - } - - fn assign_block_size(&mut self, layout_context: &LayoutContext) { - debug!("assign_block_size: assigning block_size for table_cell"); - self.assign_block_size_table_cell_base(layout_context); - } - - fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) { - self.block_flow - .compute_stacking_relative_position(layout_context) - } - - fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) { - self.block_flow - .update_late_computed_inline_position_if_necessary(inline_position) - } - - fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) { - self.block_flow - .update_late_computed_block_position_if_necessary(block_position) - } - - fn build_display_list(&mut self, _: &mut DisplayListBuildState) { - use style::servo::restyle_damage::ServoRestyleDamage; - // This is handled by TableCellStyleInfo::build_display_list() - // when the containing table builds its display list - - // we skip setting the damage in TableCellStyleInfo::build_display_list() - // because we only have immutable access - self.block_flow - .fragment - .restyle_damage - .remove(ServoRestyleDamage::REPAINT); - } - - fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) { - self.block_flow - .collect_stacking_contexts_for_block(state, StackingContextCollectionFlags::empty()); - } - - fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>) { - self.block_flow.repair_style(new_style) - } - - fn compute_overflow(&self) -> Overflow { - self.block_flow.compute_overflow() - } - - fn contains_roots_of_absolute_flow_tree(&self) -> bool { - self.block_flow.contains_roots_of_absolute_flow_tree() - } - - fn is_absolute_containing_block(&self) -> bool { - self.block_flow.is_absolute_containing_block() - } - - fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> { - self.block_flow.generated_containing_block_size(flow) - } - - fn iterate_through_fragment_border_boxes( - &self, - iterator: &mut dyn FragmentBorderBoxIterator, - level: i32, - stacking_context_position: &Point2D<Au>, - ) { - self.block_flow.iterate_through_fragment_border_boxes( - iterator, - level, - stacking_context_position, - ) - } - - fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) { - self.block_flow.mutate_fragments(mutator) - } - - fn print_extra_flow_children(&self, print_tree: &mut PrintTree) { - self.block_flow.print_extra_flow_children(print_tree); - } -} - -impl fmt::Debug for TableCellFlow { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "TableCellFlow: {:?}", self.block_flow) - } -} - -#[derive(Clone, Copy, Debug, Serialize)] -pub struct CollapsedBordersForCell { - pub inline_start_border: CollapsedBorder, - pub inline_end_border: CollapsedBorder, - pub block_start_border: CollapsedBorder, - pub block_end_border: CollapsedBorder, - pub inline_start_width: Au, - pub inline_end_width: Au, - pub block_start_width: Au, - pub block_end_width: Au, -} - -impl CollapsedBordersForCell { - fn new() -> CollapsedBordersForCell { - CollapsedBordersForCell { - inline_start_border: CollapsedBorder::new(), - inline_end_border: CollapsedBorder::new(), - block_start_border: CollapsedBorder::new(), - block_end_border: CollapsedBorder::new(), - inline_start_width: Au(0), - inline_end_width: Au(0), - block_start_width: Au(0), - block_end_width: Au(0), - } - } - - fn should_paint_inline_start_border(&self) -> bool { - self.inline_start_border.provenance != CollapsedBorderProvenance::FromPreviousTableCell - } - - fn should_paint_inline_end_border(&self) -> bool { - self.inline_end_border.provenance != CollapsedBorderProvenance::FromNextTableCell - } - - fn should_paint_block_start_border(&self) -> bool { - self.block_start_border.provenance != CollapsedBorderProvenance::FromPreviousTableCell - } - - fn should_paint_block_end_border(&self) -> bool { - self.block_end_border.provenance != CollapsedBorderProvenance::FromNextTableCell - } - - pub fn adjust_border_widths_for_painting(&self, border_widths: &mut LogicalMargin<Au>) { - border_widths.inline_start = if !self.should_paint_inline_start_border() { - Au(0) - } else { - self.inline_start_border.width - }; - border_widths.inline_end = if !self.should_paint_inline_end_border() { - Au(0) - } else { - self.inline_end_border.width - }; - border_widths.block_start = if !self.should_paint_block_start_border() { - Au(0) - } else { - self.block_start_border.width - }; - border_widths.block_end = if !self.should_paint_block_end_border() { - Au(0) - } else { - self.block_end_border.width - } - } - - pub fn adjust_border_bounds_for_painting( - &self, - border_bounds: &mut Rect<Au>, - writing_mode: WritingMode, - ) { - let inline_start_divisor = if self.should_paint_inline_start_border() { - 2 - } else { - -2 - }; - let inline_start_offset = - self.inline_start_width / 2 + self.inline_start_border.width / inline_start_divisor; - let inline_end_divisor = if self.should_paint_inline_end_border() { - 2 - } else { - -2 - }; - let inline_end_offset = - self.inline_end_width / 2 + self.inline_end_border.width / inline_end_divisor; - let block_start_divisor = if self.should_paint_block_start_border() { - 2 - } else { - -2 - }; - let block_start_offset = - self.block_start_width / 2 + self.block_start_border.width / block_start_divisor; - let block_end_divisor = if self.should_paint_block_end_border() { - 2 - } else { - -2 - }; - let block_end_offset = - self.block_end_width / 2 + self.block_end_border.width / block_end_divisor; - - // FIXME(pcwalton): Get the real container size. - let mut logical_bounds = - LogicalRect::from_physical(writing_mode, *border_bounds, Size2D::new(Au(0), Au(0))); - logical_bounds.start.i = logical_bounds.start.i - inline_start_offset; - logical_bounds.start.b = logical_bounds.start.b - block_start_offset; - logical_bounds.size.inline = - logical_bounds.size.inline + inline_start_offset + inline_end_offset; - logical_bounds.size.block = - logical_bounds.size.block + block_start_offset + block_end_offset; - *border_bounds = logical_bounds.to_physical(writing_mode, Size2D::new(Au(0), Au(0))) - } - - pub fn adjust_border_colors_and_styles_for_painting( - &self, - border_colors: &mut SideOffsets2D<Color>, - border_styles: &mut SideOffsets2D<BorderStyle>, - writing_mode: WritingMode, - ) { - let logical_border_colors = LogicalMargin::new( - writing_mode, - self.block_start_border.color, - self.inline_end_border.color, - self.block_end_border.color, - self.inline_start_border.color, - ); - *border_colors = logical_border_colors.to_physical(writing_mode); - - let logical_border_styles = LogicalMargin::new( - writing_mode, - self.block_start_border.style, - self.inline_end_border.style, - self.block_end_border.style, - self.inline_start_border.style, - ); - *border_styles = logical_border_styles.to_physical(writing_mode); - } -} diff --git a/components/layout_2020/table_colgroup.rs b/components/layout_2020/table_colgroup.rs deleted file mode 100644 index c07ad56f95a..00000000000 --- a/components/layout_2020/table_colgroup.rs +++ /dev/null @@ -1,131 +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 table formatting contexts. - -use crate::context::LayoutContext; -use crate::display_list::{DisplayListBuildState, StackingContextCollectionState}; -use crate::flow::{BaseFlow, Flow, FlowClass, ForceNonfloatedFlag, OpaqueFlow}; -use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow}; -use crate::layout_debug; -use app_units::Au; -use euclid::Point2D; -use std::fmt; -use style::logical_geometry::LogicalSize; -use style::properties::ComputedValues; -use style::values::computed::Size; - -#[allow(unsafe_code)] -unsafe impl crate::flow::HasBaseFlow for TableColGroupFlow {} - -/// A table formatting context. -#[repr(C)] -pub struct TableColGroupFlow { - /// Data common to all flows. - pub base: BaseFlow, - - /// The associated fragment. - pub fragment: Option<Fragment>, - - /// The table column fragments - pub cols: Vec<Fragment>, - - /// The specified inline-sizes of table columns. (We use `LengthPercentageOrAuto` here in - /// lieu of `ColumnInlineSize` because column groups do not establish minimum or preferred - /// inline sizes.) - pub inline_sizes: Vec<Size>, -} - -impl TableColGroupFlow { - pub fn from_fragments(fragment: Fragment, fragments: Vec<Fragment>) -> TableColGroupFlow { - let writing_mode = fragment.style().writing_mode; - TableColGroupFlow { - base: BaseFlow::new( - Some(fragment.style()), - writing_mode, - ForceNonfloatedFlag::ForceNonfloated, - ), - fragment: Some(fragment), - cols: fragments, - inline_sizes: vec![], - } - } -} - -impl Flow for TableColGroupFlow { - fn class(&self) -> FlowClass { - FlowClass::TableColGroup - } - - fn as_mut_table_colgroup(&mut self) -> &mut TableColGroupFlow { - self - } - - fn as_table_colgroup(&self) -> &TableColGroupFlow { - self - } - - fn bubble_inline_sizes(&mut self) { - let _scope = layout_debug_scope!( - "table_colgroup::bubble_inline_sizes {:x}", - self.base.debug_id() - ); - - for fragment in &self.cols { - // Retrieve the specified value from the appropriate CSS property. - let inline_size = fragment.style().content_inline_size(); - for _ in 0..fragment.column_span() { - self.inline_sizes.push(inline_size) - } - } - } - - /// Table column inline-sizes are assigned in the table flow and propagated to table row flows - /// and/or rowgroup flows. Therefore, table colgroup flows do not need to assign inline-sizes. - fn assign_inline_sizes(&mut self, _: &LayoutContext) {} - - /// Table columns do not have block-size. - fn assign_block_size(&mut self, _: &LayoutContext) {} - - fn update_late_computed_inline_position_if_necessary(&mut self, _: Au) {} - - fn update_late_computed_block_position_if_necessary(&mut self, _: Au) {} - - // Table columns are invisible. - fn build_display_list(&mut self, _: &mut DisplayListBuildState) {} - - fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) { - self.base.stacking_context_id = state.current_stacking_context_id; - self.base.clipping_and_scrolling = Some(state.current_clipping_and_scrolling); - } - - fn repair_style(&mut self, _: &crate::ServoArc<ComputedValues>) {} - - fn compute_overflow(&self) -> Overflow { - Overflow::new() - } - - fn generated_containing_block_size(&self, _: OpaqueFlow) -> LogicalSize<Au> { - panic!("Table column groups can't be containing blocks!") - } - - fn iterate_through_fragment_border_boxes( - &self, - _: &mut dyn FragmentBorderBoxIterator, - _: i32, - _: &Point2D<Au>, - ) { - } - - fn mutate_fragments(&mut self, _: &mut dyn FnMut(&mut Fragment)) {} -} - -impl fmt::Debug for TableColGroupFlow { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.fragment { - Some(ref rb) => write!(f, "TableColGroupFlow: {:?}", rb), - None => write!(f, "TableColGroupFlow"), - } - } -} diff --git a/components/layout_2020/table_row.rs b/components/layout_2020/table_row.rs deleted file mode 100644 index 824da31edad..00000000000 --- a/components/layout_2020/table_row.rs +++ /dev/null @@ -1,1158 +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 table formatting contexts. - -use crate::block::{BlockFlow, ISizeAndMarginsComputer}; -use crate::context::LayoutContext; -use crate::display_list::{ - DisplayListBuildState, StackingContextCollectionFlags, StackingContextCollectionState, -}; -use crate::flow::{ - EarlyAbsolutePositionInfo, Flow, FlowClass, GetBaseFlow, ImmutableFlowUtils, OpaqueFlow, -}; -use crate::flow_list::MutFlowListIterator; -use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow}; -use crate::layout_debug; -use crate::table::{ColumnComputedInlineSize, ColumnIntrinsicInlineSize, InternalTable, VecExt}; -use crate::table_cell::{CollapsedBordersForCell, TableCellFlow}; -use app_units::Au; -use euclid::Point2D; -use gfx_traits::print_tree::PrintTree; -use serde::{Serialize, Serializer}; -use std::cmp::max; -use std::fmt; -use std::iter::{Enumerate, IntoIterator, Peekable}; -use style::computed_values::border_collapse::T as BorderCollapse; -use style::computed_values::border_spacing::T as BorderSpacing; -use style::computed_values::border_top_style::T as BorderStyle; -use style::logical_geometry::{LogicalSize, PhysicalSide, WritingMode}; -use style::properties::ComputedValues; -use style::values::computed::{Color, Size}; - -#[allow(unsafe_code)] -unsafe impl crate::flow::HasBaseFlow for TableRowFlow {} - -/// A single row of a table. -#[repr(C)] -pub struct TableRowFlow { - /// Fields common to all block flows. - pub block_flow: BlockFlow, - - /// Information about the intrinsic inline-sizes of each cell. - pub cell_intrinsic_inline_sizes: Vec<CellIntrinsicInlineSize>, - - /// Information about the computed inline-sizes of each column. - pub column_computed_inline_sizes: Vec<ColumnComputedInlineSize>, - - /// The number of remaining rows spanned by cells in previous rows, indexed by column. - /// - /// Columns that are not included in this vector have the default rowspan of "1". If there are - /// no cells with rowspan != 1 in previous rows, this vector may be empty. - pub incoming_rowspan: Vec<u32>, - - /// The spacing for this row, propagated down from the table during the inline-size assignment - /// phase. - pub spacing: BorderSpacing, - - /// The direction of the columns, propagated down from the table during the inline-size - /// assignment phase. - pub table_writing_mode: WritingMode, - - /// Information about the borders for each cell that we bubble up to our parent. This is only - /// computed if `border-collapse` is `collapse`. - pub preliminary_collapsed_borders: CollapsedBordersForRow, - - /// Information about the borders for each cell, post-collapse. This is only computed if - /// `border-collapse` is `collapse`. - pub final_collapsed_borders: CollapsedBordersForRow, - - /// The computed cell spacing widths post-collapse. - pub collapsed_border_spacing: CollapsedBorderSpacingForRow, -} - -impl Serialize for TableRowFlow { - fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { - self.block_flow.serialize(serializer) - } -} - -/// Information about the column inline size and span for each cell. -#[derive(Clone, Copy, Serialize)] -pub struct CellIntrinsicInlineSize { - /// Inline sizes that this cell contributes to the column. - pub column_size: ColumnIntrinsicInlineSize, - /// The column span of this cell. - pub column_span: u32, - /// The row span of this cell. - pub row_span: u32, -} - -impl TableRowFlow { - pub fn from_fragment(fragment: Fragment) -> TableRowFlow { - let writing_mode = fragment.style().writing_mode; - TableRowFlow { - block_flow: BlockFlow::from_fragment(fragment), - cell_intrinsic_inline_sizes: Vec::new(), - column_computed_inline_sizes: Vec::new(), - incoming_rowspan: Vec::new(), - spacing: BorderSpacing::zero(), - table_writing_mode: writing_mode, - preliminary_collapsed_borders: CollapsedBordersForRow::new(), - final_collapsed_borders: CollapsedBordersForRow::new(), - collapsed_border_spacing: CollapsedBorderSpacingForRow::new(), - } - } - - /// Compute block-size for table-row flow. - /// - /// TODO(pcwalton): This doesn't handle floats and positioned elements right. - /// - /// Returns the block size - pub fn compute_block_size_table_row_base<'a>( - &'a mut self, - layout_context: &LayoutContext, - incoming_rowspan_data: &mut Vec<Au>, - border_info: &[TableRowSizeData], - row_index: usize, - ) -> Au { - fn include_sizes_from_previous_rows( - col: &mut usize, - incoming_rowspan: &[u32], - incoming_rowspan_data: &mut Vec<Au>, - max_block_size: &mut Au, - ) { - while let Some(span) = incoming_rowspan.get(*col) { - if *span == 1 { - break; - } - let incoming = if let Some(incoming) = incoming_rowspan_data.get(*col) { - *incoming - } else { - // This happens when we have a cell with both rowspan and colspan - // incoming_rowspan_data only records the data for the first column, - // but that's ok because we only need to account for each spanning cell - // once. So we skip ahead. - *col += 1; - continue; - }; - *max_block_size = max(*max_block_size, incoming); - *col += 1; - } - } - // Per CSS 2.1 § 17.5.3, find max_y = max(computed `block-size`, minimum block-size of - // all cells). - let mut max_block_size = Au(0); - let thread_id = self.block_flow.base.thread_id; - let content_box = self.block_flow.base.position - - self.block_flow.fragment.border_padding - - self.block_flow.fragment.margin; - - let mut col = 0; - for kid in self.block_flow.base.child_iter_mut() { - include_sizes_from_previous_rows( - &mut col, - &self.incoming_rowspan, - incoming_rowspan_data, - &mut max_block_size, - ); - kid.place_float_if_applicable(); - debug_assert!( - !kid.base().flags.is_float(), - "table cells should never float" - ); - kid.assign_block_size_for_inorder_child_if_necessary( - layout_context, - thread_id, - content_box, - ); - - let mut row_span; - let column_span; - let cell_total; - { - let cell = kid.as_mut_table_cell(); - row_span = cell.row_span; - column_span = cell.column_span as usize; - cell_total = cell.total_block_size(); - } - let child_node = kid.mut_base(); - child_node.position.start.b = Au(0); - let mut cell_block_size_pressure = max(cell_total, child_node.position.size.block); - - if row_span != 1 { - if incoming_rowspan_data.len() <= col { - incoming_rowspan_data.resize(col + 1, Au(0)); - } - let border_sizes_spanned = - get_spanned_border_size(border_info, row_index, &mut row_span); - - cell_block_size_pressure -= border_sizes_spanned; - - // XXXManishearth in case this row covers more than cell_block_size_pressure / row_span - // anyway, we should use that to reduce the pressure on future rows. This will - // require an extra slow-path loop, sadly. - cell_block_size_pressure /= row_span as i32; - incoming_rowspan_data[col] = cell_block_size_pressure; - } - - max_block_size = max(max_block_size, cell_block_size_pressure); - col += column_span; - } - include_sizes_from_previous_rows( - &mut col, - &self.incoming_rowspan, - incoming_rowspan_data, - &mut max_block_size, - ); - - // TODO: Percentage block-size - let block_size = self - .block_flow - .fragment - .style() - .content_block_size() - .to_used_value(Au(0)) - .unwrap_or(max_block_size); - max(block_size, max_block_size) - } - - pub fn assign_block_size_to_self_and_children( - &mut self, - sizes: &[TableRowSizeData], - index: usize, - ) { - // Assign the block-size of kid fragments, which is the same value as own block-size. - let block_size = sizes[index].size; - for kid in self.block_flow.base.child_iter_mut() { - let child_table_cell = kid.as_mut_table_cell(); - let block_size = if child_table_cell.row_span != 1 { - let mut row_span = child_table_cell.row_span; - let border_sizes_spanned = get_spanned_border_size(sizes, index, &mut row_span); - let row_sizes = sizes[index..] - .iter() - .take(row_span as usize) - .fold(Au(0), |accum, r| accum + r.size); - row_sizes + border_sizes_spanned - } else { - block_size - }; - { - let kid_fragment = child_table_cell.mut_fragment(); - let mut position = kid_fragment.border_box; - position.size.block = block_size; - kid_fragment.border_box = position; - } - - // Assign the child's block size. - child_table_cell.block_flow.base.position.size.block = block_size; - - // Now we know the cell height, vertical align the cell's children. - child_table_cell.valign_children(); - - // Write in the size of the relative containing block for children. (This - // information is also needed to handle RTL.) - child_table_cell - .block_flow - .base - .early_absolute_position_info = EarlyAbsolutePositionInfo { - relative_containing_block_size: self.block_flow.fragment.content_box().size, - relative_containing_block_mode: self.block_flow.fragment.style().writing_mode, - }; - } - - // Assign the block-size of own fragment - let mut position = self.block_flow.fragment.border_box; - position.size.block = block_size; - self.block_flow.fragment.border_box = position; - self.block_flow.base.position.size.block = block_size; - } - - pub fn populate_collapsed_border_spacing<'a, I>( - &mut self, - collapsed_inline_direction_border_widths_for_table: &[Au], - collapsed_block_direction_border_widths_for_table: &mut Peekable<I>, - ) where - I: Iterator<Item = &'a Au>, - { - self.collapsed_border_spacing.inline.clear(); - self.collapsed_border_spacing.inline.extend( - collapsed_inline_direction_border_widths_for_table - .into_iter() - .map(|x| *x), - ); - - if let Some(collapsed_block_direction_border_width_for_table) = - collapsed_block_direction_border_widths_for_table.next() - { - self.collapsed_border_spacing.block_start = - *collapsed_block_direction_border_width_for_table - } - if let Some(collapsed_block_direction_border_width_for_table) = - collapsed_block_direction_border_widths_for_table.peek() - { - self.collapsed_border_spacing.block_end = - **collapsed_block_direction_border_width_for_table - } - } -} - -#[derive(Debug, Default)] -pub struct TableRowSizeData { - /// The block-size of the row. - pub size: Au, - /// Border spacing up to this row (not including spacing below the row) - pub cumulative_border_spacing: Au, - /// The "segment" of the table it is in. Tables containing - /// both row groups and rows have the bare rows grouped in - /// segments separated by row groups. It's helpful to look - /// at these as if they are rowgroups themselves. - /// - /// This is enough information for us to be able to check whether we - /// are in a case where we are overflowing a rowgroup with rowspan, - /// however calculating the amount of overflow requires lookahead. - pub rowgroup_id: u32, -} - -/// Given an array of (_, cumulative_border_size), the index of the -/// current row, and the >1 row_span of the cell, calculate the amount of -/// border-spacing spanned by the row. In case the rowspan was larger -/// than required, this will fix it up. -fn get_spanned_border_size(sizes: &[TableRowSizeData], row_index: usize, row_span: &mut u32) -> Au { - // A zero rowspan is functionally equivalent to rowspan=infinity - if *row_span == 0 || row_index + *row_span as usize > sizes.len() { - *row_span = (sizes.len() - row_index) as u32; - } - let mut last_row_idx = row_index + *row_span as usize - 1; - // This is a slow path and should be rare -- this should only get triggered - // when you use `rowspan=0` or an overlarge rowspan in a table with - // mixed rows + rowgroups - if sizes[last_row_idx].rowgroup_id != sizes[row_index].rowgroup_id { - // XXXManishearth this loop can be avoided by also storing - // a "last_rowgroup_at" index so we can leapfrog back quickly - *row_span = sizes[row_index..last_row_idx + 1] - .iter() - .position(|s| s.rowgroup_id != sizes[row_index].rowgroup_id) - .unwrap() as u32; - last_row_idx = row_index + *row_span as usize - 1; - } - sizes[last_row_idx].cumulative_border_spacing - sizes[row_index].cumulative_border_spacing -} - -impl Flow for TableRowFlow { - fn class(&self) -> FlowClass { - FlowClass::TableRow - } - - fn as_mut_table_row(&mut self) -> &mut TableRowFlow { - self - } - - fn as_table_row(&self) -> &TableRowFlow { - self - } - - fn as_mut_block(&mut self) -> &mut BlockFlow { - &mut self.block_flow - } - - fn as_block(&self) -> &BlockFlow { - &self.block_flow - } - - /// Recursively (bottom-up) determines the context's preferred and minimum inline-sizes. When - /// called on this context, all child contexts have had their min/pref inline-sizes set. This - /// function must decide min/pref inline-sizes based on child context inline-sizes and - /// dimensions of any fragments it is responsible for flowing. - /// Min/pref inline-sizes set by this function are used in automatic table layout calculation. - /// The specified column inline-sizes of children cells are used in fixed table layout - /// calculation. - fn bubble_inline_sizes(&mut self) { - let _scope = layout_debug_scope!( - "table_row::bubble_inline_sizes {:x}", - self.block_flow.base.debug_id() - ); - - // Bubble up the specified inline-sizes from child table cells. - let (mut min_inline_size, mut pref_inline_size) = (Au(0), Au(0)); - let collapsing_borders = self - .block_flow - .fragment - .style() - .get_inherited_table() - .border_collapse == - BorderCollapse::Collapse; - let row_style = &*self.block_flow.fragment.style; - self.preliminary_collapsed_borders - .reset(CollapsedBorder::inline_start( - &row_style, - CollapsedBorderProvenance::FromTableRow, - )); - - { - let children_count = self.block_flow.base.children.len(); - let mut iterator = self.block_flow.base.child_iter_mut().enumerate().peekable(); - while let Some((i, kid)) = iterator.next() { - assert!(kid.is_table_cell()); - - // Collect the specified column inline-size of the cell. This is used in both - // fixed and automatic table layout calculation. - let child_specified_inline_size; - let child_column_span; - let child_row_span; - { - let child_table_cell = kid.as_mut_table_cell(); - child_specified_inline_size = child_table_cell - .block_flow - .fragment - .style - .content_inline_size(); - child_column_span = child_table_cell.column_span; - child_row_span = child_table_cell.row_span; - - // Perform border collapse if necessary. - if collapsing_borders { - perform_inline_direction_border_collapse_for_row( - row_style, - children_count, - i, - child_table_cell, - &mut iterator, - &mut self.preliminary_collapsed_borders, - ) - } - } - - // Collect minimum and preferred inline-sizes of the cell for automatic table layout - // calculation. - let child_base = kid.mut_base(); - let child_column_inline_size = ColumnIntrinsicInlineSize { - minimum_length: match child_specified_inline_size { - Size::Auto => None, - Size::LengthPercentage(ref lp) => lp.0.maybe_to_used_value(None), - } - .unwrap_or(child_base.intrinsic_inline_sizes.minimum_inline_size), - percentage: match child_specified_inline_size { - Size::Auto => 0.0, - Size::LengthPercentage(ref lp) => lp.0.as_percentage().map_or(0.0, |p| p.0), - }, - preferred: child_base.intrinsic_inline_sizes.preferred_inline_size, - constrained: match child_specified_inline_size { - Size::Auto => false, - Size::LengthPercentage(ref lp) => lp.0.maybe_to_used_value(None).is_some(), - }, - }; - min_inline_size = min_inline_size + child_column_inline_size.minimum_length; - pref_inline_size = pref_inline_size + child_column_inline_size.preferred; - self.cell_intrinsic_inline_sizes - .push(CellIntrinsicInlineSize { - column_size: child_column_inline_size, - column_span: child_column_span, - row_span: child_row_span, - }); - } - } - - self.block_flow - .base - .intrinsic_inline_sizes - .minimum_inline_size = min_inline_size; - self.block_flow - .base - .intrinsic_inline_sizes - .preferred_inline_size = max(min_inline_size, pref_inline_size); - } - - fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) { - let _scope = layout_debug_scope!( - "table_row::assign_inline_sizes {:x}", - self.block_flow.base.debug_id() - ); - debug!( - "assign_inline_sizes({}): assigning inline_size for flow", - "table_row" - ); - - let shared_context = layout_context.shared_context(); - // The position was set to the containing block by the flow's parent. - let containing_block_inline_size = self.block_flow.base.block_container_inline_size; - // FIXME: In case of border-collapse: collapse, inline_start_content_edge should be - // border_inline_start. - let inline_start_content_edge = Au(0); - let inline_end_content_edge = Au(0); - - let inline_size_computer = InternalTable; - inline_size_computer.compute_used_inline_size( - &mut self.block_flow, - shared_context, - containing_block_inline_size, - ); - - // Spread out the completed inline sizes among columns with spans > 1. - let num_columns = self.column_computed_inline_sizes.len(); - let mut computed_inline_size_for_cells = Vec::with_capacity(num_columns); - let mut col = 0; - - for cell_intrinsic_inline_size in &self.cell_intrinsic_inline_sizes { - // Skip any column occupied by a cell from a previous row. - while col < self.incoming_rowspan.len() && self.incoming_rowspan[col] != 1 { - let size = match self.column_computed_inline_sizes.get(col) { - Some(column_computed_inline_size) => *column_computed_inline_size, - None => ColumnComputedInlineSize { size: Au(0) }, // See FIXME below. - }; - computed_inline_size_for_cells.push(size); - col += 1; - } - // Start with the computed inline size for the first column in the span. - let mut column_computed_inline_size = match self.column_computed_inline_sizes.get(col) { - Some(column_computed_inline_size) => *column_computed_inline_size, - None => { - // We're in fixed layout mode and there are more cells in this row than - // columns we know about. According to CSS 2.1 § 17.5.2.1, the behavior is - // now undefined. So just use zero. - // - // FIXME(pcwalton): $10 says this isn't Web compatible. - ColumnComputedInlineSize { size: Au(0) } - }, - }; - col += 1; - - // Add in computed inline sizes for any extra columns in the span. - for _ in 1..cell_intrinsic_inline_size.column_span { - let extra_column_computed_inline_size = - match self.column_computed_inline_sizes.get(col) { - Some(column_computed_inline_size) => column_computed_inline_size, - None => break, - }; - column_computed_inline_size.size = column_computed_inline_size.size + - extra_column_computed_inline_size.size + - self.spacing.horizontal(); - col += 1; - } - - computed_inline_size_for_cells.push(column_computed_inline_size) - } - - // Set up border collapse info. - let border_collapse_info = match self - .block_flow - .fragment - .style() - .get_inherited_table() - .border_collapse - { - BorderCollapse::Collapse => Some(BorderCollapseInfoForChildTableCell { - collapsed_borders_for_row: &self.final_collapsed_borders, - collapsed_border_spacing_for_row: &self.collapsed_border_spacing, - }), - BorderCollapse::Separate => None, - }; - - // Push those inline sizes down to the cells. - let spacing = self.spacing; - let row_writing_mode = self.block_flow.base.writing_mode; - let table_writing_mode = self.table_writing_mode; - let incoming_rowspan = &self.incoming_rowspan; - let mut column_index = 0; - - self.block_flow.propagate_assigned_inline_size_to_children( - shared_context, - inline_start_content_edge, - inline_end_content_edge, - containing_block_inline_size, - |child_flow, - child_index, - content_inline_size, - _writing_mode, - inline_start_margin_edge, - inline_end_margin_edge| { - set_inline_position_of_child_flow( - child_flow, - child_index, - &mut column_index, - incoming_rowspan, - row_writing_mode, - table_writing_mode, - &computed_inline_size_for_cells, - &spacing, - &border_collapse_info, - content_inline_size, - inline_start_margin_edge, - inline_end_margin_edge, - ); - }, - ) - } - - fn assign_block_size(&mut self, _: &LayoutContext) { - // the surrounding table or rowgroup does this - } - - fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) { - self.block_flow - .compute_stacking_relative_position(layout_context) - } - - fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) { - self.block_flow - .update_late_computed_inline_position_if_necessary(inline_position) - } - - fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) { - self.block_flow - .update_late_computed_block_position_if_necessary(block_position) - } - - fn build_display_list(&mut self, _: &mut DisplayListBuildState) { - use style::servo::restyle_damage::ServoRestyleDamage; - // handled in TableCellStyleInfo::build_display_list - // we skip setting the damage in TableCellStyleInfo::build_display_list() - // because we only have immutable access - self.block_flow - .fragment - .restyle_damage - .remove(ServoRestyleDamage::REPAINT); - } - - fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) { - self.block_flow - .collect_stacking_contexts_for_block(state, StackingContextCollectionFlags::empty()); - } - - fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>) { - self.block_flow.repair_style(new_style) - } - - fn compute_overflow(&self) -> Overflow { - self.block_flow.compute_overflow() - } - - fn contains_roots_of_absolute_flow_tree(&self) -> bool { - self.block_flow.contains_roots_of_absolute_flow_tree() - } - - fn is_absolute_containing_block(&self) -> bool { - self.block_flow.is_absolute_containing_block() - } - - fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> { - self.block_flow.generated_containing_block_size(flow) - } - - fn iterate_through_fragment_border_boxes( - &self, - iterator: &mut dyn FragmentBorderBoxIterator, - level: i32, - stacking_context_position: &Point2D<Au>, - ) { - self.block_flow.iterate_through_fragment_border_boxes( - iterator, - level, - stacking_context_position, - ) - } - - fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) { - self.block_flow.mutate_fragments(mutator) - } - - fn print_extra_flow_children(&self, print_tree: &mut PrintTree) { - self.block_flow.print_extra_flow_children(print_tree); - } -} - -impl fmt::Debug for TableRowFlow { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "TableRowFlow: {:?}", self.block_flow) - } -} - -#[derive(Clone, Debug)] -pub struct CollapsedBordersForRow { - /// The size of this vector should be equal to the number of cells plus one. - pub inline: Vec<CollapsedBorder>, - /// The size of this vector should be equal to the number of cells. - pub block_start: Vec<CollapsedBorder>, - /// The size of this vector should be equal to the number of cells. - pub block_end: Vec<CollapsedBorder>, -} - -impl CollapsedBordersForRow { - pub fn new() -> CollapsedBordersForRow { - CollapsedBordersForRow { - inline: Vec::new(), - block_start: Vec::new(), - block_end: Vec::new(), - } - } - - pub fn reset(&mut self, first_inline_border: CollapsedBorder) { - self.inline.clear(); - self.inline.push(first_inline_border); - self.block_start.clear(); - self.block_end.clear() - } -} - -#[derive(Clone, Debug)] -pub struct CollapsedBorderSpacingForRow { - /// The spacing in between each column. - inline: Vec<Au>, - /// The spacing above this row. - pub block_start: Au, - /// The spacing below this row. - block_end: Au, -} - -impl CollapsedBorderSpacingForRow { - fn new() -> CollapsedBorderSpacingForRow { - CollapsedBorderSpacingForRow { - inline: Vec::new(), - block_start: Au(0), - block_end: Au(0), - } - } -} - -/// All aspects of a border that can collapse with adjacent borders. See CSS 2.1 § 17.6.2.1. -#[derive(Clone, Copy, Debug)] -pub struct CollapsedBorder { - /// The style of the border. - pub style: BorderStyle, - /// The width of the border. - pub width: Au, - /// The color of the border. - pub color: Color, - /// The type of item that this border comes from. - pub provenance: CollapsedBorderProvenance, -} - -impl Serialize for CollapsedBorder { - fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { - serializer.serialize_unit() - } -} - -/// Where a border style comes from. -/// -/// The integer values here correspond to the border conflict resolution rules in CSS 2.1 § -/// 17.6.2.1. Higher values override lower values. -// FIXME(#8586): FromTableRow, FromTableRowGroup, FromTableColumn, -// FromTableColumnGroup are unused -#[allow(dead_code)] -#[derive(Clone, Copy, Debug, PartialEq, Serialize)] -pub enum CollapsedBorderProvenance { - FromPreviousTableCell = 6, - FromNextTableCell = 5, - FromTableRow = 4, - FromTableRowGroup = 3, - FromTableColumn = 2, - FromTableColumnGroup = 1, - FromTable = 0, -} - -impl CollapsedBorder { - /// Creates a collapsible border style for no border. - pub fn new() -> CollapsedBorder { - CollapsedBorder { - style: BorderStyle::None, - width: Au(0), - color: Color::transparent(), - provenance: CollapsedBorderProvenance::FromTable, - } - } - - /// Creates a collapsed border from the block-start border described in the given CSS style - /// object. - fn top(css_style: &ComputedValues, provenance: CollapsedBorderProvenance) -> CollapsedBorder { - CollapsedBorder { - style: css_style.get_border().border_top_style, - width: Au::from(css_style.get_border().border_top_width), - color: css_style.get_border().border_top_color, - provenance: provenance, - } - } - - /// Creates a collapsed border style from the right border described in the given CSS style - /// object. - fn right(css_style: &ComputedValues, provenance: CollapsedBorderProvenance) -> CollapsedBorder { - CollapsedBorder { - style: css_style.get_border().border_right_style, - width: Au::from(css_style.get_border().border_right_width), - color: css_style.get_border().border_right_color, - provenance: provenance, - } - } - - /// Creates a collapsed border style from the bottom border described in the given CSS style - /// object. - fn bottom( - css_style: &ComputedValues, - provenance: CollapsedBorderProvenance, - ) -> CollapsedBorder { - CollapsedBorder { - style: css_style.get_border().border_bottom_style, - width: Au::from(css_style.get_border().border_bottom_width), - color: css_style.get_border().border_bottom_color, - provenance: provenance, - } - } - - /// Creates a collapsed border style from the left border described in the given CSS style - /// object. - fn left(css_style: &ComputedValues, provenance: CollapsedBorderProvenance) -> CollapsedBorder { - CollapsedBorder { - style: css_style.get_border().border_left_style, - width: Au::from(css_style.get_border().border_left_width), - color: css_style.get_border().border_left_color, - provenance: provenance, - } - } - - /// Creates a collapsed border style from the given physical side. - fn from_side( - side: PhysicalSide, - css_style: &ComputedValues, - provenance: CollapsedBorderProvenance, - ) -> CollapsedBorder { - match side { - PhysicalSide::Top => CollapsedBorder::top(css_style, provenance), - PhysicalSide::Right => CollapsedBorder::right(css_style, provenance), - PhysicalSide::Bottom => CollapsedBorder::bottom(css_style, provenance), - PhysicalSide::Left => CollapsedBorder::left(css_style, provenance), - } - } - - /// Creates a collapsed border style from the inline-start border described in the given CSS - /// style object. - pub fn inline_start( - css_style: &ComputedValues, - provenance: CollapsedBorderProvenance, - ) -> CollapsedBorder { - CollapsedBorder::from_side( - css_style.writing_mode.inline_start_physical_side(), - css_style, - provenance, - ) - } - - /// Creates a collapsed border style from the inline-start border described in the given CSS - /// style object. - pub fn inline_end( - css_style: &ComputedValues, - provenance: CollapsedBorderProvenance, - ) -> CollapsedBorder { - CollapsedBorder::from_side( - css_style.writing_mode.inline_end_physical_side(), - css_style, - provenance, - ) - } - - /// Creates a collapsed border style from the block-start border described in the given CSS - /// style object. - pub fn block_start( - css_style: &ComputedValues, - provenance: CollapsedBorderProvenance, - ) -> CollapsedBorder { - CollapsedBorder::from_side( - css_style.writing_mode.block_start_physical_side(), - css_style, - provenance, - ) - } - - /// Creates a collapsed border style from the block-end border described in the given CSS style - /// object. - pub fn block_end( - css_style: &ComputedValues, - provenance: CollapsedBorderProvenance, - ) -> CollapsedBorder { - CollapsedBorder::from_side( - css_style.writing_mode.block_end_physical_side(), - css_style, - provenance, - ) - } - - /// If `other` has a higher priority per CSS 2.1 § 17.6.2.1, replaces `self` with it. - pub fn combine(&mut self, other: &CollapsedBorder) { - match (self.style, other.style) { - // Step 1. - (BorderStyle::Hidden, _) => {}, - (_, BorderStyle::Hidden) => *self = *other, - // Step 2. - (BorderStyle::None, _) => *self = *other, - (_, BorderStyle::None) => {}, - // Step 3. - _ if self.width > other.width => {}, - _ if self.width < other.width => *self = *other, - (this_style, other_style) if this_style > other_style => {}, - (this_style, other_style) if this_style < other_style => *self = *other, - // Step 4. - _ if (self.provenance as i8) >= other.provenance as i8 => {}, - _ => *self = *other, - } - } -} - -/// Pushes column inline size, incoming rowspan, and border collapse info down to a child. -pub fn propagate_column_inline_sizes_to_child( - child_flow: &mut dyn Flow, - table_writing_mode: WritingMode, - column_computed_inline_sizes: &[ColumnComputedInlineSize], - border_spacing: &BorderSpacing, - incoming_rowspan: &mut Vec<u32>, -) { - // If the child is a row group or a row, the column inline-size and rowspan info should be copied from its - // parent. - // - // FIXME(pcwalton): This seems inefficient. Reference count it instead? - match child_flow.class() { - FlowClass::TableRowGroup => { - incoming_rowspan.clear(); - let child_table_rowgroup_flow = child_flow.as_mut_table_rowgroup(); - child_table_rowgroup_flow.spacing = *border_spacing; - for kid in child_table_rowgroup_flow.block_flow.base.child_iter_mut() { - propagate_column_inline_sizes_to_child( - kid, - table_writing_mode, - column_computed_inline_sizes, - border_spacing, - incoming_rowspan, - ); - } - }, - FlowClass::TableRow => { - let child_table_row_flow = child_flow.as_mut_table_row(); - child_table_row_flow.column_computed_inline_sizes = - column_computed_inline_sizes.to_vec(); - child_table_row_flow.spacing = *border_spacing; - child_table_row_flow.table_writing_mode = table_writing_mode; - child_table_row_flow.incoming_rowspan = incoming_rowspan.clone(); - - // Update the incoming rowspan for the next row. - let mut col = 0; - for cell in &child_table_row_flow.cell_intrinsic_inline_sizes { - // Skip any column occupied by a cell from a previous row. - while col < incoming_rowspan.len() && incoming_rowspan[col] != 1 { - if incoming_rowspan[col] > 1 { - incoming_rowspan[col] -= 1; - } - col += 1; - } - for _ in 0..cell.column_span { - if col < incoming_rowspan.len() && incoming_rowspan[col] > 1 { - incoming_rowspan[col] -= 1; - } - // If this cell spans later rows, record its rowspan. - if cell.row_span != 1 { - if incoming_rowspan.len() < col + 1 { - incoming_rowspan.resize(col + 1, 1); - } - // HTML § 4.9.11: For rowspan, the value 0 means the cell is to span all - // the remaining rows in the rowgroup. - if cell.row_span > incoming_rowspan[col] || cell.row_span == 0 { - incoming_rowspan[col] = cell.row_span; - } - } - col += 1; - } - } - }, - c => warn!("unexpected flow in table {:?}", c), - } -} - -/// Lay out table cells inline according to the computer column sizes. -fn set_inline_position_of_child_flow( - child_flow: &mut dyn Flow, - child_index: usize, - column_index: &mut usize, - incoming_rowspan: &[u32], - row_writing_mode: WritingMode, - table_writing_mode: WritingMode, - column_computed_inline_sizes: &[ColumnComputedInlineSize], - border_spacing: &BorderSpacing, - border_collapse_info: &Option<BorderCollapseInfoForChildTableCell>, - parent_content_inline_size: Au, - inline_start_margin_edge: &mut Au, - inline_end_margin_edge: &mut Au, -) { - if !child_flow.is_table_cell() { - return; - } - - let reverse_column_order = table_writing_mode.is_bidi_ltr() != row_writing_mode.is_bidi_ltr(); - - // Advance past any column occupied by a cell from a previous row. - while *column_index < incoming_rowspan.len() && incoming_rowspan[*column_index] != 1 { - let column_inline_size = column_computed_inline_sizes[*column_index].size; - let border_inline_size = match *border_collapse_info { - Some(_) => Au(0), // FIXME: Make collapsed borders account for colspan/rowspan. - None => border_spacing.horizontal(), - }; - if reverse_column_order { - *inline_end_margin_edge += column_inline_size + border_inline_size; - } else { - *inline_start_margin_edge += column_inline_size + border_inline_size; - } - *column_index += 1; - } - - // Handle border collapsing, if necessary. - let child_table_cell = child_flow.as_mut_table_cell(); - match *border_collapse_info { - Some(ref border_collapse_info) => { - // Write in the child's border collapse state. - child_table_cell.collapsed_borders = CollapsedBordersForCell { - inline_start_border: border_collapse_info - .collapsed_borders_for_row - .inline - .get(child_index) - .map_or(CollapsedBorder::new(), |x| *x), - inline_end_border: border_collapse_info - .collapsed_borders_for_row - .inline - .get(child_index + 1) - .map_or(CollapsedBorder::new(), |x| *x), - block_start_border: border_collapse_info - .collapsed_borders_for_row - .block_start - .get(child_index) - .map_or(CollapsedBorder::new(), |x| *x), - block_end_border: border_collapse_info - .collapsed_borders_for_row - .block_end - .get(child_index) - .map_or(CollapsedBorder::new(), |x| *x), - inline_start_width: border_collapse_info - .collapsed_border_spacing_for_row - .inline - .get(child_index) - .map_or(Au(0), |x| *x), - inline_end_width: border_collapse_info - .collapsed_border_spacing_for_row - .inline - .get(child_index + 1) - .map_or(Au(0), |x| *x), - block_start_width: border_collapse_info - .collapsed_border_spacing_for_row - .block_start, - block_end_width: border_collapse_info - .collapsed_border_spacing_for_row - .block_end, - }; - - // Move over past the collapsed border. - if reverse_column_order { - *inline_end_margin_edge += child_table_cell.collapsed_borders.inline_start_width; - } else { - *inline_start_margin_edge += child_table_cell.collapsed_borders.inline_start_width; - } - }, - None => { - // Take spacing into account. - if reverse_column_order { - *inline_end_margin_edge += border_spacing.horizontal(); - } else { - *inline_start_margin_edge += border_spacing.horizontal(); - } - }, - } - - let column_inline_size = column_computed_inline_sizes[*column_index].size; - *column_index += 1; - - let kid_base = &mut child_table_cell.block_flow.base; - kid_base.block_container_inline_size = column_inline_size; - - if reverse_column_order { - // Columns begin from the inline-end edge. - kid_base.position.start.i = - parent_content_inline_size - *inline_end_margin_edge - column_inline_size; - *inline_end_margin_edge += column_inline_size; - } else { - // Columns begin from the inline-start edge. - kid_base.position.start.i = *inline_start_margin_edge; - *inline_start_margin_edge += column_inline_size; - } -} - -#[derive(Clone, Copy)] -pub struct BorderCollapseInfoForChildTableCell<'a> { - collapsed_borders_for_row: &'a CollapsedBordersForRow, - collapsed_border_spacing_for_row: &'a CollapsedBorderSpacingForRow, -} - -/// Performs border-collapse in the inline direction for all the cells' inside borders in the -/// inline-direction cells and propagates the outside borders (the far left and right) up to the -/// table row. This is done eagerly here so that at least the inline inside border collapse -/// computations can be parallelized across all the rows of the table. -fn perform_inline_direction_border_collapse_for_row( - row_style: &ComputedValues, - children_count: usize, - child_index: usize, - child_table_cell: &mut TableCellFlow, - iterator: &mut Peekable<Enumerate<MutFlowListIterator>>, - preliminary_collapsed_borders: &mut CollapsedBordersForRow, -) { - // In the first cell, combine its border with the one coming from the row. - if child_index == 0 { - let first_inline_border = &mut preliminary_collapsed_borders.inline[0]; - first_inline_border.combine(&CollapsedBorder::inline_start( - &*child_table_cell.block_flow.fragment.style, - CollapsedBorderProvenance::FromNextTableCell, - )); - } - - let inline_collapsed_border = preliminary_collapsed_borders.inline.push_or_set( - child_index + 1, - CollapsedBorder::inline_end( - &*child_table_cell.block_flow.fragment.style, - CollapsedBorderProvenance::FromPreviousTableCell, - ), - ); - - if let Some(&(_, ref next_child_flow)) = iterator.peek() { - let next_child_flow = next_child_flow.as_block(); - inline_collapsed_border.combine(&CollapsedBorder::inline_start( - &*next_child_flow.fragment.style, - CollapsedBorderProvenance::FromNextTableCell, - )) - }; - - // In the last cell, also take into account the border that may - // come from the row. - if child_index + 1 == children_count { - inline_collapsed_border.combine(&CollapsedBorder::inline_end( - &row_style, - CollapsedBorderProvenance::FromTableRow, - )); - } - - let mut block_start_border = CollapsedBorder::block_start( - &*child_table_cell.block_flow.fragment.style, - CollapsedBorderProvenance::FromNextTableCell, - ); - block_start_border.combine(&CollapsedBorder::block_start( - row_style, - CollapsedBorderProvenance::FromTableRow, - )); - preliminary_collapsed_borders - .block_start - .push_or_set(child_index, block_start_border); - let mut block_end_border = CollapsedBorder::block_end( - &*child_table_cell.block_flow.fragment.style, - CollapsedBorderProvenance::FromPreviousTableCell, - ); - block_end_border.combine(&CollapsedBorder::block_end( - row_style, - CollapsedBorderProvenance::FromTableRow, - )); - - preliminary_collapsed_borders - .block_end - .push_or_set(child_index, block_end_border); -} diff --git a/components/layout_2020/table_rowgroup.rs b/components/layout_2020/table_rowgroup.rs deleted file mode 100644 index 814cbe0d2d2..00000000000 --- a/components/layout_2020/table_rowgroup.rs +++ /dev/null @@ -1,274 +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 table formatting contexts. - -use crate::block::{BlockFlow, ISizeAndMarginsComputer}; -use crate::context::LayoutContext; -use crate::display_list::{ - DisplayListBuildState, StackingContextCollectionFlags, StackingContextCollectionState, -}; -use crate::flow::{Flow, FlowClass, OpaqueFlow}; -use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow}; -use crate::layout_debug; -use crate::table::{ColumnIntrinsicInlineSize, InternalTable, TableLikeFlow}; -use app_units::Au; -use euclid::Point2D; -use gfx_traits::print_tree::PrintTree; -use serde::{Serialize, Serializer}; -use std::fmt; -use std::iter::{IntoIterator, Iterator, Peekable}; -use style::computed_values::{border_collapse, border_spacing}; -use style::logical_geometry::LogicalSize; -use style::properties::ComputedValues; - -#[allow(unsafe_code)] -unsafe impl crate::flow::HasBaseFlow for TableRowGroupFlow {} - -/// A table formatting context. -#[repr(C)] -pub struct TableRowGroupFlow { - /// Fields common to all block flows. - pub block_flow: BlockFlow, - - /// Information about the intrinsic inline-sizes of each column. - pub column_intrinsic_inline_sizes: Vec<ColumnIntrinsicInlineSize>, - - /// The spacing for this rowgroup. - pub spacing: border_spacing::T, - - /// The final width of the borders in the inline direction for each cell, computed by the - /// entire table and pushed down into each row during inline size computation. - pub collapsed_inline_direction_border_widths_for_table: Vec<Au>, - - /// The final width of the borders in the block direction for each cell, computed by the - /// entire table and pushed down into each row during inline size computation. - pub collapsed_block_direction_border_widths_for_table: Vec<Au>, -} - -impl Serialize for TableRowGroupFlow { - fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { - self.block_flow.serialize(serializer) - } -} - -impl TableRowGroupFlow { - pub fn from_fragment(fragment: Fragment) -> TableRowGroupFlow { - TableRowGroupFlow { - block_flow: BlockFlow::from_fragment(fragment), - column_intrinsic_inline_sizes: Vec::new(), - spacing: border_spacing::T::zero(), - collapsed_inline_direction_border_widths_for_table: Vec::new(), - collapsed_block_direction_border_widths_for_table: Vec::new(), - } - } - - pub fn populate_collapsed_border_spacing<'a, I>( - &mut self, - collapsed_inline_direction_border_widths_for_table: &[Au], - collapsed_block_direction_border_widths_for_table: &mut Peekable<I>, - ) where - I: Iterator<Item = &'a Au>, - { - self.collapsed_inline_direction_border_widths_for_table - .clear(); - self.collapsed_inline_direction_border_widths_for_table - .extend( - collapsed_inline_direction_border_widths_for_table - .into_iter() - .map(|x| *x), - ); - - for _ in 0..self.block_flow.base.children.len() { - if let Some(collapsed_block_direction_border_width_for_table) = - collapsed_block_direction_border_widths_for_table.next() - { - self.collapsed_block_direction_border_widths_for_table - .push(*collapsed_block_direction_border_width_for_table) - } - } - if let Some(collapsed_block_direction_border_width_for_table) = - collapsed_block_direction_border_widths_for_table.peek() - { - self.collapsed_block_direction_border_widths_for_table - .push(**collapsed_block_direction_border_width_for_table) - } - } -} - -impl Flow for TableRowGroupFlow { - fn class(&self) -> FlowClass { - FlowClass::TableRowGroup - } - - fn as_mut_table_rowgroup(&mut self) -> &mut TableRowGroupFlow { - self - } - - fn as_table_rowgroup(&self) -> &TableRowGroupFlow { - self - } - - fn as_mut_block(&mut self) -> &mut BlockFlow { - &mut self.block_flow - } - - fn as_block(&self) -> &BlockFlow { - &self.block_flow - } - - fn bubble_inline_sizes(&mut self) { - let _scope = layout_debug_scope!( - "table_rowgroup::bubble_inline_sizes {:x}", - self.block_flow.base.debug_id() - ); - // Proper calculation of intrinsic sizes in table layout requires access to the entire - // table, which we don't have yet. Defer to our parent. - } - - /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. - /// When called on this context, the context has had its inline-size set by the parent context. - fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) { - let _scope = layout_debug_scope!( - "table_rowgroup::assign_inline_sizes {:x}", - self.block_flow.base.debug_id() - ); - debug!( - "assign_inline_sizes({}): assigning inline_size for flow", - "table_rowgroup" - ); - - let shared_context = layout_context.shared_context(); - // The position was set to the containing block by the flow's parent. - let containing_block_inline_size = self.block_flow.base.block_container_inline_size; - let (inline_start_content_edge, inline_end_content_edge) = (Au(0), Au(0)); - let content_inline_size = containing_block_inline_size; - - let border_collapse = self - .block_flow - .fragment - .style - .get_inherited_table() - .border_collapse; - let inline_size_computer = InternalTable; - inline_size_computer.compute_used_inline_size( - &mut self.block_flow, - shared_context, - containing_block_inline_size, - ); - - let collapsed_inline_direction_border_widths_for_table = - &self.collapsed_inline_direction_border_widths_for_table; - let mut collapsed_block_direction_border_widths_for_table = self - .collapsed_block_direction_border_widths_for_table - .iter() - .peekable(); - self.block_flow.propagate_assigned_inline_size_to_children( - shared_context, - inline_start_content_edge, - inline_end_content_edge, - content_inline_size, - |child_flow, - _child_index, - _content_inline_size, - _writing_mode, - _inline_start_margin_edge, - _inline_end_margin_edge| { - if border_collapse == border_collapse::T::Collapse { - let child_table_row = child_flow.as_mut_table_row(); - child_table_row.populate_collapsed_border_spacing( - collapsed_inline_direction_border_widths_for_table, - &mut collapsed_block_direction_border_widths_for_table, - ); - } - }, - ); - } - - fn assign_block_size(&mut self, lc: &LayoutContext) { - debug!("assign_block_size: assigning block_size for table_rowgroup"); - self.block_flow - .assign_block_size_for_table_like_flow(self.spacing.vertical(), lc); - } - - fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) { - self.block_flow - .compute_stacking_relative_position(layout_context) - } - - fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) { - self.block_flow - .update_late_computed_inline_position_if_necessary(inline_position) - } - - fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) { - self.block_flow - .update_late_computed_block_position_if_necessary(block_position) - } - - fn build_display_list(&mut self, _: &mut DisplayListBuildState) { - use style::servo::restyle_damage::ServoRestyleDamage; - - // we skip setting the damage in TableCellStyleInfo::build_display_list() - // because we only have immutable access - self.block_flow - .fragment - .restyle_damage - .remove(ServoRestyleDamage::REPAINT); - } - - fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) { - self.block_flow.collect_stacking_contexts_for_block( - state, - StackingContextCollectionFlags::POSITION_NEVER_CREATES_CONTAINING_BLOCK, - ); - } - - fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>) { - self.block_flow.repair_style(new_style) - } - - fn compute_overflow(&self) -> Overflow { - self.block_flow.compute_overflow() - } - - fn contains_roots_of_absolute_flow_tree(&self) -> bool { - self.block_flow.contains_roots_of_absolute_flow_tree() - } - - fn is_absolute_containing_block(&self) -> bool { - self.block_flow.is_absolute_containing_block() - } - - fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> { - self.block_flow.generated_containing_block_size(flow) - } - - fn iterate_through_fragment_border_boxes( - &self, - iterator: &mut dyn FragmentBorderBoxIterator, - level: i32, - stacking_context_position: &Point2D<Au>, - ) { - self.block_flow.iterate_through_fragment_border_boxes( - iterator, - level, - stacking_context_position, - ) - } - - fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) { - self.block_flow.mutate_fragments(mutator) - } - - fn print_extra_flow_children(&self, print_tree: &mut PrintTree) { - self.block_flow.print_extra_flow_children(print_tree); - } -} - -impl fmt::Debug for TableRowGroupFlow { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "TableRowGroupFlow: {:?}", self.block_flow) - } -} diff --git a/components/layout_2020/table_wrapper.rs b/components/layout_2020/table_wrapper.rs deleted file mode 100644 index 65ecc07d3ab..00000000000 --- a/components/layout_2020/table_wrapper.rs +++ /dev/null @@ -1,996 +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 tables. -//! -//! This follows the "More Precise Definitions of Inline Layout and Table Layout" proposal written -//! by L. David Baron (Mozilla) here: -//! -//! http://dbaron.org/css/intrinsic/ -//! -//! Hereafter this document is referred to as INTRINSIC. - -use crate::block::{ - AbsoluteNonReplaced, BlockFlow, FloatNonReplaced, ISizeAndMarginsComputer, ISizeConstraintInput, -}; -use crate::block::{ISizeConstraintSolution, MarginsMayCollapseFlag}; -use crate::context::LayoutContext; -use crate::display_list::StackingContextCollectionState; -use crate::display_list::{DisplayListBuildState, StackingContextCollectionFlags}; -use crate::floats::FloatKind; -use crate::flow::{Flow, FlowClass, FlowFlags, ImmutableFlowUtils, OpaqueFlow}; -use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow}; -use crate::model::MaybeAuto; -use crate::table::{ColumnComputedInlineSize, ColumnIntrinsicInlineSize}; -use app_units::Au; -use euclid::Point2D; -use gfx_traits::print_tree::PrintTree; -use std::cmp::{max, min}; -use std::fmt; -use std::ops::Add; -use style::computed_values::{position, table_layout}; -use style::context::SharedStyleContext; -use style::logical_geometry::{LogicalRect, LogicalSize}; -use style::properties::ComputedValues; -use style::values::computed::Size; -use style::values::CSSFloat; - -#[derive(Clone, Copy, Debug, Serialize)] -pub enum TableLayout { - Fixed, - Auto, -} - -#[allow(unsafe_code)] -unsafe impl crate::flow::HasBaseFlow for TableWrapperFlow {} - -/// A table wrapper flow based on a block formatting context. -#[derive(Serialize)] -#[repr(C)] -pub struct TableWrapperFlow { - pub block_flow: BlockFlow, - - /// Intrinsic column inline sizes according to INTRINSIC § 4.1 - pub column_intrinsic_inline_sizes: Vec<ColumnIntrinsicInlineSize>, - - /// Table-layout property - pub table_layout: TableLayout, -} - -impl TableWrapperFlow { - pub fn from_fragment(fragment: Fragment) -> TableWrapperFlow { - TableWrapperFlow::from_fragment_and_float_kind(fragment, None) - } - - pub fn from_fragment_and_float_kind( - fragment: Fragment, - float_kind: Option<FloatKind>, - ) -> TableWrapperFlow { - let mut block_flow = BlockFlow::from_fragment_and_float_kind(fragment, float_kind); - let table_layout = - if block_flow.fragment().style().get_table().table_layout == table_layout::T::Fixed { - TableLayout::Fixed - } else { - TableLayout::Auto - }; - TableWrapperFlow { - block_flow: block_flow, - column_intrinsic_inline_sizes: vec![], - table_layout: table_layout, - } - } - - fn border_padding_and_spacing(&mut self) -> (Au, Au) { - let (mut table_border_padding, mut spacing) = (Au(0), Au(0)); - for kid in self.block_flow.base.child_iter_mut() { - if kid.is_table() { - let kid_table = kid.as_table(); - spacing = kid_table.total_horizontal_spacing(); - table_border_padding = kid_table - .block_flow - .fragment - .border_padding - .inline_start_end(); - break; - } - } - (table_border_padding, spacing) - } - - // Instructs our first child, which is the table itself, to compute its border and padding. - // - // This is a little weird because we're computing border/padding/margins for our child, - // when normally the child computes it itself. But it has to be this way because the - // padding will affect where we place the child. This is an odd artifact of the way that - // tables are separated into table flows and table wrapper flows. - fn compute_border_and_padding_of_table(&mut self) { - let available_inline_size = self.block_flow.base.block_container_inline_size; - for kid in self.block_flow.base.child_iter_mut() { - if !kid.is_table() { - continue; - } - - let kid_table = kid.as_mut_table(); - let kid_block_flow = &mut kid_table.block_flow; - kid_block_flow - .fragment - .compute_border_and_padding(available_inline_size); - kid_block_flow - .fragment - .compute_block_direction_margins(available_inline_size); - kid_block_flow - .fragment - .compute_inline_direction_margins(available_inline_size); - return; - } - } - - /// Calculates table column sizes for automatic layout per INTRINSIC § 4.3. - fn calculate_table_column_sizes_for_automatic_layout( - &mut self, - intermediate_column_inline_sizes: &mut [IntermediateColumnInlineSize], - ) { - let available_inline_size = self.available_inline_size(); - - // Compute all the guesses for the column sizes, and sum them. - let mut total_guess = AutoLayoutCandidateGuess::new(); - let guesses: Vec<AutoLayoutCandidateGuess> = self - .column_intrinsic_inline_sizes - .iter() - .map(|column_intrinsic_inline_size| { - let guess = AutoLayoutCandidateGuess::from_column_intrinsic_inline_size( - column_intrinsic_inline_size, - available_inline_size, - ); - total_guess = &total_guess + &guess; - guess - }) - .collect(); - - // Assign inline sizes. - let selection = - SelectedAutoLayoutCandidateGuess::select(&total_guess, available_inline_size); - let mut total_used_inline_size = Au(0); - for (intermediate_column_inline_size, guess) in intermediate_column_inline_sizes - .iter_mut() - .zip(guesses.iter()) - { - intermediate_column_inline_size.size = guess.calculate(selection); - intermediate_column_inline_size.percentage = 0.0; - total_used_inline_size = total_used_inline_size + intermediate_column_inline_size.size - } - - // Distribute excess inline-size if necessary per INTRINSIC § 4.4. - // - // FIXME(pcwalton, spec): How do I deal with fractional excess? - let excess_inline_size = available_inline_size - total_used_inline_size; - if excess_inline_size > Au(0) && - selection == - SelectedAutoLayoutCandidateGuess::UsePreferredGuessAndDistributeExcessInlineSize - { - let mut info = ExcessInlineSizeDistributionInfo::new(); - for column_intrinsic_inline_size in &self.column_intrinsic_inline_sizes { - info.update(column_intrinsic_inline_size) - } - - let mut total_distributed_excess_size = Au(0); - for (intermediate_column_inline_size, column_intrinsic_inline_size) in - intermediate_column_inline_sizes - .iter_mut() - .zip(self.column_intrinsic_inline_sizes.iter()) - { - info.distribute_excess_inline_size_to_column( - intermediate_column_inline_size, - column_intrinsic_inline_size, - excess_inline_size, - &mut total_distributed_excess_size, - ) - } - total_used_inline_size = available_inline_size - } - - self.set_inline_size(total_used_inline_size) - } - - fn available_inline_size(&mut self) -> Au { - let available_inline_size = self.block_flow.fragment.border_box.size.inline; - let (table_border_padding, spacing) = self.border_padding_and_spacing(); - - // FIXME(pcwalton, spec): INTRINSIC § 8 does not properly define how to compute this, but - // says "the basic idea is the same as the shrink-to-fit width that CSS2.1 defines". So we - // just use the shrink-to-fit inline size. - let available_inline_size = match self.block_flow.fragment.style().content_inline_size() { - Size::Auto => { - self.block_flow - .get_shrink_to_fit_inline_size(available_inline_size) - - table_border_padding - }, - // FIXME(mttr): This fixes #4421 without breaking our current reftests, but I'm not - // completely sure this is "correct". - // - // That said, `available_inline_size` is, as far as I can tell, equal to the table's - // computed width property (W) and is used from this point forward in a way that seems - // to correspond with CSS 2.1 § 17.5.2.2 under "Column and caption widths influence the - // final table width as follows: …" - _ => available_inline_size, - }; - available_inline_size - spacing - } - - fn set_inline_size(&mut self, total_used_inline_size: Au) { - let (table_border_padding, spacing) = self.border_padding_and_spacing(); - self.block_flow.fragment.border_box.size.inline = - total_used_inline_size + table_border_padding + spacing; - self.block_flow.base.position.size.inline = total_used_inline_size + - table_border_padding + - spacing + - self.block_flow.fragment.margin.inline_start_end(); - - let writing_mode = self.block_flow.base.writing_mode; - let container_mode = self.block_flow.base.block_container_writing_mode; - - if writing_mode.is_bidi_ltr() != container_mode.is_bidi_ltr() { - // If our "start" direction is different from our parent flow, then `border_box.start.i` - // depends on `border_box.size.inline`. - self.block_flow.fragment.border_box.start.i = - self.block_flow.base.block_container_inline_size - - self.block_flow.fragment.margin.inline_end - - self.block_flow.fragment.border_box.size.inline; - } - } - - fn compute_used_inline_size( - &mut self, - shared_context: &SharedStyleContext, - parent_flow_inline_size: Au, - intermediate_column_inline_sizes: &[IntermediateColumnInlineSize], - ) { - let (border_padding, spacing) = self.border_padding_and_spacing(); - let minimum_width_of_all_columns = intermediate_column_inline_sizes.iter().fold( - border_padding + spacing, - |accumulator, intermediate_column_inline_sizes| { - accumulator + intermediate_column_inline_sizes.size - }, - ); - let preferred_width_of_all_columns = self.column_intrinsic_inline_sizes.iter().fold( - border_padding + spacing, - |accumulator, column_intrinsic_inline_sizes| { - accumulator + column_intrinsic_inline_sizes.preferred - }, - ); - - // Delegate to the appropriate inline size computer to find the constraint inputs and write - // the constraint solutions in. - if self.block_flow.base.flags.is_float() { - let inline_size_computer = FloatedTable { - minimum_width_of_all_columns: minimum_width_of_all_columns, - preferred_width_of_all_columns: preferred_width_of_all_columns, - table_border_padding: border_padding, - }; - let input = inline_size_computer.compute_inline_size_constraint_inputs( - &mut self.block_flow, - parent_flow_inline_size, - shared_context, - ); - - let solution = - inline_size_computer.solve_inline_size_constraints(&mut self.block_flow, &input); - inline_size_computer - .set_inline_size_constraint_solutions(&mut self.block_flow, solution); - inline_size_computer - .set_inline_position_of_flow_if_necessary(&mut self.block_flow, solution); - return; - } - - if !self - .block_flow - .base - .flags - .contains(FlowFlags::INLINE_POSITION_IS_STATIC) - { - let inline_size_computer = AbsoluteTable { - minimum_width_of_all_columns: minimum_width_of_all_columns, - preferred_width_of_all_columns: preferred_width_of_all_columns, - table_border_padding: border_padding, - }; - let input = inline_size_computer.compute_inline_size_constraint_inputs( - &mut self.block_flow, - parent_flow_inline_size, - shared_context, - ); - - let solution = - inline_size_computer.solve_inline_size_constraints(&mut self.block_flow, &input); - inline_size_computer - .set_inline_size_constraint_solutions(&mut self.block_flow, solution); - inline_size_computer - .set_inline_position_of_flow_if_necessary(&mut self.block_flow, solution); - return; - } - - let inline_size_computer = Table { - minimum_width_of_all_columns: minimum_width_of_all_columns, - preferred_width_of_all_columns: preferred_width_of_all_columns, - table_border_padding: border_padding, - }; - let input = inline_size_computer.compute_inline_size_constraint_inputs( - &mut self.block_flow, - parent_flow_inline_size, - shared_context, - ); - - let solution = - inline_size_computer.solve_inline_size_constraints(&mut self.block_flow, &input); - inline_size_computer.set_inline_size_constraint_solutions(&mut self.block_flow, solution); - inline_size_computer - .set_inline_position_of_flow_if_necessary(&mut self.block_flow, solution); - } -} - -impl Flow for TableWrapperFlow { - fn class(&self) -> FlowClass { - FlowClass::TableWrapper - } - - fn as_table_wrapper(&self) -> &TableWrapperFlow { - self - } - - fn as_mut_block(&mut self) -> &mut BlockFlow { - &mut self.block_flow - } - - fn as_block(&self) -> &BlockFlow { - &self.block_flow - } - - fn mark_as_root(&mut self) { - self.block_flow.mark_as_root(); - } - - fn bubble_inline_sizes(&mut self) { - // Get the intrinsic column inline-sizes info from the table flow. - for kid in self.block_flow.base.child_iter_mut() { - debug_assert!(kid.is_table_caption() || kid.is_table()); - if kid.is_table() { - let table = kid.as_table(); - self.column_intrinsic_inline_sizes = table.column_intrinsic_inline_sizes.clone(); - } - } - - self.block_flow.bubble_inline_sizes(); - } - - fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) { - debug!( - "assign_inline_sizes({}): assigning inline_size for flow", - if self.block_flow.base.flags.is_float() { - "floated table_wrapper" - } else { - "table_wrapper" - } - ); - - let shared_context = layout_context.shared_context(); - self.block_flow - .initialize_container_size_for_root(shared_context); - - let mut intermediate_column_inline_sizes = self - .column_intrinsic_inline_sizes - .iter() - .map( - |column_intrinsic_inline_size| IntermediateColumnInlineSize { - size: column_intrinsic_inline_size.minimum_length, - percentage: column_intrinsic_inline_size.percentage, - }, - ) - .collect::<Vec<_>>(); - - // Our inline-size was set to the inline-size of the containing block by the flow's parent. - // Now compute the real value. - let containing_block_inline_size = self.block_flow.base.block_container_inline_size; - if self.block_flow.base.flags.is_float() { - self.block_flow - .float - .as_mut() - .unwrap() - .containing_inline_size = containing_block_inline_size; - } - - // This has to be done before computing our inline size because `compute_used_inline_size` - // internally consults the border and padding of the table. - self.compute_border_and_padding_of_table(); - - self.compute_used_inline_size( - shared_context, - containing_block_inline_size, - &intermediate_column_inline_sizes, - ); - - match self.table_layout { - TableLayout::Auto => self.calculate_table_column_sizes_for_automatic_layout( - &mut intermediate_column_inline_sizes, - ), - TableLayout::Fixed => {}, - } - - let inline_start_content_edge = self.block_flow.fragment.border_box.start.i; - let content_inline_size = self.block_flow.fragment.border_box.size.inline; - let inline_end_content_edge = self.block_flow.fragment.border_padding.inline_end + - self.block_flow.fragment.margin.inline_end; - - // In case of fixed layout, column inline-sizes are calculated in table flow. - let assigned_column_inline_sizes = match self.table_layout { - TableLayout::Fixed => None, - TableLayout::Auto => Some( - intermediate_column_inline_sizes - .iter() - .map(|sizes| ColumnComputedInlineSize { size: sizes.size }) - .collect::<Vec<_>>(), - ), - }; - - match assigned_column_inline_sizes { - None => self.block_flow.propagate_assigned_inline_size_to_children( - shared_context, - inline_start_content_edge, - inline_end_content_edge, - content_inline_size, - |_, _, _, _, _, _| {}, - ), - Some(ref assigned_column_inline_sizes) => { - self.block_flow.propagate_assigned_inline_size_to_children( - shared_context, - inline_start_content_edge, - inline_end_content_edge, - content_inline_size, - |child_flow, _, _, _, _, _| { - if child_flow.class() == FlowClass::Table { - child_flow.as_mut_table().column_computed_inline_sizes = - assigned_column_inline_sizes.to_vec(); - } - }, - ) - }, - } - } - - fn assign_block_size(&mut self, layout_context: &LayoutContext) { - debug!("assign_block_size: assigning block_size for table_wrapper"); - let remaining = self.block_flow.assign_block_size_block_base( - layout_context, - None, - MarginsMayCollapseFlag::MarginsMayNotCollapse, - ); - debug_assert!(remaining.is_none()); - } - - fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) { - self.block_flow - .compute_stacking_relative_position(layout_context) - } - - fn place_float_if_applicable<'a>(&mut self) { - self.block_flow.place_float_if_applicable() - } - - fn assign_block_size_for_inorder_child_if_necessary( - &mut self, - layout_context: &LayoutContext, - parent_thread_id: u8, - content_box: LogicalRect<Au>, - ) -> bool { - self.block_flow - .assign_block_size_for_inorder_child_if_necessary( - layout_context, - parent_thread_id, - content_box, - ) - } - - fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) { - self.block_flow - .update_late_computed_inline_position_if_necessary(inline_position) - } - - fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) { - self.block_flow - .update_late_computed_block_position_if_necessary(block_position) - } - - fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> { - self.block_flow.generated_containing_block_size(flow) - } - - fn build_display_list(&mut self, state: &mut DisplayListBuildState) { - self.block_flow.build_display_list(state); - } - - fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) { - self.block_flow.collect_stacking_contexts_for_block( - state, - StackingContextCollectionFlags::POSITION_NEVER_CREATES_CONTAINING_BLOCK | - StackingContextCollectionFlags::NEVER_CREATES_CLIP_SCROLL_NODE, - ); - } - - fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>) { - self.block_flow.repair_style(new_style) - } - - fn compute_overflow(&self) -> Overflow { - self.block_flow.compute_overflow() - } - - fn iterate_through_fragment_border_boxes( - &self, - iterator: &mut dyn FragmentBorderBoxIterator, - level: i32, - stacking_context_position: &Point2D<Au>, - ) { - self.block_flow.iterate_through_fragment_border_boxes( - iterator, - level, - stacking_context_position, - ) - } - - fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) { - self.block_flow.mutate_fragments(mutator) - } - - fn print_extra_flow_children(&self, print_tree: &mut PrintTree) { - self.block_flow.print_extra_flow_children(print_tree); - } - - fn positioning(&self) -> position::T { - self.block_flow.positioning() - } -} - -impl fmt::Debug for TableWrapperFlow { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if self.block_flow.base.flags.is_float() { - write!(f, "TableWrapperFlow(Float): {:?}", self.block_flow) - } else { - write!(f, "TableWrapperFlow: {:?}", self.block_flow) - } - } -} - -/// The layout "guesses" defined in INTRINSIC § 4.3. -struct AutoLayoutCandidateGuess { - /// The column inline-size assignment where each column is assigned its intrinsic minimum - /// inline-size. - minimum_guess: Au, - - /// The column inline-size assignment where: - /// * A column with an intrinsic percentage inline-size greater than 0% is assigned the - /// larger of: - /// - Its intrinsic percentage inline-size times the assignable inline-size; - /// - Its intrinsic minimum inline-size; - /// * Other columns receive their intrinsic minimum inline-size. - minimum_percentage_guess: Au, - - /// The column inline-size assignment where: - /// * Each column with an intrinsic percentage inline-size greater than 0% is assigned the - /// larger of: - /// - Its intrinsic percentage inline-size times the assignable inline-size; - /// - Its intrinsic minimum inline-size; - /// * Any other column that is constrained is assigned its intrinsic preferred inline-size; - /// * Other columns are assigned their intrinsic minimum inline-size. - minimum_specified_guess: Au, - - /// The column inline-size assignment where: - /// * Each column with an intrinsic percentage inline-size greater than 0% is assigned the - /// larger of: - /// - Its intrinsic percentage inline-size times the assignable inline-size; - /// - Its intrinsic minimum inline-size; - /// * Other columns are assigned their intrinsic preferred inline-size. - preferred_guess: Au, -} - -impl AutoLayoutCandidateGuess { - /// Creates a guess with all elements initialized to zero. - fn new() -> AutoLayoutCandidateGuess { - AutoLayoutCandidateGuess { - minimum_guess: Au(0), - minimum_percentage_guess: Au(0), - minimum_specified_guess: Au(0), - preferred_guess: Au(0), - } - } - - /// Fills in the inline-size guesses for this column per INTRINSIC § 4.3. - fn from_column_intrinsic_inline_size( - column_intrinsic_inline_size: &ColumnIntrinsicInlineSize, - assignable_inline_size: Au, - ) -> AutoLayoutCandidateGuess { - let minimum_percentage_guess = max( - assignable_inline_size.scale_by(column_intrinsic_inline_size.percentage), - column_intrinsic_inline_size.minimum_length, - ); - AutoLayoutCandidateGuess { - minimum_guess: column_intrinsic_inline_size.minimum_length, - minimum_percentage_guess: minimum_percentage_guess, - // FIXME(pcwalton): We need the notion of *constrainedness* per INTRINSIC § 4 to - // implement this one correctly. - minimum_specified_guess: if column_intrinsic_inline_size.percentage > 0.0 { - minimum_percentage_guess - } else if column_intrinsic_inline_size.constrained { - column_intrinsic_inline_size.preferred - } else { - column_intrinsic_inline_size.minimum_length - }, - preferred_guess: if column_intrinsic_inline_size.percentage > 0.0 { - minimum_percentage_guess - } else { - column_intrinsic_inline_size.preferred - }, - } - } - - /// Calculates the inline-size, interpolating appropriately based on the value of `selection`. - /// - /// This does *not* distribute excess inline-size. That must be done later if necessary. - fn calculate(&self, selection: SelectedAutoLayoutCandidateGuess) -> Au { - match selection { - SelectedAutoLayoutCandidateGuess::UseMinimumGuess => self.minimum_guess, - SelectedAutoLayoutCandidateGuess:: - InterpolateBetweenMinimumGuessAndMinimumPercentageGuess(weight) => { - interp(self.minimum_guess, self.minimum_percentage_guess, weight) - } - SelectedAutoLayoutCandidateGuess:: - InterpolateBetweenMinimumPercentageGuessAndMinimumSpecifiedGuess(weight) => { - interp(self.minimum_percentage_guess, self.minimum_specified_guess, weight) - } - SelectedAutoLayoutCandidateGuess:: - InterpolateBetweenMinimumSpecifiedGuessAndPreferredGuess(weight) => { - interp(self.minimum_specified_guess, self.preferred_guess, weight) - } - SelectedAutoLayoutCandidateGuess::UsePreferredGuessAndDistributeExcessInlineSize => { - self.preferred_guess - } - } - } -} - -impl<'a> Add for &'a AutoLayoutCandidateGuess { - type Output = AutoLayoutCandidateGuess; - #[inline] - fn add(self, other: &AutoLayoutCandidateGuess) -> AutoLayoutCandidateGuess { - AutoLayoutCandidateGuess { - minimum_guess: self.minimum_guess + other.minimum_guess, - minimum_percentage_guess: self.minimum_percentage_guess + - other.minimum_percentage_guess, - minimum_specified_guess: self.minimum_specified_guess + other.minimum_specified_guess, - preferred_guess: self.preferred_guess + other.preferred_guess, - } - } -} - -/// The `CSSFloat` member specifies the weight of the smaller of the two guesses, on a scale from -/// 0.0 to 1.0. -#[derive(Clone, Copy, Debug, PartialEq)] -enum SelectedAutoLayoutCandidateGuess { - UseMinimumGuess, - InterpolateBetweenMinimumGuessAndMinimumPercentageGuess(CSSFloat), - InterpolateBetweenMinimumPercentageGuessAndMinimumSpecifiedGuess(CSSFloat), - InterpolateBetweenMinimumSpecifiedGuessAndPreferredGuess(CSSFloat), - UsePreferredGuessAndDistributeExcessInlineSize, -} - -impl SelectedAutoLayoutCandidateGuess { - /// See INTRINSIC § 4.3. - /// - /// FIXME(pcwalton, INTRINSIC spec): INTRINSIC doesn't specify whether these are exclusive or - /// inclusive ranges. - fn select( - guess: &AutoLayoutCandidateGuess, - assignable_inline_size: Au, - ) -> SelectedAutoLayoutCandidateGuess { - if assignable_inline_size < guess.minimum_guess { - SelectedAutoLayoutCandidateGuess::UseMinimumGuess - } else if assignable_inline_size < guess.minimum_percentage_guess { - let weight = weight( - guess.minimum_guess, - assignable_inline_size, - guess.minimum_percentage_guess, - ); - SelectedAutoLayoutCandidateGuess::InterpolateBetweenMinimumGuessAndMinimumPercentageGuess(weight) - } else if assignable_inline_size < guess.minimum_specified_guess { - let weight = weight( - guess.minimum_percentage_guess, - assignable_inline_size, - guess.minimum_specified_guess, - ); - SelectedAutoLayoutCandidateGuess::InterpolateBetweenMinimumPercentageGuessAndMinimumSpecifiedGuess(weight) - } else if assignable_inline_size < guess.preferred_guess { - let weight = weight( - guess.minimum_specified_guess, - assignable_inline_size, - guess.preferred_guess, - ); - SelectedAutoLayoutCandidateGuess::InterpolateBetweenMinimumSpecifiedGuessAndPreferredGuess(weight) - } else { - SelectedAutoLayoutCandidateGuess::UsePreferredGuessAndDistributeExcessInlineSize - } - } -} - -/// Computes the weight needed to linearly interpolate `middle` between two guesses `low` and -/// `high` as specified by INTRINSIC § 4.3. -fn weight(low: Au, middle: Au, high: Au) -> CSSFloat { - (middle - low).to_f32_px() / (high - low).to_f32_px() -} - -/// Linearly interpolates between two guesses, as specified by INTRINSIC § 4.3. -fn interp(low: Au, high: Au, weight: CSSFloat) -> Au { - low + (high - low).scale_by(weight) -} - -struct ExcessInlineSizeDistributionInfo { - preferred_inline_size_of_nonconstrained_columns_with_no_percentage: Au, - count_of_nonconstrained_columns_with_no_percentage: u32, - preferred_inline_size_of_constrained_columns_with_no_percentage: Au, - total_percentage: CSSFloat, - column_count: u32, -} - -impl ExcessInlineSizeDistributionInfo { - fn new() -> ExcessInlineSizeDistributionInfo { - ExcessInlineSizeDistributionInfo { - preferred_inline_size_of_nonconstrained_columns_with_no_percentage: Au(0), - count_of_nonconstrained_columns_with_no_percentage: 0, - preferred_inline_size_of_constrained_columns_with_no_percentage: Au(0), - total_percentage: 0.0, - column_count: 0, - } - } - - fn update(&mut self, column_intrinsic_inline_size: &ColumnIntrinsicInlineSize) { - if !column_intrinsic_inline_size.constrained && - column_intrinsic_inline_size.percentage == 0.0 - { - self.preferred_inline_size_of_nonconstrained_columns_with_no_percentage = self - .preferred_inline_size_of_nonconstrained_columns_with_no_percentage + - column_intrinsic_inline_size.preferred; - self.count_of_nonconstrained_columns_with_no_percentage += 1 - } - if column_intrinsic_inline_size.constrained && - column_intrinsic_inline_size.percentage == 0.0 - { - self.preferred_inline_size_of_constrained_columns_with_no_percentage = self - .preferred_inline_size_of_constrained_columns_with_no_percentage + - column_intrinsic_inline_size.preferred - } - self.total_percentage += column_intrinsic_inline_size.percentage; - self.column_count += 1 - } - - /// Based on the information here, distributes excess inline-size to the given column per - /// INTRINSIC § 4.4. - /// - /// `#[inline]` so the compiler will hoist out the branch, which is loop-invariant. - #[inline] - fn distribute_excess_inline_size_to_column( - &self, - intermediate_column_inline_size: &mut IntermediateColumnInlineSize, - column_intrinsic_inline_size: &ColumnIntrinsicInlineSize, - excess_inline_size: Au, - total_distributed_excess_size: &mut Au, - ) { - let proportion = - if self.preferred_inline_size_of_nonconstrained_columns_with_no_percentage > Au(0) { - // FIXME(spec, pcwalton): Gecko and WebKit do *something* here when there are - // nonconstrained columns with no percentage *and* no preferred width. What do they - // do? - if !column_intrinsic_inline_size.constrained && - column_intrinsic_inline_size.percentage == 0.0 - { - column_intrinsic_inline_size.preferred.to_f32_px() / - self.preferred_inline_size_of_nonconstrained_columns_with_no_percentage - .to_f32_px() - } else { - 0.0 - } - } else if self.count_of_nonconstrained_columns_with_no_percentage > 0 { - 1.0 / (self.count_of_nonconstrained_columns_with_no_percentage as CSSFloat) - } else if self.preferred_inline_size_of_constrained_columns_with_no_percentage > Au(0) { - column_intrinsic_inline_size.preferred.to_f32_px() / - self.preferred_inline_size_of_constrained_columns_with_no_percentage - .to_f32_px() - } else if self.total_percentage > 0.0 { - column_intrinsic_inline_size.percentage / self.total_percentage - } else { - 1.0 / (self.column_count as CSSFloat) - }; - - // The `min` here has the effect of throwing away fractional excess at the end of the - // table. - let amount_to_distribute = min( - excess_inline_size.scale_by(proportion), - excess_inline_size - *total_distributed_excess_size, - ); - *total_distributed_excess_size = *total_distributed_excess_size + amount_to_distribute; - intermediate_column_inline_size.size = - intermediate_column_inline_size.size + amount_to_distribute - } -} - -/// An intermediate column size assignment. -struct IntermediateColumnInlineSize { - size: Au, - percentage: f32, -} - -/// Returns the computed inline size of the table wrapper represented by `block`. -/// -/// `table_border_padding` is the sum of the sizes of all border and padding in the inline -/// direction of the table contained within this table wrapper. -fn initial_computed_inline_size( - block: &mut BlockFlow, - containing_block_inline_size: Au, - minimum_width_of_all_columns: Au, - preferred_width_of_all_columns: Au, - table_border_padding: Au, -) -> MaybeAuto { - match block.fragment.style.content_inline_size() { - Size::Auto => { - if preferred_width_of_all_columns + table_border_padding <= containing_block_inline_size - { - MaybeAuto::Specified(preferred_width_of_all_columns + table_border_padding) - } else if minimum_width_of_all_columns > containing_block_inline_size { - MaybeAuto::Specified(minimum_width_of_all_columns) - } else { - MaybeAuto::Auto - } - }, - Size::LengthPercentage(ref lp) => { - let used = lp.to_used_value(containing_block_inline_size); - MaybeAuto::Specified(max( - used - table_border_padding, - minimum_width_of_all_columns, - )) - }, - } -} - -struct Table { - minimum_width_of_all_columns: Au, - preferred_width_of_all_columns: Au, - table_border_padding: Au, -} - -impl ISizeAndMarginsComputer for Table { - fn compute_border_and_padding(&self, block: &mut BlockFlow, containing_block_inline_size: Au) { - block - .fragment - .compute_border_and_padding(containing_block_inline_size) - } - - fn initial_computed_inline_size( - &self, - block: &mut BlockFlow, - parent_flow_inline_size: Au, - shared_context: &SharedStyleContext, - ) -> MaybeAuto { - let containing_block_inline_size = - self.containing_block_inline_size(block, parent_flow_inline_size, shared_context); - initial_computed_inline_size( - block, - containing_block_inline_size, - self.minimum_width_of_all_columns, - self.preferred_width_of_all_columns, - self.table_border_padding, - ) - } - - fn solve_inline_size_constraints( - &self, - block: &mut BlockFlow, - input: &ISizeConstraintInput, - ) -> ISizeConstraintSolution { - self.solve_block_inline_size_constraints(block, input) - } -} - -struct FloatedTable { - minimum_width_of_all_columns: Au, - preferred_width_of_all_columns: Au, - table_border_padding: Au, -} - -impl ISizeAndMarginsComputer for FloatedTable { - fn compute_border_and_padding(&self, block: &mut BlockFlow, containing_block_inline_size: Au) { - block - .fragment - .compute_border_and_padding(containing_block_inline_size) - } - - fn initial_computed_inline_size( - &self, - block: &mut BlockFlow, - parent_flow_inline_size: Au, - shared_context: &SharedStyleContext, - ) -> MaybeAuto { - let containing_block_inline_size = - self.containing_block_inline_size(block, parent_flow_inline_size, shared_context); - initial_computed_inline_size( - block, - containing_block_inline_size, - self.minimum_width_of_all_columns, - self.preferred_width_of_all_columns, - self.table_border_padding, - ) - } - - fn solve_inline_size_constraints( - &self, - block: &mut BlockFlow, - input: &ISizeConstraintInput, - ) -> ISizeConstraintSolution { - FloatNonReplaced.solve_inline_size_constraints(block, input) - } -} - -struct AbsoluteTable { - minimum_width_of_all_columns: Au, - preferred_width_of_all_columns: Au, - table_border_padding: Au, -} - -impl ISizeAndMarginsComputer for AbsoluteTable { - fn compute_border_and_padding(&self, block: &mut BlockFlow, containing_block_inline_size: Au) { - block - .fragment - .compute_border_and_padding(containing_block_inline_size) - } - - fn initial_computed_inline_size( - &self, - block: &mut BlockFlow, - parent_flow_inline_size: Au, - shared_context: &SharedStyleContext, - ) -> MaybeAuto { - let containing_block_inline_size = - self.containing_block_inline_size(block, parent_flow_inline_size, shared_context); - initial_computed_inline_size( - block, - containing_block_inline_size, - self.minimum_width_of_all_columns, - self.preferred_width_of_all_columns, - self.table_border_padding, - ) - } - - fn containing_block_inline_size( - &self, - block: &mut BlockFlow, - parent_flow_inline_size: Au, - shared_context: &SharedStyleContext, - ) -> Au { - AbsoluteNonReplaced.containing_block_inline_size( - block, - parent_flow_inline_size, - shared_context, - ) - } - - fn solve_inline_size_constraints( - &self, - block: &mut BlockFlow, - input: &ISizeConstraintInput, - ) -> ISizeConstraintSolution { - AbsoluteNonReplaced.solve_inline_size_constraints(block, input) - } - - fn set_inline_position_of_flow_if_necessary( - &self, - block: &mut BlockFlow, - solution: ISizeConstraintSolution, - ) { - AbsoluteNonReplaced.set_inline_position_of_flow_if_necessary(block, solution); - } -} diff --git a/components/layout_2020/tests/size_of.rs b/components/layout_2020/tests/size_of.rs deleted file mode 100644 index e2cec625075..00000000000 --- a/components/layout_2020/tests/size_of.rs +++ /dev/null @@ -1,18 +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/. */ - -#![cfg(target_pointer_width = "64")] - -#[macro_use] -extern crate size_of_test; - -use layout::Fragment; -use layout::SpecificFragmentInfo; - -size_of_test!(test_size_of_fragment, Fragment, 176); -size_of_test!( - test_size_of_specific_fragment_info, - SpecificFragmentInfo, - 24 -); diff --git a/components/layout_2020/text.rs b/components/layout_2020/text.rs deleted file mode 100644 index 199bd7e07f2..00000000000 --- a/components/layout_2020/text.rs +++ /dev/null @@ -1,798 +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/. */ - -//! Text layout. - -use crate::context::LayoutFontContext; -use crate::fragment::{Fragment, ScannedTextFlags}; -use crate::fragment::{ScannedTextFragmentInfo, SpecificFragmentInfo, UnscannedTextFragmentInfo}; -use crate::inline::{InlineFragmentNodeFlags, InlineFragments}; -use crate::linked_list::split_off_head; -use app_units::Au; -use gfx::font::{FontMetrics, FontRef, RunMetrics, ShapingFlags, ShapingOptions}; -use gfx::text::glyph::ByteIndex; -use gfx::text::text_run::TextRun; -use gfx::text::util::{self, CompressionMode}; -use range::Range; -use servo_atoms::Atom; -use std::borrow::ToOwned; -use std::collections::LinkedList; -use std::mem; -use std::sync::Arc; -use style::computed_values::text_rendering::T as TextRendering; -use style::computed_values::white_space::T as WhiteSpace; -use style::computed_values::word_break::T as WordBreak; -use style::logical_geometry::{LogicalSize, WritingMode}; -use style::properties::style_structs::Font as FontStyleStruct; -use style::properties::ComputedValues; -use style::values::generics::text::LineHeight; -use style::values::specified::text::{TextTransform, TextTransformCase}; -use unicode_bidi as bidi; -use unicode_script::{get_script, Script}; -use xi_unicode::LineBreakLeafIter; - -/// Returns the concatenated text of a list of unscanned text fragments. -fn text(fragments: &LinkedList<Fragment>) -> String { - // FIXME: Some of this work is later duplicated in split_first_fragment_at_newline_if_necessary - // and transform_text. This code should be refactored so that the all the scanning for - // newlines is done in a single pass. - - let mut text = String::new(); - - for fragment in fragments { - if let SpecificFragmentInfo::UnscannedText(ref info) = fragment.specific { - if fragment.white_space().preserve_newlines() { - text.push_str(&info.text); - } else { - text.push_str(&info.text.replace("\n", " ")); - } - } - } - text -} - -/// A stack-allocated object for scanning an inline flow into `TextRun`-containing `TextFragment`s. -pub struct TextRunScanner { - pub clump: LinkedList<Fragment>, -} - -impl TextRunScanner { - pub fn new() -> TextRunScanner { - TextRunScanner { - clump: LinkedList::new(), - } - } - - pub fn scan_for_runs( - &mut self, - font_context: &mut LayoutFontContext, - mut fragments: LinkedList<Fragment>, - ) -> InlineFragments { - debug!( - "TextRunScanner: scanning {} fragments for text runs...", - fragments.len() - ); - debug_assert!(!fragments.is_empty()); - - // Calculate bidi embedding levels, so we can split bidirectional fragments for reordering. - let text = text(&fragments); - let para_level = fragments - .front() - .unwrap() - .style - .writing_mode - .to_bidi_level(); - let bidi_info = bidi::BidiInfo::new(&text, Some(para_level)); - - // Optimization: If all the text is LTR, don't bother splitting on bidi levels. - let bidi_levels = if bidi_info.has_rtl() { - Some(&bidi_info.levels[..]) - } else { - None - }; - - // FIXME(pcwalton): We want to be sure not to allocate multiple times, since this is a - // performance-critical spot, but this may overestimate and allocate too much memory. - let mut new_fragments = Vec::with_capacity(fragments.len()); - let mut last_whitespace = false; - let mut paragraph_bytes_processed = 0; - - // The first time we process a text run we will set this - // linebreaker. There is no way for the linebreaker to start - // with an empty state; you must give it its first input immediately. - // - // This linebreaker is shared across text runs, so we can know if - // there is a break at the beginning of a text run or clump, e.g. - // in the case of FooBar<span>Baz</span> - let mut linebreaker = None; - - while !fragments.is_empty() { - // Create a clump. - split_first_fragment_at_newline_if_necessary(&mut fragments); - self.clump.append(&mut split_off_head(&mut fragments)); - while !fragments.is_empty() && - self.clump - .back() - .unwrap() - .can_merge_with_fragment(fragments.front().unwrap()) - { - split_first_fragment_at_newline_if_necessary(&mut fragments); - self.clump.append(&mut split_off_head(&mut fragments)); - } - - // Flush that clump to the list of fragments we're building up. - last_whitespace = self.flush_clump_to_list( - font_context, - &mut new_fragments, - &mut paragraph_bytes_processed, - bidi_levels, - last_whitespace, - &mut linebreaker, - ); - } - - debug!("TextRunScanner: complete."); - InlineFragments { - fragments: new_fragments, - } - } - - /// A "clump" is a range of inline flow leaves that can be merged together into a single - /// fragment. Adjacent text with the same style can be merged, and nothing else can. - /// - /// The flow keeps track of the fragments contained by all non-leaf DOM nodes. This is necessary - /// for correct painting order. Since we compress several leaf fragments here, the mapping must - /// be adjusted. - fn flush_clump_to_list( - &mut self, - mut font_context: &mut LayoutFontContext, - out_fragments: &mut Vec<Fragment>, - paragraph_bytes_processed: &mut usize, - bidi_levels: Option<&[bidi::Level]>, - mut last_whitespace: bool, - linebreaker: &mut Option<LineBreakLeafIter>, - ) -> bool { - debug!( - "TextRunScanner: flushing {} fragments in range", - self.clump.len() - ); - - debug_assert!(!self.clump.is_empty()); - match self.clump.front().unwrap().specific { - SpecificFragmentInfo::UnscannedText(_) => {}, - _ => { - debug_assert!( - self.clump.len() == 1, - "WAT: can't coalesce non-text nodes in flush_clump_to_list()!" - ); - out_fragments.push(self.clump.pop_front().unwrap()); - return false; - }, - } - - // Concatenate all of the transformed strings together, saving the new character indices. - let mut mappings: Vec<RunMapping> = Vec::new(); - let runs = { - let font_group; - let compression; - let text_transform; - let letter_spacing; - let word_spacing; - let text_rendering; - let word_break; - { - let in_fragment = self.clump.front().unwrap(); - let font_style = in_fragment.style().clone_font(); - let inherited_text_style = in_fragment.style().get_inherited_text(); - font_group = font_context.font_group(font_style); - compression = match in_fragment.white_space() { - WhiteSpace::Normal | WhiteSpace::Nowrap => { - CompressionMode::CompressWhitespaceNewline - }, - WhiteSpace::Pre | WhiteSpace::PreWrap => CompressionMode::CompressNone, - WhiteSpace::PreLine => CompressionMode::CompressWhitespace, - }; - text_transform = inherited_text_style.text_transform; - letter_spacing = inherited_text_style.letter_spacing; - word_spacing = inherited_text_style.word_spacing.to_hash_key(); - text_rendering = inherited_text_style.text_rendering; - word_break = inherited_text_style.word_break; - } - - // First, transform/compress text of all the nodes. - let (mut run_info_list, mut run_info) = (Vec::new(), RunInfo::new()); - let mut insertion_point = None; - - for (fragment_index, in_fragment) in self.clump.iter().enumerate() { - debug!(" flushing {:?}", in_fragment); - let mut mapping = RunMapping::new(&run_info_list[..], fragment_index); - let text; - let selection; - match in_fragment.specific { - SpecificFragmentInfo::UnscannedText(ref text_fragment_info) => { - text = &text_fragment_info.text; - selection = text_fragment_info.selection; - }, - _ => panic!("Expected an unscanned text fragment!"), - }; - insertion_point = match selection { - Some(range) if range.is_empty() => { - // `range` is the range within the current fragment. To get the range - // within the text run, offset it by the length of the preceding fragments. - Some(range.begin() + ByteIndex(run_info.text.len() as isize)) - }, - _ => None, - }; - - let (mut start_position, mut end_position) = (0, 0); - for (byte_index, character) in text.char_indices() { - if !character.is_control() { - let font = font_group - .borrow_mut() - .find_by_codepoint(&mut font_context, character); - - let bidi_level = match bidi_levels { - Some(levels) => levels[*paragraph_bytes_processed], - None => bidi::Level::ltr(), - }; - - // Break the run if the new character has a different explicit script than the - // previous characters. - // - // TODO: Special handling of paired punctuation characters. - // http://www.unicode.org/reports/tr24/#Common - let script = get_script(character); - let compatible_script = is_compatible(script, run_info.script); - if compatible_script && !is_specific(run_info.script) && is_specific(script) - { - run_info.script = script; - } - - let selected = match selection { - Some(range) => range.contains(ByteIndex(byte_index as isize)), - None => false, - }; - - // Now, if necessary, flush the mapping we were building up. - let flush_run = !run_info.has_font(&font) || - run_info.bidi_level != bidi_level || - !compatible_script; - let new_mapping_needed = flush_run || mapping.selected != selected; - - if new_mapping_needed { - // We ignore empty mappings at the very start of a fragment. - // The run info values are uninitialized at this point so - // flushing an empty mapping is pointless. - if end_position > 0 { - mapping.flush( - &mut mappings, - &mut run_info, - &**text, - compression, - text_transform, - &mut last_whitespace, - &mut start_position, - end_position, - ); - } - if run_info.text.len() > 0 { - if flush_run { - run_info.flush(&mut run_info_list, &mut insertion_point); - run_info = RunInfo::new(); - } - mapping = RunMapping::new(&run_info_list[..], fragment_index); - } - run_info.font = font; - run_info.bidi_level = bidi_level; - run_info.script = script; - mapping.selected = selected; - } - } - - // Consume this character. - end_position += character.len_utf8(); - *paragraph_bytes_processed += character.len_utf8(); - } - - // Flush the last mapping we created for this fragment to the list. - mapping.flush( - &mut mappings, - &mut run_info, - &**text, - compression, - text_transform, - &mut last_whitespace, - &mut start_position, - end_position, - ); - } - - // Push the final run info. - run_info.flush(&mut run_info_list, &mut insertion_point); - - // Per CSS 2.1 § 16.4, "when the resultant space between two characters is not the same - // as the default space, user agents should not use ligatures." This ensures that, for - // example, `finally` with a wide `letter-spacing` renders as `f i n a l l y` and not - // `fi n a l l y`. - let mut flags = ShapingFlags::empty(); - if letter_spacing.0.px() != 0. { - flags.insert(ShapingFlags::IGNORE_LIGATURES_SHAPING_FLAG); - } - if text_rendering == TextRendering::Optimizespeed { - flags.insert(ShapingFlags::IGNORE_LIGATURES_SHAPING_FLAG); - flags.insert(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG) - } - if word_break == WordBreak::KeepAll { - flags.insert(ShapingFlags::KEEP_ALL_FLAG); - } - let options = ShapingOptions { - letter_spacing: if letter_spacing.0.px() == 0. { - None - } else { - Some(Au::from(letter_spacing.0)) - }, - word_spacing, - script: Script::Common, - flags: flags, - }; - - let mut result = Vec::with_capacity(run_info_list.len()); - for run_info in run_info_list { - let mut options = options; - options.script = run_info.script; - if run_info.bidi_level.is_rtl() { - options.flags.insert(ShapingFlags::RTL_FLAG); - } - - // If no font is found (including fallbacks), there's no way we can render. - let font = run_info - .font - .or_else(|| font_group.borrow_mut().first(&mut font_context)) - .expect("No font found for text run!"); - - let (run, break_at_zero) = TextRun::new( - &mut *font.borrow_mut(), - run_info.text, - &options, - run_info.bidi_level, - linebreaker, - ); - result.push(( - ScannedTextRun { - run: Arc::new(run), - insertion_point: run_info.insertion_point, - }, - break_at_zero, - )) - } - result - }; - - // Make new fragments with the runs and adjusted text indices. - debug!("TextRunScanner: pushing {} fragment(s)", self.clump.len()); - let mut mappings = mappings.into_iter().peekable(); - let mut prev_fragments_to_meld = Vec::new(); - - for (logical_offset, old_fragment) in mem::replace(&mut self.clump, LinkedList::new()) - .into_iter() - .enumerate() - { - let mut is_first_mapping_of_this_old_fragment = true; - loop { - match mappings.peek() { - Some(mapping) if mapping.old_fragment_index == logical_offset => {}, - Some(_) | None => { - if is_first_mapping_of_this_old_fragment { - // There were no mappings for this unscanned fragment. Transfer its - // flags to the previous/next sibling elements instead. - if let Some(ref mut last_fragment) = out_fragments.last_mut() { - last_fragment.meld_with_next_inline_fragment(&old_fragment); - } - prev_fragments_to_meld.push(old_fragment); - } - break; - }, - }; - let mapping = mappings.next().unwrap(); - let (scanned_run, break_at_zero) = runs[mapping.text_run_index].clone(); - - let mut byte_range = Range::new( - ByteIndex(mapping.byte_range.begin() as isize), - ByteIndex(mapping.byte_range.length() as isize), - ); - - let mut flags = ScannedTextFlags::empty(); - if !break_at_zero && mapping.byte_range.begin() == 0 { - // If this is the first segment of the text run, - // and the text run doesn't break at zero, suppress line breaks - flags.insert(ScannedTextFlags::SUPPRESS_LINE_BREAK_BEFORE) - } - let text_size = old_fragment.border_box.size; - - let requires_line_break_afterward_if_wrapping_on_newlines = scanned_run.run.text - [mapping.byte_range.begin()..mapping.byte_range.end()] - .ends_with('\n'); - - if requires_line_break_afterward_if_wrapping_on_newlines { - byte_range.extend_by(ByteIndex(-1)); // Trim the '\n' - flags.insert( - ScannedTextFlags::REQUIRES_LINE_BREAK_AFTERWARD_IF_WRAPPING_ON_NEWLINES, - ); - } - - if mapping.selected { - flags.insert(ScannedTextFlags::SELECTED); - } - - let insertion_point = - if mapping.contains_insertion_point(scanned_run.insertion_point) { - scanned_run.insertion_point - } else { - None - }; - - let mut new_text_fragment_info = Box::new(ScannedTextFragmentInfo::new( - scanned_run.run, - byte_range, - text_size, - insertion_point, - flags, - )); - - let new_metrics = new_text_fragment_info.run.metrics_for_range(&byte_range); - let writing_mode = old_fragment.style.writing_mode; - let bounding_box_size = bounding_box_for_run_metrics(&new_metrics, writing_mode); - new_text_fragment_info.content_size = bounding_box_size; - - let mut new_fragment = old_fragment.transform( - bounding_box_size, - SpecificFragmentInfo::ScannedText(new_text_fragment_info), - ); - - let is_last_mapping_of_this_old_fragment = match mappings.peek() { - Some(mapping) if mapping.old_fragment_index == logical_offset => false, - _ => true, - }; - - if let Some(ref mut context) = new_fragment.inline_context { - for node in &mut context.nodes { - if !is_last_mapping_of_this_old_fragment { - node.flags - .remove(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT); - } - if !is_first_mapping_of_this_old_fragment { - node.flags - .remove(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT); - } - } - } - - for prev_fragment in prev_fragments_to_meld.drain(..) { - new_fragment.meld_with_prev_inline_fragment(&prev_fragment); - } - - is_first_mapping_of_this_old_fragment = false; - out_fragments.push(new_fragment) - } - } - - last_whitespace - } -} - -#[inline] -fn bounding_box_for_run_metrics( - metrics: &RunMetrics, - writing_mode: WritingMode, -) -> LogicalSize<Au> { - // TODO: When the text-orientation property is supported, the block and inline directions may - // be swapped for horizontal glyphs in vertical lines. - LogicalSize::new( - writing_mode, - metrics.bounding_box.size.width, - metrics.bounding_box.size.height, - ) -} - -/// Returns the metrics of the font represented by the given `FontStyleStruct`. -/// -/// `#[inline]` because often the caller only needs a few fields from the font metrics. -/// -/// # Panics -/// -/// Panics if no font can be found for the given font style. -#[inline] -pub fn font_metrics_for_style( - mut font_context: &mut LayoutFontContext, - style: crate::ServoArc<FontStyleStruct>, -) -> FontMetrics { - let font_group = font_context.font_group(style); - let font = font_group.borrow_mut().first(&mut font_context); - let font = font.as_ref().unwrap().borrow(); - - font.metrics.clone() -} - -/// Returns the line block-size needed by the given computed style and font size. -pub fn line_height_from_style(style: &ComputedValues, metrics: &FontMetrics) -> Au { - let font_size = style.get_font().font_size.size(); - match style.get_inherited_text().line_height { - LineHeight::Normal => Au::from(metrics.line_gap), - LineHeight::Number(l) => font_size.scale_by(l.0), - LineHeight::Length(l) => Au::from(l), - } -} - -fn split_first_fragment_at_newline_if_necessary(fragments: &mut LinkedList<Fragment>) { - if fragments.is_empty() { - return; - } - - let new_fragment = { - let first_fragment = fragments.front_mut().unwrap(); - let string_before; - let selection_before; - { - if !first_fragment.white_space().preserve_newlines() { - return; - } - - let unscanned_text_fragment_info = match first_fragment.specific { - SpecificFragmentInfo::UnscannedText(ref mut unscanned_text_fragment_info) => { - unscanned_text_fragment_info - }, - _ => return, - }; - - let position = match unscanned_text_fragment_info.text.find('\n') { - Some(position) if position < unscanned_text_fragment_info.text.len() - 1 => { - position - }, - Some(_) | None => return, - }; - - string_before = unscanned_text_fragment_info.text[..(position + 1)].to_owned(); - unscanned_text_fragment_info.text = unscanned_text_fragment_info.text[(position + 1)..] - .to_owned() - .into_boxed_str(); - let offset = ByteIndex(string_before.len() as isize); - match unscanned_text_fragment_info.selection { - Some(ref mut selection) if selection.begin() >= offset => { - // Selection is entirely in the second fragment. - selection_before = None; - selection.shift_by(-offset); - }, - Some(ref mut selection) if selection.end() > offset => { - // Selection is split across two fragments. - selection_before = Some(Range::new(selection.begin(), offset)); - *selection = Range::new(ByteIndex(0), selection.end() - offset); - }, - _ => { - // Selection is entirely in the first fragment. - selection_before = unscanned_text_fragment_info.selection; - unscanned_text_fragment_info.selection = None; - }, - }; - } - first_fragment.transform( - first_fragment.border_box.size, - SpecificFragmentInfo::UnscannedText(Box::new(UnscannedTextFragmentInfo::new( - string_before.into_boxed_str(), - selection_before, - ))), - ) - }; - - fragments.push_front(new_fragment); -} - -/// Information about a text run that we're about to create. This is used in `scan_for_runs`. -struct RunInfo { - /// The text that will go in this text run. - text: String, - /// The insertion point in this text run, if applicable. - insertion_point: Option<ByteIndex>, - /// The font that the text should be rendered with. - font: Option<FontRef>, - /// The bidirection embedding level of this text run. - bidi_level: bidi::Level, - /// The Unicode script property of this text run. - script: Script, -} - -impl RunInfo { - fn new() -> RunInfo { - RunInfo { - text: String::new(), - insertion_point: None, - font: None, - bidi_level: bidi::Level::ltr(), - script: Script::Common, - } - } - - /// Finish processing this RunInfo and add it to the "done" list. - /// - /// * `insertion_point`: The position of the insertion point, in characters relative to the start - /// of this text run. - fn flush(mut self, list: &mut Vec<RunInfo>, insertion_point: &mut Option<ByteIndex>) { - if let Some(idx) = *insertion_point { - let char_len = ByteIndex(self.text.len() as isize); - if idx <= char_len { - // The insertion point is in this text run. - self.insertion_point = insertion_point.take() - } else { - // Continue looking for the insertion point in the next text run. - *insertion_point = Some(idx - char_len) - } - } - list.push(self); - } - - fn has_font(&self, font: &Option<FontRef>) -> bool { - fn identifier(font: &Option<FontRef>) -> Option<Atom> { - font.as_ref().map(|f| f.borrow().identifier()) - } - - identifier(&self.font) == identifier(font) - } -} - -/// A mapping from a portion of an unscanned text fragment to the text run we're going to create -/// for it. -#[derive(Clone, Copy, Debug)] -struct RunMapping { - /// The range of byte indices within the text fragment. - byte_range: Range<usize>, - /// The index of the unscanned text fragment that this mapping corresponds to. - old_fragment_index: usize, - /// The index of the text run we're going to create. - text_run_index: usize, - /// Is the text in this fragment selected? - selected: bool, -} - -impl RunMapping { - /// Given the current set of text runs, creates a run mapping for the next fragment. - /// `run_info_list` describes the set of runs we've seen already. - fn new(run_info_list: &[RunInfo], fragment_index: usize) -> RunMapping { - RunMapping { - byte_range: Range::new(0, 0), - old_fragment_index: fragment_index, - text_run_index: run_info_list.len(), - selected: false, - } - } - - /// Flushes this run mapping to the list. `run_info` describes the text run that we're - /// currently working on. `text` refers to the text of this fragment. - fn flush( - mut self, - mappings: &mut Vec<RunMapping>, - run_info: &mut RunInfo, - text: &str, - compression: CompressionMode, - text_transform: TextTransform, - last_whitespace: &mut bool, - start_position: &mut usize, - end_position: usize, - ) { - let was_empty = *start_position == end_position; - let old_byte_length = run_info.text.len(); - *last_whitespace = util::transform_text( - &text[(*start_position)..end_position], - compression, - *last_whitespace, - &mut run_info.text, - ); - - // Account for `text-transform`. (Confusingly, this is not handled in "text - // transformation" above, but we follow Gecko in the naming.) - let is_first_run = *start_position == 0; - apply_style_transform_if_necessary( - &mut run_info.text, - old_byte_length, - text_transform, - *last_whitespace, - is_first_run, - ); - *start_position = end_position; - - let new_byte_length = run_info.text.len(); - let is_empty = new_byte_length == old_byte_length; - - // Don't save mappings that contain only discarded characters. - // (But keep ones that contained no characters to begin with, since they might have been - // generated by an empty flow to draw its borders/padding/insertion point.) - if is_empty && !was_empty { - return; - } - - self.byte_range = Range::new(old_byte_length, new_byte_length - old_byte_length); - mappings.push(self) - } - - /// Is the insertion point for this text run within this mapping? - /// - /// NOTE: We treat the range as inclusive at both ends, since the insertion point can lie - /// before the first character *or* after the last character, and should be drawn even if the - /// text is empty. - fn contains_insertion_point(&self, insertion_point: Option<ByteIndex>) -> bool { - match insertion_point.map(ByteIndex::to_usize) { - None => false, - Some(idx) => self.byte_range.begin() <= idx && idx <= self.byte_range.end(), - } - } -} - -/// Accounts for `text-transform`. -/// -/// FIXME(#4311, pcwalton): Title-case mapping can change length of the string; -/// case mapping should be language-specific; `full-width`; -/// use graphemes instead of characters. -fn apply_style_transform_if_necessary( - string: &mut String, - first_character_position: usize, - text_transform: TextTransform, - last_whitespace: bool, - is_first_run: bool, -) { - match text_transform.case_ { - TextTransformCase::None => {}, - TextTransformCase::Uppercase => { - let original = string[first_character_position..].to_owned(); - string.truncate(first_character_position); - for ch in original.chars().flat_map(|ch| ch.to_uppercase()) { - string.push(ch); - } - }, - TextTransformCase::Lowercase => { - let original = string[first_character_position..].to_owned(); - string.truncate(first_character_position); - for ch in original.chars().flat_map(|ch| ch.to_lowercase()) { - string.push(ch); - } - }, - TextTransformCase::Capitalize => { - let original = string[first_character_position..].to_owned(); - string.truncate(first_character_position); - - let mut capitalize_next_letter = is_first_run || last_whitespace; - for character in original.chars() { - // FIXME(#4311, pcwalton): Should be the CSS/Unicode notion of a *typographic - // letter unit*, not an *alphabetic* character: - // - // http://dev.w3.org/csswg/css-text/#typographic-letter-unit - if capitalize_next_letter && character.is_alphabetic() { - string.push(character.to_uppercase().next().unwrap()); - capitalize_next_letter = false; - continue; - } - - string.push(character); - - // FIXME(#4311, pcwalton): Try UAX29 instead of just whitespace. - if character.is_whitespace() { - capitalize_next_letter = true - } - } - }, - } -} - -#[derive(Clone)] -struct ScannedTextRun { - run: Arc<TextRun>, - insertion_point: Option<ByteIndex>, -} - -/// Can a character with script `b` continue a text run with script `a`? -fn is_compatible(a: Script, b: Script) -> bool { - a == b || !is_specific(a) || !is_specific(b) -} - -/// Returns true if the script is not invalid or inherited. -fn is_specific(script: Script) -> bool { - script != Script::Common && script != Script::Inherited -} diff --git a/components/layout_2020/traversal.rs b/components/layout_2020/traversal.rs index e968c184341..5aefdc6e939 100644 --- a/components/layout_2020/traversal.rs +++ b/components/layout_2020/traversal.rs @@ -2,21 +2,12 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -//! Traversals over the DOM and flow trees, running the layout computations. - -use crate::construct::FlowConstructor; use crate::context::LayoutContext; -use crate::display_list::DisplayListBuildState; -use crate::flow::{Flow, FlowFlags, GetBaseFlow, ImmutableFlowUtils}; -use crate::wrapper::ThreadSafeLayoutNodeHelpers; -use crate::wrapper::{GetRawData, LayoutNodeLayoutData}; -use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutNode}; -use servo_config::opts; +use crate::wrapper::GetRawData; +use script_layout_interface::wrapper_traits::LayoutNode; use style::context::{SharedStyleContext, StyleContext}; use style::data::ElementData; use style::dom::{NodeInfo, TElement, TNode}; -use style::selector_parser::RestyleDamage; -use style::servo::restyle_damage::ServoRestyleDamage; use style::traversal::PerLevelTraversalData; use style::traversal::{recalc_style_at, DomTraversal}; @@ -25,13 +16,10 @@ pub struct RecalcStyleAndConstructFlows<'a> { } impl<'a> RecalcStyleAndConstructFlows<'a> { - /// Creates a traversal context, taking ownership of the shared layout context. pub fn new(context: LayoutContext<'a>) -> Self { RecalcStyleAndConstructFlows { context: context } } - /// Consumes this traversal context, returning ownership of the shared layout - /// context to the caller. pub fn destroy(self) -> LayoutContext<'a> { self.context } @@ -53,8 +41,6 @@ where ) where F: FnMut(E::ConcreteNode), { - // FIXME(pcwalton): Stop allocating here. Ideally this should just be - // done by the HTML parser. unsafe { node.initialize_data() }; if !node.is_text_node() { @@ -65,14 +51,14 @@ where } fn process_postorder(&self, _style_context: &mut StyleContext<E>, node: E::ConcreteNode) { - construct_flows_at(&self.context, node); + if let Some(el) = node.as_element() { + unsafe { + el.unset_dirty_descendants(); + } + } } fn text_node_needs_traversal(node: E::ConcreteNode, parent_data: &ElementData) -> bool { - // Text nodes never need styling. However, there are two cases they may need - // flow construction: - // (1) They child doesn't yet have layout data (preorder traversal initializes it). - // (2) The parent element has restyle damage (so the text flow also needs fixup). node.get_raw_data().is_none() || !parent_data.damage.is_empty() } @@ -80,271 +66,3 @@ where &self.context.style_context } } - -/// A top-down traversal. -pub trait PreorderFlowTraversal { - /// The operation to perform. Return true to continue or false to stop. - fn process(&self, flow: &mut dyn Flow); - - /// Returns true if this node should be processed and false if neither this node nor its - /// descendants should be processed. - fn should_process_subtree(&self, _flow: &mut dyn Flow) -> bool { - true - } - - /// Returns true if this node must be processed in-order. If this returns false, - /// we skip the operation for this node, but continue processing the descendants. - /// This is called *after* parent nodes are visited. - fn should_process(&self, _flow: &mut dyn Flow) -> bool { - true - } - - /// Traverses the tree in preorder. - fn traverse(&self, flow: &mut dyn Flow) { - if !self.should_process_subtree(flow) { - return; - } - if self.should_process(flow) { - self.process(flow); - } - for kid in flow.mut_base().child_iter_mut() { - self.traverse(kid); - } - } - - /// Traverse the Absolute flow tree in preorder. - /// - /// Traverse all your direct absolute descendants, who will then traverse - /// their direct absolute descendants. - /// - /// Return true if the traversal is to continue or false to stop. - fn traverse_absolute_flows(&self, flow: &mut dyn Flow) { - if self.should_process(flow) { - self.process(flow); - } - for descendant_link in flow.mut_base().abs_descendants.iter() { - self.traverse_absolute_flows(descendant_link) - } - } -} - -/// A bottom-up traversal, with a optional in-order pass. -pub trait PostorderFlowTraversal { - /// The operation to perform. Return true to continue or false to stop. - fn process(&self, flow: &mut dyn Flow); - - /// Returns false if this node must be processed in-order. If this returns false, we skip the - /// operation for this node, but continue processing the ancestors. This is called *after* - /// child nodes are visited. - fn should_process(&self, _flow: &mut dyn Flow) -> bool { - true - } - - /// Traverses the tree in postorder. - fn traverse(&self, flow: &mut dyn Flow) { - for kid in flow.mut_base().child_iter_mut() { - self.traverse(kid); - } - if self.should_process(flow) { - self.process(flow); - } - } -} - -/// An in-order (sequential only) traversal. -pub trait InorderFlowTraversal { - /// The operation to perform. Returns the level of the tree we're at. - fn process(&mut self, flow: &mut dyn Flow, level: u32); - - /// Returns true if this node should be processed and false if neither this node nor its - /// descendants should be processed. - fn should_process_subtree(&mut self, _flow: &mut dyn Flow) -> bool { - true - } - - /// Traverses the tree in-order. - fn traverse(&mut self, flow: &mut dyn Flow, level: u32) { - if !self.should_process_subtree(flow) { - return; - } - self.process(flow, level); - for kid in flow.mut_base().child_iter_mut() { - self.traverse(kid, level + 1); - } - } -} - -/// A bottom-up, parallelizable traversal. -pub trait PostorderNodeMutTraversal<ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode> { - /// The operation to perform. Return true to continue or false to stop. - fn process(&mut self, node: &ConcreteThreadSafeLayoutNode); -} - -/// The flow construction traversal, which builds flows for styled nodes. -#[inline] -#[allow(unsafe_code)] -fn construct_flows_at<N>(context: &LayoutContext, node: N) -where - N: LayoutNode, -{ - debug!("construct_flows_at: {:?}", node); - - // Construct flows for this node. - { - let tnode = node.to_threadsafe(); - - // Always reconstruct if incremental layout is turned off. - let nonincremental_layout = opts::get().nonincremental_layout; - if nonincremental_layout || - tnode.restyle_damage() != RestyleDamage::empty() || - node.as_element() - .map_or(false, |el| el.has_dirty_descendants()) - { - let mut flow_constructor = FlowConstructor::new(context); - if nonincremental_layout || !flow_constructor.repair_if_possible(&tnode) { - flow_constructor.process(&tnode); - debug!( - "Constructed flow for {:?}: {:x}", - tnode, - tnode.flow_debug_id() - ); - } - } - - tnode - .mutate_layout_data() - .unwrap() - .flags - .insert(crate::data::LayoutDataFlags::HAS_BEEN_TRAVERSED); - } - - if let Some(el) = node.as_element() { - unsafe { - el.unset_dirty_descendants(); - } - } -} - -/// The bubble-inline-sizes traversal, the first part of layout computation. This computes -/// preferred and intrinsic inline-sizes and bubbles them up the tree. -pub struct BubbleISizes<'a> { - pub layout_context: &'a LayoutContext<'a>, -} - -impl<'a> PostorderFlowTraversal for BubbleISizes<'a> { - #[inline] - fn process(&self, flow: &mut dyn Flow) { - flow.bubble_inline_sizes(); - flow.mut_base() - .restyle_damage - .remove(ServoRestyleDamage::BUBBLE_ISIZES); - } - - #[inline] - fn should_process(&self, flow: &mut dyn Flow) -> bool { - flow.base() - .restyle_damage - .contains(ServoRestyleDamage::BUBBLE_ISIZES) - } -} - -/// The assign-inline-sizes traversal. In Gecko this corresponds to `Reflow`. -#[derive(Clone, Copy)] -pub struct AssignISizes<'a> { - pub layout_context: &'a LayoutContext<'a>, -} - -impl<'a> PreorderFlowTraversal for AssignISizes<'a> { - #[inline] - fn process(&self, flow: &mut dyn Flow) { - flow.assign_inline_sizes(self.layout_context); - } - - #[inline] - fn should_process(&self, flow: &mut dyn Flow) -> bool { - flow.base() - .restyle_damage - .intersects(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW) - } -} - -/// The assign-block-sizes-and-store-overflow traversal, the last (and most expensive) part of -/// layout computation. Determines the final block-sizes for all layout objects and computes -/// positions. In Gecko this corresponds to `Reflow`. -#[derive(Clone, Copy)] -pub struct AssignBSizes<'a> { - pub layout_context: &'a LayoutContext<'a>, -} - -impl<'a> PostorderFlowTraversal for AssignBSizes<'a> { - #[inline] - fn process(&self, flow: &mut dyn Flow) { - // Can't do anything with anything that floats might flow through until we reach their - // inorder parent. - // - // NB: We must return without resetting the restyle bits for these, as we haven't actually - // reflowed anything! - if flow.floats_might_flow_through() { - return; - } - - flow.assign_block_size(self.layout_context); - } - - #[inline] - fn should_process(&self, flow: &mut dyn Flow) -> bool { - let base = flow.base(); - base.restyle_damage.intersects(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW) && - // The fragmentation countainer is responsible for calling - // Flow::fragment recursively - !base.flags.contains(FlowFlags::CAN_BE_FRAGMENTED) - } -} - -pub struct ComputeStackingRelativePositions<'a> { - pub layout_context: &'a LayoutContext<'a>, -} - -impl<'a> PreorderFlowTraversal for ComputeStackingRelativePositions<'a> { - #[inline] - fn should_process_subtree(&self, flow: &mut dyn Flow) -> bool { - flow.base() - .restyle_damage - .contains(ServoRestyleDamage::REPOSITION) - } - - #[inline] - fn process(&self, flow: &mut dyn Flow) { - flow.compute_stacking_relative_position(self.layout_context); - flow.mut_base() - .restyle_damage - .remove(ServoRestyleDamage::REPOSITION) - } -} - -pub struct BuildDisplayList<'a> { - pub state: DisplayListBuildState<'a>, -} - -impl<'a> BuildDisplayList<'a> { - #[inline] - pub fn traverse(&mut self, flow: &mut dyn Flow) { - let parent_stacking_context_id = self.state.current_stacking_context_id; - self.state.current_stacking_context_id = flow.base().stacking_context_id; - - let parent_clipping_and_scrolling = self.state.current_clipping_and_scrolling; - self.state.current_clipping_and_scrolling = flow.clipping_and_scrolling(); - - flow.build_display_list(&mut self.state); - flow.mut_base() - .restyle_damage - .remove(ServoRestyleDamage::REPAINT); - - for kid in flow.mut_base().child_iter_mut() { - self.traverse(kid); - } - - self.state.current_stacking_context_id = parent_stacking_context_id; - self.state.current_clipping_and_scrolling = parent_clipping_and_scrolling; - } -} diff --git a/components/layout_2020/wrapper.rs b/components/layout_2020/wrapper.rs index 063d7f3d25d..02a0f8ceeee 100644 --- a/components/layout_2020/wrapper.rs +++ b/components/layout_2020/wrapper.rs @@ -2,65 +2,10 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -//! A safe wrapper for DOM nodes that prevents layout from mutating the DOM, from letting DOM nodes -//! escape, and from generally doing anything that it isn't supposed to. This is accomplished via -//! a simple whitelist of allowed operations, along with some lifetime magic to prevent nodes from -//! escaping. -//! -//! As a security wrapper is only as good as its whitelist, be careful when adding operations to -//! this list. The cardinal rules are: -//! -//! 1. Layout is not allowed to mutate the DOM. -//! -//! 2. Layout is not allowed to see anything with `LayoutDom` in the name, because it could hang -//! onto these objects and cause use-after-free. -//! -//! When implementing wrapper functions, be careful that you do not touch the borrow flags, or you -//! will race and cause spurious thread failure. (Note that I do not believe these races are -//! exploitable, but they'll result in brokenness nonetheless.) -//! -//! Rules of the road for this file: -//! -//! * Do not call any methods on DOM nodes without checking to see whether they use borrow flags. -//! -//! o Instead of `get_attr()`, use `.get_attr_val_for_layout()`. -//! -//! o Instead of `html_element_in_html_document()`, use -//! `html_element_in_html_document_for_layout()`. - #![allow(unsafe_code)] -use crate::data::{LayoutData, LayoutDataFlags, StyleAndLayoutData}; -use atomic_refcell::{AtomicRef, AtomicRefMut}; +use crate::data::StyleAndLayoutData; use script_layout_interface::wrapper_traits::GetLayoutData; -use script_layout_interface::wrapper_traits::{ThreadSafeLayoutElement, ThreadSafeLayoutNode}; -use style::dom::{NodeInfo, TNode}; -use style::selector_parser::RestyleDamage; -use style::values::computed::counters::ContentItem; -use style::values::generics::counters::Content; - -pub trait LayoutNodeLayoutData { - /// Similar to borrow_data*, but returns the full PersistentLayoutData rather - /// than only the style::data::ElementData. - fn borrow_layout_data(&self) -> Option<AtomicRef<LayoutData>>; - fn mutate_layout_data(&self) -> Option<AtomicRefMut<LayoutData>>; - fn flow_debug_id(self) -> usize; -} - -impl<T: GetLayoutData> LayoutNodeLayoutData for T { - fn borrow_layout_data(&self) -> Option<AtomicRef<LayoutData>> { - self.get_raw_data().map(|d| d.layout_data.borrow()) - } - - fn mutate_layout_data(&self) -> Option<AtomicRefMut<LayoutData>> { - self.get_raw_data().map(|d| d.layout_data.borrow_mut()) - } - - fn flow_debug_id(self) -> usize { - self.borrow_layout_data() - .map_or(0, |d| d.flow_construction_result.debug_id()) - } -} pub trait GetRawData { fn get_raw_data(&self) -> Option<&StyleAndLayoutData>; @@ -74,101 +19,3 @@ impl<T: GetLayoutData> GetRawData for T { }) } } - -pub trait ThreadSafeLayoutNodeHelpers { - /// Returns the layout data flags for this node. - fn flags(self) -> LayoutDataFlags; - - /// Adds the given flags to this node. - fn insert_flags(self, new_flags: LayoutDataFlags); - - /// Removes the given flags from this node. - fn remove_flags(self, flags: LayoutDataFlags); - - /// If this is a text node, generated content, or a form element, copies out - /// its content. Otherwise, panics. - /// - /// FIXME(pcwalton): This might have too much copying and/or allocation. Profile this. - fn text_content(&self) -> TextContent; - - /// The RestyleDamage from any restyling, or RestyleDamage::rebuild_and_reflow() if this - /// is the first time layout is visiting this node. We implement this here, rather than - /// with the rest of the wrapper layer, because we need layout code to determine whether - /// layout has visited the node. - fn restyle_damage(self) -> RestyleDamage; -} - -impl<T: ThreadSafeLayoutNode> ThreadSafeLayoutNodeHelpers for T { - fn flags(self) -> LayoutDataFlags { - self.borrow_layout_data().as_ref().unwrap().flags - } - - fn insert_flags(self, new_flags: LayoutDataFlags) { - self.mutate_layout_data().unwrap().flags.insert(new_flags); - } - - fn remove_flags(self, flags: LayoutDataFlags) { - self.mutate_layout_data().unwrap().flags.remove(flags); - } - - fn text_content(&self) -> TextContent { - if self.get_pseudo_element_type().is_replaced_content() { - let style = self.as_element().unwrap().resolved_style(); - - return TextContent::GeneratedContent(match style.as_ref().get_counters().content { - Content::Items(ref value) => value.to_vec(), - _ => vec![], - }); - } - - TextContent::Text(self.node_text_content().into_boxed_str()) - } - - fn restyle_damage(self) -> RestyleDamage { - // We need the underlying node to potentially access the parent in the - // case of text nodes. This is safe as long as we don't let the parent - // escape and never access its descendants. - let mut node = unsafe { self.unsafe_get() }; - - // If this is a text node, use the parent element, since that's what - // controls our style. - if node.is_text_node() { - node = node.parent_node().unwrap(); - debug_assert!(node.is_element()); - } - - let damage = { - let data = node.get_raw_data().unwrap(); - - if !data - .layout_data - .borrow() - .flags - .contains(crate::data::LayoutDataFlags::HAS_BEEN_TRAVERSED) - { - // We're reflowing a node that was styled for the first time and - // has never been visited by layout. Return rebuild_and_reflow, - // because that's what the code expects. - RestyleDamage::rebuild_and_reflow() - } else { - data.style_data.element_data.borrow().damage - } - }; - - damage - } -} - -pub enum TextContent { - Text(Box<str>), - GeneratedContent(Vec<ContentItem>), -} - -impl TextContent { - pub fn is_empty(&self) -> bool { - match *self { - TextContent::Text(_) => false, - TextContent::GeneratedContent(ref content) => content.is_empty(), - } - } -} diff --git a/components/layout_thread_2020/lib.rs b/components/layout_thread_2020/lib.rs index 4f5018e0a98..2e3f5956e43 100644 --- a/components/layout_thread_2020/lib.rs +++ b/components/layout_thread_2020/lib.rs @@ -13,8 +13,6 @@ extern crate crossbeam_channel; #[macro_use] extern crate html5ever; #[macro_use] -extern crate layout; -#[macro_use] extern crate lazy_static; #[macro_use] extern crate log; @@ -30,7 +28,7 @@ use crossbeam_channel::{unbounded, Receiver, Sender}; use embedder_traits::resources::{self, Resource}; use euclid::{Point2D, Rect, Size2D, TypedScale, TypedSize2D}; use fnv::FnvHashMap; -use fxhash::{FxHashMap, FxHashSet}; +use fxhash::FxHashMap; use gfx::font; use gfx::font_cache_thread::FontCacheThread; use gfx::font_context; @@ -38,19 +36,11 @@ use gfx_traits::{node_id_from_scroll_id, Epoch}; use histogram::Histogram; use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; use ipc_channel::router::ROUTER; -use layout::animation; -use layout::construct::ConstructionResult; -use layout::context::malloc_size_of_persistent_local_context; use layout::context::LayoutContext; -use layout::context::RegisteredPainter; -use layout::context::RegisteredPainters; -use layout::display_list::items::{OpaqueNode, WebRenderImageInfo}; -use layout::display_list::{IndexableText, ToLayout, WebRenderDisplayListConverter}; -use layout::flow::{Flow, GetBaseFlow, ImmutableFlowUtils, MutableOwnedFlowUtils}; +use layout::display_list::items::OpaqueNode; +use layout::display_list::{IndexableText, WebRenderDisplayListConverter}; +use layout::flow::{Flow, GetBaseFlow}; use layout::flow_ref::FlowRef; -use layout::incremental::{RelayoutMode, SpecialRestyleDamage}; -use layout::layout_debug; -use layout::parallel; use layout::query::{ process_content_box_request, process_content_boxes_request, LayoutRPCImpl, LayoutThreadData, }; @@ -59,11 +49,7 @@ use layout::query::{process_node_scroll_area_request, process_node_scroll_id_req use layout::query::{ process_offset_parent_query, process_resolved_style_request, process_style_query, }; -use layout::sequential; -use layout::traversal::{ - ComputeStackingRelativePositions, PreorderFlowTraversal, RecalcStyleAndConstructFlows, -}; -use layout::wrapper::LayoutNodeLayoutData; +use layout::traversal::RecalcStyleAndConstructFlows; use layout_traits::LayoutThreadFactory; use libc::c_void; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; @@ -71,30 +57,29 @@ use metrics::{PaintTimeMetrics, ProfilerMetadataFactory, ProgressiveWebMetric}; use msg::constellation_msg::{ BackgroundHangMonitor, BackgroundHangMonitorRegister, HangAnnotation, }; -use msg::constellation_msg::{BrowsingContextId, MonitoredComponentId, TopLevelBrowsingContextId}; +use msg::constellation_msg::{MonitoredComponentId, TopLevelBrowsingContextId}; use msg::constellation_msg::{LayoutHangAnnotation, MonitoredComponentType, PipelineId}; -use net_traits::image_cache::{ImageCache, UsePlaceholder}; +use net_traits::image_cache::ImageCache; use parking_lot::RwLock; use profile_traits::mem::{self as profile_mem, Report, ReportKind, ReportsChan}; use profile_traits::time::{self as profile_time, profile, TimerMetadata}; use profile_traits::time::{TimerMetadataFrameType, TimerMetadataReflowType}; -use script_layout_interface::message::{LayoutThreadInit, Msg, NodesFromPointQueryType, Reflow}; +use script_layout_interface::message::{LayoutThreadInit, Msg, NodesFromPointQueryType}; use script_layout_interface::message::{QueryMsg, ReflowComplete, ReflowGoal, ScriptReflow}; use script_layout_interface::rpc::TextIndexResponse; use script_layout_interface::rpc::{LayoutRPC, OffsetParentResponse, StyleResponse}; use script_layout_interface::wrapper_traits::LayoutNode; use script_traits::Painter; use script_traits::{ConstellationControlMsg, LayoutControlMsg, LayoutMsg as ConstellationMsg}; -use script_traits::{DrawAPaintImageResult, IFrameSizeMsg, PaintWorkletError, WindowSizeType}; +use script_traits::{DrawAPaintImageResult, PaintWorkletError}; use script_traits::{ScrollState, UntrustedNodeAddress}; use selectors::Element; use servo_arc::Arc as ServoArc; use servo_atoms::Atom; use servo_config::opts; use servo_config::pref; -use servo_geometry::{DeviceIndependentPixel, MaxRect}; +use servo_geometry::DeviceIndependentPixel; use servo_url::ServoUrl; -use std::borrow::ToOwned; use std::cell::{Cell, RefCell}; use std::collections::HashMap; use std::ops::{Deref, DerefMut}; @@ -111,7 +96,6 @@ use style::driver; use style::error_reporting::RustLogReporter; use style::global_style_data::{GLOBAL_STYLE_DATA, STYLE_THREAD_POOL}; use style::invalidation::element::restyle_hints::RestyleHint; -use style::logical_geometry::LogicalPoint; use style::media_queries::{Device, MediaList, MediaType}; use style::properties::PropertyId; use style::selector_parser::SnapshotMap; @@ -161,9 +145,6 @@ pub struct LayoutThread { /// A means of communication with the background hang monitor. background_hang_monitor: Box<dyn BackgroundHangMonitor>, - /// The channel on which messages can be sent to the constellation. - constellation_chan: IpcSender<ConstellationMsg>, - /// The channel on which messages can be sent to the script thread. script_chan: IpcSender<ConstellationControlMsg>, @@ -173,9 +154,6 @@ pub struct LayoutThread { /// The channel on which messages can be sent to the memory profiler. mem_profiler_chan: profile_mem::ProfilerChan, - /// Reference to the script thread image cache. - image_cache: Arc<dyn ImageCache>, - /// Public interface to the font cache thread. font_cache_thread: FontCacheThread, @@ -194,7 +172,7 @@ pub struct LayoutThread { new_animations_sender: Sender<Animation>, /// Receives newly-discovered animations. - new_animations_receiver: Receiver<Animation>, + _new_animations_receiver: Receiver<Animation>, /// The number of Web fonts that have been requested but not yet loaded. outstanding_web_fonts: Arc<AtomicUsize>, @@ -224,8 +202,6 @@ pub struct LayoutThread { /// All the other elements of this struct are read-only. rw_data: Arc<Mutex<LayoutThreadData>>, - webrender_image_cache: Arc<RwLock<FnvHashMap<(ServoUrl, UsePlaceholder), WebRenderImageInfo>>>, - /// The executors for paint worklets. registered_painters: RegisteredPaintersImpl, @@ -245,9 +221,6 @@ pub struct LayoutThread { /// The time a layout query has waited before serviced by layout thread. layout_query_waiting_time: Histogram, - /// The sizes of all iframes encountered during the last layout operation. - last_iframe_sizes: RefCell<HashMap<BrowsingContextId, TypedSize2D<f32, CSSPixel>>>, - /// Flag that indicates if LayoutThread is busy handling a request. busy: Arc<AtomicBool>, @@ -302,7 +275,7 @@ impl LayoutThreadFactory for LayoutThread { background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>, constellation_chan: IpcSender<ConstellationMsg>, script_chan: IpcSender<ConstellationControlMsg>, - image_cache: Arc<dyn ImageCache>, + _image_cache: Arc<dyn ImageCache>, font_cache_thread: FontCacheThread, time_profiler_chan: profile_time::ProfilerChan, mem_profiler_chan: profile_mem::ProfilerChan, @@ -352,7 +325,6 @@ impl LayoutThreadFactory for LayoutThread { background_hang_monitor, constellation_chan, script_chan, - image_cache.clone(), font_cache_thread, time_profiler_chan, mem_profiler_chan.clone(), @@ -526,7 +498,6 @@ impl LayoutThread { background_hang_monitor: Box<dyn BackgroundHangMonitor>, constellation_chan: IpcSender<ConstellationMsg>, script_chan: IpcSender<ConstellationControlMsg>, - image_cache: Arc<dyn ImageCache>, font_cache_thread: FontCacheThread, time_profiler_chan: profile_time::ProfilerChan, mem_profiler_chan: profile_mem::ProfilerChan, @@ -574,11 +545,9 @@ impl LayoutThread { pipeline_port: pipeline_receiver, script_chan: script_chan.clone(), background_hang_monitor, - constellation_chan: constellation_chan.clone(), time_profiler_chan: time_profiler_chan, mem_profiler_chan: mem_profiler_chan, registered_painters: RegisteredPaintersImpl(Default::default()), - image_cache: image_cache.clone(), font_cache_thread: font_cache_thread, first_reflow: Cell::new(true), font_cache_receiver: font_cache_receiver, @@ -586,7 +555,7 @@ impl LayoutThread { parallel_flag: true, generation: Cell::new(0), new_animations_sender: new_animations_sender, - new_animations_receiver: new_animations_receiver, + _new_animations_receiver: new_animations_receiver, outstanding_web_fonts: Arc::new(AtomicUsize::new(0)), root_flow: RefCell::new(None), document_shared_lock: None, @@ -614,7 +583,6 @@ impl LayoutThread { nodes_from_point_response: vec![], element_inner_text_response: String::new(), })), - webrender_image_cache: Arc::new(RwLock::new(FnvHashMap::default())), timer: if pref!(layout.animations.test.enabled) { Timer::test_mode() } else { @@ -622,7 +590,6 @@ impl LayoutThread { }, paint_time_metrics: paint_time_metrics, layout_query_waiting_time: Histogram::new(), - last_iframe_sizes: Default::default(), busy, load_webfonts_synchronously, initial_window_size, @@ -655,7 +622,6 @@ impl LayoutThread { fn build_layout_context<'a>( &'a self, guards: StylesheetGuards<'a>, - script_initiated_layout: bool, snapshot_map: &'a SnapshotMap, ) -> LayoutContext<'a> { let thread_local_style_context_creation_data = @@ -676,20 +642,6 @@ impl LayoutThread { traversal_flags: TraversalFlags::empty(), snapshot_map: snapshot_map, }, - image_cache: self.image_cache.clone(), - font_cache_thread: Mutex::new(self.font_cache_thread.clone()), - webrender_image_cache: self.webrender_image_cache.clone(), - pending_images: if script_initiated_layout { - Some(Mutex::new(vec![])) - } else { - None - }, - newly_transitioning_nodes: if script_initiated_layout { - Some(Mutex::new(vec![])) - } else { - None - }, - registered_painters: &self.registered_painters, } } @@ -862,23 +814,7 @@ impl LayoutThread { Msg::SetFinalUrl(final_url) => { self.url = final_url; }, - Msg::RegisterPaint(name, mut properties, painter) => { - debug!("Registering the painter"); - let properties = properties - .drain(..) - .filter_map(|name| { - let id = PropertyId::parse_enabled_for_all_content(&*name).ok()?; - Some((name.clone(), id)) - }) - .filter(|&(_, ref id)| !id.is_shorthand()) - .collect(); - let registered_painter = RegisteredPainterImpl { - name: name.clone(), - properties, - painter, - }; - self.registered_painters.0.insert(name, registered_painter); - }, + Msg::RegisterPaint(_name, _properties, _painter) => {}, Msg::PrepareToExit(response_chan) => { self.prepare_to_exit(response_chan); return false; @@ -926,13 +862,6 @@ impl LayoutThread { size: self.stylist.size_of(&mut ops), }); - // The LayoutThread has data in Persistent TLS... - reports.push(Report { - path: path![formatted_url, "layout-thread", "local-context"], - kind: ReportKind::ExplicitJemallocHeapSize, - size: malloc_size_of_persistent_local_context(&mut ops), - }); - reports_chan.send(reports); } @@ -1045,171 +974,35 @@ impl LayoutThread { self.stylist.set_quirks_mode(quirks_mode); } - fn try_get_layout_root<N: LayoutNode>(&self, node: N) -> Option<FlowRef> { - let result = node.mutate_layout_data()?.flow_construction_result.get(); - - let mut flow = match result { - ConstructionResult::Flow(mut flow, abs_descendants) => { - // Note: Assuming that the root has display 'static' (as per - // CSS Section 9.3.1). Otherwise, if it were absolutely - // positioned, it would return a reference to itself in - // `abs_descendants` and would lead to a circular reference. - // Set Root as CB for any remaining absolute descendants. - flow.set_absolute_descendants(abs_descendants); - flow - }, - _ => return None, - }; - - FlowRef::deref_mut(&mut flow).mark_as_root(); - - Some(flow) - } - - /// Performs layout constraint solving. - /// - /// This corresponds to `Reflow()` in Gecko and `layout()` in WebKit/Blink and should be - /// benchmarked against those two. It is marked `#[inline(never)]` to aid profiling. - #[inline(never)] - fn solve_constraints(layout_root: &mut dyn Flow, layout_context: &LayoutContext) { - let _scope = layout_debug_scope!("solve_constraints"); - sequential::reflow(layout_root, layout_context, RelayoutMode::Incremental); - } - - /// Performs layout constraint solving in parallel. - /// - /// This corresponds to `Reflow()` in Gecko and `layout()` in WebKit/Blink and should be - /// benchmarked against those two. It is marked `#[inline(never)]` to aid profiling. - #[inline(never)] - fn solve_constraints_parallel( - traversal: &rayon::ThreadPool, - layout_root: &mut dyn Flow, - profiler_metadata: Option<TimerMetadata>, - time_profiler_chan: profile_time::ProfilerChan, - layout_context: &LayoutContext, - ) { - let _scope = layout_debug_scope!("solve_constraints_parallel"); - - // NOTE: this currently computes borders, so any pruning should separate that - // operation out. - parallel::reflow( - layout_root, - profiler_metadata, - time_profiler_chan, - layout_context, - traversal, - ); + fn try_get_layout_root<N: LayoutNode>(&self, _node: N) -> Option<FlowRef> { + None } /// Computes the stacking-relative positions of all flows and, if the painting is dirty and the /// reflow type need it, builds the display list. fn compute_abs_pos_and_build_display_list( &self, - data: &Reflow, reflow_goal: &ReflowGoal, document: Option<&ServoLayoutDocument>, layout_root: &mut dyn Flow, - layout_context: &mut LayoutContext, rw_data: &mut LayoutThreadData, ) { - let writing_mode = layout_root.base().writing_mode; let (metadata, sender) = (self.profiler_metadata(), self.time_profiler_chan.clone()); profile( profile_time::ProfilerCategory::LayoutDispListBuild, metadata.clone(), sender.clone(), || { - layout_root.mut_base().stacking_relative_position = - LogicalPoint::zero(writing_mode) - .to_physical(writing_mode, self.viewport_size) - .to_vector(); - - layout_root.mut_base().clip = data.page_clip_rect; - - let traversal = ComputeStackingRelativePositions { - layout_context: layout_context, - }; - traversal.traverse(layout_root); - if layout_root .base() .restyle_damage .contains(ServoRestyleDamage::REPAINT) || rw_data.display_list.is_none() { - if reflow_goal.needs_display_list() { - let background_color = get_root_flow_background_color(layout_root); - let mut build_state = sequential::build_display_list_for_subtree( - layout_root, - layout_context, - background_color, - data.page_clip_rect.size, - ); - - debug!("Done building display list."); - - let root_size = { - let root_flow = layout_root.base(); - if self.stylist.viewport_constraints().is_some() { - root_flow.position.size.to_physical(root_flow.writing_mode) - } else { - root_flow.overflow.scroll.size - } - }; - - let origin = Rect::new(Point2D::new(Au(0), Au(0)), root_size).to_layout(); - build_state.root_stacking_context.bounds = origin; - build_state.root_stacking_context.overflow = origin; - - if !build_state.iframe_sizes.is_empty() { - // build_state.iframe_sizes is only used here, so its okay to replace - // it with an empty vector - let iframe_sizes = - std::mem::replace(&mut build_state.iframe_sizes, vec![]); - // Collect the last frame's iframe sizes to compute any differences. - // Every frame starts with a fresh collection so that any removed - // iframes do not linger. - let last_iframe_sizes = std::mem::replace( - &mut *self.last_iframe_sizes.borrow_mut(), - HashMap::default(), - ); - let mut size_messages = vec![]; - for new_size in iframe_sizes { - // Only notify the constellation about existing iframes - // that have a new size, or iframes that did not previously - // exist. - if let Some(old_size) = last_iframe_sizes.get(&new_size.id) { - if *old_size != new_size.size { - size_messages.push(IFrameSizeMsg { - data: new_size, - type_: WindowSizeType::Resize, - }); - } - } else { - size_messages.push(IFrameSizeMsg { - data: new_size, - type_: WindowSizeType::Initial, - }); - } - self.last_iframe_sizes - .borrow_mut() - .insert(new_size.id, new_size.size); - } - - if !size_messages.is_empty() { - let msg = ConstellationMsg::IFrameSizes(size_messages); - if let Err(e) = self.constellation_chan.send(msg) { - warn!("Layout resize to constellation failed ({}).", e); - } - } - } - - rw_data.indexable_text = std::mem::replace( - &mut build_state.indexable_text, - IndexableText::default(), - ); - rw_data.display_list = Some(build_state.to_display_list()); - } + layout_root + .mut_base() + .restyle_damage + .remove(ServoRestyleDamage::REPAINT); } if !reflow_goal.needs_display() { @@ -1228,13 +1021,6 @@ impl LayoutThread { let display_list = rw_data.display_list.as_mut().unwrap(); - if self.dump_display_list { - display_list.print(); - } - if self.dump_display_list_json { - println!("{}", serde_json::to_string_pretty(&display_list).unwrap()); - } - debug!("Layout done!"); // TODO: Avoid the temporary conversion and build webrender sc/dl directly! @@ -1500,7 +1286,7 @@ impl LayoutThread { self.stylist.flush(&guards, Some(element), Some(&map)); // Create a layout context for use throughout the following passes. - let mut layout_context = self.build_layout_context(guards.clone(), true, &map); + let mut layout_context = self.build_layout_context(guards.clone(), &map); let (thread_pool, num_threads) = if self.parallel_flag { ( @@ -1579,12 +1365,9 @@ impl LayoutThread { if let Some(mut root_flow) = self.root_flow.borrow().clone() { self.perform_post_style_recalc_layout_passes( &mut root_flow, - &data.reflow_info, &data.reflow_goal, Some(&document), &mut rw_data, - &mut layout_context, - FxHashSet::default(), ); } @@ -1593,7 +1376,6 @@ impl LayoutThread { &data.reflow_goal, &mut *rw_data, &mut layout_context, - data.result.borrow_mut().as_mut().unwrap(), ); } @@ -1602,32 +1384,14 @@ impl LayoutThread { reflow_goal: &ReflowGoal, rw_data: &mut LayoutThreadData, context: &mut LayoutContext, - reflow_result: &mut ReflowComplete, ) { - let pending_images = match context.pending_images { - Some(ref pending) => std::mem::replace(&mut *pending.lock().unwrap(), vec![]), - None => vec![], - }; - reflow_result.pending_images = pending_images; - - let newly_transitioning_nodes = match context.newly_transitioning_nodes { - Some(ref nodes) => std::mem::replace(&mut *nodes.lock().unwrap(), vec![]), - None => vec![], - }; - reflow_result.newly_transitioning_nodes = newly_transitioning_nodes; - - let mut root_flow = match self.root_flow.borrow().clone() { - Some(root_flow) => root_flow, - None => return, - }; - let root_flow = FlowRef::deref_mut(&mut root_flow); match *reflow_goal { ReflowGoal::LayoutQuery(ref querymsg, _) => match querymsg { &QueryMsg::ContentBoxQuery(node) => { - rw_data.content_box_response = process_content_box_request(node, root_flow); + rw_data.content_box_response = process_content_box_request(node); }, &QueryMsg::ContentBoxesQuery(node) => { - rw_data.content_boxes_response = process_content_boxes_request(node, root_flow); + rw_data.content_boxes_response = process_content_boxes_request(node); }, &QueryMsg::TextIndexQuery(node, point_in_node) => { let point_in_node = Point2D::new( @@ -1638,11 +1402,11 @@ impl LayoutThread { TextIndexResponse(rw_data.indexable_text.text_index(node, point_in_node)); }, &QueryMsg::NodeGeometryQuery(node) => { - rw_data.client_rect_response = process_node_geometry_request(node, root_flow); + rw_data.client_rect_response = process_node_geometry_request(node); }, &QueryMsg::NodeScrollGeometryQuery(node) => { rw_data.scroll_area_response = - process_node_scroll_area_request(node, root_flow); + process_node_scroll_area_request(node); }, &QueryMsg::NodeScrollIdQuery(node) => { let node = unsafe { ServoLayoutNode::new(&node) }; @@ -1652,10 +1416,10 @@ impl LayoutThread { &QueryMsg::ResolvedStyleQuery(node, ref pseudo, ref property) => { let node = unsafe { ServoLayoutNode::new(&node) }; rw_data.resolved_style_response = - process_resolved_style_request(context, node, pseudo, property, root_flow); + process_resolved_style_request(context, node, pseudo, property); }, &QueryMsg::OffsetParentQuery(node) => { - rw_data.offset_parent_response = process_offset_parent_query(node, root_flow); + rw_data.offset_parent_response = process_offset_parent_query(node); }, &QueryMsg::StyleQuery(node) => { let node = unsafe { ServoLayoutNode::new(&node) }; @@ -1737,203 +1501,62 @@ impl LayoutThread { } if let Some(mut root_flow) = self.root_flow.borrow().clone() { - let reflow_info = Reflow { - page_clip_rect: Rect::max_rect(), - }; - // Unwrap here should not panic since self.root_flow is only ever set to Some(_) // in handle_reflow() where self.document_shared_lock is as well. let author_shared_lock = self.document_shared_lock.clone().unwrap(); let author_guard = author_shared_lock.read(); let ua_or_user_guard = UA_STYLESHEETS.shared_lock.read(); - let guards = StylesheetGuards { + let _guards = StylesheetGuards { author: &author_guard, ua_or_user: &ua_or_user_guard, }; - let snapshots = SnapshotMap::new(); - let mut layout_context = self.build_layout_context(guards, false, &snapshots); - let invalid_nodes = { - // Perform an abbreviated style recalc that operates without access to the DOM. - let animations = self.running_animations.read(); - profile( - profile_time::ProfilerCategory::LayoutStyleRecalc, - self.profiler_metadata(), - self.time_profiler_chan.clone(), - || { - animation::recalc_style_for_animations::<ServoLayoutElement>( - &layout_context, - FlowRef::deref_mut(&mut root_flow), - &animations, - ) - }, - ) - }; self.perform_post_style_recalc_layout_passes( &mut root_flow, - &reflow_info, &ReflowGoal::TickAnimations, None, &mut *rw_data, - &mut layout_context, - invalid_nodes, ); - assert!(layout_context.pending_images.is_none()); - assert!(layout_context.newly_transitioning_nodes.is_none()); } } fn perform_post_style_recalc_layout_passes( &self, root_flow: &mut FlowRef, - data: &Reflow, reflow_goal: &ReflowGoal, document: Option<&ServoLayoutDocument>, rw_data: &mut LayoutThreadData, - context: &mut LayoutContext, - invalid_nodes: FxHashSet<OpaqueNode>, ) { - { - let mut newly_transitioning_nodes = context - .newly_transitioning_nodes - .as_ref() - .map(|nodes| nodes.lock().unwrap()); - let newly_transitioning_nodes = - newly_transitioning_nodes.as_mut().map(|nodes| &mut **nodes); - // Kick off animations if any were triggered, expire completed ones. - animation::update_animation_state::<ServoLayoutElement>( - &self.constellation_chan, - &self.script_chan, - &mut *self.running_animations.write(), - &mut *self.expired_animations.write(), - invalid_nodes, - newly_transitioning_nodes, - &self.new_animations_receiver, - self.id, - &self.timer, - ); - } - profile( profile_time::ProfilerCategory::LayoutRestyleDamagePropagation, self.profiler_metadata(), self.time_profiler_chan.clone(), - || { - // Call `compute_layout_damage` even in non-incremental mode, because it sets flags - // that are needed in both incremental and non-incremental traversals. - let damage = FlowRef::deref_mut(root_flow).compute_layout_damage(); - - if self.nonincremental_layout || - damage.contains(SpecialRestyleDamage::REFLOW_ENTIRE_DOCUMENT) - { - FlowRef::deref_mut(root_flow).reflow_entire_document() - } - }, - ); - - if self.trace_layout { - layout_debug::begin_trace(root_flow.clone()); - } - - // Resolve generated content. - profile( - profile_time::ProfilerCategory::LayoutGeneratedContent, - self.profiler_metadata(), - self.time_profiler_chan.clone(), - || sequential::resolve_generated_content(FlowRef::deref_mut(root_flow), &context), - ); - - // Guess float placement. - profile( - profile_time::ProfilerCategory::LayoutFloatPlacementSpeculation, - self.profiler_metadata(), - self.time_profiler_chan.clone(), - || sequential::guess_float_placement(FlowRef::deref_mut(root_flow)), - ); - - // Perform the primary layout passes over the flow tree to compute the locations of all - // the boxes. - if root_flow - .base() - .restyle_damage - .intersects(ServoRestyleDamage::REFLOW | ServoRestyleDamage::REFLOW_OUT_OF_FLOW) - { - profile( - profile_time::ProfilerCategory::LayoutMain, - self.profiler_metadata(), - self.time_profiler_chan.clone(), - || { - let profiler_metadata = self.profiler_metadata(); - - let thread_pool = if self.parallel_flag { - STYLE_THREAD_POOL.style_thread_pool.as_ref() - } else { - None - }; - - if let Some(pool) = thread_pool { - // Parallel mode. - LayoutThread::solve_constraints_parallel( - pool, - FlowRef::deref_mut(root_flow), - profiler_metadata, - self.time_profiler_chan.clone(), - &*context, - ); - } else { - //Sequential mode - LayoutThread::solve_constraints(FlowRef::deref_mut(root_flow), &context) - } - }, - ); - } - - profile( - profile_time::ProfilerCategory::LayoutStoreOverflow, - self.profiler_metadata(), - self.time_profiler_chan.clone(), - || { - sequential::store_overflow(context, FlowRef::deref_mut(root_flow) as &mut dyn Flow); - }, + || {}, ); self.perform_post_main_layout_passes( - data, root_flow, reflow_goal, document, rw_data, - context, ); } fn perform_post_main_layout_passes( &self, - data: &Reflow, mut root_flow: &mut FlowRef, reflow_goal: &ReflowGoal, document: Option<&ServoLayoutDocument>, rw_data: &mut LayoutThreadData, - layout_context: &mut LayoutContext, ) { // Build the display list if necessary, and send it to the painter. self.compute_abs_pos_and_build_display_list( - data, reflow_goal, document, FlowRef::deref_mut(&mut root_flow), - &mut *layout_context, rw_data, ); - if self.trace_layout { - layout_debug::end_trace(self.generation.get()); - } - - if self.dump_flow_tree { - root_flow.print("Post layout flow tree".to_owned()); - } - self.generation.set(self.generation.get() + 1); } @@ -1945,10 +1568,6 @@ impl LayoutThread { ServoRestyleDamage::REFLOW | ServoRestyleDamage::REPOSITION, ); - - for child in flow.mut_base().child_iter_mut() { - LayoutThread::reflow_all_nodes(child); - } } /// Returns profiling information which is passed to the time profiler. @@ -1975,51 +1594,6 @@ impl ProfilerMetadataFactory for LayoutThread { } } -// The default computed value for background-color is transparent (see -// http://dev.w3.org/csswg/css-backgrounds/#background-color). However, we -// need to propagate the background color from the root HTML/Body -// element (http://dev.w3.org/csswg/css-backgrounds/#special-backgrounds) if -// it is non-transparent. The phrase in the spec "If the canvas background -// is not opaque, what shows through is UA-dependent." is handled by rust-layers -// clearing the frame buffer to white. This ensures that setting a background -// color on an iframe element, while the iframe content itself has a default -// transparent background color is handled correctly. -fn get_root_flow_background_color(flow: &mut dyn Flow) -> webrender_api::ColorF { - let transparent = webrender_api::ColorF { - r: 0.0, - g: 0.0, - b: 0.0, - a: 0.0, - }; - if !flow.is_block_like() { - return transparent; - } - - let block_flow = flow.as_mut_block(); - let kid = match block_flow.base.children.iter_mut().next() { - None => return transparent, - Some(kid) => kid, - }; - if !kid.is_block_like() { - return transparent; - } - - let kid_block_flow = kid.as_block(); - let color = kid_block_flow.fragment.style.resolve_color( - kid_block_flow - .fragment - .style - .get_background() - .background_color, - ); - webrender_api::ColorF::new( - color.red_f32(), - color.green_f32(), - color.blue_f32(), - color.alpha_f32(), - ) -} - fn get_ua_stylesheets() -> Result<UserAgentStylesheets, &'static str> { fn parse_ua_stylesheet( shared_lock: &SharedRwLock, @@ -2144,8 +1718,6 @@ impl Painter for RegisteredPainterImpl { } } -impl RegisteredPainter for RegisteredPainterImpl {} - struct RegisteredPaintersImpl(FnvHashMap<Atom, RegisteredPainterImpl>); impl RegisteredSpeculativePainters for RegisteredPaintersImpl { @@ -2155,11 +1727,3 @@ impl RegisteredSpeculativePainters for RegisteredPaintersImpl { .map(|painter| painter as &dyn RegisteredSpeculativePainter) } } - -impl RegisteredPainters for RegisteredPaintersImpl { - fn get(&self, name: &Atom) -> Option<&dyn RegisteredPainter> { - self.0 - .get(&name) - .map(|painter| painter as &dyn RegisteredPainter) - } -} |