servo: Redo representation of CSS values, and many other related things.
authorBrian J. Burg <burg@cs.washington.edu>
Fri, 07 Sep 2012 17:00:09 -0700
changeset 361891 d24d1405c7ea9e23e415085a77da05ce94b55681
parent 361890 4fc91b3bae6ce66d70b9a073b9f32b990edd3095
child 361892 a2b01f75162944928cc6ec4f5724315054fc2e21
push id10863
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 23:02:23 +0000
treeherdermozilla-aurora@0931190cd725 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
servo: Redo representation of CSS values, and many other related things. Source-Repo: https://github.com/servo/servo Source-Revision: fd3ade2a3c822fcc1568f9c3e31981d202d38be7
servo/src/servo/css/parser.rs
servo/src/servo/css/parser_util.rs
servo/src/servo/css/resolve/apply.rs
servo/src/servo/css/resolve/matching.rs
servo/src/servo/css/styles.rs
servo/src/servo/css/values.rs
servo/src/servo/layout/base.rs
servo/src/servo/layout/block.rs
servo/src/servo/layout/box_builder.rs
servo/src/servo/layout/display_list_builder.rs
servo/src/servo/layout/inline.rs
--- a/servo/src/servo/css/parser.rs
+++ b/servo/src/servo/css/parser.rs
@@ -1,15 +1,14 @@
 #[doc="Constructs a list of css style rules from a token stream"]
 
 // TODO: fail according to the css spec instead of failing when things
 // are not as expected
 
-use css::values::{TextColor, BackgroundColor, FontSize, Height, Width,
-                     Display, StyleDeclaration};
+use css::values::*;
 // Disambiguate parsed Selector, Rule values from tokens
 use css = css::values;
 use tok = lexer;
 use lexer::Token;
 use comm::recv;
 use option::{map, is_none};
 use vec::push;
 use parser_util::*;
