Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
Rust
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235//! CSS 2.1 §11.1.1's `overflow` property, reduced to the one question the three-engine//! oracle can answer without platform-dependent pixels: does a box get a scrollbar on an//! axis. The dump's per-node probe (`docs/dump-format.md` -> "Scrollbar probe") reports//! each element's client and scroll dimensions, which differ exactly when the element//! scrolls that axis, and `harness::compare::agrees_on_scrollbars` scores this seam//! against the browsers' unanimous verdict. One plain-data function a generated candidate//! implements://!//! - [`resolve`]-shaped ([`OverflowResolver`]): given a box's computed `overflow`, its own//! client (padding-box) size and the extent of its content, returns whether a scrollbar//! appears on each axis.//!//! Scope. In: `visible`, `hidden`, `scroll` and `auto` on block-level boxes, and whether a//! scrollbar appears in the boolean sense. Out: the pixel width a scrollbar steals from//! its descendants (platform- and theme-dependent, and deliberately not in the dump//! contract), `overflow-x`/`overflow-y` as independent values, scroll position,//! interactivity, nested scroll containers, and painting a scrollbar in `paint.rs`.//!//! Two things a candidate has to get right that the property's name does not suggest,//! both of them measured off Chromium, Firefox and WebKit rather than read off the//! property's definition (all three agree on every case in this seam's corpus)://!//! - A box reports overflow when its content spills past its padding box whatever its own//! `overflow` value -- `visible` included. What `overflow` changes is the other//! direction: a box that clips keeps its own spill out of its ancestors' extents, which//! is why [`OverflowInputs::content_width`] already has a clipping descendant's spill//! removed before a candidate sees it.//! - The root element's client box is the viewport's, not its own, so the root reports//! overflow whenever the page is larger than the viewport. [`inputs_from_layout`]//! supplies that client box; [`OverflowInputs::is_root`] says which box it was.//!//! [`resolve_baseline`] is the seam's deliberately wrong starting candidate: no box ever//! gets a scrollbar, which is what `crates/layout` did before this seam existed -- nothing//! in it resolved `overflow` at all.
/// The computed `overflow` value, as CSS 2.1 11.1.1 defines it.#[derive(Debug, Clone, Copy, PartialEq, Eq)]pub enum Overflow { Visible, Hidden, Scroll, Auto,}
impl Overflow { /// The computed keyword as `getComputedStyle` spells it. Anything unrecognized is /// `visible`, the initial value. pub fn parse(keyword: &str) -> Overflow { match keyword.trim() { "hidden" => Overflow::Hidden, "scroll" => Overflow::Scroll, "auto" => Overflow::Auto, _ => Overflow::Visible, } }
/// Whether this value clips the box's content, keeping the box's own spill out of its /// ancestors' extents. Every value but `visible` does. pub fn scrolls(self) -> bool { !matches!(self, Overflow::Visible) }}
/// Plain-data inputs to one box's scrollbar decision. `content_width`/`content_height` are/// the extent of everything the box lays out inside it, measured from its own content-box/// origin; `client_width`/`client_height` are its padding box, the same pair CSSOM-View/// reports. `fragment.rs` measures both from the already-laid-out fragment tree, so a/// candidate here is never asked to redo layout.#[derive(Debug, Clone, Copy, PartialEq)]pub struct OverflowInputs { pub overflow: Overflow, pub client_width: f64, pub client_height: f64, pub content_width: f64, pub content_height: f64, /// Whether `box-sizing` is `border-box` rather than `content-box` /// (`dom::computed::is_border_box`). It changes what the box's own `width` meant, and /// so whether content that fits the border box still overflows the padding box. pub border_box_sizing: bool, /// Whether this is the root element, whose `client_width`/`client_height` above are /// the viewport's rather than its own box's (this module's own doc comment). pub is_root: bool,}
/// Whether a scrollbar appears on each axis, in the boolean sense the dump's probe/// reports and nothing more.#[derive(Debug, Clone, Copy, PartialEq)]pub struct OverflowOutputs { pub scrollbar_x: bool, pub scrollbar_y: bool,}
/// 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 OverflowResolver = fn(&OverflowInputs) -> OverflowOutputs;
/// The seam's deliberately wrong starting candidate (this module's own doc comment): no/// box ever scrolls, whatever its `overflow` value and however far its content spills.pub fn resolve_baseline(_inputs: &OverflowInputs) -> OverflowOutputs { OverflowOutputs { scrollbar_x: false, scrollbar_y: false }}
/// Every box's inputs, read off the finished fragment tree rather than computed during/// layout -- the same shape `crate::stacking::StackingInputs::from_layout` takes, and for/// the same reason: this seam's scope stops at whether a scrollbar appears, so nothing it/// decides feeds back into where a box is placed. `viewport` supplies the root element's/// client box, which is the viewport's rather than the root box's own.////// A child that clips contributes only its own border box to its parent's content extent;/// a child whose `overflow` is `visible` contributes its whole subtree, since its own/// spilled content keeps spilling into the parent's scrolling area.pub fn inputs_from_layout( document: &dom::Document, root: &crate::fragment::Fragment, viewport: (f64, f64),) -> Vec<(dom::node::NodeId, OverflowInputs)> { let mut out = Vec::new(); scan(document, root, 0.0, 0.0, true, viewport, &mut out); out}
/// Appends `frag`'s inputs and its descendants', and returns the subtree's own extent in/// absolute coordinates as `(right, bottom)`.fn scan( document: &dom::Document, frag: &crate::fragment::Fragment, parent_x: f64, parent_y: f64, is_root: bool, viewport: (f64, f64), out: &mut Vec<(dom::node::NodeId, OverflowInputs)>,) -> (f64, f64) { let x = parent_x + frag.offset_x; let y = parent_y + frag.offset_y;
let border_left = dom::computed::border_left_width(document, frag.node); let border_right = dom::computed::border_right_width(document, frag.node); let border_top = dom::computed::border_top_width(document, frag.node); let border_bottom = dom::computed::border_bottom_width(document, frag.node);
let padding_box_left = x + border_left; let padding_box_top = y + border_top; let padding_box_width = (frag.width - border_left - border_right).max(0.0); let padding_box_height = (frag.height - border_top - border_bottom).max(0.0);
let mut content_right = padding_box_left; let mut content_bottom = padding_box_top; for child in &frag.children { let (child_right, child_bottom) = scan(document, child, x, y, false, viewport, out); content_right = content_right.max(child_right); content_bottom = content_bottom.max(child_bottom); }
// `overflow-x` alone: `overflow-x`/`overflow-y` as independent values are out of this // seam's scope (`specs/overflow-scrollbars.md`), and every corpus it is scored on sets // only the `overflow` shorthand, so the two computed values are always equal there. let overflow = Overflow::parse(&dom::computed::overflow_x(document, frag.node));
let (client_width, client_height) = if is_root { viewport } else { (padding_box_width, padding_box_height) };
out.push(( frag.node, OverflowInputs { overflow, client_width, client_height, content_width: (content_right - padding_box_left).max(0.0), content_height: (content_bottom - padding_box_top).max(0.0), border_box_sizing: dom::computed::is_border_box(document, frag.node), is_root, }, ));
if overflow.scrolls() { // A clipping box's own spill stops here: its parent's scrolling area sees the box // itself, not what the box clipped. (x + frag.width, y + frag.height) } else { ((x + frag.width).max(content_right), (y + frag.height).max(content_bottom)) }}
#[cfg(test)]mod tests { use super::*;
fn inputs() -> OverflowInputs { OverflowInputs { overflow: Overflow::Scroll, client_width: 100.0, client_height: 50.0, content_width: 400.0, content_height: 200.0, border_box_sizing: false, is_root: false, } }
#[test] fn baseline_reports_no_scrollbar_on_a_box_that_plainly_scrolls() { let out = resolve_baseline(&inputs()); assert!(!out.scrollbar_x, "not the 400px of content in a 100px box"); assert!(!out.scrollbar_y, "not the 200px of content in a 50px box"); }
#[test] fn baseline_reports_no_scrollbar_on_the_root_either() { let mut i = inputs(); i.overflow = Overflow::Visible; i.is_root = true; let out = resolve_baseline(&i); assert!(!out.scrollbar_x); assert!(!out.scrollbar_y); }
#[test] fn every_value_but_visible_gives_the_box_a_scrolling_box() { assert!(!Overflow::Visible.scrolls()); assert!(Overflow::Hidden.scrolls()); assert!(Overflow::Scroll.scrolls()); assert!(Overflow::Auto.scrolls()); }
#[test] fn parse_falls_back_to_the_initial_value() { assert_eq!(Overflow::parse("hidden"), Overflow::Hidden); assert_eq!(Overflow::parse("scroll"), Overflow::Scroll); assert_eq!(Overflow::parse("auto"), Overflow::Auto); assert_eq!(Overflow::parse("visible"), Overflow::Visible); assert_eq!(Overflow::parse("clip"), Overflow::Visible, "not a CSS 2.1 value"); }}