Something went wrong. Try again.
we (web engine): Experimental web browser project to understand the limits of Claude
Something went wrong. Try again.
Rust
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427//! CSS cascade and computed style resolution.//!//! For each DOM element, resolves the final computed value of every CSS property//! by collecting matching rules, applying the cascade (specificity + source order),//! handling property inheritance, and resolving relative values.
use we_css::parser::{Declaration, Stylesheet};use we_css::values::{expand_shorthand, parse_value, Color, CssValue, LengthUnit};use we_dom::{Document, NodeData, NodeId};
use crate::matching::collect_matching_rules;
// ---------------------------------------------------------------------------// Display// ---------------------------------------------------------------------------
/// CSS `display` property values (subset).#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]pub enum Display { Block, #[default] Inline, Flex, InlineFlex, None,}
// ---------------------------------------------------------------------------// Position// ---------------------------------------------------------------------------
/// CSS `position` property values.#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]pub enum Position { #[default] Static, Relative, Absolute, Fixed, Sticky,}
// ---------------------------------------------------------------------------// FontWeight// ---------------------------------------------------------------------------
/// CSS `font-weight` as a numeric value (100-900).#[derive(Debug, Clone, Copy, PartialEq)]pub struct FontWeight(pub f32);
impl Default for FontWeight { fn default() -> Self { FontWeight(400.0) // normal }}
// ---------------------------------------------------------------------------// FontStyle// ---------------------------------------------------------------------------
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]pub enum FontStyle { #[default] Normal, Italic, Oblique,}
// ---------------------------------------------------------------------------// TextAlign// ---------------------------------------------------------------------------
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]pub enum TextAlign { #[default] Left, Right, Center, Justify,}
// ---------------------------------------------------------------------------// TextDecoration// ---------------------------------------------------------------------------
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]pub enum TextDecoration { #[default] None, Underline, Overline, LineThrough,}
// ---------------------------------------------------------------------------// BoxSizing// ---------------------------------------------------------------------------
/// CSS `box-sizing` property values.#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]pub enum BoxSizing { #[default] ContentBox, BorderBox,}
// ---------------------------------------------------------------------------// Float// ---------------------------------------------------------------------------
/// CSS `float` property values.#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]pub enum Float { #[default] None, Left, Right,}
// ---------------------------------------------------------------------------// Clear// ---------------------------------------------------------------------------
/// CSS `clear` property values.#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]pub enum Clear { #[default] None, Left, Right, Both,}
// ---------------------------------------------------------------------------// Overflow// ---------------------------------------------------------------------------
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]pub enum Overflow { #[default] Visible, Hidden, Scroll, Auto,}
// ---------------------------------------------------------------------------// Visibility// ---------------------------------------------------------------------------
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]pub enum Visibility { #[default] Visible, Hidden, Collapse,}
// ---------------------------------------------------------------------------// Flex enums// ---------------------------------------------------------------------------
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]pub enum FlexDirection { #[default] Row, RowReverse, Column, ColumnReverse,}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]pub enum FlexWrap { #[default] Nowrap, Wrap, WrapReverse,}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]pub enum JustifyContent { #[default] FlexStart, FlexEnd, Center, SpaceBetween, SpaceAround, SpaceEvenly,}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]pub enum AlignItems { #[default] Stretch, FlexStart, FlexEnd, Center, Baseline,}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]pub enum AlignContent { #[default] Stretch, FlexStart, FlexEnd, Center, SpaceBetween, SpaceAround,}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]pub enum AlignSelf { #[default] Auto, FlexStart, FlexEnd, Center, Baseline, Stretch,}
// ---------------------------------------------------------------------------// BorderStyle// ---------------------------------------------------------------------------
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]pub enum BorderStyle { #[default] None, Hidden, Dotted, Dashed, Solid, Double, Groove, Ridge, Inset, Outset,}
// ---------------------------------------------------------------------------// LengthOrAuto// ---------------------------------------------------------------------------
/// A computed length (resolved to px), a percentage (unresolved), or `auto`.#[derive(Debug, Clone, Copy, PartialEq, Default)]pub enum LengthOrAuto { Length(f32), /// Percentage value (0.0–100.0), resolved during layout against the containing block. Percentage(f32), #[default] Auto,}
// ---------------------------------------------------------------------------// ComputedStyle// ---------------------------------------------------------------------------
/// The fully resolved computed style for a single element.#[derive(Debug, Clone, PartialEq)]pub struct ComputedStyle { // Display pub display: Display,
// Box model: margin pub margin_top: LengthOrAuto, pub margin_right: LengthOrAuto, pub margin_bottom: LengthOrAuto, pub margin_left: LengthOrAuto,
// Box model: padding (percentages resolve against containing block width) pub padding_top: LengthOrAuto, pub padding_right: LengthOrAuto, pub padding_bottom: LengthOrAuto, pub padding_left: LengthOrAuto,
// Box model: border width pub border_top_width: f32, pub border_right_width: f32, pub border_bottom_width: f32, pub border_left_width: f32,
// Box model: border style pub border_top_style: BorderStyle, pub border_right_style: BorderStyle, pub border_bottom_style: BorderStyle, pub border_left_style: BorderStyle,
// Box model: border color pub border_top_color: Color, pub border_right_color: Color, pub border_bottom_color: Color, pub border_left_color: Color,
// Box model: dimensions pub width: LengthOrAuto, pub height: LengthOrAuto,
// Box model: sizing pub box_sizing: BoxSizing,
// Text / inherited pub color: Color, pub font_size: f32, pub font_weight: FontWeight, pub font_style: FontStyle, pub font_family: String, pub text_align: TextAlign, pub text_decoration: TextDecoration, pub line_height: f32,
// Background pub background_color: Color,
// Position pub position: Position, pub top: LengthOrAuto, pub right: LengthOrAuto, pub bottom: LengthOrAuto, pub left: LengthOrAuto, pub z_index: Option<i32>,
// Float pub float: Float, pub clear: Clear,
// Overflow pub overflow: Overflow,
// Visibility (inherited) pub visibility: Visibility,
// Flex container properties pub flex_direction: FlexDirection, pub flex_wrap: FlexWrap, pub justify_content: JustifyContent, pub align_items: AlignItems, pub align_content: AlignContent, pub row_gap: f32, pub column_gap: f32,
// Flex item properties pub flex_grow: f32, pub flex_shrink: f32, pub flex_basis: LengthOrAuto, pub align_self: AlignSelf, pub order: i32,}
impl Default for ComputedStyle { fn default() -> Self { ComputedStyle { display: Display::Inline,
margin_top: LengthOrAuto::Length(0.0), margin_right: LengthOrAuto::Length(0.0), margin_bottom: LengthOrAuto::Length(0.0), margin_left: LengthOrAuto::Length(0.0),
padding_top: LengthOrAuto::Length(0.0), padding_right: LengthOrAuto::Length(0.0), padding_bottom: LengthOrAuto::Length(0.0), padding_left: LengthOrAuto::Length(0.0),
border_top_width: 0.0, border_right_width: 0.0, border_bottom_width: 0.0, border_left_width: 0.0,
border_top_style: BorderStyle::None, border_right_style: BorderStyle::None, border_bottom_style: BorderStyle::None, border_left_style: BorderStyle::None,
border_top_color: Color::rgb(0, 0, 0), border_right_color: Color::rgb(0, 0, 0), border_bottom_color: Color::rgb(0, 0, 0), border_left_color: Color::rgb(0, 0, 0),
width: LengthOrAuto::Auto, height: LengthOrAuto::Auto,
box_sizing: BoxSizing::ContentBox,
color: Color::rgb(0, 0, 0), font_size: 16.0, font_weight: FontWeight(400.0), font_style: FontStyle::Normal, font_family: String::new(), text_align: TextAlign::Left, text_decoration: TextDecoration::None, line_height: 19.2, // 1.2 * 16
background_color: Color::new(0, 0, 0, 0), // transparent
position: Position::Static, top: LengthOrAuto::Auto, right: LengthOrAuto::Auto, bottom: LengthOrAuto::Auto, left: LengthOrAuto::Auto, z_index: None,
float: Float::None, clear: Clear::None,
overflow: Overflow::Visible, visibility: Visibility::Visible,
flex_direction: FlexDirection::Row, flex_wrap: FlexWrap::Nowrap, justify_content: JustifyContent::FlexStart, align_items: AlignItems::Stretch, align_content: AlignContent::Stretch, row_gap: 0.0, column_gap: 0.0,
flex_grow: 0.0, flex_shrink: 1.0, flex_basis: LengthOrAuto::Auto, align_self: AlignSelf::Auto, order: 0, } }}
// ---------------------------------------------------------------------------// Property classification: inherited vs non-inherited// ---------------------------------------------------------------------------
fn is_inherited_property(property: &str) -> bool { matches!( property, "color" | "font-size" | "font-weight" | "font-style" | "font-family" | "text-align" | "text-decoration" | "line-height" | "visibility" )}
// ---------------------------------------------------------------------------// User-agent stylesheet// ---------------------------------------------------------------------------
/// Returns the user-agent default stylesheet.pub fn ua_stylesheet() -> Stylesheet { use we_css::parser::Parser; Parser::parse(UA_CSS)}
const UA_CSS: &str = r#"html, body, div, p, pre, h1, h2, h3, h4, h5, h6,ul, ol, li, blockquote, section, article, nav,header, footer, main, hr { display: block;}
span, a, em, strong, b, i, u, code, small, sub, sup, br { display: inline;}
head, title, script, style, link, meta { display: none;}
body { margin: 8px;}
h1 { font-size: 2em; margin-top: 0.67em; margin-bottom: 0.67em; font-weight: bold;}
h2 { font-size: 1.5em; margin-top: 0.83em; margin-bottom: 0.83em; font-weight: bold;}
h3 { font-size: 1.17em; margin-top: 1em; margin-bottom: 1em; font-weight: bold;}
h4 { font-size: 1em; margin-top: 1.33em; margin-bottom: 1.33em; font-weight: bold;}
h5 { font-size: 0.83em; margin-top: 1.67em; margin-bottom: 1.67em; font-weight: bold;}
h6 { font-size: 0.67em; margin-top: 2.33em; margin-bottom: 2.33em; font-weight: bold;}
p { margin-top: 1em; margin-bottom: 1em;}
strong, b { font-weight: bold;}
em, i { font-style: italic;}
a { color: blue; text-decoration: underline;}
u { text-decoration: underline;}"#;
// ---------------------------------------------------------------------------// Resolve a CssValue to f32 px given context// ---------------------------------------------------------------------------
fn resolve_length_unit(value: f64, unit: LengthUnit, em_base: f32, viewport: (f32, f32)) -> f32 { let v = value as f32; let (vw, vh) = viewport; match unit { LengthUnit::Px => v, LengthUnit::Em => v * em_base, LengthUnit::Rem => v * 16.0, // root font size is always 16px for now LengthUnit::Pt => v * (96.0 / 72.0), LengthUnit::Cm => v * (96.0 / 2.54), LengthUnit::Mm => v * (96.0 / 25.4), LengthUnit::In => v * 96.0, LengthUnit::Pc => v * 16.0, LengthUnit::Vw => v * vw / 100.0, LengthUnit::Vh => v * vh / 100.0, LengthUnit::Vmin => v * vw.min(vh) / 100.0, LengthUnit::Vmax => v * vw.max(vh) / 100.0, }}
/// Resolve a CSS value to `LengthOrAuto` for layout properties (width, height,/// margin, padding, top/right/bottom/left). Percentages are preserved as/// `LengthOrAuto::Percentage` for later resolution during layout against the/// containing block.fn resolve_layout_length_or_auto( value: &CssValue, current_font_size: f32, viewport: (f32, f32),) -> LengthOrAuto { match value { CssValue::Auto => LengthOrAuto::Auto, CssValue::Percentage(p) => LengthOrAuto::Percentage(*p as f32), CssValue::Length(n, unit) => { LengthOrAuto::Length(resolve_length_unit(*n, *unit, current_font_size, viewport)) } CssValue::Zero => LengthOrAuto::Length(0.0), CssValue::Number(n) if *n == 0.0 => LengthOrAuto::Length(0.0), _ => LengthOrAuto::Auto, }}
fn resolve_color(value: &CssValue, current_color: Color) -> Option<Color> { match value { CssValue::Color(c) => Some(*c), CssValue::CurrentColor => Some(current_color), CssValue::Transparent => Some(Color::new(0, 0, 0, 0)), _ => None, }}
// ---------------------------------------------------------------------------// Apply a single property value to a ComputedStyle// ---------------------------------------------------------------------------
fn apply_property( style: &mut ComputedStyle, property: &str, value: &CssValue, parent: &ComputedStyle, viewport: (f32, f32),) { // Handle inherit/initial/unset match value { CssValue::Inherit => { inherit_property(style, property, parent); return; } CssValue::Initial => { reset_property_to_initial(style, property); return; } CssValue::Unset => { if is_inherited_property(property) { inherit_property(style, property, parent); } else { reset_property_to_initial(style, property); } return; } _ => {} }
let parent_fs = parent.font_size; let current_fs = style.font_size;
match property { "display" => { style.display = match value { CssValue::Keyword(k) => match k.as_str() { "block" => Display::Block, "inline" => Display::Inline, "flex" => Display::Flex, "inline-flex" => Display::InlineFlex, _ => Display::Block, }, CssValue::None => Display::None, _ => style.display, }; }
// Margin (percentages preserved for layout resolution) "margin-top" => { style.margin_top = resolve_layout_length_or_auto(value, current_fs, viewport); } "margin-right" => { style.margin_right = resolve_layout_length_or_auto(value, current_fs, viewport); } "margin-bottom" => { style.margin_bottom = resolve_layout_length_or_auto(value, current_fs, viewport); } "margin-left" => { style.margin_left = resolve_layout_length_or_auto(value, current_fs, viewport); }
// Padding (percentages preserved for layout resolution) "padding-top" => { style.padding_top = resolve_layout_length_or_auto(value, current_fs, viewport); } "padding-right" => { style.padding_right = resolve_layout_length_or_auto(value, current_fs, viewport); } "padding-bottom" => { style.padding_bottom = resolve_layout_length_or_auto(value, current_fs, viewport); } "padding-left" => { style.padding_left = resolve_layout_length_or_auto(value, current_fs, viewport); }
// Border width "border-top-width" | "border-right-width" | "border-bottom-width" | "border-left-width" => { let w = resolve_border_width(value, parent_fs, viewport); match property { "border-top-width" => style.border_top_width = w, "border-right-width" => style.border_right_width = w, "border-bottom-width" => style.border_bottom_width = w, "border-left-width" => style.border_left_width = w, _ => {} } }
// Border width shorthand (single value applied to all sides) "border-width" => { let w = resolve_border_width(value, parent_fs, viewport); style.border_top_width = w; style.border_right_width = w; style.border_bottom_width = w; style.border_left_width = w; }
// Border style "border-top-style" | "border-right-style" | "border-bottom-style" | "border-left-style" => { let s = parse_border_style(value); match property { "border-top-style" => style.border_top_style = s, "border-right-style" => style.border_right_style = s, "border-bottom-style" => style.border_bottom_style = s, "border-left-style" => style.border_left_style = s, _ => {} } }
"border-style" => { let s = parse_border_style(value); style.border_top_style = s; style.border_right_style = s; style.border_bottom_style = s; style.border_left_style = s; }
// Border color "border-top-color" | "border-right-color" | "border-bottom-color" | "border-left-color" => { if let Some(c) = resolve_color(value, style.color) { match property { "border-top-color" => style.border_top_color = c, "border-right-color" => style.border_right_color = c, "border-bottom-color" => style.border_bottom_color = c, "border-left-color" => style.border_left_color = c, _ => {} } } }
"border-color" => { if let Some(c) = resolve_color(value, style.color) { style.border_top_color = c; style.border_right_color = c; style.border_bottom_color = c; style.border_left_color = c; } }
// Dimensions (percentages preserved for layout resolution) "width" => { style.width = resolve_layout_length_or_auto(value, current_fs, viewport); } "height" => { style.height = resolve_layout_length_or_auto(value, current_fs, viewport); }
// Box sizing "box-sizing" => { style.box_sizing = match value { CssValue::Keyword(k) => match k.as_str() { "content-box" => BoxSizing::ContentBox, "border-box" => BoxSizing::BorderBox, _ => style.box_sizing, }, _ => style.box_sizing, }; }
// Color (inherited) "color" => { if let Some(c) = resolve_color(value, parent.color) { style.color = c; // Update border colors to match (currentColor default) } }
// Font-size (inherited) — special: em units relative to parent "font-size" => { match value { CssValue::Length(n, unit) => { style.font_size = resolve_length_unit(*n, *unit, parent_fs, viewport); } CssValue::Percentage(p) => { style.font_size = (*p / 100.0) as f32 * parent_fs; } CssValue::Zero => { style.font_size = 0.0; } CssValue::Keyword(k) => { style.font_size = match k.as_str() { "xx-small" => 9.0, "x-small" => 10.0, "small" => 13.0, "medium" => 16.0, "large" => 18.0, "x-large" => 24.0, "xx-large" => 32.0, "smaller" => parent_fs * 0.833, "larger" => parent_fs * 1.2, _ => style.font_size, }; } _ => {} } // Update line-height when font-size changes style.line_height = style.font_size * 1.2; }
// Font-weight (inherited) "font-weight" => { style.font_weight = match value { CssValue::Keyword(k) => match k.as_str() { "normal" => FontWeight(400.0), "bold" => FontWeight(700.0), "lighter" => FontWeight((parent.font_weight.0 - 100.0).max(100.0)), "bolder" => FontWeight((parent.font_weight.0 + 300.0).min(900.0)), _ => style.font_weight, }, CssValue::Number(n) => FontWeight(*n as f32), _ => style.font_weight, }; }
// Font-style (inherited) "font-style" => { style.font_style = match value { CssValue::Keyword(k) => match k.as_str() { "normal" => FontStyle::Normal, "italic" => FontStyle::Italic, "oblique" => FontStyle::Oblique, _ => style.font_style, }, _ => style.font_style, }; }
// Font-family (inherited) "font-family" => { if let CssValue::String(s) | CssValue::Keyword(s) = value { style.font_family = s.clone(); } }
// Text-align (inherited) "text-align" => { style.text_align = match value { CssValue::Keyword(k) => match k.as_str() { "left" => TextAlign::Left, "right" => TextAlign::Right, "center" => TextAlign::Center, "justify" => TextAlign::Justify, _ => style.text_align, }, _ => style.text_align, }; }
// Text-decoration (inherited) "text-decoration" => { style.text_decoration = match value { CssValue::Keyword(k) => match k.as_str() { "underline" => TextDecoration::Underline, "overline" => TextDecoration::Overline, "line-through" => TextDecoration::LineThrough, _ => style.text_decoration, }, CssValue::None => TextDecoration::None, _ => style.text_decoration, }; }
// Line-height (inherited) "line-height" => match value { CssValue::Keyword(k) if k == "normal" => { style.line_height = style.font_size * 1.2; } CssValue::Number(n) => { style.line_height = *n as f32 * style.font_size; } CssValue::Length(n, unit) => { style.line_height = resolve_length_unit(*n, *unit, style.font_size, viewport); } CssValue::Percentage(p) => { style.line_height = (*p / 100.0) as f32 * style.font_size; } _ => {} },
// Background color "background-color" => { if let Some(c) = resolve_color(value, style.color) { style.background_color = c; } }
// Position "position" => { style.position = match value { CssValue::Keyword(k) => match k.as_str() { "static" => Position::Static, "relative" => Position::Relative, "absolute" => Position::Absolute, "fixed" => Position::Fixed, "sticky" => Position::Sticky, _ => style.position, }, _ => style.position, }; }
// Position offsets (percentages preserved for layout resolution) "top" => style.top = resolve_layout_length_or_auto(value, current_fs, viewport), "right" => style.right = resolve_layout_length_or_auto(value, current_fs, viewport), "bottom" => style.bottom = resolve_layout_length_or_auto(value, current_fs, viewport), "left" => style.left = resolve_layout_length_or_auto(value, current_fs, viewport),
// z-index "z-index" => { style.z_index = match value { CssValue::Number(n) => Some(*n as i32), CssValue::Keyword(k) if k == "auto" => None, _ => style.z_index, }; }
// Float "float" => { style.float = match value { CssValue::Keyword(k) => match k.as_str() { "none" => Float::None, "left" => Float::Left, "right" => Float::Right, _ => style.float, }, _ => style.float, }; }
// Clear "clear" => { style.clear = match value { CssValue::Keyword(k) => match k.as_str() { "none" => Clear::None, "left" => Clear::Left, "right" => Clear::Right, "both" => Clear::Both, _ => style.clear, }, _ => style.clear, }; }
// Overflow "overflow" => { style.overflow = match value { CssValue::Keyword(k) => match k.as_str() { "visible" => Overflow::Visible, "hidden" => Overflow::Hidden, "scroll" => Overflow::Scroll, _ => style.overflow, }, CssValue::Auto => Overflow::Auto, _ => style.overflow, }; }
// Visibility (inherited) "visibility" => { style.visibility = match value { CssValue::Keyword(k) => match k.as_str() { "visible" => Visibility::Visible, "hidden" => Visibility::Hidden, "collapse" => Visibility::Collapse, _ => style.visibility, }, _ => style.visibility, }; }
// Flex container properties "flex-direction" => { style.flex_direction = match value { CssValue::Keyword(k) => match k.as_str() { "row" => FlexDirection::Row, "row-reverse" => FlexDirection::RowReverse, "column" => FlexDirection::Column, "column-reverse" => FlexDirection::ColumnReverse, _ => style.flex_direction, }, _ => style.flex_direction, }; } "flex-wrap" => { style.flex_wrap = match value { CssValue::Keyword(k) => match k.as_str() { "nowrap" => FlexWrap::Nowrap, "wrap" => FlexWrap::Wrap, "wrap-reverse" => FlexWrap::WrapReverse, _ => style.flex_wrap, }, _ => style.flex_wrap, }; } "justify-content" => { style.justify_content = match value { CssValue::Keyword(k) => match k.as_str() { "flex-start" => JustifyContent::FlexStart, "flex-end" => JustifyContent::FlexEnd, "center" => JustifyContent::Center, "space-between" => JustifyContent::SpaceBetween, "space-around" => JustifyContent::SpaceAround, "space-evenly" => JustifyContent::SpaceEvenly, _ => style.justify_content, }, _ => style.justify_content, }; } "align-items" => { style.align_items = match value { CssValue::Keyword(k) => match k.as_str() { "stretch" => AlignItems::Stretch, "flex-start" => AlignItems::FlexStart, "flex-end" => AlignItems::FlexEnd, "center" => AlignItems::Center, "baseline" => AlignItems::Baseline, _ => style.align_items, }, _ => style.align_items, }; } "align-content" => { style.align_content = match value { CssValue::Keyword(k) => match k.as_str() { "stretch" => AlignContent::Stretch, "flex-start" => AlignContent::FlexStart, "flex-end" => AlignContent::FlexEnd, "center" => AlignContent::Center, "space-between" => AlignContent::SpaceBetween, "space-around" => AlignContent::SpaceAround, _ => style.align_content, }, _ => style.align_content, }; } "row-gap" => { if let CssValue::Length(n, unit) = value { style.row_gap = resolve_length_unit(*n, *unit, current_fs, viewport); } else if let CssValue::Zero = value { style.row_gap = 0.0; } } "column-gap" => { if let CssValue::Length(n, unit) = value { style.column_gap = resolve_length_unit(*n, *unit, current_fs, viewport); } else if let CssValue::Zero = value { style.column_gap = 0.0; } }
// Flex item properties "flex-grow" => { if let CssValue::Number(n) = value { style.flex_grow = *n as f32; } else if let CssValue::Zero = value { style.flex_grow = 0.0; } } "flex-shrink" => { if let CssValue::Number(n) = value { style.flex_shrink = *n as f32; } else if let CssValue::Zero = value { style.flex_shrink = 0.0; } } "flex-basis" => { style.flex_basis = resolve_layout_length_or_auto(value, current_fs, viewport); } "align-self" => { style.align_self = match value { CssValue::Keyword(k) => match k.as_str() { "flex-start" => AlignSelf::FlexStart, "flex-end" => AlignSelf::FlexEnd, "center" => AlignSelf::Center, "baseline" => AlignSelf::Baseline, "stretch" => AlignSelf::Stretch, _ => style.align_self, }, CssValue::Auto => AlignSelf::Auto, _ => style.align_self, }; } "order" => { if let CssValue::Number(n) = value { style.order = *n as i32; } else if let CssValue::Zero = value { style.order = 0; } }
_ => {} // Unknown property — ignore }}
fn resolve_border_width(value: &CssValue, em_base: f32, viewport: (f32, f32)) -> f32 { match value { CssValue::Length(n, unit) => resolve_length_unit(*n, *unit, em_base, viewport), CssValue::Zero => 0.0, CssValue::Number(n) if *n == 0.0 => 0.0, CssValue::Keyword(k) => match k.as_str() { "thin" => 1.0, "medium" => 3.0, "thick" => 5.0, _ => 0.0, }, _ => 0.0, }}
fn parse_border_style(value: &CssValue) -> BorderStyle { match value { CssValue::Keyword(k) => match k.as_str() { "none" => BorderStyle::None, "hidden" => BorderStyle::Hidden, "dotted" => BorderStyle::Dotted, "dashed" => BorderStyle::Dashed, "solid" => BorderStyle::Solid, "double" => BorderStyle::Double, "groove" => BorderStyle::Groove, "ridge" => BorderStyle::Ridge, "inset" => BorderStyle::Inset, "outset" => BorderStyle::Outset, _ => BorderStyle::None, }, CssValue::None => BorderStyle::None, _ => BorderStyle::None, }}
fn inherit_property(style: &mut ComputedStyle, property: &str, parent: &ComputedStyle) { match property { "color" => style.color = parent.color, "font-size" => { style.font_size = parent.font_size; style.line_height = style.font_size * 1.2; } "font-weight" => style.font_weight = parent.font_weight, "font-style" => style.font_style = parent.font_style, "font-family" => style.font_family = parent.font_family.clone(), "text-align" => style.text_align = parent.text_align, "text-decoration" => style.text_decoration = parent.text_decoration, "line-height" => style.line_height = parent.line_height, "visibility" => style.visibility = parent.visibility, // Non-inherited properties: inherit from parent if explicitly requested "display" => style.display = parent.display, "margin-top" => style.margin_top = parent.margin_top, "margin-right" => style.margin_right = parent.margin_right, "margin-bottom" => style.margin_bottom = parent.margin_bottom, "margin-left" => style.margin_left = parent.margin_left, "padding-top" => style.padding_top = parent.padding_top, "padding-right" => style.padding_right = parent.padding_right, "padding-bottom" => style.padding_bottom = parent.padding_bottom, "padding-left" => style.padding_left = parent.padding_left, "width" => style.width = parent.width, "height" => style.height = parent.height, "box-sizing" => style.box_sizing = parent.box_sizing, "background-color" => style.background_color = parent.background_color, "position" => style.position = parent.position, "float" => style.float = parent.float, "clear" => style.clear = parent.clear, "overflow" => style.overflow = parent.overflow, "flex-direction" => style.flex_direction = parent.flex_direction, "flex-wrap" => style.flex_wrap = parent.flex_wrap, "justify-content" => style.justify_content = parent.justify_content, "align-items" => style.align_items = parent.align_items, "align-content" => style.align_content = parent.align_content, "row-gap" => style.row_gap = parent.row_gap, "column-gap" => style.column_gap = parent.column_gap, "flex-grow" => style.flex_grow = parent.flex_grow, "flex-shrink" => style.flex_shrink = parent.flex_shrink, "flex-basis" => style.flex_basis = parent.flex_basis, "align-self" => style.align_self = parent.align_self, "order" => style.order = parent.order, _ => {} }}
fn reset_property_to_initial(style: &mut ComputedStyle, property: &str) { let initial = ComputedStyle::default(); match property { "display" => style.display = initial.display, "margin-top" => style.margin_top = initial.margin_top, "margin-right" => style.margin_right = initial.margin_right, "margin-bottom" => style.margin_bottom = initial.margin_bottom, "margin-left" => style.margin_left = initial.margin_left, "padding-top" => style.padding_top = initial.padding_top, "padding-right" => style.padding_right = initial.padding_right, "padding-bottom" => style.padding_bottom = initial.padding_bottom, "padding-left" => style.padding_left = initial.padding_left, "border-top-width" => style.border_top_width = initial.border_top_width, "border-right-width" => style.border_right_width = initial.border_right_width, "border-bottom-width" => style.border_bottom_width = initial.border_bottom_width, "border-left-width" => style.border_left_width = initial.border_left_width, "width" => style.width = initial.width, "height" => style.height = initial.height, "box-sizing" => style.box_sizing = initial.box_sizing, "color" => style.color = initial.color, "font-size" => { style.font_size = initial.font_size; style.line_height = initial.line_height; } "font-weight" => style.font_weight = initial.font_weight, "font-style" => style.font_style = initial.font_style, "font-family" => style.font_family = initial.font_family.clone(), "text-align" => style.text_align = initial.text_align, "text-decoration" => style.text_decoration = initial.text_decoration, "line-height" => style.line_height = initial.line_height, "background-color" => style.background_color = initial.background_color, "position" => style.position = initial.position, "top" => style.top = initial.top, "right" => style.right = initial.right, "bottom" => style.bottom = initial.bottom, "left" => style.left = initial.left, "float" => style.float = initial.float, "clear" => style.clear = initial.clear, "overflow" => style.overflow = initial.overflow, "visibility" => style.visibility = initial.visibility, "flex-direction" => style.flex_direction = initial.flex_direction, "flex-wrap" => style.flex_wrap = initial.flex_wrap, "justify-content" => style.justify_content = initial.justify_content, "align-items" => style.align_items = initial.align_items, "align-content" => style.align_content = initial.align_content, "row-gap" => style.row_gap = initial.row_gap, "column-gap" => style.column_gap = initial.column_gap, "flex-grow" => style.flex_grow = initial.flex_grow, "flex-shrink" => style.flex_shrink = initial.flex_shrink, "flex-basis" => style.flex_basis = initial.flex_basis, "align-self" => style.align_self = initial.align_self, "order" => style.order = initial.order, _ => {} }}
// ---------------------------------------------------------------------------// Styled tree// ---------------------------------------------------------------------------
/// A node in the styled tree: a DOM node paired with its computed style.#[derive(Debug)]pub struct StyledNode { pub node: NodeId, pub style: ComputedStyle, pub children: Vec<StyledNode>,}
/// Extract CSS stylesheets from `<style>` elements in the document.////// Walks the DOM tree, finds all `<style>` elements, extracts their text/// content, and parses each as a CSS stylesheet. Returns stylesheets in/// document order.pub fn extract_stylesheets(doc: &Document) -> Vec<Stylesheet> { let mut stylesheets = Vec::new(); collect_style_elements(doc, doc.root(), &mut stylesheets); stylesheets}
fn collect_style_elements(doc: &Document, node: NodeId, stylesheets: &mut Vec<Stylesheet>) { match doc.node_data(node) { NodeData::Element { tag_name, .. } if tag_name == "style" => { let mut css_text = String::new(); for child in doc.children(node) { if let NodeData::Text { data } = doc.node_data(child) { css_text.push_str(data); } } if !css_text.is_empty() { stylesheets.push(we_css::parser::Parser::parse(&css_text)); } } _ => { for child in doc.children(node) { collect_style_elements(doc, child, stylesheets); } } }}
/// Resolve styles for an entire document tree.////// `stylesheets` is a list of author stylesheets (the UA stylesheet is/// automatically prepended).pub fn resolve_styles( doc: &Document, author_stylesheets: &[Stylesheet], viewport: (f32, f32),) -> Option<StyledNode> { let ua = ua_stylesheet();
// Combine UA + author stylesheets into a single list for rule collection. // UA rules come first (lower priority), author rules come after. let mut combined = Stylesheet { rules: ua.rules.clone(), }; for ss in author_stylesheets { combined.rules.extend(ss.rules.iter().cloned()); }
let root = doc.root(); resolve_node(doc, root, &combined, &ComputedStyle::default(), viewport)}
fn resolve_node( doc: &Document, node: NodeId, stylesheet: &Stylesheet, parent_style: &ComputedStyle, viewport: (f32, f32),) -> Option<StyledNode> { match doc.node_data(node) { NodeData::Document => { // Document node: resolve children, return first element child or wrapper. let mut children = Vec::new(); for child in doc.children(node) { if let Some(styled) = resolve_node(doc, child, stylesheet, parent_style, viewport) { children.push(styled); } } if children.len() == 1 { children.into_iter().next() } else if children.is_empty() { None } else { Some(StyledNode { node, style: parent_style.clone(), children, }) } } NodeData::Element { .. } => { let style = compute_style_for_element(doc, node, stylesheet, parent_style, viewport);
if style.display == Display::None { return None; }
let mut children = Vec::new(); for child in doc.children(node) { if let Some(styled) = resolve_node(doc, child, stylesheet, &style, viewport) { children.push(styled); } }
Some(StyledNode { node, style, children, }) } NodeData::Text { data } => { if data.trim().is_empty() { return None; } // Text nodes inherit all properties from their parent. Some(StyledNode { node, style: parent_style.clone(), children: Vec::new(), }) } NodeData::Comment { .. } => None, }}
/// Compute the style for a single element node.fn compute_style_for_element( doc: &Document, node: NodeId, stylesheet: &Stylesheet, parent_style: &ComputedStyle, viewport: (f32, f32),) -> ComputedStyle { // Start from initial values, inheriting inherited properties from parent let mut style = ComputedStyle { color: parent_style.color, font_size: parent_style.font_size, font_weight: parent_style.font_weight, font_style: parent_style.font_style, font_family: parent_style.font_family.clone(), text_align: parent_style.text_align, text_decoration: parent_style.text_decoration, line_height: parent_style.line_height, visibility: parent_style.visibility, ..ComputedStyle::default() };
// Step 2: Collect matching rules, sorted by specificity + source order let matched_rules = collect_matching_rules(doc, node, stylesheet);
// Step 3: Separate normal and !important declarations let mut normal_decls: Vec<(String, CssValue)> = Vec::new(); let mut important_decls: Vec<(String, CssValue)> = Vec::new();
for matched in &matched_rules { for decl in &matched.rule.declarations { let property = &decl.property;
// Try shorthand expansion first if let Some(longhands) = expand_shorthand(property, &decl.value, decl.important) { for lh in longhands { if lh.important { important_decls.push((lh.property, lh.value)); } else { normal_decls.push((lh.property, lh.value)); } } } else { // Regular longhand property let value = parse_value(&decl.value); if decl.important { important_decls.push((property.clone(), value)); } else { normal_decls.push((property.clone(), value)); } } } }
// Step 4: Apply inline style declarations (from style attribute). // Inline styles have specificity (1,0,0,0) — higher than any selector. // We apply them after stylesheet rules so they override. let inline_decls = parse_inline_style(doc, node);
// Step 5: Apply normal declarations (already in specificity order) for (prop, value) in &normal_decls { apply_property(&mut style, prop, value, parent_style, viewport); }
// Step 6: Apply inline style normal declarations (override stylesheet normals) for decl in &inline_decls { if !decl.important { let property = decl.property.as_str(); if let Some(longhands) = expand_shorthand(property, &decl.value, false) { for lh in &longhands { apply_property(&mut style, &lh.property, &lh.value, parent_style, viewport); } } else { let value = parse_value(&decl.value); apply_property(&mut style, property, &value, parent_style, viewport); } } }
// Step 7: Apply !important declarations (override everything normal) for (prop, value) in &important_decls { apply_property(&mut style, prop, value, parent_style, viewport); }
// Step 8: Apply inline style !important declarations (highest priority) for decl in &inline_decls { if decl.important { let property = decl.property.as_str(); if let Some(longhands) = expand_shorthand(property, &decl.value, true) { for lh in &longhands { apply_property(&mut style, &lh.property, &lh.value, parent_style, viewport); } } else { let value = parse_value(&decl.value); apply_property(&mut style, property, &value, parent_style, viewport); } } }
style}
/// Parse inline style from the `style` attribute of an element.fn parse_inline_style(doc: &Document, node: NodeId) -> Vec<Declaration> { if let Some(style_attr) = doc.get_attribute(node, "style") { // Wrap in a dummy rule so the CSS parser can parse it let css = format!("x {{ {style_attr} }}"); let ss = we_css::parser::Parser::parse(&css); if let Some(we_css::parser::Rule::Style(rule)) = ss.rules.into_iter().next() { rule.declarations } else { Vec::new() } } else { Vec::new() }}
// ---------------------------------------------------------------------------// Tests// ---------------------------------------------------------------------------
#[cfg(test)]mod tests { use super::*; use we_css::parser::Parser;
fn make_doc_with_body() -> (Document, NodeId, NodeId, NodeId) { let mut doc = Document::new(); let root = doc.root(); let html = doc.create_element("html"); let body = doc.create_element("body"); doc.append_child(root, html); doc.append_child(html, body); (doc, root, html, body) }
// ----------------------------------------------------------------------- // UA defaults // -----------------------------------------------------------------------
#[test] fn ua_body_has_8px_margin() { let (doc, _, _, _) = make_doc_with_body(); let styled = resolve_styles(&doc, &[], (800.0, 600.0)).unwrap(); // styled is <html>, first child is <body> let body = &styled.children[0]; assert_eq!(body.style.margin_top, LengthOrAuto::Length(8.0)); assert_eq!(body.style.margin_right, LengthOrAuto::Length(8.0)); assert_eq!(body.style.margin_bottom, LengthOrAuto::Length(8.0)); assert_eq!(body.style.margin_left, LengthOrAuto::Length(8.0)); }
#[test] fn ua_h1_font_size() { let (mut doc, _, _, body) = make_doc_with_body(); let h1 = doc.create_element("h1"); let text = doc.create_text("Title"); doc.append_child(body, h1); doc.append_child(h1, text);
let styled = resolve_styles(&doc, &[], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let h1_node = &body_node.children[0];
// h1 = 2em of parent (16px) = 32px assert_eq!(h1_node.style.font_size, 32.0); assert_eq!(h1_node.style.font_weight, FontWeight(700.0)); }
#[test] fn ua_h2_font_size() { let (mut doc, _, _, body) = make_doc_with_body(); let h2 = doc.create_element("h2"); doc.append_child(body, h2);
let styled = resolve_styles(&doc, &[], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let h2_node = &body_node.children[0];
// h2 = 1.5em = 24px assert_eq!(h2_node.style.font_size, 24.0); }
#[test] fn ua_p_has_1em_margins() { let (mut doc, _, _, body) = make_doc_with_body(); let p = doc.create_element("p"); let text = doc.create_text("text"); doc.append_child(body, p); doc.append_child(p, text);
let styled = resolve_styles(&doc, &[], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let p_node = &body_node.children[0];
// p margin-top/bottom = 1em = 16px assert_eq!(p_node.style.margin_top, LengthOrAuto::Length(16.0)); assert_eq!(p_node.style.margin_bottom, LengthOrAuto::Length(16.0)); }
#[test] fn ua_display_block_elements() { let (mut doc, _, _, body) = make_doc_with_body(); let div = doc.create_element("div"); doc.append_child(body, div);
let styled = resolve_styles(&doc, &[], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0]; assert_eq!(div_node.style.display, Display::Block); }
#[test] fn ua_display_inline_elements() { let (mut doc, _, _, body) = make_doc_with_body(); let p = doc.create_element("p"); let span = doc.create_element("span"); let text = doc.create_text("x"); doc.append_child(body, p); doc.append_child(p, span); doc.append_child(span, text);
let styled = resolve_styles(&doc, &[], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let p_node = &body_node.children[0]; let span_node = &p_node.children[0]; assert_eq!(span_node.style.display, Display::Inline); }
#[test] fn ua_display_none_for_head() { let (mut doc, _, html, _) = make_doc_with_body(); let head = doc.create_element("head"); let title = doc.create_element("title"); doc.append_child(html, head); doc.append_child(head, title);
let styled = resolve_styles(&doc, &[], (800.0, 600.0)).unwrap(); // head should not appear in styled tree (display: none) for child in &styled.children { if let NodeData::Element { tag_name, .. } = doc.node_data(child.node) { assert_ne!(tag_name.as_str(), "head"); } } }
// ----------------------------------------------------------------------- // Author stylesheets // -----------------------------------------------------------------------
#[test] fn author_color_override() { let (mut doc, _, _, body) = make_doc_with_body(); let p = doc.create_element("p"); let text = doc.create_text("hello"); doc.append_child(body, p); doc.append_child(p, text);
let ss = Parser::parse("p { color: red; }"); let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let p_node = &body_node.children[0]; assert_eq!(p_node.style.color, Color::rgb(255, 0, 0)); }
#[test] fn author_background_color() { let (mut doc, _, _, body) = make_doc_with_body(); let div = doc.create_element("div"); doc.append_child(body, div);
let ss = Parser::parse("div { background-color: blue; }"); let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0]; assert_eq!(div_node.style.background_color, Color::rgb(0, 0, 255)); }
#[test] fn author_font_size_px() { let (mut doc, _, _, body) = make_doc_with_body(); let p = doc.create_element("p"); doc.append_child(body, p);
let ss = Parser::parse("p { font-size: 24px; }"); let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let p_node = &body_node.children[0]; assert_eq!(p_node.style.font_size, 24.0); }
#[test] fn author_margin_px() { let (mut doc, _, _, body) = make_doc_with_body(); let div = doc.create_element("div"); doc.append_child(body, div);
let ss = Parser::parse("div { margin-top: 20px; margin-bottom: 10px; }"); let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0]; assert_eq!(div_node.style.margin_top, LengthOrAuto::Length(20.0)); assert_eq!(div_node.style.margin_bottom, LengthOrAuto::Length(10.0)); }
// ----------------------------------------------------------------------- // Cascade: specificity ordering // -----------------------------------------------------------------------
#[test] fn higher_specificity_wins() { let (mut doc, _, _, body) = make_doc_with_body(); let p = doc.create_element("p"); doc.set_attribute(p, "class", "highlight"); let text = doc.create_text("x"); doc.append_child(body, p); doc.append_child(p, text);
// .highlight (0,1,0) > p (0,0,1) let ss = Parser::parse("p { color: red; } .highlight { color: green; }"); let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let p_node = &body_node.children[0]; assert_eq!(p_node.style.color, Color::rgb(0, 128, 0)); }
#[test] fn source_order_tiebreak() { let (mut doc, _, _, body) = make_doc_with_body(); let p = doc.create_element("p"); let text = doc.create_text("x"); doc.append_child(body, p); doc.append_child(p, text);
// Same specificity: later wins let ss = Parser::parse("p { color: red; } p { color: blue; }"); let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let p_node = &body_node.children[0]; assert_eq!(p_node.style.color, Color::rgb(0, 0, 255)); }
#[test] fn important_overrides_specificity() { let (mut doc, _, _, body) = make_doc_with_body(); let p = doc.create_element("p"); doc.set_attribute(p, "id", "main"); let text = doc.create_text("x"); doc.append_child(body, p); doc.append_child(p, text);
// #main (1,0,0) has higher specificity, but p has !important let ss = Parser::parse("#main { color: blue; } p { color: red !important; }"); let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let p_node = &body_node.children[0]; assert_eq!(p_node.style.color, Color::rgb(255, 0, 0)); }
// ----------------------------------------------------------------------- // Inheritance // -----------------------------------------------------------------------
#[test] fn color_inherits_to_children() { let (mut doc, _, _, body) = make_doc_with_body(); let div = doc.create_element("div"); let p = doc.create_element("p"); let text = doc.create_text("x"); doc.append_child(body, div); doc.append_child(div, p); doc.append_child(p, text);
let ss = Parser::parse("div { color: green; }"); let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0]; let p_node = &div_node.children[0];
assert_eq!(div_node.style.color, Color::rgb(0, 128, 0)); // p inherits color from div assert_eq!(p_node.style.color, Color::rgb(0, 128, 0)); }
#[test] fn font_size_inherits() { let (mut doc, _, _, body) = make_doc_with_body(); let div = doc.create_element("div"); let p = doc.create_element("p"); let text = doc.create_text("x"); doc.append_child(body, div); doc.append_child(div, p); doc.append_child(p, text);
let ss = Parser::parse("div { font-size: 20px; }"); let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0]; let p_node = &div_node.children[0];
assert_eq!(div_node.style.font_size, 20.0); assert_eq!(p_node.style.font_size, 20.0); }
#[test] fn margin_does_not_inherit() { let (mut doc, _, _, body) = make_doc_with_body(); let div = doc.create_element("div"); let span = doc.create_element("span"); let text = doc.create_text("x"); doc.append_child(body, div); doc.append_child(div, span); doc.append_child(span, text);
let ss = Parser::parse("div { margin-top: 50px; }"); let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0]; let span_node = &div_node.children[0];
assert_eq!(div_node.style.margin_top, LengthOrAuto::Length(50.0)); // span should NOT inherit margin assert_eq!(span_node.style.margin_top, LengthOrAuto::Length(0.0)); }
// ----------------------------------------------------------------------- // inherit / initial / unset keywords // -----------------------------------------------------------------------
#[test] fn inherit_keyword_for_non_inherited() { let (mut doc, _, _, body) = make_doc_with_body(); let div = doc.create_element("div"); let p = doc.create_element("p"); let text = doc.create_text("x"); doc.append_child(body, div); doc.append_child(div, p); doc.append_child(p, text);
let ss = Parser::parse("div { background-color: red; } p { background-color: inherit; }"); let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0]; let p_node = &div_node.children[0];
assert_eq!(div_node.style.background_color, Color::rgb(255, 0, 0)); assert_eq!(p_node.style.background_color, Color::rgb(255, 0, 0)); }
#[test] fn initial_keyword_resets() { let (mut doc, _, _, body) = make_doc_with_body(); let div = doc.create_element("div"); let p = doc.create_element("p"); let text = doc.create_text("x"); doc.append_child(body, div); doc.append_child(div, p); doc.append_child(p, text);
// div sets color to red, p resets to initial (black) let ss = Parser::parse("div { color: red; } p { color: initial; }"); let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0]; let p_node = &div_node.children[0];
assert_eq!(div_node.style.color, Color::rgb(255, 0, 0)); assert_eq!(p_node.style.color, Color::rgb(0, 0, 0)); // initial }
#[test] fn unset_inherits_for_inherited_property() { let (mut doc, _, _, body) = make_doc_with_body(); let div = doc.create_element("div"); let p = doc.create_element("p"); let text = doc.create_text("x"); doc.append_child(body, div); doc.append_child(div, p); doc.append_child(p, text);
// color is inherited, so unset => inherit let ss = Parser::parse("div { color: green; } p { color: unset; }"); let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0]; let p_node = &div_node.children[0];
assert_eq!(p_node.style.color, div_node.style.color); }
#[test] fn unset_resets_for_non_inherited_property() { let (mut doc, _, _, body) = make_doc_with_body(); let div = doc.create_element("div"); let p = doc.create_element("p"); let text = doc.create_text("x"); doc.append_child(body, div); doc.append_child(div, p); doc.append_child(p, text);
// margin is non-inherited, so unset => initial (0) let ss = Parser::parse("p { margin-top: unset; }"); let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let p_node = &body_node.children[0];
// UA sets p margin-top to 1em=16px, but unset resets to initial (0) assert_eq!(p_node.style.margin_top, LengthOrAuto::Length(0.0)); }
// ----------------------------------------------------------------------- // Em unit resolution // -----------------------------------------------------------------------
#[test] fn em_margin_relative_to_font_size() { let (mut doc, _, _, body) = make_doc_with_body(); let div = doc.create_element("div"); let text = doc.create_text("x"); doc.append_child(body, div); doc.append_child(div, text);
let ss = Parser::parse("div { font-size: 20px; margin-top: 2em; }"); let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0];
assert_eq!(div_node.style.font_size, 20.0); // margin-top 2em resolves relative to the element's own computed font-size (20px) assert_eq!(div_node.style.margin_top, LengthOrAuto::Length(40.0)); }
#[test] fn em_font_size_relative_to_parent() { let (mut doc, _, _, body) = make_doc_with_body(); let div = doc.create_element("div"); let p = doc.create_element("p"); let text = doc.create_text("x"); doc.append_child(body, div); doc.append_child(div, p); doc.append_child(p, text);
let ss = Parser::parse("div { font-size: 20px; } p { font-size: 1.5em; }"); let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0]; let p_node = &div_node.children[0];
assert_eq!(div_node.style.font_size, 20.0); // p font-size = 1.5 * parent(20px) = 30px assert_eq!(p_node.style.font_size, 30.0); }
// ----------------------------------------------------------------------- // Inline styles // -----------------------------------------------------------------------
#[test] fn inline_style_overrides_stylesheet() { let (mut doc, _, _, body) = make_doc_with_body(); let p = doc.create_element("p"); doc.set_attribute(p, "style", "color: green;"); let text = doc.create_text("x"); doc.append_child(body, p); doc.append_child(p, text);
let ss = Parser::parse("p { color: red; }"); let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let p_node = &body_node.children[0];
// Inline style wins over author stylesheet assert_eq!(p_node.style.color, Color::rgb(0, 128, 0)); }
#[test] fn inline_style_important() { let (mut doc, _, _, body) = make_doc_with_body(); let p = doc.create_element("p"); doc.set_attribute(p, "style", "color: green !important;"); let text = doc.create_text("x"); doc.append_child(body, p); doc.append_child(p, text);
let ss = Parser::parse("p { color: red !important; }"); let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let p_node = &body_node.children[0];
// Inline !important beats stylesheet !important assert_eq!(p_node.style.color, Color::rgb(0, 128, 0)); }
// ----------------------------------------------------------------------- // Shorthand expansion // -----------------------------------------------------------------------
#[test] fn margin_shorthand_in_stylesheet() { let (mut doc, _, _, body) = make_doc_with_body(); let div = doc.create_element("div"); let text = doc.create_text("x"); doc.append_child(body, div); doc.append_child(div, text);
let ss = Parser::parse("div { margin: 10px 20px; }"); let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0];
assert_eq!(div_node.style.margin_top, LengthOrAuto::Length(10.0)); assert_eq!(div_node.style.margin_right, LengthOrAuto::Length(20.0)); assert_eq!(div_node.style.margin_bottom, LengthOrAuto::Length(10.0)); assert_eq!(div_node.style.margin_left, LengthOrAuto::Length(20.0)); }
#[test] fn padding_shorthand() { let (mut doc, _, _, body) = make_doc_with_body(); let div = doc.create_element("div"); let text = doc.create_text("x"); doc.append_child(body, div); doc.append_child(div, text);
let ss = Parser::parse("div { padding: 5px; }"); let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0];
assert_eq!(div_node.style.padding_top, LengthOrAuto::Length(5.0)); assert_eq!(div_node.style.padding_right, LengthOrAuto::Length(5.0)); assert_eq!(div_node.style.padding_bottom, LengthOrAuto::Length(5.0)); assert_eq!(div_node.style.padding_left, LengthOrAuto::Length(5.0)); }
// ----------------------------------------------------------------------- // UA + author cascade // -----------------------------------------------------------------------
#[test] fn author_overrides_ua() { let (mut doc, _, _, body) = make_doc_with_body(); let p = doc.create_element("p"); let text = doc.create_text("x"); doc.append_child(body, p); doc.append_child(p, text);
// UA gives p margin-top=1em=16px. Author overrides to 0. let ss = Parser::parse("p { margin-top: 0; margin-bottom: 0; }"); let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let p_node = &body_node.children[0];
assert_eq!(p_node.style.margin_top, LengthOrAuto::Length(0.0)); assert_eq!(p_node.style.margin_bottom, LengthOrAuto::Length(0.0)); }
// ----------------------------------------------------------------------- // Text node inherits parent style // -----------------------------------------------------------------------
#[test] fn text_node_inherits_style() { let (mut doc, _, _, body) = make_doc_with_body(); let p = doc.create_element("p"); let text = doc.create_text("hello"); doc.append_child(body, p); doc.append_child(p, text);
let ss = Parser::parse("p { color: red; font-size: 20px; }"); let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let p_node = &body_node.children[0]; let text_node = &p_node.children[0];
assert_eq!(text_node.style.color, Color::rgb(255, 0, 0)); assert_eq!(text_node.style.font_size, 20.0); }
// ----------------------------------------------------------------------- // Multiple stylesheets // -----------------------------------------------------------------------
#[test] fn multiple_author_stylesheets() { let (mut doc, _, _, body) = make_doc_with_body(); let p = doc.create_element("p"); let text = doc.create_text("x"); doc.append_child(body, p); doc.append_child(p, text);
let ss1 = Parser::parse("p { color: red; }"); let ss2 = Parser::parse("p { color: blue; }"); let styled = resolve_styles(&doc, &[ss1, ss2], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let p_node = &body_node.children[0];
// Later stylesheet wins (higher source order) assert_eq!(p_node.style.color, Color::rgb(0, 0, 255)); }
// ----------------------------------------------------------------------- // Border // -----------------------------------------------------------------------
#[test] fn border_shorthand_all_sides() { let (mut doc, _, _, body) = make_doc_with_body(); let div = doc.create_element("div"); let text = doc.create_text("x"); doc.append_child(body, div); doc.append_child(div, text);
let ss = Parser::parse("div { border: 2px solid red; }"); let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0];
assert_eq!(div_node.style.border_top_width, 2.0); assert_eq!(div_node.style.border_top_style, BorderStyle::Solid); assert_eq!(div_node.style.border_top_color, Color::rgb(255, 0, 0)); }
// ----------------------------------------------------------------------- // Position // -----------------------------------------------------------------------
#[test] fn position_property() { let (mut doc, _, _, body) = make_doc_with_body(); let div = doc.create_element("div"); let text = doc.create_text("x"); doc.append_child(body, div); doc.append_child(div, text);
let ss = Parser::parse("div { position: relative; top: 10px; }"); let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0];
assert_eq!(div_node.style.position, Position::Relative); assert_eq!(div_node.style.top, LengthOrAuto::Length(10.0)); }
// ----------------------------------------------------------------------- // Display: none removes from tree // -----------------------------------------------------------------------
#[test] fn display_none_removes_element() { let (mut doc, _, _, body) = make_doc_with_body(); let div1 = doc.create_element("div"); let t1 = doc.create_text("visible"); doc.append_child(body, div1); doc.append_child(div1, t1);
let div2 = doc.create_element("div"); doc.set_attribute(div2, "class", "hidden"); let t2 = doc.create_text("hidden"); doc.append_child(body, div2); doc.append_child(div2, t2);
let ss = Parser::parse(".hidden { display: none; }"); let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0];
// Only div1 should appear assert_eq!(body_node.children.len(), 1); }
// ----------------------------------------------------------------------- // Visibility // -----------------------------------------------------------------------
#[test] fn visibility_inherited() { let (mut doc, _, _, body) = make_doc_with_body(); let div = doc.create_element("div"); let p = doc.create_element("p"); let text = doc.create_text("x"); doc.append_child(body, div); doc.append_child(div, p); doc.append_child(p, text);
let ss = Parser::parse("div { visibility: hidden; }"); let styled = resolve_styles(&doc, &[ss], (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0]; let p_node = &div_node.children[0];
assert_eq!(div_node.style.visibility, Visibility::Hidden); assert_eq!(p_node.style.visibility, Visibility::Hidden); }
// ----------------------------------------------------------------------- // extract_stylesheets // -----------------------------------------------------------------------
#[test] fn extract_single_style_element() { let (mut doc, _, html, _) = make_doc_with_body(); let head = doc.create_element("head"); let style = doc.create_element("style"); let css_text = doc.create_text("p { color: red; }"); doc.append_child(html, head); doc.append_child(head, style); doc.append_child(style, css_text);
let sheets = extract_stylesheets(&doc); assert_eq!(sheets.len(), 1); assert!(!sheets[0].rules.is_empty()); }
#[test] fn extract_multiple_style_elements() { let (mut doc, _, html, _) = make_doc_with_body(); let head = doc.create_element("head"); doc.append_child(html, head);
let style1 = doc.create_element("style"); let css1 = doc.create_text("p { color: red; }"); doc.append_child(head, style1); doc.append_child(style1, css1);
let style2 = doc.create_element("style"); let css2 = doc.create_text("div { margin: 10px; }"); doc.append_child(head, style2); doc.append_child(style2, css2);
let sheets = extract_stylesheets(&doc); assert_eq!(sheets.len(), 2); }
#[test] fn extract_no_style_elements() { let (doc, _, _, _) = make_doc_with_body(); let sheets = extract_stylesheets(&doc); assert!(sheets.is_empty()); }
#[test] fn extract_empty_style_element() { let (mut doc, _, html, _) = make_doc_with_body(); let head = doc.create_element("head"); let style = doc.create_element("style"); doc.append_child(html, head); doc.append_child(head, style); // No text child — empty style element.
let sheets = extract_stylesheets(&doc); assert!(sheets.is_empty()); }
#[test] fn extract_style_in_body() { let (mut doc, _, _, body) = make_doc_with_body(); // Style elements in body should also be extracted. let style = doc.create_element("style"); let css_text = doc.create_text("h1 { font-size: 2em; }"); doc.append_child(body, style); doc.append_child(style, css_text);
let sheets = extract_stylesheets(&doc); assert_eq!(sheets.len(), 1); }
#[test] fn extract_and_resolve_styles_from_dom() { // Build a DOM with <style> and verify resolved styles. let (mut doc, _, html, body) = make_doc_with_body(); let head = doc.create_element("head"); let style = doc.create_element("style"); let css_text = doc.create_text("p { color: red; font-size: 24px; }"); doc.append_child(html, head); doc.append_child(head, style); doc.append_child(style, css_text);
let p = doc.create_element("p"); let text = doc.create_text("hello"); doc.append_child(body, p); doc.append_child(p, text);
let sheets = extract_stylesheets(&doc); let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let p_node = &body_node.children[0];
assert_eq!(p_node.style.color, Color::rgb(255, 0, 0)); assert_eq!(p_node.style.font_size, 24.0); }
#[test] fn style_attribute_works_with_extract() { // Inline style attributes should work even without <style> elements. let (mut doc, _, _, body) = make_doc_with_body(); let div = doc.create_element("div"); doc.set_attribute(div, "style", "color: green; margin-top: 20px;"); let text = doc.create_text("styled"); doc.append_child(body, div); doc.append_child(div, text);
let sheets = extract_stylesheets(&doc); assert!(sheets.is_empty());
let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let div_node = &body_node.children[0];
assert_eq!(div_node.style.color, Color::rgb(0, 128, 0)); assert_eq!(div_node.style.margin_top, LengthOrAuto::Length(20.0)); }
#[test] fn style_element_and_inline_style_combined() { let (mut doc, _, html, body) = make_doc_with_body(); let head = doc.create_element("head"); let style = doc.create_element("style"); let css_text = doc.create_text("p { color: red; font-size: 20px; }"); doc.append_child(html, head); doc.append_child(head, style); doc.append_child(style, css_text);
let p = doc.create_element("p"); // Inline style overrides stylesheet for color. doc.set_attribute(p, "style", "color: blue;"); let text = doc.create_text("hello"); doc.append_child(body, p); doc.append_child(p, text);
let sheets = extract_stylesheets(&doc); let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let p_node = &body_node.children[0];
// Inline style wins for color. assert_eq!(p_node.style.color, Color::rgb(0, 0, 255)); // Font-size comes from stylesheet. assert_eq!(p_node.style.font_size, 20.0); }
#[test] fn multiple_style_elements_cascade_correctly() { let (mut doc, _, html, body) = make_doc_with_body(); let head = doc.create_element("head"); doc.append_child(html, head);
// First stylesheet sets color to red. let style1 = doc.create_element("style"); let css1 = doc.create_text("p { color: red; }"); doc.append_child(head, style1); doc.append_child(style1, css1);
// Second stylesheet sets color to blue (later wins in cascade). let style2 = doc.create_element("style"); let css2 = doc.create_text("p { color: blue; }"); doc.append_child(head, style2); doc.append_child(style2, css2);
let p = doc.create_element("p"); let text = doc.create_text("hello"); doc.append_child(body, p); doc.append_child(p, text);
let sheets = extract_stylesheets(&doc); assert_eq!(sheets.len(), 2);
let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let body_node = &styled.children[0]; let p_node = &body_node.children[0];
// Later stylesheet wins. assert_eq!(p_node.style.color, Color::rgb(0, 0, 255)); }
#[test] fn box_sizing_content_box_default() { let html_str = r#"<!DOCTYPE html><html><head></head><body><div>Test</div></body></html>"#; let doc = we_html::parse_html(html_str); let sheets = extract_stylesheets(&doc); let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let body = &styled.children[0]; let div = &body.children[0]; assert_eq!(div.style.box_sizing, BoxSizing::ContentBox); }
#[test] fn box_sizing_border_box_parsed() { let html_str = r#"<!DOCTYPE html><html><head><style>div { box-sizing: border-box; }</style></head><body><div>Test</div></body></html>"#; let doc = we_html::parse_html(html_str); let sheets = extract_stylesheets(&doc); let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let body = &styled.children[0]; let div = &body.children[0]; assert_eq!(div.style.box_sizing, BoxSizing::BorderBox); }
#[test] fn box_sizing_not_inherited() { let html_str = r#"<!DOCTYPE html><html><head><style>.parent { box-sizing: border-box; }</style></head><body><div class="parent"><p>Child</p></div></body></html>"#; let doc = we_html::parse_html(html_str); let sheets = extract_stylesheets(&doc); let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let body = &styled.children[0]; let parent = &body.children[0]; let child = &parent.children[0]; assert_eq!(parent.style.box_sizing, BoxSizing::BorderBox); assert_eq!(child.style.box_sizing, BoxSizing::ContentBox); }
#[test] fn z_index_parsing() { let html_str = r#"<!DOCTYPE html><html><head><style>.a { z-index: 5; }.b { z-index: -3; }.c { z-index: auto; }</style></head><body><div class="a">A</div><div class="b">B</div><div class="c">C</div></body></html>"#; let doc = we_html::parse_html(html_str); let sheets = extract_stylesheets(&doc); let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let body = &styled.children[0]; let a = &body.children[0]; let b = &body.children[1]; let c = &body.children[2]; assert_eq!(a.style.z_index, Some(5)); assert_eq!(b.style.z_index, Some(-3)); assert_eq!(c.style.z_index, None); }
#[test] fn z_index_default_is_auto() { let html_str = r#"<!DOCTYPE html><html><body><div>test</div></body></html>"#; let doc = we_html::parse_html(html_str); let sheets = extract_stylesheets(&doc); let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let body = &styled.children[0]; let div = &body.children[0]; assert_eq!(div.style.z_index, None); }
#[test] fn position_sticky_parsing() { let html_str = r#"<!DOCTYPE html><html><head><style>.s { position: sticky; top: 10px; }</style></head><body><div class="s">Sticky</div></body></html>"#; let doc = we_html::parse_html(html_str); let sheets = extract_stylesheets(&doc); let styled = resolve_styles(&doc, &sheets, (800.0, 600.0)).unwrap(); let body = &styled.children[0]; let div = &body.children[0]; assert_eq!(div.style.position, Position::Sticky); assert_eq!(div.style.top, LengthOrAuto::Length(10.0)); }}