Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205//! The `absolute` generated function's place in the engine: CSS 2.1 absolute positioning for//! block-level, non-replaced boxes with no text -- 9.3.1 (positioning schemes), 9.3.2//! (`top`/`right`/`bottom`/`left`), 10.3.7 (width) and 10.6.4 (height) -- isolated behind one//! function with plain-data inputs and outputs (`roadmap.md` -> "Proof of concept", claim 3).//! The types a candidate implements against are re-exported from [`crate::absolute_types`] so//! `layout::absolute::...` 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.//!//! `fragment.rs` `layout_absolute_box` calls the resolver twice per box//! (`absolute_call_site.txt`, which a job's prompt carries), after the `containing_block`//! function has chosen the box's containing block and every percentage in the inputs has been//! resolved against that block's padding-box size, so the function sees `Length` or `Auto`//! only (`percentage.rs` is its own function for the same reason). The first call, with//! `content_height: None`, gives the used width the box's children are laid out against; the//! second, with `content_height: Some`, is authoritative for everything, because a height//! that depends on the box's content (CSS 2.1 10.6.4) is known only once the children are//! laid out.//!//! **Written against any types file.** A job's types file is model-written and chooses its//! inputs from the engine's fixed list ([`crate::absolute_engine`]), so nothing here names an//! input field. The engine builds an [`AbsoluteEngineValues`] 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_baseline`] answers only a call made through [`call`].//!//! [`baseline`] is the function's deliberately wrong starting candidate: every offset is//! ignored and the box is placed at its static position with whatever size its own//! `width`/`height` already give it in-flow -- exactly what `fragment.rs` did before this//! function existed. Unlike every other function in this codebase, no hand-written correct//! implementation is written before a model has had a chance at this one (`roadmap.md` ->//! "Proof of concept", claim 3), so a page runner has no hand rule to stand in for it//! (`generation::absolute_page_runner`).
use std::cell::Cell;
pub use crate::absolute_engine::{AbsoluteEngineAnswer, AbsoluteEngineValues};pub use crate::absolute_types::{AbsoluteInputs, AbsoluteOutputs, AbsoluteResolver};
use dom::computed::LengthPercentageOrAuto;use dom::node::NodeId;
include!(concat!(env!("OUT_DIR"), "/absolute_inputs_from.rs"));
thread_local! { /// The engine's values for the call [`call`] is making, so [`resolve_baseline`] can /// answer it; `None` outside a call. static CALL_VALUES: Cell<Option<AbsoluteEngineValues>> = const { Cell::new(None) };}
/// One call the engine made to the function, as `fragment.rs` `layout_absolute_box` records/// it (`crate::call_recording`): the box and the engine's values for it. A box is asked/// twice; the second call, the one with a content height, is the one recorded.#[derive(Debug, Clone, Copy, PartialEq)]pub struct AbsoluteCall { pub node: NodeId, /// The engine's values at the call (`inputs_from` turns them into what the resolver was /// handed). pub values: AbsoluteEngineValues,}
/// 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 `absolute`/// resolver.pub fn call(resolver: AbsoluteResolver, values: &AbsoluteEngineValues) -> AbsoluteOutputs { let previous = CALL_VALUES.with(|v| v.replace(Some(*values))); let out = resolver(&inputs_from(values)); CALL_VALUES.with(|v| v.set(previous)); out}
fn length_or_auto_zero(lp: LengthPercentageOrAuto) -> f64 { match lp { LengthPercentageOrAuto::Length(px) => px, LengthPercentageOrAuto::Auto | LengthPercentageOrAuto::Percentage(_) | LengthPercentageOrAuto::Unresolved => 0.0, }}
/// The deliberately wrong starting candidate (this module's own doc comment): every offset/// (`top`/`right`/`bottom`/`left`) is ignored outright, and the box is placed at its static/// position with whatever `width`/`height` it would have used in-flow -- an explicit length/// folds `border_box_sizing` back to a content size the same way/// `crate::width::resolve_block_width_baseline` does, and every other shape (`Auto`, an/// unresolved percentage) falls back to zero, since this baseline does not attempt the/// width/height equations at all.pub fn baseline(inputs: &AbsoluteEngineValues) -> AbsoluteOutputs { let edge_lr = inputs.padding_left + inputs.padding_right + inputs.border_left + inputs.border_right; let edge_tb = inputs.padding_top + inputs.padding_bottom + inputs.border_top + inputs.border_bottom; let used_width = match inputs.width { LengthPercentageOrAuto::Length(w) if inputs.border_box_sizing => (w - edge_lr).max(0.0), LengthPercentageOrAuto::Length(w) => w, LengthPercentageOrAuto::Auto | LengthPercentageOrAuto::Percentage(_) | LengthPercentageOrAuto::Unresolved => { 0.0 } }; let used_height = match inputs.height { LengthPercentageOrAuto::Length(h) if inputs.border_box_sizing => (h - edge_tb).max(0.0), LengthPercentageOrAuto::Length(h) => h, LengthPercentageOrAuto::Auto | LengthPercentageOrAuto::Percentage(_) | LengthPercentageOrAuto::Unresolved => { 0.0 } }; AbsoluteOutputs { left: inputs.static_left, top: inputs.static_top, used_width, used_height, margin_left: length_or_auto_zero(inputs.margin_left), margin_right: length_or_auto_zero(inputs.margin_right), margin_top: length_or_auto_zero(inputs.margin_top), margin_bottom: length_or_auto_zero(inputs.margin_bottom), }}
/// [`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_baseline(_inputs: &AbsoluteInputs) -> AbsoluteOutputs { let values = CALL_VALUES .with(Cell::get) .expect("absolute::resolve_baseline answers only a call made through absolute::call"); baseline(&values)}
#[cfg(test)]mod tests { use super::*; use crate::width::Direction;
fn values() -> AbsoluteEngineValues { AbsoluteEngineValues { containing_block_width: 800.0, containing_block_height: 600.0, static_left: 10.0, static_top: 20.0, top: LengthPercentageOrAuto::Length(50.0), right: LengthPercentageOrAuto::Auto, bottom: LengthPercentageOrAuto::Auto, left: LengthPercentageOrAuto::Length(30.0), width: LengthPercentageOrAuto::Length(100.0), height: LengthPercentageOrAuto::Length(40.0), margin_left: LengthPercentageOrAuto::Length(0.0), margin_right: LengthPercentageOrAuto::Length(0.0), margin_top: LengthPercentageOrAuto::Length(0.0), margin_bottom: LengthPercentageOrAuto::Length(0.0), padding_left: 0.0, padding_right: 0.0, padding_top: 0.0, padding_bottom: 0.0, border_left: 0.0, border_right: 0.0, border_top: 0.0, border_bottom: 0.0, border_box_sizing: false, direction: Direction::Ltr, content_height: None, } }
#[test] fn baseline_ignores_every_offset_and_uses_the_static_position() { let out = call(resolve_baseline, &values()); assert_eq!(out.left, 10.0, "the static position, not the explicit left:30px"); assert_eq!(out.top, 20.0, "the static position, not the explicit top:50px"); assert_eq!(out.used_width, 100.0); assert_eq!(out.used_height, 40.0); }
#[test] fn baseline_folds_border_box_sizing_back_to_a_content_size() { let mut v = values(); v.border_box_sizing = true; v.padding_left = 5.0; v.padding_right = 5.0; v.border_top = 2.0; v.border_bottom = 2.0; let out = call(resolve_baseline, &v); assert_eq!(out.used_width, 90.0, "100 - 2*5 padding"); assert_eq!(out.used_height, 36.0, "40 - 2*2 border"); }
#[test] fn the_baseline_resolver_answers_a_call_with_the_values_of_that_call() { let v = values(); assert_eq!(call(resolve_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 absolute::call")] fn the_baseline_resolver_refuses_a_call_the_engine_did_not_make() { resolve_baseline(&inputs_from(&values())); }
#[test] fn inputs_are_the_engine_values_of_the_same_name() { let i = inputs_from(&values()); assert_eq!(i.static_left, 10.0); assert_eq!(i.left, LengthPercentageOrAuto::Length(30.0)); }}