@@ -151,27 +150,30 @@ impl TokenReader : ParserMethods {
         let mut desc_list : ~[StyleDeclaration]= ~[];
         
         // Get the description to be applied to the selector
         loop {
             let tok = self.get();
             match tok {
               tok::EndDescription => { break; }
               tok::Description(prop, val) => {
-                let desc = match prop {
-                  // TODO: have color parsing return an option instead of a real value
-                  ~"background-color" => parse_color(val).map(|res| BackgroundColor(res)),
-                  ~"color" => parse_color(val).map(|res| TextColor(res)),
-                  ~"display" => parse_display_type(val).map(|res| Display(res)),
-                  ~"font-size" => parse_font_size(val).map(|res| FontSize(res)),
-                  ~"height" => parse_size(val).map(|res| Height(res)),
-                  ~"width" => parse_size(val).map(|res| Width(res)),
+                let desc : Option<StyleDeclaration> = match prop {
+                  // TODO: have color parsing return a ParseResult instead of a real value
+                  ~"background-color" => parse_color(val).map(|res| BackgroundColor(Specified(BgColor(res)))),
+                  ~"color" => parse_color(val).map(|res| Color(Specified(TextColor(res)))),
+                  ~"display" => parse_display_type(val).extract(|res| Display(res)),
+                  ~"font-size" => parse_font_size(val).extract(|res| FontSize(res)),
+                  ~"height" => parse_box_sizing(val).extract(|res| Height(res)),
+                  ~"width" => parse_box_sizing(val).extract(|res| Width(res)),
                   _ => { #debug["Recieved unknown style property '%s'", val]; None }
                 };
-                desc.map(|res| push(desc_list, res));
+                match desc {
+                  Some(d) => push(desc_list, d),
+                  None => { #debug["Couldn't parse value '%s' for property '%s'", val, prop] }
+                }
               }
               tok::Eof => { return None; }
               tok::StartDescription | tok::Descendant |  tok::Child | tok::Sibling 
               | tok::Comma | tok::Element(_) | tok::Attr(_) => {
                 fail #fmt["Unexpected token %? in description", tok]; 
               }
             }
         }
--- a/servo/src/servo/css/parser_util.rs
+++ b/servo/src/servo/css/parser_util.rs
@@ -1,68 +1,81 @@
 #[doc = "Helper functions to parse values of specific attributes."]
 
-use css::values::{DisplayType, Inline, Block, DisplayNone};
-use css::values::{Unit, Pt, Mm, Px, Percent, Auto};
+use css::values::*;
 use str::{pop_char, from_chars};
 use float::from_str;
 use option::map;
 
 export parse_font_size;
 export parse_size;
+export parse_box_sizing;
 export parse_display_type;
 
-fn parse_unit(str : ~str) -> Option<Unit> {
+
+fn parse_length(str : ~str) -> Option<Length> {
+    // TODO: use these once we stop lexing below
+    const PTS_PER_INCH: float = 72.0;
+    const CM_PER_INCH: float = 2.54;
+    const PX_PER_PT: float = 1.0 / 0.75;
+
     match str {
-      s if s.ends_with(~"%") => from_str(str.substr(0, str.len() - 1)).map(|f| Percent(f)),
-      s if s.ends_with(~"in") => from_str(str.substr(0, str.len() - 2)).map(|f| Pt(72.0*f)),
-      s if s.ends_with(~"cm") => from_str(str.substr(0, str.len() - 2)).map(|f| Mm(10.0*f)),
-      s if s.ends_with(~"mm") => from_str(str.substr(0, str.len() - 2)).map(|f| Mm(f)),
-      s if s.ends_with(~"pt") => from_str(str.substr(0, str.len() - 2)).map(|f| Pt(f)),
-      s if s.ends_with(~"pc") => from_str(str.substr(0, str.len() - 2)).map(|f| Pt(12.0*f)),
+      s if s.ends_with(~"in") => from_str(str.substr(0, str.len() - 2)).map(|f| Px(1.0/0.75 * 72.0 * f)),
+      s if s.ends_with(~"cm") => from_str(str.substr(0, str.len() - 2)).map(|f| Px(f / 2.54 * 72.0 * 1.0/0.75)),
+      s if s.ends_with(~"mm") => from_str(str.substr(0, str.len() - 2)).map(|f| Px(f * 0.1 / 2.54 * 72.0 * 1.0/0.75)),
+      s if s.ends_with(~"pt") => from_str(str.substr(0, str.len() - 2)).map(|f| Px(1.0/0.75 * f)),
+      s if s.ends_with(~"pc") => from_str(str.substr(0, str.len() - 2)).map(|f| Px(1.0/0.75 * 12.0 * f)),
       s if s.ends_with(~"px") => from_str(str.substr(0, str.len() - 2)).map(|f| Px(f)),
-      s if s.ends_with(~"ex") | s.ends_with(~"em") => fail ~"Em and Ex sizes not yet supported",
+      s if s.ends_with(~"em") => from_str(str.substr(0, str.len() - 2)).map(|f| Em(f)),
+      s if s.ends_with(~"ex") => from_str(str.substr(0, str.len() - 2)).map(|f| Em(0.5*f)),
       _ => None,
     }
 }
 
-fn parse_font_size(str : ~str) -> Option<Unit> {
-    // The default pixel size, not sure if this is accurate.
-    let default = 16.0;
-
+fn parse_absolute_size(str : ~str) -> ParseResult<AbsoluteSize> {
     match str {
-      ~"xx-small" => Some(Px(0.6*default)),
-      ~"x-small" => Some(Px(0.75*default)),
-      ~"small" => Some(Px(8.0/9.0*default)),
-      ~"medium" => Some(Px(default)),
-      ~"large" => Some(Px(1.2*default)),
-      ~"x-large" => Some(Px(1.5*default)),
-      ~"xx-large" => Some(Px(2.0*default)),
-      ~"smaller" => Some(Percent(80.0)),
-      ~"larger" => Some(Percent(125.0)),
-      ~"inherit" => Some(Percent(100.0)),
-      _  => parse_unit(str),
+      ~"xx-small" => Value(XXSmall),
+      ~"x-small" => Value(XSmall),
+      ~"small" => Value(Small),
+      ~"medium" => Value(Medium),
+      ~"large" => Value(Large),
+      ~"x-large" => Value(XLarge),
+      ~"xx-large" => Value(XXLarge),
+      _  => Fail
+    }
+}
+
+fn parse_relative_size(str: ~str) -> ParseResult<RelativeSize> {
+    match str {
+      ~"smaller" => Value(Smaller),
+      ~"larger" => Value(Larger),
+      _ => Fail
     }
 }
 
+fn parse_font_size(str: ~str) -> ParseResult<CSSFontSize> {
+    // TODO: complete me
+    Value(LengthSize(Px(14.0)))
+}
+
 // For width / height, and anything else with the same attribute values
-fn parse_size(str : ~str) -> Option<Unit> {
+fn parse_box_sizing(str : ~str) -> ParseResult<BoxSizing> {
     match str {
-      ~"auto" => Some(Auto),
-      ~"inherit" => Some(Percent(100.0)),
-      _ => parse_unit(str),
+      ~"auto" => Value(BoxAuto),
+      ~"inherit" => CSSInherit,
+      _ => Fail,
     }
 }
 
-fn parse_display_type(str : ~str) -> Option<DisplayType> {
+fn parse_display_type(str : ~str) -> ParseResult<CSSDisplay> {
     match str {
-      ~"inline" => Some(Inline),
-      ~"block" => Some(Block),
-      ~"none" => Some(DisplayNone),
-      _ => { #debug["Recieved unknown display value '%s'", str]; None }
+      ~"inline" => Value(DisplayInline),
+      ~"block" => Value(DisplayBlock),
+      ~"none" => Value(DisplayNone),
+      _ => { #debug["Recieved unknown display value '%s'", str]; Fail }
     }
 }
 
 #[cfg(test)]
 mod test {
     import css::lexer::spawn_css_lexer_from_string;
     import css::parser::build_stylesheet;
     import css::values::{Stylesheet, Element, FontSize, Width, Height};
--- a/servo/src/servo/css/resolve/apply.rs
+++ b/servo/src/servo/css/resolve/apply.rs
@@ -1,108 +1,117 @@
 #[doc="Applies the appropriate CSS style to boxes."]
 
-import dom::base::{Element, HTMLImageElement, Node};
+import dom = dom::base;
 import gfx::geometry::au_to_px;
 import layout::base::{Box, BTree, NTree, LayoutData, SpecifiedStyle, ImageHolder,
               BlockBox, InlineBox, IntrinsicBox, TextBox};
 import layout::traverse::{top_down_traversal};
 import std::net::url::Url;
 import resource::image_cache_task::ImageCacheTask;
 
-import css::values::{Percent, Mm, Pt, Px, Auto, PtToPx, MmToPx};
+import css::values::*;
+
+trait ResolveMethods<T> {
+    pure fn initial() -> T;
+}
+
+impl CSSValue<CSSBackgroundColor> : ResolveMethods<CSSBackgroundColor> {
+    pure fn initial() -> CSSBackgroundColor { return BgTransparent; }
+}
+
+impl CSSValue<CSSDisplay> : ResolveMethods<CSSDisplay> {
+    pure fn initial() -> CSSDisplay { return DisplayInline; }
+}
+
+impl CSSValue<BoxSizing> : ResolveMethods<BoxSizing> {
+    pure fn initial() -> BoxSizing { return BoxAuto; }
+}
+
+impl CSSValue<CSSFontSize> : ResolveMethods<CSSFontSize> {
+    pure fn initial() -> CSSFontSize { return AbsoluteSize(Medium); }
+}
+
 
 struct StyleApplicator {
     box: @Box;
     doc_url: &Url;
     image_cache_task: ImageCacheTask;
     reflow: fn~();
 }
 
+
 fn apply_style(box: @Box, doc_url: &Url, image_cache_task: ImageCacheTask, reflow: fn~()) {
     let applicator = StyleApplicator {
         box: box,
         doc_url: doc_url,
         image_cache_task: image_cache_task,
         reflow: reflow
     };
 
     applicator.apply_css_style();
 }
 
+// TODO: this is misleadingly-named. It is actually trying to resolve CSS 'inherit' values.
+
 #[doc="A wrapper around a set of functions that can be applied as a top-down traversal of layout
        boxes."]
 fn inheritance_wrapper(box : @Box, doc_url: &Url, image_cache_task: ImageCacheTask, reflow: fn~()) {
     let applicator = StyleApplicator {
         box: box,
         doc_url: doc_url,
         image_cache_task: image_cache_task,
         reflow: reflow
     };
     applicator.apply_style();
-    inhereit_height(box);
-    inhereit_width(box);
+    inherit_fontsize(box);
+    inherit_height(box);
+    inherit_width(box);
 }
 
+/* Turns symbolic (abs, rel) and relative font sizes into absolute lengths */
+    fn inherit_fontsize(box : @Box) {
+        // TODO: complete this
+        return
+    }
+
 #[doc="Compute the specified height of a layout box based on it's css specification and its
        parent's height."]
-fn inhereit_height(box : @Box) {
+fn inherit_height(box : @Box) {
     let style = box.node.get_specified_style();
-    
+    let inherit_val = match box.tree.parent {
+        None => style.height.initial(),
+        Some(node) => node.appearance.height
+    };
+
     box.appearance.height = match style.height {
-        None =>  Auto,
-        Some(h) => match h {
-            Auto | Px(*) => h,
-            Pt(*) => PtToPx(h),
-            Mm(*) => MmToPx(h),
-            Percent(em) => {
-                match box.tree.parent {
-                    None => Auto,
-                    Some(parent) => {
-                        match parent.appearance.height {
-                            //This is a poorly constrained case, so we ignore the percentage
-                            Auto => Auto,
-                            Px(f) => Px(em*f/100.0),
-                            Percent(*) | Mm(*) | Pt(*) => {
-                                fail ~"failed inheriting heights, parent should only be Px or Auto"
-                            }
-                        }
-                    }
-                }
-            }
+        Initial => style.height.initial(),
+        Inherit => inherit_val,
+        Specified(val) => match val { // BoxSizing
+            BoxPercent(*) | BoxAuto | BoxLength(Px(_)) => val,
+            BoxLength(Em(n)) => BoxLength(Px(n * box.appearance.font_size.abs()))
         }
     }
 }
 
 #[doc="Compute the specified width of a layout box based on it's css specification and its
        parent's width."]
-fn inhereit_width(box : @Box) {
+fn inherit_width(box : @Box) {
     let style = box.node.get_specified_style();
-    
+    let inherit_val = match box.tree.parent {
+        None => style.height.initial(),
+        Some(node) => node.appearance.width
+    };
+
     box.appearance.width = match style.width {
-        None =>  Auto,
-        Some(h) => match h {
-            Auto | Px(*) => h,
-            Pt(*) => PtToPx(h),
-            Mm(*) => MmToPx(h),
-            Percent(em) => {
-                match box.tree.parent {
-                    None => Auto,
-                    Some(parent) => {
-                        match parent.appearance.width {
-                            //This is a poorly constrained case, so we ignore the percentage
-                            Auto => Auto,
-                            Px(f) => Px(em*f/100.0),
-                            Percent(*) | Mm(*) | Pt(*) => {
-                                fail ~"failed inheriting widths, parent should only be Px or Auto"
-                            }
-                        }
-                    }
-                }
-            }
+        Initial => style.width.initial(),
+        Inherit => inherit_val,
+        Specified(val) => match val { // BoxSizing
+            BoxPercent(*) | BoxAuto | BoxLength(Px(_)) => val,
+            BoxLength(Em(n)) => BoxLength(Px(n * box.appearance.font_size.abs()))
         }
     }
 }
 
 impl StyleApplicator {
     fn apply_css_style() {
         let doc_url = copy *self.doc_url;
         let image_cache_task = self.image_cache_task;
@@ -119,26 +128,19 @@ impl StyleApplicator {
       value for the given type of element and use that instead.
 
      "]
     fn apply_style() {
 
         // Right now, we only handle images.
         do self.box.node.read |node| {
             match node.kind {
-              ~Element(element) => {
-                let style = self.box.node.get_specified_style();
-
-                self.box.appearance.background_color = match style.background_color {
-                  Some(col) => col,
-                  None => node.kind.default_color()
-                };
-
+              ~dom::Element(element) => {
                 match element.kind {
-                  ~HTMLImageElement(*) => {
+                  ~dom::HTMLImageElement(*) => {
                     let url = element.get_attr(~"src");
                     
                     if url.is_some() {
                         // FIXME: Some sort of BASE HREF support!
                         // FIXME: Parse URLs!
                         let new_url = make_url(option::unwrap(url), Some(copy *self.doc_url));
                         self.box.appearance.background_image = Some(ImageHolder(new_url, self.image_cache_task, self.reflow))
                     };
@@ -187,17 +189,17 @@ mod test {
         do g2.aux |aux| { aux.specified_style.height = Some(Px(10.0)); }
 
         let parent_box = parent.construct_boxes();
         let child_box = parent_box.get().tree.first_child.get();
         let child2_box = parent_box.get().tree.last_child.get();
         let g1_box = child_box.tree.first_child.get();
         let g2_box = child_box.tree.last_child.get();
         
-        top_down_traversal(parent_box.get(), inhereit_height);
+        top_down_traversal(parent_box.get(), inherit_height);
 
         assert parent_box.get().appearance.height == Px(100.0);
         assert child_box.appearance.height == Auto;
         assert child2_box.appearance.height == Px(50.0);
         assert g1_box.appearance.height == Auto;
         assert g2_box.appearance.height == Px(10.0);
     }
 }
--- a/servo/src/servo/css/resolve/matching.rs
+++ b/servo/src/servo/css/resolve/matching.rs
@@ -1,17 +1,15 @@
 #[doc="Performs CSS selector matching."]
 
 import dom::base::{LayoutData};
 import dom::base;
 import base::{ElementData, Node, Text};
 
-import values::{Selector, StyleDeclaration, FontSize, Display, TextColor, BackgroundColor,
-                    Stylesheet, Element, Child, Descendant, Sibling, Attr, Exact, Exists, Includes,
-                    StartsWith, Width, Height};
+import values::*;
 import styles::{SpecifiedStyle};
 
 #[doc="Check if a CSS attribute matches the attribute of an HTML element."]
 fn attrs_match(attr: Attr, elmt: ElementData) -> bool {
     match attr {
       Exists(name) => {
         match elmt.get_attr(name) {
           Some(_) => true,
@@ -164,22 +162,22 @@ trait PrivStyleMethods {
     fn update_style(decl : StyleDeclaration);
 }
 
 impl Node : PrivStyleMethods {
     #[doc="Update the computed style of an HTML element with a style specified by CSS."]
     fn update_style(decl : StyleDeclaration) {
         self.aux(|layout| {
             match decl {
-              BackgroundColor(col) => layout.specified_style.background_color = Some(col),
-              Display(dis) => layout.specified_style.display_type = Some(dis),
-              FontSize(size) => layout.specified_style.font_size = Some(size),
-              Height(size) => layout.specified_style.height = Some(size),
-              TextColor(col) => layout.specified_style.text_color = Some(col),
-              Width(size) => layout.specified_style.width = Some(size)
+              BackgroundColor(col) => layout.specified_style.background_color = col,
+              Display(dis) => layout.specified_style.display_type = dis,
+              FontSize(size) => layout.specified_style.font_size = size,
+              Height(size) => layout.specified_style.height = size,
+              Color(col) => layout.specified_style.text_color = col,
+              Width(size) => layout.specified_style.width = size
             };
         })
     }
 }
 
 trait MatchingMethods {
     fn match_css_style(styles : Stylesheet);
 }
--- a/servo/src/servo/css/styles.rs
+++ b/servo/src/servo/css/styles.rs
@@ -1,86 +1,86 @@
 #[doc="High-level interface to CSS selector matching."]
 
 import std::arc::{ARC, get, clone};
 
-import css::values::{DisplayType, DisplayNone, Inline, Block, Unit, Auto};
+import css::values::*;
 import css::values::Stylesheet;
 import dom::base::{HTMLDivElement, HTMLHeadElement, HTMLImageElement, UnknownElement, HTMLScriptElement};
 import dom::base::{Comment, Doctype, Element, Node, NodeKind, Text};
 import util::color::{Color, rgb};
 import util::color::css_colors::{white, black};
 import layout::base::{LayoutData, NTree};
 
-type SpecifiedStyle = {mut background_color : Option<Color>,
-                        mut display_type : Option<DisplayType>,
-                        mut font_size : Option<Unit>,
-                        mut height : Option<Unit>,
-                        mut text_color : Option<Color>,
-                        mut width : Option<Unit>
+type SpecifiedStyle = {mut background_color : CSSValue<CSSBackgroundColor>,
+                        mut display_type : CSSValue<CSSDisplay>,
+                        mut font_size : CSSValue<CSSFontSize>,
+                        mut height : CSSValue<BoxSizing>,
+                        mut text_color : CSSValue<CSSColor>,
+                        mut width : CSSValue<BoxSizing>
                        };
 
 trait DefaultStyleMethods {
     fn default_color() -> Color;
-    fn default_display_type() -> DisplayType;
-    fn default_width() -> Unit;
-    fn default_height() -> Unit;
+    fn default_display_type() -> CSSDisplay;
+    fn default_width() -> BoxSizing;
+    fn default_height() -> BoxSizing;
 }
 
 /// Default styles for various attributes in case they don't get initialized from CSS selectors.
 impl NodeKind : DefaultStyleMethods {
     fn default_color() -> Color {
         match self {
           Text(*) => white(),
           Element(*) => white(),
             _ => fail ~"unstyleable node type encountered"
         }
     }
 
-    fn default_display_type() -> DisplayType {
+    fn default_display_type() -> CSSDisplay {
         match self {
-          Text(*) => { Inline }
+          Text(*) => { DisplayInline }
           Element(element) => {
             match *element.kind {
-              HTMLDivElement => Block,
+              HTMLDivElement => DisplayBlock,
               HTMLHeadElement => DisplayNone,
-              HTMLImageElement(*) => Inline,
+              HTMLImageElement(*) => DisplayInline,
               HTMLScriptElement => DisplayNone,
-              UnknownElement => Inline,
+              UnknownElement => DisplayInline,
             }
           },
           Comment(*) | Doctype(*) => DisplayNone
         }
     }
     
-    fn default_width() -> Unit {
-        Auto
+    fn default_width() -> BoxSizing {
+        BoxAuto
     }
 
-    fn default_height() -> Unit {
-        Auto
+    fn default_height() -> BoxSizing {
+        BoxAuto
     }
 }
 
 /**
  * Create a specified style that can be used to initialize a node before selector matching.
  *
  * Everything is initialized to none except the display style. The default value of the display
  * style is computed so that it can be used to short-circuit selector matching to avoid computing
  * style for children of display:none objects.
  */
 fn empty_style_for_node_kind(kind: NodeKind) -> SpecifiedStyle {
     let display_type = kind.default_display_type();
 
-    {mut background_color : None,
-     mut display_type : Some(display_type),
-     mut font_size : None,
-     mut height : None,
-     mut text_color : None,
-     mut width : None}
+    {mut background_color : Initial,
+     mut display_type : Specified(display_type),
+     mut font_size : Initial,
+     mut height : Initial,
+     mut text_color : Initial,
+     mut width : Initial}
 }
 
 trait StylePriv {
     fn initialize_style() -> ~[@LayoutData];
 }
 
 impl Node : StylePriv {
     #[doc="
--- a/servo/src/servo/css/values.rs
+++ b/servo/src/servo/css/values.rs
@@ -1,49 +1,167 @@
-import util::color::Color;
+use SharedColor = util::color::Color;
+use cmp::Eq;
 
 #[doc = "
   Defines how css rules, both selectors and style specifications, are
   stored.  CSS selector-matching rules, as presented by 
   http://www.w3.org/TR/CSS2/selector.html are represented by nested types.
 "]
 
-enum DisplayType {
-    Inline,
-    Block,
-    ListItem,
-    InlineBlock,
-    Table,
-    InlineTable,
-    TableRowGroup,
-    TableHeaderGroup,
-    TableFooterGroup,
-    TableRow,
-    TableColumnGroup,
-    TableColumn,
-    TableCell,
-    TableCaption,
+// CSS Units
+
+enum ParseResult<T> {
+    Value(T),
+    CSSInitial,
+    CSSInherit,
+    Fail
+}
+
+enum CSSValue<T : copy> {
+    Specified(T),
+    Initial,
+    Inherit
+}
+
+impl<T : copy> ParseResult<T> {
+    pure fn extract<U>(f: fn(CSSValue<T>) -> U) -> Option<U> { extract(self, f) }
+}
+
+pure fn extract<T : copy, U>(res: ParseResult<T>, f: fn(CSSValue<T>) -> U) -> Option<U> {
+    match res {
+        Fail => None,
+        CSSInitial => Some(f(Initial)),
+        CSSInherit => Some(f(Inherit)),
+        Value(x) => Some(f(Specified(x)))
+    }
+}
+
+impl<T: Eq copy> CSSValue<T> : Eq {
+    pure fn eq(&&other: CSSValue<T>) -> bool {
+        match (self, other) {
+            (Initial, Initial) => true,
+            (Inherit, Inherit) => true,
+            (Specified(a), Specified(b)) => a == b,
+            _ => false
+        }
+    }
+}
+
+enum Auto = ();
+
+enum Length {
+    Em(float), // normalized to 'em'
+    Px(float) // normalized to 'px'
+}
+
+impl Length {
+    pure fn rel() -> float {
+        match self {
+            Em(x) => x,
+            _ => fail ~"attempted to access relative unit of an absolute length"
+        }
+    }
+    pure fn abs() -> float {
+        match self {
+            Em(x) => x,
+            _ => fail ~"attempted to access relative unit of an absolute length"
+        }
+    }
+}
+
+enum BoxSizing { // used by width, height, top, left, etc
+    BoxLength(Length),
+    BoxPercent(float),
+    BoxAuto
+}
+
+enum AbsoluteSize {
+    XXSmall,
+    XSmall,
+    Small,
+    Medium,
+    Large,
+    XLarge,
+    XXLarge
+}
+
+enum RelativeSize {
+    Larger,
+    Smaller
+}
+
+// CSS property values
+
+enum CSSBackgroundAttachment {
+    BgAttachScroll,
+    BgAttachFixed
+}
+
+enum CSSBackgroundColor {
+    BgColor(SharedColor),
+    BgTransparent
+}
+
+enum CSSBackgroundRepeat {
+    BgRepeat,
+    BgRepeatX,
+    BgRepeatY,
+    BgNoRepeat
+}
+
+enum CSSColor {
+    TextColor(SharedColor)
+}
+
+enum CSSDirection {
+    DirectionLtr,
+    DirectionRtl
+}
+
+enum CSSDisplay {
+    DisplayInline,
+    DisplayBlock,
+    DisplayListItem,
+    DisplayInlineBlock,
+    DisplayTable,
+    DisplayInlineTable,
+    DisplayTableRowGroup,
+    DisplayTableHeaderGroup,
+    DisplayTableFooterGroup,
+    DisplayTableRow,
+    DisplayTableColumnGroup,
+    DisplayTableColumn,
+    DisplayTableCell,
+    DisplayTableCaption,
     DisplayNone
 }
 
-enum Unit {
-    Auto,
-    Percent(float),
-    Mm(float),
-    Pt(float),
-    Px(float)
+enum CSSFloat {
+    FloatLeft,
+    FloatRight,
+    FloatNone
 }
 
+enum CSSFontSize {
+    AbsoluteSize(AbsoluteSize),
+    RelativeSize(RelativeSize),
+    LengthSize(Length),
+    PercentSize(float)
+}
+
+// Stylesheet parts
+
 enum StyleDeclaration {
-    BackgroundColor(Color),
-    Display(DisplayType),
-    FontSize(Unit),
-    Height(Unit),
-    TextColor(Color),
-    Width(Unit)
+    BackgroundColor(CSSValue<CSSBackgroundColor>),
+    Display(CSSValue<CSSDisplay>),
+    FontSize(CSSValue<CSSFontSize>),
+    Height(CSSValue<BoxSizing>),
+    Color(CSSValue<CSSColor>),
+    Width(CSSValue<BoxSizing>)
 }
 
 enum Attr{
     Exists(~str),
     Exact(~str, ~str),
     Includes(~str, ~str),
     StartsWith(~str, ~str)
 }
@@ -54,74 +172,109 @@ enum Selector{
     Descendant(~Selector, ~Selector),
     Sibling(~Selector, ~Selector)
 }
 
 type Rule = (~[~Selector], ~[StyleDeclaration]);
 
 type Stylesheet = ~[~Rule];
 
-#[doc="Convert between units measured in millimeteres and pixels"]
-pure fn MmToPx(u : Unit) -> Unit {
-    match u {
-        Mm(m) => Px(m * 3.7795),
-        _ => fail ~"Calling MmToPx on a unit that is not a Mm"
+
+impl Length: cmp::Eq {
+    pure fn eq(&&other: Length) -> bool {
+        match (self, other) {
+          (Em(a), Em(b)) => a == b,
+          (Px(a), Px(b)) => a == b,
+          (_, _) => false
+        }
     }
 }
 
-#[doc="Convert between units measured in points and pixels"]
-pure fn PtToPx(u : Unit) -> Unit {
-    match u {
-        Pt(m) => Px(m * 1.3333),
-        _ => fail ~"Calling PtToPx on a unit that is not a Pt"
+impl BoxSizing: cmp::Eq {
+    pure fn eq(&&other: BoxSizing) -> bool {
+        match (self, other) {
+          (BoxLength(a), BoxLength(b)) => a == b,
+          (BoxPercent(a), BoxPercent(b)) => a == b,
+          (BoxAuto, BoxAuto) => true,
+          (_, _) => false
+        }
     }
 }
 
-impl DisplayType: cmp::Eq {
-    pure fn eq(&&other: DisplayType) -> bool {
+impl AbsoluteSize: cmp::Eq {
+    pure fn eq(&&other: AbsoluteSize) -> bool {
+        self as uint == other as uint
+    }
+}
+
+impl RelativeSize: cmp::Eq {
+    pure fn eq(&&other: RelativeSize) -> bool {
         self as uint == other as uint
     }
 }
 
-impl Unit: cmp::Eq {
-    pure fn eq(&&other: Unit) -> bool {
+
+
+impl CSSBackgroundColor: cmp::Eq {
+    pure fn eq(&&other: CSSBackgroundColor) -> bool {
         match (self, other) {
-          (Auto, Auto) => true,
-          (Auto, _) => false,
-          (Percent(a), Percent(b)) => a == b,
-          (Percent(*), _) => false,
-          (Mm(a), Mm(b)) => a == b,
-          (Mm(*), _) => false,
-          (Pt(a), Pt(b)) => a == b,
-          (Pt(*), _) => false,
-          (Px(a), Px(b)) => a == b,
-          (Px(*), _) => false
+            (BgColor(a), BgColor(b)) => a == b,
+            (BgTransparent, BgTransparent) => true,
+            (_, _) => false
         }
     }
 }
 
+
+impl CSSColor: cmp::Eq {
+    pure fn eq(&&other: CSSColor) -> bool {
+        match (self, other) {
+            (TextColor(a), TextColor(b)) => a == b
+        }
+    }
+}
+
+impl CSSDisplay: cmp::Eq {
+    pure fn eq(&&other: CSSDisplay) -> bool {
+        self as uint == other as uint
+    }
+}
+
+
+impl CSSFontSize: cmp::Eq {
+    pure fn eq(&&other: CSSFontSize) -> bool {
+        match (self, other) {
+            (AbsoluteSize(a), AbsoluteSize(b)) => a == b,
+            (RelativeSize(a), RelativeSize(b)) => a == b,
+            (LengthSize(a),   LengthSize(b))   => a == b,
+            (PercentSize(a),  PercentSize(b))  => a == b,
+            (_, _) => false
+        }
+    }
+}
+/*
 impl StyleDeclaration: cmp::Eq {
     pure fn eq(&&other: StyleDeclaration) -> bool {
         match (self, other) {
           (BackgroundColor(a), BackgroundColor(b)) => a == b,
           (Display(a), Display(b)) => a == b,
           (FontSize(a), FontSize(b)) => a == b,
           (Height(a), Height(b)) => a == b,
-          (TextColor(a), TextColor(b)) => a == b,
+          (Color(a), Color(b)) => a == b,
           (Width(a), Width(b)) => a == b,
 
           (BackgroundColor(*), _)
           | (Display(*), _)
           | (FontSize(*), _)
           | (Height(*), _)
-          | (TextColor(*), _)
+          | (Color(*), _)
           | (Width(*), _) => false
         }
     }
-}
+}*/
 
 impl Attr: cmp::Eq {
     pure fn eq(&&other: Attr) -> bool {
         match (copy self, copy other) {
           (Exists(a), Exists(b)) => a == b,
 
           (Exact(a, aa), Exact(b, bb))
           | (Includes(a, aa), Includes(b, bb))
--- a/servo/src/servo/layout/base.rs
+++ b/servo/src/servo/layout/base.rs
@@ -1,12 +1,12 @@
 #[doc="Fundamental layout structures and algorithms."]
 
-import css::values::Unit;
 import css::styles::SpecifiedStyle;
+import css::values::{BoxSizing, Length, Px};
 import dom::base::{Element, ElementKind, HTMLDivElement, HTMLImageElement, Node, NodeData};
 import dom::base::{NodeKind};
 import dom::rcu;
 import gfx::geometry;
 import gfx::geometry::{au, zero_size_au};
 import geom::point::Point2D;
 import geom::rect::Rect;
 import geom::size::Size2D;
@@ -37,23 +37,26 @@ impl BoxKind : cmp::Eq {
           (InlineBox, InlineBox) => true,
           _ => fail ~"unimplemented case in BoxKind.eq"
         }
     }
 }
 
 struct Appearance {
     let mut background_image: Option<ImageHolder>;
-    let mut background_color: Color;
-    let mut width: Unit;
-    let mut height: Unit;
+    // TODO: create some sort of layout-specific enum to differentiate between
+    // relative and resolved values.
+    let mut width: BoxSizing;
+    let mut height: BoxSizing;
+    let mut font_size: Length;
 
     new(kind: NodeKind) {
+        // TODO: these should come from initial() or elsewhere
+        self.font_size = Px(14.0);
         self.background_image = None;
-        self.background_color = kind.default_color();
         self.width = kind.default_width();
         self.height = kind.default_height();
     }
 
     // This will be very unhappy if it is getting run in parallel with
     // anything trying to read the background image
     fn get_image() -> Option<~ARC<~Image>> {
         let mut image = None;
--- a/servo/src/servo/layout/block.rs
+++ b/servo/src/servo/layout/block.rs
@@ -1,11 +1,11 @@
 #[doc="Block layout."]
 
-import css::values::{Px, Mm, Pt, Auto, Percent, Unit};
+import css::values::*;
 import geom::point::Point2D;
 import geom::size::Size2D;
 import gfx::geometry::{px_to_au, au};
 import util::tree;
 import base::{Box, BlockBox, BTree};
 
 trait BlockLayoutMethods {
     fn reflow_block();
@@ -32,25 +32,25 @@ impl @Box : BlockLayoutMethods {
         // relative heights of the children in the box
         for tree::each_child(BTree, self) |c| {
             // FIXME subtract borders, margins, etc
             c.bounds.origin = Point2D(au(0), au(current_height));
             current_height += *c.bounds.size.height;
         }
 
         let height = match self.appearance.height { 
-            Px(p) => px_to_au(p.to_int()),
-            Auto => au(current_height),
+            BoxLength(Px(p)) => px_to_au(p.to_int()),
+            BoxAuto => au(current_height),
             _ => fail ~"inhereit_height failed, height is neither a Px or auto"
         };
 
         // FIXME: Width is wrong in the calculation below.
         let width = match self.appearance.width { 
-            Px(p) => px_to_au(p.to_int()),
-            Auto => self.bounds.size.width, // Do nothing here, width was set by top-down pass
+            BoxLength(Px(p)) => px_to_au(p.to_int()),
+            BoxAuto => self.bounds.size.width, // Do nothing here, width was set by top-down pass
             _ => fail ~"inhereit_width failed, width is neither a Px or auto"
         };
 
         self.bounds.size = Size2D(width, height);
 
         #debug["reflow_block size=%?", copy self.bounds];
     }
 }
--- a/servo/src/servo/layout/box_builder.rs
+++ b/servo/src/servo/layout/box_builder.rs
@@ -1,11 +1,11 @@
 #[doc="Creates CSS boxes from a DOM."]
 
-import css::values::{DisplayType, Block, Inline, DisplayNone};
+import css::values::{CSSDisplay, DisplayBlock, DisplayInline, DisplayNone, Specified};
 import dom::base::{ElementData, HTMLDivElement, HTMLImageElement, Element, Text, Node, Doctype, Comment};
 import gfx::geometry::zero_size_au;
 import layout::base::{Appearance, BTree, BlockBox, Box, BoxKind, InlineBox, IntrinsicBox, NTree};
 import layout::base::{TextBoxKind};
 import layout::text::TextBox;
 import util::tree;
 import option::is_none;
 
@@ -44,24 +44,24 @@ impl ctxt {
             // Create boxes for the child. Get its primary box.
             let kid_box = kid.construct_boxes();
             if (kid_box.is_none()) {
                 again;
             }
 
             // Determine the child's display.
             let disp = kid.get_specified_style().display_type;
-            if disp != Some(Inline) {
+            if disp != Specified(DisplayInline) {
                 self.finish_anonymous_box_if_necessary();
             }
 
             // Add the child's box to the current enclosing box or the current anonymous box.
             match kid.get_specified_style().display_type {
-              Some(Block) => BTree.add_child(self.parent_box, kid_box.get()),
-              Some(Inline) => {
+              Specified(DisplayBlock) => BTree.add_child(self.parent_box, kid_box.get()),
+              Specified(DisplayInline) => {
                 let anon_box = match self.anon_box {
                   None => {
                     //
                     // The anonymous box inherits the attributes of its parents for now, so
                     // that properties of intrinsic boxes are not spread to their parenting
                     // anonymous box.
                     //
                     // TODO: check what CSS actually specifies
@@ -70,17 +70,17 @@ impl ctxt {
                     let b = @Box(self.parent_node, InlineBox);
                     self.anon_box = Some(b);
                     b
                   }
                   Some(b) => b
                 };
                 BTree.add_child(anon_box, kid_box.get());
               }
-              Some(DisplayNone) => {
+              Specified(DisplayNone) => {
                 // Nothing to do.
               }
               _ => { //hack for now
               }
             }
         }
     }
 
@@ -91,48 +91,48 @@ impl ctxt {
     fn construct_boxes_for_inline_children() {
         for NTree.each_child(self.parent_node) |kid| {
 
             // Construct boxes for the child. Get its primary box.
             let kid_box = kid.construct_boxes();
 
             // Determine the child's display.
             let disp = kid.get_specified_style().display_type;
-            if disp != Some(Inline) {
+            if disp != Specified(DisplayInline) {
                 // TODO
             }
 
             // Add the child's box to the current enclosing box.
             match kid.get_specified_style().display_type {
-              Some(Block) => {
+              Specified(DisplayBlock) => {
                 // TODO
                 #warn("TODO: non-inline display found inside inline box");
                 BTree.add_child(self.parent_box, kid_box.get());
               }
-              Some(Inline) => {
+              Specified(DisplayInline) => {
                 BTree.add_child(self.parent_box, kid_box.get());
               }
-              Some(DisplayNone) => {
+              Specified(DisplayNone) => {
                 // Nothing to do.
               }
               _  => { //hack for now
               }
             }
         }
     }
 
     #[doc="Constructs boxes for the parent's children."]
     fn construct_boxes_for_children() {
         #debug("parent node:");
         self.parent_node.dump();
 
         match self.parent_node.get_specified_style().display_type {
-          Some(Block) => self.construct_boxes_for_block_children(),
-          Some(Inline) => self.construct_boxes_for_inline_children(),
-          Some(DisplayNone) => { /* Nothing to do. */ }
+          Specified(DisplayBlock) => self.construct_boxes_for_block_children(),
+          Specified(DisplayInline) => self.construct_boxes_for_inline_children(),
+          Specified(DisplayNone) => { /* Nothing to do. */ }
           _ => { //hack for now
           }
         }
 
         self.finish_anonymous_box_if_necessary();
         assert is_none(self.anon_box);
     }
 
@@ -159,21 +159,21 @@ impl Node : PrivBoxBuilder {
       size.
      "]
     fn determine_box_kind() -> Option<BoxKind> {
         match self.read(|n| copy n.kind) {
             ~Text(string) => Some(TextBoxKind(@TextBox(copy string))),
             ~Element(element) => {
                 match (copy *element.kind, self.get_specified_style().display_type)  {
                     (HTMLImageElement({size}), _) => Some(IntrinsicBox(@size)),
-                    (_, Some(Block)) => Some(BlockBox),
-                    (_, Some(Inline)) => Some(InlineBox),
-                    (_, Some(DisplayNone)) => None,
-                    (_, Some(_)) => Some(InlineBox),
-                    (_, None) => {
+                    (_, Specified(DisplayBlock)) => Some(BlockBox),
+                    (_, Specified(DisplayInline)) => Some(InlineBox),
+                    (_, Specified(DisplayNone)) => None,
+                    (_, Specified(_)) => Some(InlineBox),
+                    (_, _) => {
                         fail ~"The specified display style should be a default instead of none"
                     }
                 }
             },
           ~Doctype(*)
           | ~Comment(*) => None
         }
     }
--- a/servo/src/servo/layout/display_list_builder.rs
+++ b/servo/src/servo/layout/display_list_builder.rs
@@ -1,10 +1,11 @@
 export build_display_list;
 
+import css::values::{BgColor, BgTransparent, Specified};
 import base::{Box, BTree, ImageHolder, TextBoxKind};
 import dl = display_list;
 import dom::base::{Text, NodeScope};
 import dom::rcu::Scope;
 import either::{Left, Right};
 import geom::point::Point2D;
 import geom::rect::Rect;
 import geom::size::Size2D;
@@ -60,17 +61,16 @@ Creates a display list item for a single
 * `origin` - The coordinates of upper-left corner of the passed in box.
 
 "]
 #[allow(non_implicitly_copyable_typarams)]
 fn box_to_display_items(list: dl::display_list, box: @Box, origin: Point2D<au>) {
     #debug("request to display a box from origin %?", origin);
 
     let bounds = Rect(origin, copy box.bounds.size);
-    let col = box.appearance.background_color;
 
     match box.kind {
       TextBoxKind(subbox) => {
         let run = copy subbox.run;
         assert run.is_some();
         list.push(dl::display_item({
             item_type: dl::display_item_solid_color(255u8, 255u8, 255u8),
             bounds: bounds
@@ -91,20 +91,26 @@ fn box_to_display_items(list: dl::displa
     
     if image.is_some() {
         let display_item = dl::display_item({
             item_type: dl::display_item_image(option::unwrap(image)),
             bounds: bounds
         });
         list.push(display_item);
     } else {
-        #debug("Assigning color %? to box with bounds %?", col, bounds);
-        let col = box.appearance.background_color;
+        // DAC
+        // TODO: shouldn't need to unbox CSSValue by now
+        let boxed_color = box.node.get_specified_style().background_color;
+        let color = match boxed_color {
+            Specified(BgColor(c)) => c,
+            Specified(BgTransparent) | _ => util::color::rgba(0,0,0,0.0)
+        };
+        #debug("Assigning color %? to box with bounds %?", color, bounds);
         list.push(dl::display_item({
-            item_type: dl::display_item_solid_color(col.red, col.green, col.blue),
+            item_type: dl::display_item_solid_color(color.red, color.green, color.blue),
             bounds: bounds
         }));
     }
 }
 
 
 fn should_convert_text_boxes_to_solid_color_background_items() {
     #[test];
--- a/servo/src/servo/layout/inline.rs
+++ b/servo/src/servo/layout/inline.rs
@@ -1,12 +1,12 @@
 #[doc="Inline layout."]
 
 import base::{Box, InlineBox, BTree};
-import css::values::{Auto, Px};
+import css::values::{BoxAuto, BoxLength, Px};
 import dom::rcu;
 import geom::point::Point2D;
 import geom::size::Size2D;
 import gfx::geometry::{au, px_to_au};
 import num::Num;
 import util::tree;
 
 trait InlineLayout {
@@ -29,24 +29,24 @@ impl @Box : InlineLayout {
         // loop over children and set them at the proper horizontal offset
         for tree::each_child(BTree, self) |kid| {
             kid.bounds.origin = Point2D(au(x), au(y));
             x += *kid.bounds.size.width;
             current_height = i32::max(current_height, *kid.bounds.size.height);
         }
 
         let height = match self.appearance.height { 
-            Px(p) => px_to_au(p.to_int()),
-            Auto => au(current_height),
+            BoxLength(Px(p)) => px_to_au(p.to_int()),
+            BoxAuto => au(current_height),
             _ => fail ~"inhereit_height failed, height is neither a Px or auto"
         };
 
         let width = match self.appearance.width { 
-            Px(p) => px_to_au(p.to_int()),
-            Auto => au(i32::max(x, *self.bounds.size.width)),
+            BoxLength(Px(p)) => px_to_au(p.to_int()),
+            BoxAuto => au(i32::max(x, *self.bounds.size.width)),
             _ => fail ~"inhereit_width failed, width is neither a Px or auto"
         };
 
         // The maximum available width should have been set in the top-down pass
         self.bounds.size = Size2D(width, height);
 
         #debug["reflow_inline size=%?", copy self.bounds];
     }