//! The `width` generated function's place in the engine: CSS 2.1 10.3.3 block width //! resolution, isolated behind one function with plain-data inputs and outputs //! (`roadmap.md` -> "Phase 1 — Engine bootstrap" step 4, "Generated algorithms, one //! category at a time"). The types a candidate implements against are re-exported from //! [`crate::width_types`] so `layout::width::...` names them; this module says how the //! engine fills them, and holds the baseline the engine runs when the committed function //! does not build against a new types file. A candidate crate compiles independently of the //! DOM/style layer, and the runner swaps resolvers without touching anything else in //! `fragment.rs`. //! //! **Written against any types file.** A job's types file is model-written and chooses its //! inputs from the engine's fixed list ([`crate::width_engine`]), so nothing here names an //! input field. The engine builds a [`WidthEngineValues`] and calls through [`call`], which //! fills the inputs with [`inputs_from`] (written by `build.rs` from the types file in //! place). The baseline reads the engine's values rather than the inputs, which may not //! hold what it needs, so [`resolve_block_width_baseline`] answers only a call made through //! [`call`]. The committed generated function (`generated/width.rs`) is the one part written //! against one types file: it and that file are replaced together. //! //! [`baseline`] is this function's deliberately wrong starting candidate: every box is zero //! wide with zero margins, whatever its `width`, margins or containing block. use std::cell::Cell; pub use crate::width_engine::{WidthEngineAnswer, WidthEngineValues}; pub use crate::width_types::{WidthInputs, WidthOutputs, WidthResolver}; use dom::node::NodeId; include!(concat!(env!("OUT_DIR"), "/width_inputs_from.rs")); /// The containing block's `direction`, the one property CSS 2.1 10.3.3's over-constraint /// rule branches on. The current corpus is exclusively left-to-right /// (`crates/layout/src/fragment.rs`'s module doc), so `Ltr` is the only value any case /// exercises today; a candidate's handling of `Rtl` is unverified by this corpus. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Direction { Ltr, Rtl, } thread_local! { /// The engine's values for the call [`call`] is making, so /// [`resolve_block_width_baseline`] can answer it; `None` outside a call. static CALL_VALUES: Cell> = const { Cell::new(None) }; } /// One call the engine made to the function for a box's own width, as `fragment.rs` /// `layout_box_styled` records it (`crate::call_recording`): the box and the engine's values /// for it. The call a ratio box makes again for its margins, the float path (which does not /// call) and a box whose width a table or a ratio replaces are not recorded, because their /// answer is not the width the browsers measure for the box. #[derive(Debug, Clone, Copy, PartialEq)] pub struct WidthCall { pub node: NodeId, /// The engine's values at the call (`inputs_from` turns them into what the resolver /// was handed). pub values: WidthEngineValues, } /// Asks `resolver` about the box `values` describes: fills the types file's inputs from /// `values` ([`inputs_from`]) and returns the answer. How the engine calls every `width` /// resolver. pub fn call(resolver: WidthResolver, values: &WidthEngineValues) -> WidthOutputs { let previous = CALL_VALUES.with(|v| v.replace(Some(*values))); let out = resolver(&inputs_from(values)); CALL_VALUES.with(|v| v.set(previous)); out } /// Deliberately wrong starting candidate: a box is zero wide with zero margins. pub fn baseline(_values: &WidthEngineValues) -> WidthOutputs { WidthOutputs { used_width: 0.0, used_margin_left: 0.0, used_margin_right: 0.0 } } /// [`baseline`] as a resolver, for the engine's tables of resolvers /// (`fragment::Resolvers::baselines`). It ignores `inputs` and reads the values of the call /// in progress, so it answers only a call made through [`call`], which is every call the /// engine makes; called any other way it panics. pub fn resolve_block_width_baseline(_inputs: &WidthInputs) -> WidthOutputs { let values = CALL_VALUES .with(Cell::get) .expect("width::resolve_block_width_baseline answers only a call made through width::call"); baseline(&values) } #[cfg(test)] mod tests { use super::*; use dom::computed::LengthPercentageOrAuto; fn values(containing_block_width: f64, width: LengthPercentageOrAuto) -> WidthEngineValues { WidthEngineValues { containing_block_width, width, margin_left: LengthPercentageOrAuto::Length(0.0), margin_right: LengthPercentageOrAuto::Length(0.0), padding_left: 0.0, padding_right: 0.0, border_left: 0.0, border_right: 0.0, border_box_sizing: false, direction: Direction::Ltr, min_width: None, max_width: None, } } fn answer(v: &WidthEngineValues) -> WidthOutputs { call(resolve_block_width_baseline, v) } #[test] fn the_starting_candidate_makes_every_box_zero_wide() { assert_eq!( answer(&values(800.0, LengthPercentageOrAuto::Length(220.0))), WidthOutputs { used_width: 0.0, used_margin_left: 0.0, used_margin_right: 0.0 } ); assert_eq!(answer(&values(800.0, LengthPercentageOrAuto::Auto)).used_width, 0.0); } #[test] fn the_baseline_resolver_answers_a_call_with_the_values_of_that_call() { let v = values(800.0, LengthPercentageOrAuto::Auto); assert_eq!(call(resolve_block_width_baseline, &v), baseline(&v)); assert!(CALL_VALUES.with(Cell::get).is_none(), "the values outlive the call"); } #[test] #[should_panic(expected = "answers only a call made through width::call")] fn the_baseline_resolver_refuses_a_call_the_engine_did_not_make() { resolve_block_width_baseline(&inputs_from(&values(800.0, LengthPercentageOrAuto::Auto))); } }