diff --git a/src/servo/content.rs b/src/servo/content.rs index a1e5613c268..72200e804da 100644 --- a/src/servo/content.rs +++ b/src/servo/content.rs @@ -20,7 +20,6 @@ import dom::style; import dom::style::Stylesheet; import gfx::renderer::Sink; import parser::html_lexer::spawn_html_lexer_task; -import parser::css_builder::build_stylesheet; import parser::html_builder::build_dom; import layout::layout_task; import layout_task::{Layout, BuildMsg}; diff --git a/src/servo/dom/style.rs b/src/servo/dom/style.rs index 42ec1620763..f74c2adb153 100644 --- a/src/servo/dom/style.rs +++ b/src/servo/dom/style.rs @@ -1,16 +1,37 @@ import util::color::Color; -enum DisplayType{ +#[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, structural types, +"] + +enum DisplayType { DisBlock, DisInline, DisNone } -enum StyleDeclaration{ - FontSize(uint), // Currently assumes format '# pt' +enum Unit { + Auto, + Percent(float), + In(float), + Mm(float), + Cm(float), + Em(float), + Ex(float), + Pt(float), + Pc(float), + Px(float) +} + +enum StyleDeclaration { + BackgroundColor(Color), Display(DisplayType), + FontSize(Unit), + Height(Unit), TextColor(Color), - BackgroundColor(Color) + Width(Unit) } enum Attr{ diff --git a/src/servo/layout/base.rs b/src/servo/layout/base.rs index 32b1d0515c5..03ae0345bf2 100644 --- a/src/servo/layout/base.rs +++ b/src/servo/layout/base.rs @@ -13,9 +13,9 @@ import image::base::image; import layout::block::block_layout_methods; import layout::inline::inline_layout_methods; import util::tree; -import util::color::Color; +import util::color::{Color, css_colors}; import text::text_box; -import style::style::computed_style; +import style::style::SpecifiedStyle; import text::text_layout_methods; import vec::{push, push_all}; @@ -28,11 +28,11 @@ enum BoxKind { class Appearance { let mut background_image: option<@image>; - let mut background_color: option; + let mut background_color: Color; new() { self.background_image = none; - self.background_color = none; + self.background_color = css_colors::black(); } } @@ -53,7 +53,7 @@ class Box { } enum LayoutData = { - mut computed_style: ~computed_style, + mut specified_style: ~SpecifiedStyle, mut box: option<@Box> }; diff --git a/src/servo/layout/box_builder.rs b/src/servo/layout/box_builder.rs index fb89d127be3..f8ea00caed3 100644 --- a/src/servo/layout/box_builder.rs +++ b/src/servo/layout/box_builder.rs @@ -39,9 +39,8 @@ fn create_context(parent_node: Node, parent_box: @Box) -> ctxt { impl methods for ctxt { #[doc=" - Constructs boxes for the parent's children, when the parent's 'display' - attribute is 'block'. - "] + Constructs boxes for the parent's children, when the parent's 'display' attribute is 'block'. + "] fn construct_boxes_for_block_children() { for NTree.each_child(self.parent_node) |kid| { @@ -49,46 +48,48 @@ impl methods for ctxt { let kid_box = kid.construct_boxes(); // Determine the child's display. - let disp = kid.get_computed_style().display; - if disp != DisInline { + let disp = kid.get_specified_style().display_type; + if disp != some(DisInline) { self.finish_anonymous_box_if_necessary(); } // Add the child's box to the current enclosing box or the current anonymous box. - alt kid.get_computed_style().display { - DisBlock { - BTree.add_child(self.parent_box, kid_box); - } - DisInline { - let anon_box = alt 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 - // + alt kid.get_specified_style().display_type { + some(DisBlock) { + BTree.add_child(self.parent_box, kid_box); + } + some(DisInline) { + let anon_box = alt 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 + // - let b = @Box(self.parent_node, InlineBox); - self.anon_box = some(b); - b - } - some(b) { b } - }; - BTree.add_child(anon_box, kid_box); - } - DisNone { - // Nothing to do. - } + let b = @Box(self.parent_node, InlineBox); + self.anon_box = some(b); + b + } + some(b) { b } + }; + BTree.add_child(anon_box, kid_box); + } + some(DisNone) { + // Nothing to do. + } + _ { //hack for now + } } } } #[doc=" - Constructs boxes for the parent's children, when the parent's 'display' - attribute is 'inline'. - "] + Constructs boxes for the parent's children, when the parent's 'display' + attribute is 'inline'. + "] fn construct_boxes_for_inline_children() { for NTree.each_child(self.parent_node) |kid| { @@ -96,24 +97,26 @@ impl methods for ctxt { let kid_box = kid.construct_boxes(); // Determine the child's display. - let disp = kid.get_computed_style().display; - if disp != DisInline { + let disp = kid.get_specified_style().display_type; + if disp != some(DisInline) { // TODO } // Add the child's box to the current enclosing box. - alt kid.get_computed_style().display { - DisBlock { + alt kid.get_specified_style().display_type { + some(DisBlock) { // TODO #warn("TODO: non-inline display found inside inline box"); BTree.add_child(self.parent_box, kid_box); } - DisInline { + some(DisInline) { BTree.add_child(self.parent_box, kid_box); } - DisNone { + some(DisNone) { // Nothing to do. } + _ { //hack for now + } } } } @@ -123,10 +126,12 @@ impl methods for ctxt { #debug("parent node:"); self.parent_node.dump(); - alt self.parent_node.get_computed_style().display { - DisBlock { self.construct_boxes_for_block_children(); } - DisInline { self.construct_boxes_for_inline_children(); } - DisNone { /* Nothing to do. */ } + alt self.parent_node.get_specified_style().display_type { + some(DisBlock) { self.construct_boxes_for_block_children(); } + some(DisInline) { self.construct_boxes_for_inline_children(); } + some(DisNone) { /* Nothing to do. */ } + _ { //hack for now + } } self.finish_anonymous_box_if_necessary(); @@ -134,13 +139,13 @@ impl methods for ctxt { } #[doc=" - Flushes the anonymous box we're creating if it exists. This appends the - anonymous box to the block. + Flushes the anonymous box we're creating if it exists. This appends the + anonymous box to the block. "] fn finish_anonymous_box_if_necessary() { alt copy self.anon_box { - none { /* Nothing to do. */ } - some(b) { BTree.add_child(self.parent_box, b); } + none { /* Nothing to do. */ } + some(b) { BTree.add_child(self.parent_box, b); } } self.anon_box = none; } @@ -152,21 +157,21 @@ trait box_builder_priv { impl box_builder_priv of box_builder_priv for Node { #[doc=" - Determines the kind of box that this node needs. Also, for images, computes the intrinsic - size. - "] + Determines the kind of box that this node needs. Also, for images, computes the intrinsic + size. + "] fn determine_box_kind() -> BoxKind { alt self.read(|n| copy n.kind) { - ~Text(string) { - TextBox(@text_box(copy string)) - } - ~Element(element) { - alt *element.kind { - HTMLDivElement { BlockBox } - HTMLImageElement({size}) { IntrinsicBox(@size) } - UnknownElement { InlineBox } - } + ~Text(string) { + TextBox(@text_box(copy string)) + } + ~Element(element) { + alt *element.kind { + HTMLDivElement { BlockBox } + HTMLImageElement({size}) { IntrinsicBox(@size) } + UnknownElement { InlineBox } } + } } } } @@ -181,13 +186,13 @@ impl box_builder_methods of box_builder_methods for Node { let box_kind = self.determine_box_kind(); let my_box = @Box(self, box_kind); alt box_kind { - BlockBox | InlineBox { - let cx = create_context(self, my_box); - cx.construct_boxes_for_children(); - } - _ { - // Nothing to do. - } + BlockBox | InlineBox { + let cx = create_context(self, my_box); + cx.construct_boxes_for_children(); + } + _ { + // Nothing to do. + } } ret my_box; } diff --git a/src/servo/layout/display_list_builder.rs b/src/servo/layout/display_list_builder.rs index 3c928068fc3..3c1d99f3824 100644 --- a/src/servo/layout/display_list_builder.rs +++ b/src/servo/layout/display_list_builder.rs @@ -70,9 +70,10 @@ fn box_to_display_items(box: @Box, origin: Point2D) -> ~[dl::display_item] { #debug("request to display a box from origin %?", origin); let bounds = Rect(origin, copy box.bounds.size); + let col = box.appearance.background_color; - alt (box.kind, box.appearance.background_image, box.appearance.background_color) { - (TextBox(subbox), _, _) { + alt (box.kind, box.appearance.background_image) { + (TextBox(subbox), _) { let run = copy subbox.run; assert run.is_some(); push(items, dl::display_item({ @@ -84,28 +85,20 @@ fn box_to_display_items(box: @Box, origin: Point2D) -> ~[dl::display_item] { bounds: bounds })); } - (_, some(image), some(*)) | (_, some(image), none) { + (_, some(image)) { push(items, dl::display_item({ item_type: dl::display_item_image(~copy *image), bounds: bounds })); } - (_, none, some(col)) { + (_, none) { #debug("Assigning color %? to box with bounds %?", col, bounds); + let col = box.appearance.background_color; push(items, dl::display_item({ item_type: dl::display_item_solid_color(col.red, col.green, col.blue), bounds: bounds })); } - (_, none, none) { - let r = rand::rng(); - push(items, dl::display_item({ - item_type: dl::display_item_solid_color(r.next() as u8, - r.next() as u8, - r.next() as u8), - bounds: bounds - })); - } } #debug("layout: display items: %?", items); diff --git a/src/servo/layout/style/apply.rs b/src/servo/layout/style/apply.rs index 83e744d59ed..95f796db753 100644 --- a/src/servo/layout/style/apply.rs +++ b/src/servo/layout/style/apply.rs @@ -3,8 +3,8 @@ import dom::base::{Element, HTMLImageElement, Node}; import dom::rcu::ReaderMethods; import image::base::load; -import base::{Box, BTree, NTree, LayoutData, BoxTreeReadMethods}; -import style::style_methods; +import base::{Box, BTree, NTree, LayoutData, BoxTreeReadMethods, SpecifiedStyle}; +import style::{default_style_methods, style_methods}; trait ApplyStyleBoxMethods { fn apply_style_for_subtree(); @@ -19,15 +19,24 @@ impl ApplyStyleBoxMethods of ApplyStyleBoxMethods for @Box { } } - #[doc="Applies CSS style."] + #[doc="Applies CSS style to a layout box. + + Get the specified style and apply the existing traits to a + layout box. If a trait does not exist, calculate the default + value for the given type of element and use that instead. + + "] fn apply_style() { // Right now, we only handle images. self.node.read(|node| { alt node.kind { ~Element(element) { - let style = self.node.get_computed_style(); + let style = self.node.get_specified_style(); - self.appearance.background_color = some(style.back_color); + self.appearance.background_color = alt style.background_color { + some(col) { col } + none { node.kind.default_color() } + }; alt element.kind { ~HTMLImageElement(*) { diff --git a/src/servo/layout/style/matching.rs b/src/servo/layout/style/matching.rs index 836b95fc9a9..6c2b96c3e14 100644 --- a/src/servo/layout/style/matching.rs +++ b/src/servo/layout/style/matching.rs @@ -5,9 +5,9 @@ import dom::base; import base::{ElementData, Node, Text}; import dom::style::{Selector, StyleDeclaration, FontSize, Display, TextColor, BackgroundColor, Stylesheet, Element, Child, Descendant, Sibling, Attr, Exact, Exists, Includes, - StartsWith}; + StartsWith, Width, Height}; import dom::rcu::ReaderMethods; -import style::{computed_style, default_style_for_node_kind}; +import style::{SpecifiedStyle}; export matching_methods; @@ -70,20 +70,20 @@ impl priv_matching_methods of priv_matching_methods for Node { Child(_, _) | Descendant(_, _) | Sibling(_, _) { ret false; } Element(tag, attrs) { alt self.read(|n| copy *n.kind) { - base::Element(elmt) { - if !(tag == ~"*" || tag == elmt.tag_name) { - ret false; - } - - let mut i = 0u; - while i < attrs.len() { - if !attrs_match(attrs[i], elmt) { ret false; } - i += 1u; - } - - ret true; + base::Element(elmt) { + if !(tag == ~"*" || tag == elmt.tag_name) { + ret false; } - Text(str) { /*fall through, currently unsupported*/ } + + let mut i = 0u; + while i < attrs.len() { + if !attrs_match(attrs[i], elmt) { ret false; } + i += 1u; + } + + ret true; + } + Text(str) { /*fall through, currently unsupported*/ } } } } @@ -113,16 +113,16 @@ impl priv_matching_methods of priv_matching_methods for Node { //loop over all ancestors to check if they are the person //we should be descended from. let mut cur_parent = alt self.read(|n| n.tree.parent) { - some(parent) { parent } - none { ret false; } + some(parent) { parent } + none { ret false; } }; loop { if cur_parent.matches_selector(sel1) { ret true; } cur_parent = alt cur_parent.read(|n| n.tree.parent) { - some(parent) { parent } - none { ret false; } + some(parent) { parent } + none { ret false; } }; } } @@ -131,18 +131,18 @@ impl priv_matching_methods of priv_matching_methods for Node { // Loop over this node's previous siblings to see if they match. alt self.read(|n| n.tree.prev_sibling) { - some(sib) { - let mut cur_sib = sib; - loop { - if cur_sib.matches_selector(sel1) { ret true; } - - cur_sib = alt cur_sib.read(|n| n.tree.prev_sibling) { - some(sib) { sib } - none { break; } - }; - } + some(sib) { + let mut cur_sib = sib; + loop { + if cur_sib.matches_selector(sel1) { ret true; } + + cur_sib = alt cur_sib.read(|n| n.tree.prev_sibling) { + some(sib) { sib } + none { break; } + }; } - none { } + } + none { } } // check the rest of the siblings @@ -176,9 +176,12 @@ impl priv_style_methods of priv_style_methods for Node { fn update_style(decl : StyleDeclaration) { self.aux(|layout| { alt decl { - Display(dis) { layout.computed_style.display = dis; } - BackgroundColor(col) { layout.computed_style.back_color = col; } - TextColor(*) | FontSize(*) { /* not supported yet */ } + 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); } } }) } @@ -208,7 +211,7 @@ impl matching_methods of matching_methods for Node { } } - self.aux(|a| #debug["Changed the style to: %?", copy *a.computed_style]); + self.aux(|a| #debug["Changed the style to: %?", copy *a.specified_style]); } } @@ -308,8 +311,7 @@ mod test { scope.add_child(gchild, ggchild); scope.add_child(ggchild, gggchild); - let sel1 = Descendant(~Element(~"*", ~[Exact(~"class", ~"blue")]), - ~Element(~"*", ~[])); + let sel1 = Descendant(~Element(~"*", ~[Exact(~"class", ~"blue")]), ~Element(~"*", ~[])); assert !root.matches_selector(~copy sel1); assert child1.matches_selector(~copy sel1); @@ -338,8 +340,7 @@ mod test { assert !ggchild.matches_selector(~copy sel3); assert !gggchild.matches_selector(~sel3); - let sel4 = Descendant(~Child(~Element(~"*", ~[Exists(~"class")]), - ~Element(~"*", ~[])), + let sel4 = Descendant(~Child(~Element(~"*", ~[Exists(~"class")]), ~Element(~"*", ~[])), ~Element(~"*", ~[])); assert !root.matches_selector(~copy sel4); diff --git a/src/servo/layout/style/style.rs b/src/servo/layout/style/style.rs index e94992d776a..199d0bbc5ee 100644 --- a/src/servo/layout/style/style.rs +++ b/src/servo/layout/style/style.rs @@ -2,7 +2,7 @@ import arc::{arc, get, clone}; -import dom::style::{DisplayType, DisBlock, DisInline, DisNone, Stylesheet}; +import dom::style::{DisplayType, DisBlock, DisInline, DisNone, Stylesheet, Unit}; import dom::base::{Element, HTMLDivElement, HTMLHeadElement, HTMLImageElement, Node, NodeKind}; import dom::base::{Text}; import dom::rcu::ReaderMethods; @@ -11,26 +11,63 @@ import util::color::{Color, rgb}; import util::color::css_colors::{white, black}; import base::{LayoutData, NTree, NodeTreeReadMethods}; -type computed_style = {mut display : DisplayType, mut back_color : Color}; +type SpecifiedStyle = {mut background_color : option, + mut display_type : option, + mut font_size : option, + mut height : option, + mut text_color : option, + mut width : option + }; -#[doc="Returns the default style for the given node kind."] -fn default_style_for_node_kind(kind: NodeKind) -> computed_style { - alt kind { - Text(*) { - {mut display: DisInline, mut back_color: white()} - } - Element(element) { - let r = rand::rng(); - let rand_color = rgb(r.next() as u8, r.next() as u8, r.next() as u8); +trait default_style_methods { + fn default_color() -> Color; + fn default_display_type() -> DisplayType; +} - alt *element.kind { - HTMLDivElement { {mut display: DisBlock, mut back_color: rand_color} } - HTMLHeadElement { {mut display: DisNone, mut back_color: rand_color} } - HTMLImageElement(*) { {mut display: DisInline, mut back_color: rand_color} } - UnknownElement { {mut display: DisInline, mut back_color: rand_color} } +#[doc="Default stylesfor various attributes in case they don't get initialized from css selectors"] +impl default_style_methods of default_style_methods for NodeKind { + fn default_color() -> Color { + alt self { + Text(*) { white() } + Element(*) { + let r = rand::rng(); + rgb(r.next() as u8, r.next() as u8, r.next() as u8) + } } - } } + + fn default_display_type() -> DisplayType { + alt self { + Text(*) { DisInline } + Element(element) { + alt *element.kind { + HTMLDivElement { DisBlock } + HTMLHeadElement { DisNone } + HTMLImageElement(*) { DisInline } + UnknownElement { DisInline } + } + } + } + } +} + +#[doc="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 thee 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} } trait style_priv { @@ -40,13 +77,17 @@ trait style_priv { impl style_priv of style_priv for Node { #[doc="Set a default auxilliary data so that other threads can modify it. - This is, importantly, the function that creates the layout data for the node (the reader- - auxiliary box in the RCU model) and populates it with the default style. + This is, importantly, the function that creates the layout + data for the node (the reader-auxiliary box in the RCU model) + and populates it with the default style. + "] + // TODO: we should look into folding this into building the dom, + // instead of doing a linear sweep afterwards. fn initialize_style() { let node_kind = self.read(|n| copy *n.kind); let the_layout_data = @LayoutData({ - mut computed_style : ~default_style_for_node_kind(node_kind), + mut specified_style : ~empty_style_for_node_kind(node_kind), mut box : none }); @@ -56,7 +97,7 @@ impl style_priv of style_priv for Node { trait style_methods { fn initialize_style_for_subtree(); - fn get_computed_style() -> computed_style; + fn get_specified_style() -> SpecifiedStyle; fn recompute_style_for_subtree(styles : arc); } @@ -76,11 +117,11 @@ impl style_methods of style_methods for Node { TODO: Return a safe reference; don't copy. "] - fn get_computed_style() -> computed_style { + fn get_specified_style() -> SpecifiedStyle { if !self.has_aux() { fail ~"get_computed_style() called on a node without a style!"; } - ret copy *self.aux(|x| copy x).computed_style; + ret copy *self.aux(|x| copy x).specified_style; } #[doc=" diff --git a/src/servo/parser/css_builder.rs b/src/servo/parser/css_builder.rs index 370380c3c8c..81cab772dc7 100644 --- a/src/servo/parser/css_builder.rs +++ b/src/servo/parser/css_builder.rs @@ -1,27 +1,30 @@ -#[doc="Constructs a list of style rules from a token stream"] +#[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 import dom::style; -import style::{DisInline, DisBlock, DisNone, Display, TextColor, BackgroundColor, FontSize}; +import style::{DisInline, DisBlock, DisNone, Display, TextColor, BackgroundColor, FontSize, + Height, Width, StyleDeclaration, Selector}; import parser::css_lexer::{Token, StartDescription, EndDescription, Descendant, Child, Sibling, Comma, Element, Attr, Description, Eof}; import comm::recv; -import option::is_none; +import option::{map, is_none}; +import vec::push; +import parser::parser_util::{parse_display_type, parse_font_size, parse_size}; import util::color::parsing::parse_color; import vec::push; type TokenReader = {stream : port, mut lookahead : option}; -trait methods { +trait util_methods { fn get() -> Token; fn unget(-tok : Token); } -impl methods of methods for TokenReader { +impl util_methods of util_methods for TokenReader { fn get() -> Token { alt copy self.lookahead { some(tok) { self.lookahead = none; copy tok } @@ -35,153 +38,160 @@ impl methods of methods for TokenReader { } } -fn parse_element(reader : TokenReader) -> option<~style::Selector> { - // Get the current element type - let elmt_name = alt reader.get() { - Element(tag) { copy tag } - Eof { ret none; } - _ { fail ~"Expected an element" } - }; - - let mut attr_list = ~[]; - - // Get the attributes associated with that element - loop { - let tok = reader.get(); - alt tok { - Attr(attr) { push(attr_list, copy attr); } - StartDescription | Descendant | Child | Sibling | Comma { - reader.unget(tok); - break; - } - Eof { ret none; } - Element(_) { fail ~"Unexpected second element without " - + ~"relation to first element"; } - EndDescription { fail ~"Unexpected '}'"; } - Description(_, _) { fail ~"Unexpected description"; } - } - } - - ret some(~style::Element(elmt_name, attr_list)); +trait parser_methods { + fn parse_element() -> option<~style::Selector>; + fn parse_selector() -> option<~[~Selector]>; + fn parse_description() -> option<~[StyleDeclaration]>; + fn parse_rule() -> option<~style::Rule>; } -fn parse_rule(reader : TokenReader) -> option<~style::Rule> { - let mut sel_list = ~[]; - let mut desc_list = ~[]; +impl parser_methods of parser_methods for TokenReader { + fn parse_element() -> option<~style::Selector> { + // Get the current element type + let elmt_name = alt self.get() { + Element(tag) { copy tag } + Eof { ret none; } + _ { fail ~"Expected an element" } + }; - // Collect all the selectors that this rule applies to - loop { - let mut cur_sel; + let mut attr_list = ~[]; - alt parse_element(reader) { - some(elmt) { cur_sel = copy elmt; } - none { ret none; } // we hit an eof in the middle of a rule - } + // Get the attributes associated with that element + loop { + let tok = self.get(); + alt tok { + Attr(attr) { push(attr_list, copy attr); } + StartDescription | Descendant | Child | Sibling | Comma { + self.unget(tok); + break; + } + Eof { ret none; } + Element(_) { fail ~"Unexpected second element without relation to first element"; } + EndDescription { fail ~"Unexpected '}'"; } + Description(_, _) { fail ~"Unexpected description"; } + } + } + ret some(~style::Element(elmt_name, attr_list)); + } + fn parse_selector() -> option<~[~Selector]> { + let mut sel_list = ~[]; + + // Collect all the selectors that this rule applies to loop { - let tok = reader.get(); - alt tok { - Descendant { - alt parse_element(reader) { - some(elmt) { - let built_sel <- cur_sel; - let new_sel = copy elmt; - cur_sel <- ~style::Descendant(built_sel, new_sel) - } - none { ret none; } - } - } - Child { - alt parse_element(reader) { - some(elmt) { - let built_sel <- cur_sel; - let new_sel = copy elmt; - cur_sel <- ~style::Child(built_sel, new_sel) - } - none { ret none; } - } - } - Sibling { - alt parse_element(reader) { - some(elmt) { - let built_sel <- cur_sel; - let new_sel = copy elmt; - cur_sel <- ~style::Sibling(built_sel, new_sel) - } - none { ret none; } - } - } - StartDescription { - let built_sel <- cur_sel; - push(sel_list, built_sel); - reader.unget(StartDescription); - break; - } - Comma { + let mut cur_sel; + + alt self.parse_element() { + some(elmt) { cur_sel = copy elmt; } + none { ret none; } // we hit an eof in the middle of a rule + } + + loop { + let tok = self.get(); let built_sel <- cur_sel; - push(sel_list, built_sel); - reader.unget(Comma); - break; - } - Attr(_) | EndDescription | Element(_) | Description(_, _) { - fail #fmt["Unexpected token %? in elements", tok]; - } - Eof { ret none; } - } - } - - // check if we should break out of the nesting loop as well - let tok = reader.get(); - alt tok { - StartDescription { break; } - Comma { } - _ { reader.unget(tok); } - } - } - - // Get the description to be applied to the selector - loop { - let tok = reader.get(); - alt tok { - EndDescription { break; } - Description(prop, val) { - alt prop { - ~"font-size" { - // TODO, support more ways to declare a font size than # pt - assert val.ends_with(~"pt"); - let num = val.substr(0u, val.len() - 2u); - alt uint::from_str(num) { - some(n) { push(desc_list, FontSize(n)); } - none { fail ~"Nonnumber provided as font size"; } + alt tok { + Descendant { + alt self.parse_element() { + some(elmt) { + let new_sel = copy elmt; + cur_sel <- ~style::Descendant(built_sel, new_sel) + } + none { ret none; } + } + } + Child { + alt self.parse_element() { + some(elmt) { + let new_sel = copy elmt; + cur_sel <- ~style::Child(built_sel, new_sel) + } + none { ret none; } + } + } + Sibling { + alt self.parse_element() { + some(elmt) { + let new_sel = copy elmt; + cur_sel <- ~style::Sibling(built_sel, new_sel) + } + none { ret none; } + } + } + StartDescription { + push(sel_list, built_sel); + self.unget(StartDescription); + break; + } + Comma { + push(sel_list, built_sel); + self.unget(Comma); + break; + } + Attr(_) | EndDescription | Element(_) | Description(_, _) { + fail #fmt["Unexpected token %? in elements", tok]; + } + Eof { ret none; } } - } - ~"display" { - alt val { - ~"inline" { push(desc_list, Display(DisInline)); } - ~"block" { push(desc_list, Display(DisBlock)); } - ~"none" { push(desc_list, Display(DisNone)); } - _ { #debug["Recieved unknown display value '%s'", val]; } - } - } - ~"color" { - push(desc_list, TextColor(parse_color(val))); - } - ~"background-color" { - push(desc_list, BackgroundColor(parse_color(val))); - } - _ { #debug["Recieved unknown style property '%s'", val]; } } - } - Eof { ret none; } - StartDescription | Descendant | Child | Sibling - | Comma | Element(_) | Attr(_) { - fail #fmt["Unexpected token %? in description", tok]; - } + + // check if we should break out of the nesting loop as well + // TODO: fix this when rust gets labelled loops + let tok = self.get(); + alt tok { + StartDescription { break; } + Comma { } + _ { self.unget(tok); } + } } + + ret some(sel_list); } - ret some(~(sel_list, desc_list)); + fn parse_description() -> option<~[StyleDeclaration]> { + let mut desc_list : ~[StyleDeclaration]= ~[]; + + // Get the description to be applied to the selector + loop { + let tok = self.get(); + alt tok { + EndDescription { break; } + Description(prop, val) { + alt prop { + // TODO: have color parsing return an option instead of a real value + ~"background-color" { push(desc_list, BackgroundColor(parse_color(val))); } + ~"color" { push(desc_list, TextColor(parse_color(val))); } + ~"display" { parse_display_type(val).map(|res| push(desc_list, Display(res))); } + ~"font-size" { parse_font_size(val).map(|res| push(desc_list, FontSize(res))); } + ~"height" { parse_size(val).map(|res| push(desc_list, Height(res))); } + ~"width" { parse_size(val).map(|res| push(desc_list, Width(res))); } + _ { #debug["Recieved unknown style property '%s'", val]; } + } + } + Eof { ret none; } + StartDescription | Descendant | Child | Sibling | Comma | Element(_) | Attr(_) { + fail #fmt["Unexpected token %? in description", tok]; + } + } + } + + ret some(desc_list); + } + + fn parse_rule() -> option<~style::Rule> { + let sel_list = alt self.parse_selector() { + some(list){ copy list } + none { ret none; } + }; + + // Get the description to be applied to the selector + let desc_list = alt self.parse_description() { + some(list) { copy list } + none { ret none; } + }; + + ret some(~(sel_list, desc_list)); + } } fn build_stylesheet(stream : port) -> ~[~style::Rule] { @@ -189,9 +199,9 @@ fn build_stylesheet(stream : port) -> ~[~style::Rule] { let reader = {stream : stream, mut lookahead : none}; loop { - alt parse_rule(reader) { - some(rule) { push(rule_list, copy rule); } - none { break; } + alt reader.parse_rule() { + some(rule) { push(rule_list, copy rule); } + none { break; } } } diff --git a/src/servo/parser/css_lexer.rs b/src/servo/parser/css_lexer.rs index 3354a3f4ad6..22b1ea1aaed 100644 --- a/src/servo/parser/css_lexer.rs +++ b/src/servo/parser/css_lexer.rs @@ -1,3 +1,5 @@ +#[doc = "Code to lex and tokenize css files."] + import comm::{port, chan}; import dom::style; import option::is_none; @@ -48,9 +50,9 @@ impl css_methods of css_methods for CssLexer { } let token = alt self.parser_state { - CssDescription { self.parse_css_description(ch) } + CssDescription { self.parse_css_description(ch) } CssAttribute { self.parse_css_attribute(ch) } - CssElement { self.parse_css_element(ch) } + CssElement { self.parse_css_element(ch) } CssRelation { self.parse_css_relation(ch) } }; @@ -221,12 +223,36 @@ impl css_methods of css_methods for CssLexer { } fn parser(reader: io::reader, state : ParserState) -> CssLexer { - ret { input_state: {mut lookahead: none, reader: reader}, - mut parser_state: state }; + ret { input_state: {mut lookahead: none, reader: reader}, mut parser_state: state }; +} + +fn lex_css_from_bytes(-content : ~[u8], result_chan : chan) { + let reader = io::bytes_reader(content); + let lexer = parser(reader, CssElement); + + loop { + let token = lexer.parse_css(); + let should_break = (token == Eof); + + result_chan.send(token); + + if should_break { + break; + } + } +} + +fn spawn_css_lexer_from_string(-content : ~str) -> port { + let result_port = port(); + let result_chan = chan(result_port); + + task::spawn(|| lex_css_from_bytes(str::bytes(content), result_chan)); + + ret result_port; } #[warn(no_non_implicitly_copyable_typarams)] -fn spawn_css_lexer_task(-filename: ~str) -> port { +fn spawn_css_lexer_from_file(-filename: ~str) -> port { let result_port = port(); let result_chan = chan(result_port); @@ -235,22 +261,11 @@ fn spawn_css_lexer_task(-filename: ~str) -> port { let file_try = io::read_whole_file(filename); // Check if the given css file existed, if it does, parse it, - // otherwise just send an eof. This is a hack to allow - // guessing that if foo.html exists, foo.css is the - // corresponding stylesheet. + // otherwise just send an eof. if file_try.is_ok() { #debug["Lexing css sheet %s", copy filename]; let file_data = file_try.get(); - let reader = io::bytes_reader(file_data); - - let lexer = parser(reader, CssElement); - - loop { - let token = lexer.parse_css(); - let should_break = token == Eof; - result_chan.send(token); - if should_break { break; } - } + lex_css_from_bytes(file_data, result_chan); } else { #debug["Failed to open css sheet %s", copy filename]; result_chan.send(Eof); diff --git a/src/servo/parser/html_builder.rs b/src/servo/parser/html_builder.rs index 3dd7c48f21c..c96bc17f593 100644 --- a/src/servo/parser/html_builder.rs +++ b/src/servo/parser/html_builder.rs @@ -11,11 +11,12 @@ import parser = parser::html_lexer; import parser::Token; import dom::style::Stylesheet; import vec::{push, push_all_move, flat_map}; + import dvec::extensions; -enum css_message { - file(~str), - exit +enum CSSMessage { + File(~str), + Exit } #[warn(no_non_implicitly_copyable_typarams)] @@ -59,15 +60,15 @@ fn link_up_attribute(scope: NodeScope, node: Node, -key: ~str, -value: ~str) { fn build_element_kind(tag_name: ~str) -> ~ElementKind { alt tag_name { - ~"div" { ~HTMLDivElement } - ~"img" { - ~HTMLImageElement({ - mut size: Size2D(geometry::px_to_au(100), - geometry::px_to_au(100)) - }) - } - ~"head" { ~HTMLHeadElement } - _ { ~UnknownElement } + ~"div" { ~HTMLDivElement } + ~"img" { + ~HTMLImageElement({ + mut size: Size2D(geometry::px_to_au(100), + geometry::px_to_au(100)) + }) + } + ~"head" { ~HTMLHeadElement } + _ { ~UnknownElement } } } @@ -85,25 +86,26 @@ spawned, collates them, and sends them to the given result channel. * `from_parent` - A port on which to receive new links. "] -fn css_link_listener(to_parent : chan, from_parent : port) { +fn css_link_listener(to_parent : chan, from_parent : port) { let mut result_vec = ~[]; loop { alt from_parent.recv() { - file(filename) { + File(filename) { let result_port = comm::port(); let result_chan = comm::chan(result_port); let filename = copy filename; task::spawn(|| { //TODO: deal with extraneous copies let filename <- copy filename; - let css_stream = css_lexer::spawn_css_lexer_task(filename); + let css_stream = css_lexer::spawn_css_lexer_from_file(filename); let mut css_rules = css_builder::build_stylesheet(css_stream); result_chan.send(css_rules); }); + push(result_vec, result_port); } - exit { + Exit { break; } } @@ -159,7 +161,7 @@ fn build_dom(scope: NodeScope, stream: port) -> (Node, port) alt elmt.get_attr(~"href") { some(filename) { #debug["Linking to a css sheet named: %s", filename]; - style_chan.send(file(copy filename)); + style_chan.send(File(copy filename)); } none { /* fall through*/ } } @@ -189,7 +191,7 @@ fn build_dom(scope: NodeScope, stream: port) -> (Node, port) } } - style_chan.send(exit); + style_chan.send(Exit); ret (cur_node, style_port); } diff --git a/src/servo/parser/lexer_util.rs b/src/servo/parser/lexer_util.rs index 31efb947e82..85f91ca355b 100644 --- a/src/servo/parser/lexer_util.rs +++ b/src/servo/parser/lexer_util.rs @@ -1,3 +1,5 @@ +#[doc = "A collection of functions that are useful for both css and html parsing."] + import option::is_none; import str::from_bytes; import vec::push; @@ -17,7 +19,6 @@ trait u8_methods { fn is_alpha() -> bool; } - impl u8_methods of u8_methods for u8 { fn is_whitespace() -> bool { ret self == ' ' as u8 || self == '\n' as u8 || self == '\t' as u8; @@ -67,34 +68,26 @@ impl util_methods of util_methods for InputState { fn expect(ch: u8) { alt self.get() { - CoeChar(c) { - if c != ch { - self.parse_err(#fmt("expected '%c'", ch as char)); - } - } - CoeEof { - self.parse_err(#fmt("expected '%c' at eof", ch as char)); - } + CoeChar(c) { if c != ch { self.parse_err(#fmt("expected '%c'", ch as char)); } } + CoeEof { self.parse_err(#fmt("expected '%c' at eof", ch as char)); } } } - + fn parse_ident() -> ~str { let mut result: ~[u8] = ~[]; loop { alt self.get() { - CoeChar(c) { - if (c.is_alpha()) { - push(result, c); - } else if result.len() == 0u { - self.parse_err(~"expected ident"); - } else { - self.unget(c); - break; - } - } - CoeEof { - self.parse_err(~"expected ident"); + CoeChar(c) { + if (c.is_alpha()) { push(result, c); } + else if result.len() == 0u { self.parse_err(~"expected ident"); } + else { + self.unget(c); + break; } + } + CoeEof { + self.parse_err(~"expected ident"); + } } } ret str::from_bytes(result); @@ -110,15 +103,15 @@ impl util_methods of util_methods for InputState { fn eat_whitespace() { loop { alt self.get() { - CoeChar(c) { - if !c.is_whitespace() { - self.unget(c); - ret; - } - } - CoeEof { + CoeChar(c) { + if !c.is_whitespace() { + self.unget(c); ret; } + } + CoeEof { + ret; + } } } } diff --git a/src/servo/parser/parser_util.rs b/src/servo/parser/parser_util.rs new file mode 100644 index 00000000000..5d7813ca569 --- /dev/null +++ b/src/servo/parser/parser_util.rs @@ -0,0 +1,98 @@ +#[doc = "Helper functions to parse values of specific attributes."] + +import dom::style::*; +import str::{pop_char, from_chars}; +import float::from_str; +import option::map; + +export parse_font_size; +export parse_size; +export parse_display_type; + +fn parse_unit(str : ~str) -> option { + alt 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| In(f)) } + s if s.ends_with(~"cm") { from_str(str.substr(0, str.len() - 2)).map(|f| Cm(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| Pc(f)) } + s if s.ends_with(~"px") { from_str(str.substr(0, str.len() - 2)).map(|f| Px(f)) } + 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| Ex(f)) } + _ { none } + } +} + +fn parse_font_size(str : ~str) -> option { + // The default pixel size, not sure if this is accurate. + let default = 16.0; + + alt 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(Em(0.8)) } + ~"larger" { some(Em(1.25)) } + ~"inherit" { some(Em(1.0)) } + _ { parse_unit(str) } + } +} + +// For width / height, and anything else with the same attribute values +fn parse_size(str : ~str) -> option { + alt str { + ~"auto" { some(Auto) } + ~"inherit" { some(Em(1.0)) } + _ { parse_unit(str) } + } +} + +fn parse_display_type(str : ~str) -> option { + alt str { + ~"inline" { some(DisInline) } + ~"block" { some(DisBlock) } + ~"none" { some(DisNone) } + _ { #debug["Recieved unknown display value '%s'", str]; none } + } +} + +#[cfg(test)] +mod test { + import css_lexer::spawn_css_lexer_from_string; + import css_builder::build_stylesheet; + + #[test] + fn should_match_font_sizes() { + let input = ~"* {font-size:12pt; font-size:inherit; font-size:2em; font-size:x-small}"; + let token_port = spawn_css_lexer_from_string(input); + let actual_rule = build_stylesheet(token_port); + let expected_rule : Stylesheet = ~[~(~[~Element(~"*", ~[])], + ~[FontSize(Pt(12.0)), + FontSize(Em(1.0)), + FontSize(Em(2.0)), + FontSize(Px(12.0))])]; + + assert actual_rule == expected_rule; + } + + #[test] + fn should_match_width_height() { + let input = ~"* {width:20%; height:auto; width:20px; width:3in; height:70mm; height:3cm}"; + let token_port = spawn_css_lexer_from_string(input); + let actual_rule = build_stylesheet(token_port); + let expected_rule : Stylesheet = ~[~(~[~Element(~"*", ~[])], + ~[Width(Percent(20.0)), + Height(Auto), + Width(Px(20.0)), + Width(In(3.0)), + Height(Mm(70.0)), + Height(Cm(3.0))])]; + + assert actual_rule == expected_rule; + } +} diff --git a/src/servo/servo.rc b/src/servo/servo.rc index a64ce32b954..223bf9b7ccf 100755 --- a/src/servo/servo.rc +++ b/src/servo/servo.rc @@ -57,6 +57,7 @@ mod layout { mod parser { mod lexer_util; + mod parser_util; mod css_lexer; mod html_lexer; mod html_builder;