Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
Rust
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235//! CSS 2.1 §10.1's four rules for which box is a given box's containing block, isolated//! as a seam the way `crate::absolute` isolates §10.3.7/§10.6.4's equations. One//! plain-data function a generated candidate implements://!//! - [`resolve`]-shaped ([`ContainingBlockResolver`]): given a box's own `position`, its//! ancestors nearest-first (each with its own `position`, whether it is a block//! container, and its content-box and padding-box rects), and the viewport rect,//! returns the rect that box's containing block covers, which §10.1 rule chose it, and//! which ancestor it belongs to.//!//! The four rules, as §10.1 states them://!//! 1. The root element's containing block is the initial containing block -- a rectangle//! the size of the viewport, anchored at the canvas origin.//! 2. A `static` or `relative` box's containing block is the content edge of its nearest//! block-container ancestor.//! 3. A `fixed` box's containing block is the viewport.//! 4. An `absolute` box's containing block is the padding edge of the nearest ancestor//! whose `position` is not `static`; if there is no such ancestor, the initial//! containing block.//!//! Rules 3 and 4 are the pair that decides geometry in this engine: `crate::fragment`//! takes a box out of normal flow exactly when its `position` is `absolute` or `fixed`//! (`dom::computed::Position::is_out_of_flow`), and asks this seam which rect to solve//! §10.3.7/§10.6.4 against. Rules 1 and 2 are the rest of the function's domain -- an//! in-flow box's containing block, which normal flow already produces by construction --//! and a candidate has to get them right for the corpus's own static and root cases, not//! because `fragment.rs` calls this seam to lay an in-flow box out.//!//! Every rect here is in viewport coordinates: the x/y of the root element's border box//! is the canvas origin, and `fragment.rs` builds the ancestor chain by accumulating each//! box's placement on the way up. The rect [`resolve`] returns is read in the same frame.//!//! Scope. In: `position: fixed` on block-level, non-replaced boxes with no text; the four//! §10.1 rules above; a fixed box escaping positioned ancestors, `relative` and//! `absolute` alike. Out: scrolling and scroll position (the oracle never scrolls, so a//! fixed box and a box against the initial containing block land in the same place at//! scroll 0); `transform`/`filter`/`contain` making an ancestor the containing block for//! its fixed descendants, which is CSS Transforms rather than CSS 2.1;//! `position: sticky`, which `dom::computed::position` still maps to `Static`; paged//! media, where §10.1 rule 3 names the page area instead of the viewport.//!//! [`resolve_baseline`] is the seam's deliberately wrong starting candidate: a `fixed`//! box is treated as a `static` one, sent to rule 2's nearest block container instead of//! the viewport -- the engine's behaviour before this seam existed, when//! `dom::computed::position` mapped `fixed` to `Static` and a fixed box was laid out in//! normal flow. It also answers rule 4's no-positioned-ancestor fallback with the root//! element's own padding box rather than the initial containing block, which is what//! `fragment.rs` did for an absolutely positioned box that reached the tree root.
use dom::computed::Position;
/// One box's rect, in viewport coordinates (this module's own doc comment).#[derive(Debug, Clone, Copy, PartialEq)]pub struct Rect { pub x: f64, pub y: f64, pub width: f64, pub height: f64,}
/// One ancestor of the box whose containing block is being resolved, as CSS 2.1 §10.1/// needs to see it: what kind of positioning scheme it uses, whether it is the kind of/// box rule 2 can land on, and the two edges rules 2 and 4 name.#[derive(Debug, Clone, Copy, PartialEq)]pub struct AncestorBox { pub position: Position, /// Whether this ancestor is a block container -- the only kind of box CSS 2.1 §10.1 /// rule 2 lets a `static` or `relative` box's containing block be. Every box this /// engine builds is one today (there is no inline or table layout yet), so a /// candidate that reads it is right about the rule rather than about this corpus. pub is_block_container: bool, /// Whether this ancestor is the root element (`<html>`) -- the box CSS 2.1 §10.1 /// rule 1 gives the initial containing block to, and the last entry in any complete /// ancestor chain. pub is_root: bool, pub content_box: Rect, pub padding_box: Rect,}
/// Plain-data inputs to one box's containing-block choice.#[derive(Debug, Clone, PartialEq)]pub struct ContainingBlockInputs { /// The box's own positioning scheme -- what CSS 2.1 §10.1 branches on first. pub position: Position, /// Every ancestor of the box, nearest first, ending at the root element. Empty for /// the root element itself, which has no ancestor box and takes rule 1. pub ancestors: Vec<AncestorBox>, /// The initial containing block: a viewport-sized rectangle at the canvas origin. pub viewport: Rect,}
/// Which CSS 2.1 §10.1 rule chose the rect, carried for reporting rather than for/// geometry -- `fragment.rs` reads [`ContainingBlockOutputs::rect`] and/// [`ContainingBlockOutputs::ancestor`], never this.#[derive(Debug, Clone, Copy, PartialEq, Eq)]pub enum ContainingBlockRule { /// Rule 1: the box is the root element, so its containing block is the initial /// containing block. RootElement, /// Rule 2: a `static` or `relative` box takes the content edge of its nearest /// block-container ancestor. NearestBlockContainerContentEdge, /// Rule 3: a `fixed` box takes the viewport. Viewport, /// Rule 4: an `absolute` box takes the padding edge of the nearest ancestor whose /// `position` is not `static`. NearestPositionedAncestorPaddingEdge, /// Rule 4's fallback: an `absolute` box with no such ancestor takes the initial /// containing block. InitialContainingBlock,}
/// The chosen containing block.#[derive(Debug, Clone, Copy, PartialEq)]pub struct ContainingBlockOutputs { /// The rect the containing block covers, in viewport coordinates. pub rect: Rect, pub rule: ContainingBlockRule, /// Which ancestor `rect` belongs to, as an index into /// [`ContainingBlockInputs::ancestors`], or `None` when the rect is the initial /// containing block and belongs to no box. Reported, not consumed: `fragment.rs` reads /// `rect` alone and always hangs the resolved box's fragment from its DOM parent, /// since that is where a browser's own `getClientRects` measures it from. pub ancestor: Option<usize>,}
/// One candidate's implementation of the seam: plain data in, plain data out. Every/// candidate module the generation loop compiles exports a function of this type named/// `resolve`.pub type ContainingBlockResolver = fn(&ContainingBlockInputs) -> ContainingBlockOutputs;
/// The seam's deliberately wrong starting candidate (this module's own doc comment): a/// `fixed` box goes to rule 2's nearest block container as if it were `static`, and rule/// 4's fallback lands on the root element's padding box rather than the initial/// containing block.pub fn resolve_baseline(inputs: &ContainingBlockInputs) -> ContainingBlockOutputs { let Some(last) = inputs.ancestors.len().checked_sub(1) else { return ContainingBlockOutputs { rect: inputs.viewport, rule: ContainingBlockRule::RootElement, ancestor: None, }; };
match inputs.position { Position::Absolute => { let found = inputs .ancestors .iter() .position(|a| a.position != Position::Static) .unwrap_or(last); ContainingBlockOutputs { rect: inputs.ancestors[found].padding_box, rule: ContainingBlockRule::NearestPositionedAncestorPaddingEdge, ancestor: Some(found), } } Position::Static | Position::Relative | Position::Fixed => { let found = inputs.ancestors.iter().position(|a| a.is_block_container).unwrap_or(last); ContainingBlockOutputs { rect: inputs.ancestors[found].content_box, rule: ContainingBlockRule::NearestBlockContainerContentEdge, ancestor: Some(found), } } }}
#[cfg(test)]mod tests { use super::*;
const VIEWPORT: Rect = Rect { x: 0.0, y: 0.0, width: 800.0, height: 600.0 };
fn ancestor(position: Position, is_root: bool, x: f64, y: f64) -> AncestorBox { AncestorBox { position, is_block_container: true, is_root, content_box: Rect { x: x + 10.0, y: y + 10.0, width: 200.0, height: 100.0 }, padding_box: Rect { x, y, width: 220.0, height: 120.0 }, } }
fn inputs(position: Position, ancestors: Vec<AncestorBox>) -> ContainingBlockInputs { ContainingBlockInputs { position, ancestors, viewport: VIEWPORT } }
#[test] fn the_root_element_takes_the_initial_containing_block() { let out = resolve_baseline(&inputs(Position::Static, Vec::new())); assert_eq!(out.rect, VIEWPORT); assert_eq!(out.rule, ContainingBlockRule::RootElement); assert_eq!(out.ancestor, None); }
#[test] fn baseline_sends_an_absolute_box_to_the_nearest_positioned_ancestors_padding_edge() { let chain = vec![ ancestor(Position::Static, false, 5.0, 5.0), ancestor(Position::Relative, false, 40.0, 60.0), ancestor(Position::Static, true, 0.0, 0.0), ]; let out = resolve_baseline(&inputs(Position::Absolute, chain.clone())); assert_eq!(out.ancestor, Some(1)); assert_eq!(out.rect, chain[1].padding_box); }
#[test] fn baseline_falls_back_to_the_root_boxs_padding_edge_not_the_viewport() { let chain = vec![ ancestor(Position::Static, false, 5.0, 5.0), ancestor(Position::Static, true, 0.0, 0.0), ]; let out = resolve_baseline(&inputs(Position::Absolute, chain.clone())); assert_eq!(out.ancestor, Some(1), "the root box, which is what fragment.rs used to do"); assert_eq!(out.rect, chain[1].padding_box); assert_ne!(out.rect, VIEWPORT); }
#[test] fn baseline_treats_a_fixed_box_as_a_static_one() { let chain = vec![ ancestor(Position::Relative, false, 40.0, 60.0), ancestor(Position::Static, true, 0.0, 0.0), ]; let out = resolve_baseline(&inputs(Position::Fixed, chain.clone())); assert_eq!(out.rect, chain[0].content_box, "the nearest block container's content edge"); assert_ne!(out.rect, VIEWPORT, "not the viewport, which is what rule 3 asks for"); assert_eq!(out.rule, ContainingBlockRule::NearestBlockContainerContentEdge); }}