Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
Rust
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593//! Box tree construction, verified first by box *existence* (`roadmap.md` -> "Phase 1//! -- Engine bootstrap" step 2): whether an element generates a box at all, before its//! kind or geometry is decided.//!//! CSS 2.1 9.2.5: an element with `display: none` generates no box, and neither does//! anything in its subtree, whatever its own descendants' `display` says -- their//! subtree is simply never reached during layout, so their own `display` never gets a//! chance to matter. There is no other box-existence rule in Phase 0's scope: no//! inline content, no text, so every other computed `display` generates exactly one//! box.//!//! Box *kind* is decided here only as far as the float seam needs (`crate::float`,//! `specs/floats-bfc.md`): a box whose `float` is `left` or `right` leaves normal flow//! ([`Box::float`]), the way CSS 2.1 9.7 orders the decision -- `position: absolute`//! wins and forces `float` to `none`, and the root element cannot float. `children`//! still lists every generated child in document order, floats included: a float is//! placed against the flow position it would have had among its siblings (CSS 2.1//! 9.5.1 rules 4-6), so its slot in that order is the one structural fact//! `crate::fragment` needs to build each block formatting context's float list, and//! it keeps that list itself, in layout order, rather than reading a per-context list//! from here.//!//! Box kind is also decided here as far as the inline-atomic function needs//! (`crate::inline_atomic`, `specs/inline-atomic.md`): a box whose computed `display` is//! inline-level ([`Box::inline_level`]) participates in an inline formatting context//! rather than stacking in normal flow, and a container all of whose in-flow children//! are inline-level establishes one//! ([`BoxTree::establishes_inline_formatting_context`], CSS 2.1 9.4.2). Only//! `inline-block` is inline-level in this build: `inline` itself needs text, which//! nothing here has. Still one box per element either way -- an atomic inline is one//! opaque box on a line, not several -- so the box-existence rule above is unchanged,//! and a container that mixes block-level and inline-level children is out of scope//! (CSS 2.1 9.2.1.1's anonymous block boxes are not modelled), which is why the test//! is "all of them" rather than "any of them".//!//! **Anonymous table objects** (CSS 2.1 17.2.1) are the one place a box here is not//! generated by an element ([`Box::anonymous`], `crate::table`,//! `specs/tables-fixed-layout.md`). They are generated only inside a table whose used//! layout is CSS 2.1 17.5.2.1's fixed algorithm ([`uses_fixed_table_layout`]), which is//! the only table `crate::fragment` has a walk for: an auto-layout table's contents//! stack the way they did before this module knew the keywords, and generating a box with//! no element inside one would put a fragment with no element of its own into every dump.//! Two of 17.2.1's rules are in scope, because a fixed-layout table with cells of//! explicit size can reach them://!//! - a run of consecutive children of a `display: table` box that are not rows is//! wrapped in an anonymous `table-row` box (17.2.1's "generate missing child wrappers"//! applied to a table's non-row children), and//! - a run of consecutive children of a row that are not cells is wrapped in an//! anonymous `table-cell` box (the same rule applied to a row's non-cell children).//!//! The two compose, so a plain block child of a table is wrapped in a row and then in a//! cell. 17.2.1's remaining rule -- the anonymous *table* wrapped around a stray row or//! cell whose parent is neither -- is out of scope: such a table's `table-layout` is the//! initial `auto`, which no generated function computes. A `display: table-cell` box//! whose parent is not a table or a row therefore keeps stacking exactly the way it did//! before this module knew the keyword.//!//! An anonymous box is generated inside a parent element and has no element of its own,//! so it produces no fragment: `crate::fragment`'s table walk places its children's//! fragments under the nearest enclosing fragment that does have an element, offsets//! included, which is what keeps `fragment::Fragment`, `Fragment::walk_absolute`, the//! stacking and scrollbar walks in `crate::lib` and every dump unchanged. Its//! [`node`](Box::node) names the element it was generated inside, which is also the//! element 17.2.1 makes it inherit from.
use dom::computed::{self, Display, Float, LengthPercentageOrAuto};use dom::node::NodeId;use dom::Document;
use crate::float::FloatSide;
/// One box's identity. Every box but an anonymous table object (this module's own doc/// comment) was generated for the element its [`Box::node`] names; CSS 2.1 9.2.1.1's/// anonymous *block* boxes, around mixed inline/block content, are still out of scope/// and are not modelled here.#[derive(Debug, Clone, Copy, PartialEq, Eq)]pub struct BoxId(pub usize);
#[derive(Debug)]pub struct Box { /// The element this box was generated for, or -- for an anonymous table object /// ([`anonymous`](Box::anonymous)) -- the element it was generated inside, which is /// the one CSS 2.1 17.2.1 makes it inherit from. Two boxes can therefore name the /// same element, which is why nothing here looks a box up by its node. pub node: NodeId, pub children: Vec<BoxId>, /// `Some` for a box that has left normal flow into its block formatting context's /// float list (this module's own doc comment): its computed `float` is `left` or /// `right`, it is not absolutely positioned (CSS 2.1 9.7 step 2 turns such a box's /// `float` into `none`) and it is not the root. pub float: Option<FloatSide>, /// Whether this box is inline-level -- placed on a line by `crate::inline_atomic` /// rather than stacked in normal flow (CSS 2.1 9.2.2). True for `display: /// inline-block` and nothing else in this build (this module's own doc comment). /// False for the root, and false for a box that has left normal flow: CSS 2.1 9.7 /// computes a float's `display` to a block-level value, and 9.3.1 does the same for /// an absolutely positioned box, so neither ever joins an inline formatting context. pub inline_level: bool, /// Whether this box is an anonymous table object rather than an element's own box /// (CSS 2.1 17.2.1, this module's own doc comment): a row generated around a table's /// non-row children, or a cell generated around a row's non-cell children. Such a /// box produces no fragment, and `crate::fragment`'s table walk places its children /// under the nearest enclosing fragment instead. Which of the two it is never needs /// asking: the table walk already knows a table's children are rows and a row's /// children are cells. False for every box in a document with no table in it. pub anonymous: bool,}
/// The box tree for one document: one [`Box`] per element that generates one, in the/// same shape as the DOM restricted to those elements.#[derive(Debug)]pub struct BoxTree { boxes: Vec<Box>, root: BoxId,}
impl BoxTree { /// Builds the box tree for `document`. `display: none` prunes an element and its /// whole subtree from the walk, per the module doc comment. /// /// Panics if the root element itself is `display: none` -- every case in the /// current corpus resets `html, body { display: block }` /// (`crates/harness/src/cases.rs` `RESET`), so this cannot happen yet; a corpus /// that could produce it would need this to return `Option<Self>` instead. pub fn build(document: &Document) -> Self { let mut boxes = Vec::new(); let root = build_box(document, document.root(), &mut boxes) .expect("the root element is display: none, which the current corpus never produces"); BoxTree { boxes, root } }
pub fn boxes(&self) -> &[Box] { &self.boxes }
pub fn root(&self) -> BoxId { self.root }
/// Whether `node` generates a box in this tree. pub fn has_box_for(&self, node: NodeId) -> bool { self.boxes.iter().any(|b| b.node == node) }
/// Whether the box `id` establishes an inline formatting context (CSS 2.1 9.4.2): /// it has at least one child and every one of them is inline-level. An empty /// container establishes nothing -- there is no line box without content to put on /// one -- and so does one holding anything that is not inline-level, which covers /// both a container mixing block-level and inline-level children (CSS 2.1 9.2.1.1's /// anonymous block boxes, not modelled) and one holding a float or an absolutely /// positioned box beside its atomic inlines. All three are out of /// `crate::inline_atomic`'s scope, and a container that is any of them keeps /// stacking exactly the way it did before this test existed. /// /// `_document` is unused: CSS 2.1 9.7 and 9.3.1 already made every out-of-flow box /// block-level in [`Box::inline_level`], so the test reads the box tree alone. The /// parameter stays because `display-contents` extends this test with a rule that does /// read computed style. Anonymous table objects did not need one: a cell holding only /// atomic inlines establishes an inline formatting context by this same test, /// generated or not, and a table's own children are cells, which are never /// inline-level. pub fn establishes_inline_formatting_context(&self, _document: &Document, id: BoxId) -> bool { let children = &self.boxes[id.0].children; !children.is_empty() && children.iter().all(|c| self.boxes[c.0].inline_level) }}
fn build_box(document: &Document, node: NodeId, boxes: &mut Vec<Box>) -> Option<BoxId> { if computed::is_display_none(document, node) { return None; } let children: Vec<BoxId> = node .element_children() .into_iter() .filter_map(|child| build_box(document, child, boxes)) .collect(); let children = wrap_anonymous_table_children(document, node, children, boxes); let id = BoxId(boxes.len()); let float = float_side(document, node); boxes.push(Box { node, children, float, inline_level: is_inline_level(document, node, float), anonymous: false, }); Some(id)}
/// CSS 2.1 17.2.1's two in-scope rules applied to one parent's already-built child list/// (this module's own doc comment): inside a table, every run of consecutive children/// that are not rows becomes one anonymous row; inside a row, every run that is not a/// cell becomes one anonymous cell. Any other parent's children are returned untouched,/// which is every parent in a document with no table in it.////// The anonymous boxes a table generates are wrapped again by the row rule in turn, so a/// run of blocks directly inside a table ends up inside a row inside a cell -- the/// composition 17.2.1 describes, reached here by generating the row and then asking the/// same function about its children.fn wrap_anonymous_table_children( document: &Document, parent: NodeId, children: Vec<BoxId>, boxes: &mut Vec<Box>,) -> Vec<BoxId> { // The `display` a child of this parent must already have to stand on its own, which // is also the kind of box generated around a run of children that do not. Only a // fixed-layout table generates any of these (this module's own doc comment), and a row // is asked about the table it sits in rather than about itself, `table-layout` being // the table's property. let wanted = match computed::display(document, parent) { Display::Table if uses_fixed_table_layout(document, parent) => Display::TableRow, Display::TableRow if parent.dom_parent().is_some_and(|table| uses_fixed_table_layout(document, table)) => { Display::TableCell } _ => return children, }; let mut out: Vec<BoxId> = Vec::with_capacity(children.len()); let mut run: Vec<BoxId> = Vec::new(); for child in children { if computed::display(document, boxes[child.0].node) == wanted { flush_anonymous_run(document, parent, wanted, &mut run, &mut out, boxes); out.push(child); } else { run.push(child); } } flush_anonymous_run(document, parent, wanted, &mut run, &mut out, boxes); out}
/// Closes one run of consecutive children that need a wrapper, pushing the anonymous box/// that holds them onto `out`. A row generated inside a table has its own children/// wrapped in turn (`wrap_anonymous_table_children`'s own doc comment); a cell's do not,/// a cell's contents being ordinary block-level content.fn flush_anonymous_run( document: &Document, parent: NodeId, generated_kind: Display, run: &mut Vec<BoxId>, out: &mut Vec<BoxId>, boxes: &mut Vec<Box>,) { if run.is_empty() { return; } let mut children = std::mem::take(run); if generated_kind == Display::TableRow { children = wrap_run_in_anonymous_cells(document, parent, children, boxes); } let id = BoxId(boxes.len()); boxes.push(Box { node: parent, children, float: None, inline_level: false, anonymous: true }); out.push(id);}
/// The cell rule applied inside an anonymous row, whose parent element is the table/// rather than a row -- so the `display` a child must have to stand on its own is still/// `table-cell`, but `wrap_anonymous_table_children` cannot be asked about it by/// `display`, the anonymous row having none of its own.fn wrap_run_in_anonymous_cells( document: &Document, parent: NodeId, children: Vec<BoxId>, boxes: &mut Vec<Box>,) -> Vec<BoxId> { let mut out: Vec<BoxId> = Vec::with_capacity(children.len()); let mut run: Vec<BoxId> = Vec::new(); for child in children { if computed::display(document, boxes[child.0].node) == Display::TableCell { flush_anonymous_run(document, parent, Display::TableCell, &mut run, &mut out, boxes); out.push(child); } else { run.push(child); } } flush_anonymous_run(document, parent, Display::TableCell, &mut run, &mut out, boxes); out}
/// Whether `node`'s used table layout is CSS 2.1 17.5.2.1's fixed algorithm, the one/// `crate::table` implements and `crate::fragment` has a walk for: a `display: table` box/// whose `table-layout` computes to `fixed` and whose own `width` is not `auto`. A/// `width: auto` table uses the automatic algorithm however its `table-layout` is written/// (17.5.2.1's own paragraph on `auto`), and so does a `width` this build cannot reduce to/// a definite length -- a `calc()` mixing a length and a percentage, which reaches/// `LengthPercentageOrAuto::Unresolved`. Every other box answers false, which is what/// keeps a document with no fixed-layout table in it laying out exactly as it did before/// this rule existed.pub fn uses_fixed_table_layout(document: &Document, node: NodeId) -> bool { computed::display(document, node) == Display::Table && computed::table_layout(document, node) == computed::TableLayout::Fixed && matches!( computed::width(document, node), LengthPercentageOrAuto::Length(_) | LengthPercentageOrAuto::Percentage(_) )}
/// CSS 2.1 9.2.2 applied to one element, after the two rules that override `display`:/// a float's computed `display` becomes a block-level value (9.7 step 2), and so does an/// absolutely positioned box's (9.3.1), so neither is inline-level however it was/// written. The root element is never inline-level either (9.7 step 1).fn is_inline_level(document: &Document, node: NodeId, float: Option<FloatSide>) -> bool { if node == document.root() || float.is_some() || computed::position(document, node).is_out_of_flow() { return false; } computed::display(document, node).is_inline_level()}
/// CSS 2.1 9.7 applied to one element: an out-of-flow `position` -- `absolute` or/// `fixed` -- forces `float` to `none` (step 2), and the root element never floats (step 1 makes it `display: block`/// regardless, and there is nothing for it to float within).fn float_side(document: &Document, node: NodeId) -> Option<FloatSide> { if node == document.root() || computed::position(document, node).is_out_of_flow() { return None; } match computed::float(document, node) { Float::None => None, Float::Left => Some(FloatSide::Left), Float::Right => Some(FloatSide::Right), }}
#[cfg(test)]mod tests { use super::*;
/// `document()`-shaped markup (`crates/harness/src/cases.rs`) parses to a fixed /// element order: `[html, head, style, body, div(hidden), div(inner), div(visible)]` /// -- html5ever synthesizes `head`/`body` per the standard HTML tree-construction /// algorithm even though this markup never writes them out. #[test] fn display_none_prunes_the_subtree() { let html = "<!doctype html><style>.hidden { display: none; }</style>\ <div class=hidden><div class=inner></div></div><div id=visible></div>"; let document = Document::parse(html); let tree = BoxTree::build(&document);
let elements = document.elements_in_document_order(); assert_eq!(elements.len(), 7, "expected [html, head, style, body, hidden, inner, visible]"); let (hidden, inner, visible) = (elements[4], elements[5], elements[6]); assert_eq!(hidden.tag(), "DIV"); assert_eq!(inner.tag(), "DIV"); assert_eq!(visible.tag(), "DIV");
assert!(!tree.has_box_for(hidden), "a display:none element must have no box"); assert!(!tree.has_box_for(inner), "a child of a display:none element must have no box either"); assert!(tree.has_box_for(visible), "an ordinary sibling must still have a box"); }
/// CSS 2.1 9.7: `float` is read as-is on an ordinary box, forced to `none` by /// `position: absolute`, and a float still keeps its slot among its siblings. #[test] fn float_leaves_flow_unless_absolutely_positioned() { let html = "<!doctype html><style>.f{float:left}.a{float:right;position:absolute}</style>\ <div class=f></div><div class=a></div><div></div>"; let document = Document::parse(html); let tree = BoxTree::build(&document); let body = tree.boxes().iter().find(|b| b.node.tag() == "BODY").expect("body generates a box"); assert_eq!(body.children.len(), 3, "every child, floats included, stays in `children`"); let kinds: Vec<Option<FloatSide>> = body.children.iter().map(|c| tree.boxes()[c.0].float).collect(); assert_eq!(kinds, vec![Some(FloatSide::Left), None, None]); assert_eq!(tree.boxes()[tree.root().0].float, None); }
fn body_box<'a>(tree: &'a BoxTree) -> (BoxId, &'a Box) { tree.boxes() .iter() .enumerate() .find(|(_, b)| b.node.tag() == "BODY") .map(|(i, b)| (BoxId(i), b)) .expect("body generates a box") }
/// CSS 2.1 9.4.2: a container whose in-flow children are all `inline-block` /// establishes an inline formatting context; one holding a block-level child does /// not, and neither does an empty one. #[test] fn a_container_of_only_atomic_inlines_establishes_an_inline_formatting_context() { let html = "<!doctype html><style>.i{display:inline-block;width:10px;height:10px}</style>\ <div class=i></div><div class=i></div>"; let document = Document::parse(html); let tree = BoxTree::build(&document); let (body, _) = body_box(&tree); assert!(tree.establishes_inline_formatting_context(&document, body)); for &child in &tree.boxes()[body.0].children { assert!(tree.boxes()[child.0].inline_level); } }
#[test] fn a_mixed_container_establishes_none_and_neither_does_an_empty_one() { let html = "<!doctype html><style>.i{display:inline-block}</style>\ <div class=i></div><div></div>"; let document = Document::parse(html); let tree = BoxTree::build(&document); let (body, _) = body_box(&tree); assert!( !tree.establishes_inline_formatting_context(&document, body), "a block-level sibling keeps the container stacking, out of this scope" );
let empty = Document::parse("<!doctype html><div></div>"); let empty_tree = BoxTree::build(&empty); let (_, leaf) = (BoxId(0), &empty_tree.boxes()[0]); assert!(leaf.children.is_empty(), "the innermost box is the empty div"); assert!(!empty_tree.establishes_inline_formatting_context(&empty, BoxId(0))); }
/// CSS 2.1 9.7 and 9.3.1 both compute an out-of-flow box's `display` to a /// block-level value, so neither a float nor an absolutely positioned box is /// inline-level however it was written -- and neither keeps its container from /// establishing an inline formatting context around the rest. #[test] fn an_out_of_flow_inline_block_is_not_inline_level() { let html = "<!doctype html><style>.i{display:inline-block}\ .f{display:inline-block;float:left}.a{display:inline-block;position:absolute}\ </style><div class=i></div><div class=f></div><div class=a></div>"; let document = Document::parse(html); let tree = BoxTree::build(&document); let (body, _) = body_box(&tree); let inline_level: Vec<bool> = tree.boxes()[body.0].children.iter().map(|c| tree.boxes()[c.0].inline_level).collect(); assert_eq!(inline_level, vec![true, false, false]); assert!( !tree.establishes_inline_formatting_context(&document, body), "a float or an absolutely positioned box beside an atomic inline is out of scope, \ so the container keeps stacking" ); }
/// The box `id`'s children as `(display, anonymous)` pairs, an anonymous box reading /// as the `display` of the element it was generated inside (`Box::node`'s own doc /// comment), which is never the keyword it stands for -- so the flag is what the /// assertions below read, not the keyword. fn child_shape(document: &Document, tree: &BoxTree, id: BoxId) -> Vec<(computed::Display, bool)> { tree.boxes()[id.0] .children .iter() .map(|&c| { let b = &tree.boxes()[c.0]; (computed::display(document, b.node), b.anonymous) }) .collect() }
fn box_with_class<'a>(document: &Document, tree: &'a BoxTree, class: &str) -> BoxId { tree.boxes() .iter() .enumerate() .find(|(_, b)| { !b.anonymous && document.nodes()[b.node.index()].as_element().is_some_and(|e| e.attr("class") == Some(class)) }) .map(|(i, _)| BoxId(i)) .unwrap_or_else(|| panic!("no box for .{class}")) }
/// A fixed-layout table, the only kind that generates anonymous table objects /// ([`uses_fixed_table_layout`]); every test below builds its table this way. const FIXED_TABLE: &str = ".t{display:table;table-layout:fixed;width:300px}";
/// CSS 2.1 17.2.1: cells that are direct children of a table are wrapped in one /// anonymous row, and consecutive cells share it rather than getting one each. #[test] fn cells_directly_inside_a_table_share_one_anonymous_row() { let html = &format!( "<!doctype html><style>{FIXED_TABLE}.c{{display:table-cell}}</style>\ <div class=t><div class=c></div><div class=c></div></div>" ); let document = Document::parse(html); let tree = BoxTree::build(&document); let table = box_with_class(&document, &tree, "t"); let shape = child_shape(&document, &tree, table); assert_eq!(shape.len(), 1, "one anonymous row around both cells"); assert!(shape[0].1, "the table's only child is anonymous"); let row = tree.boxes()[table.0].children[0]; let cells = child_shape(&document, &tree, row); assert_eq!(cells, vec![ (computed::Display::TableCell, false), (computed::Display::TableCell, false), ]); }
/// The same rule the other way round: a row element's children that are not cells are /// wrapped in one anonymous cell, and a real cell beside them keeps its own box. #[test] fn non_cell_children_of_a_row_get_one_anonymous_cell_each_run() { let html = &format!( "<!doctype html><style>{FIXED_TABLE}.r{{display:table-row}}\ .c{{display:table-cell}}</style>\ <div class=t><div class=r><div class=b></div><div class=b></div>\ <div class=c></div><div class=b></div></div></div>" ); let document = Document::parse(html); let tree = BoxTree::build(&document); let row = box_with_class(&document, &tree, "r"); let shape = child_shape(&document, &tree, row); assert_eq!(shape.len(), 3, "one anonymous cell per run of blocks, with the real cell between them"); assert!(shape[0].1, "the first two blocks share an anonymous cell"); assert_eq!(shape[1], (computed::Display::TableCell, false)); assert!(shape[2].1, "the trailing block gets its own anonymous cell"); assert_eq!(tree.boxes()[tree.boxes()[row.0].children[0].0].children.len(), 2); }
/// The two rules compose (this module's own doc comment): a block directly inside a /// table ends up inside an anonymous row inside an anonymous cell. #[test] fn a_block_directly_inside_a_table_is_wrapped_in_a_row_and_then_a_cell() { let html = &format!("<!doctype html><style>{FIXED_TABLE}</style><div class=t><div class=b></div></div>"); let document = Document::parse(html); let tree = BoxTree::build(&document); let table = box_with_class(&document, &tree, "t"); let row = tree.boxes()[table.0].children[0]; assert!(tree.boxes()[row.0].anonymous); let cell = tree.boxes()[row.0].children[0]; assert!(tree.boxes()[cell.0].anonymous); assert_eq!(tree.boxes()[cell.0].children.len(), 1, "the block itself, one box as always"); }
/// An anonymous box is generated inside a parent element and inherits from it, so it /// carries that element's `NodeId` (`Box::node`'s own doc comment) -- and generates no /// box of its own for box-existence purposes, which is what keeps every dump /// unchanged. #[test] fn an_anonymous_box_carries_the_element_it_was_generated_inside() { let html = &format!( "<!doctype html><style>{FIXED_TABLE}.c{{display:table-cell}}</style>\ <div class=t><div class=c></div></div>" ); let document = Document::parse(html); let tree = BoxTree::build(&document); let table = box_with_class(&document, &tree, "t"); let row = tree.boxes()[table.0].children[0]; assert_eq!(tree.boxes()[row.0].node, tree.boxes()[table.0].node); assert_eq!(tree.boxes().iter().filter(|b| b.anonymous).count(), 1); }
/// An auto-layout table generates no anonymous box at all: `crate::fragment` has no /// walk for one, so a generated box there would put a fragment with no element of its /// own into every dump ([`uses_fixed_table_layout`]). #[test] fn an_auto_layout_table_generates_no_anonymous_box() { for table_style in [ "display:table", // table-layout defaults to auto "display:table;width:300px", // still auto "display:table;table-layout:fixed", // fixed, but width: auto "display:table;table-layout:fixed;width:calc(50% + 10px)", // no definite width ] { let html = format!( "<!doctype html><style>.t{{{table_style}}}.c{{display:table-cell}}</style>\ <div class=t><div class=c></div></div>" ); let document = Document::parse(&html); let tree = BoxTree::build(&document); assert!( tree.boxes().iter().all(|b| !b.anonymous), "{table_style} does not use the fixed algorithm, so nothing is wrapped" ); } }
/// 17.2.1's anonymous-table rule is out of scope, so a stray cell whose parent is /// neither a table nor a row is left exactly as it was before this module knew the /// keyword: one block-level box, no wrapper. #[test] fn a_stray_cell_outside_a_table_is_not_wrapped() { let html = "<!doctype html><style>.c{display:table-cell}</style>\ <div><div class=c></div><div></div></div>"; let document = Document::parse(html); let tree = BoxTree::build(&document); assert_eq!(tree.boxes().iter().filter(|b| b.anonymous).count(), 0); let container = tree.boxes().iter().find(|b| b.children.len() == 2).expect("the plain container"); assert_eq!(container.children.len(), 2, "both children keep their own box, unwrapped"); }
/// A document with no table in it generates no anonymous box at all -- the property /// every corpus case that predates `specs/tables-fixed-layout.md` relies on. #[test] fn a_document_without_a_table_generates_no_anonymous_box() { let html = "<!doctype html><style>.i{display:inline-block}</style>\ <div><div class=i></div><div></div></div><div></div>"; let document = Document::parse(html); let tree = BoxTree::build(&document); assert!(tree.boxes().iter().all(|b| !b.anonymous)); }}