diff --git a/src/servo/content.rs b/src/servo/content.rs index 0ea22d036a2..2023cc3343f 100644 --- a/src/servo/content.rs +++ b/src/servo/content.rs @@ -50,7 +50,7 @@ fn content(to_layout: chan) -> chan { let css_chan = comm::chan(css_port); task::spawn {|| let css_stream = parser::lexer:: - spawn_css_parser_task(new_file); + spawn_css_lexer_task(new_file); let css_rules = parser::css_builder:: build_stylesheet(css_stream); css_chan.send(css_rules); @@ -74,7 +74,7 @@ fn content(to_layout: chan) -> chan { join_layout(scope, to_layout); // Send new document to layout. - to_layout.send(layout::build(root)); + to_layout.send(layout::build(root, css_rules)); // Indicate that reader was forked so any further // changes will be isolated. diff --git a/src/servo/dom/style.rs b/src/servo/dom/style.rs index 10e1019ddec..f754ab908b9 100644 --- a/src/servo/dom/style.rs +++ b/src/servo/dom/style.rs @@ -2,8 +2,9 @@ import io::println; enum display_type{ - block, - inline + di_block, + di_inline, + di_none } enum style_decl{ @@ -66,8 +67,9 @@ fn print_list_vert(list : [T], print : fn(T) -> str) -> str { fn print_display(dis_ty : display_type) -> str { alt dis_ty { - block { "block" } - inline { "inline" } + di_block { "block" } + di_inline { "inline" } + di_none { "none" } } } @@ -130,7 +132,7 @@ fn test_pretty_print() { let elmt2 = ~element("body", [exact("class", "2")]); let test2 = [~([~descendant(elmt1, elmt2)], - [display(block), text_color(0u)])]; + [display(di_block), text_color(0u)])]; let actual2 = print_sheet(test2); let expected2 = "CSS Rules:\n-Selectors: (Element * with attributes: ) " diff --git a/src/servo/layout/base.rs b/src/servo/layout/base.rs index f60ac46b239..aae5b8e0b45 100644 --- a/src/servo/layout/base.rs +++ b/src/servo/layout/base.rs @@ -21,9 +21,11 @@ enum box_kind { class appearance { let mut background_image: option<@image>; + let mut background_color: option; new() { self.background_image = none; + self.background_color = none; } } diff --git a/src/servo/layout/box_builder.rs b/src/servo/layout/box_builder.rs index b303e6a84f8..13a45f2add2 100644 --- a/src/servo/layout/box_builder.rs +++ b/src/servo/layout/box_builder.rs @@ -1,12 +1,13 @@ #[doc="Creates CSS boxes from a DOM."] import dom::base::{element, es_div, es_img, nk_element, nk_text, node}; +import dom::style::{display_type, di_block, di_inline, di_none}; import dom::rcu::reader_methods; import gfx::geom; import /*layout::*/base::{appearance, bk_block, bk_inline, bk_intrinsic}; import /*layout::*/base::{bk_text, box, box_kind, btree, node_methods, ntree}; import /*layout::*/base::{rd_tree_ops, wr_tree_ops}; -import /*layout::*/style::style::{di_block, di_inline, style_methods}; +import /*layout::*/style::style::{style_methods}; import /*layout::*/text::text_box; import util::tree; @@ -62,7 +63,9 @@ impl methods for ctxt { // Add the child's box to the current enclosing box or the current // anonymous box. alt kid.get_computed_style().display { - di_block { btree.add_child(self.parent_box, kid_box); } + di_block { + btree.add_child(self.parent_box, kid_box); + } di_inline { let anon_box = alt self.anon_box { none { diff --git a/src/servo/layout/layout.rs b/src/servo/layout/layout.rs index 612c66cabdd..fb1c8291dc4 100644 --- a/src/servo/layout/layout.rs +++ b/src/servo/layout/layout.rs @@ -11,6 +11,7 @@ import gfx::geom; import gfx::renderer; import dom::base::node; import dom::rcu::scope; +import dom::style::stylesheet; import /*layout::*/base::*; import /*layout::*/style::apply::apply_style_methods; import /*layout::*/style::style::style_methods; @@ -18,7 +19,7 @@ import box_builder::box_builder_methods; import dl = display_list; enum msg { - build(node), + build(node, stylesheet), ping(chan), exit } @@ -29,16 +30,16 @@ fn layout(to_renderer: chan) -> chan { alt po.recv() { ping(ch) { ch.send(content::pong); } exit { break; } - build(node) { + build(node, styles) { #debug("layout: received layout request for:"); node.dump(); - node.recompute_style_for_subtree(); + node.recompute_style_for_subtree(styles); let this_box = node.construct_boxes(); this_box.dump(); - this_box.apply_style_for_subtree(); + this_box.apply_style_for_subtree(); this_box.reflow(geom::px_to_au(800)); let dlist = build_display_list(this_box); @@ -61,25 +62,41 @@ fn build_display_list(box: @base::box) -> display_list::display_list { } fn box_to_display_item(box: @base::box) -> dl::display_item { - let mut item; - alt box.appearance.background_image { - some(image) { - item = dl::display_item({ - item_type: dl::display_item_image(~copy *image), - bounds: copy box.bounds - }); - } - none { - let r = rand::rng(); - item = dl::display_item({ - item_type: dl::display_item_solid_color(r.next() as u8, - r.next() as u8, - r.next() as u8), - bounds: copy box.bounds - }); - } - } + let mut item; + alt box.appearance.background_image { + some(image) { + item = dl::display_item({ + item_type: dl::display_item_image(~copy *image), + bounds: copy box.bounds + }); + } + none { + alt box.appearance.background_color { + some(col) { + let red_col = (col >> 16u) & 255u; + let green_col = (col >> 8u) & 255u; + let blue_col = col & 255u; - #debug("layout: display item: %?", item); + item = dl::display_item({ + item_type: dl::display_item_solid_color(red_col as u8, + green_col as u8, + blue_col as u8), + bounds: copy box.bounds + }); + } + none { + let r = rand::rng(); + item = dl::display_item({ + item_type: dl::display_item_solid_color(r.next() as u8, + r.next() as u8, + r.next() as u8), + bounds: copy box.bounds + }); + } + } + } + } + + #debug("layout: display item: %?", item); ret item; } diff --git a/src/servo/layout/style/apply.rs b/src/servo/layout/style/apply.rs index 41ea6ef691d..e2fc94e8ffc 100644 --- a/src/servo/layout/style/apply.rs +++ b/src/servo/layout/style/apply.rs @@ -3,46 +3,51 @@ import dom::base::{es_img, nk_element, node}; import dom::rcu::reader_methods; import image::base::load; import /*layout::*/base::*; +import style::style_methods; impl apply_style_methods for @box { - fn apply_style_for_subtree() { - self.apply_style(); - for btree.each_child(self) { - |child| - child.apply_style_for_subtree(); - } - } + fn apply_style_for_subtree() { + self.apply_style(); + for btree.each_child(self) { + |child| + child.apply_style_for_subtree(); + } + } - #[doc="Applies CSS style."] - fn apply_style() { - // Right now, we only handle images. - self.node.rd { - |node| - alt node.kind { - ~nk_element(element) { - alt element.subclass { - ~es_img(*) { - alt element.get_attr("src") { - some(url) { - // FIXME: Some sort of BASE HREF support! - // FIXME: Parse URLs! - // FIXME: Don't load synchronously! - #debug("loading image from %s", url); - let image = @load(url); - self.appearance.background_image = - some(image); - } - none { - /* Ignore. */ - } - } - } - _ { /* Ignore. */ } - } - } - _ { /* Ignore. */ } - } - } - } + #[doc="Applies CSS style."] + fn apply_style() { + // Right now, we only handle images. + self.node.rd { + |node| + alt node.kind { + ~nk_element(element) { + let style = self.node.get_computed_style(); + + self.appearance.background_color = some(style.back_color); + + alt element.subclass { + ~es_img(*) { + alt element.get_attr("src") { + some(url) { + // FIXME: Some sort of BASE HREF support! + // FIXME: Parse URLs! + // FIXME: Don't load synchronously! + #debug("loading image from %s", url); + let image = @load(url); + self.appearance.background_image = + some(image); + } + none { + /* Ignore. */ + } + } + } + _ { /* Ignore. */ } + } + } + _ { /* Ignore. */ } + } + } + } } diff --git a/src/servo/layout/style/matching.rs b/src/servo/layout/style/matching.rs new file mode 100644 index 00000000000..2a5629de7a3 --- /dev/null +++ b/src/servo/layout/style/matching.rs @@ -0,0 +1,339 @@ +#[doc="Perform css selector matching"] + +import dom::base::{node, nk_element, nk_text}; +import dom::style::{selector, style_decl, font_size, display, text_color, + background_color, stylesheet, element, child, descendant, + sibling, attr, exact, exists, includes, starts_with}; +import dom::rcu::{reader_methods}; +import style::{computed_style, default_style_for_node_kind}; +import base::{layout_data}; + +export matching_methods; + +#[doc="Update the computed style of an html element with a style specified + by css."] +fn update_style(style : @computed_style, decl : style_decl) { + alt decl { + display(dis) { (*style).display = dis; } + background_color(col) { (*style).back_color = col; } + text_color(*) | font_size(*) { /* not supported yet */ } + } +} + +#[doc="Check if a css attribute matches the attribute of an html element."] +fn attrs_match(attr : attr, elmt : dom::base::element) -> bool { + alt attr { + exists(name) { + alt elmt.get_attr(name) { + some(_) { ret true; } + none { ret false; } + } + } + exact(name, val) { + alt elmt.get_attr(name) { + some(value) { ret value == val; } + none { ret false; } + } + } + includes(name, val) { + // Comply with css spec, if the specified attribute is empty + // it cannot match. + if val == "" { ret false; } + + alt elmt.get_attr(name) { + some(value) { ret value.split_char(' ').contains(val); } + none { ret false; } + } + } + starts_with(name, val) { + alt elmt.get_attr(name) { + some(value) { + //check that there is only one attribute value and it + //starts with the perscribed value + if !value.starts_with(val) || value.contains(" ") { ret false; } + + // We match on either the exact value or value-foo + if value.len() == val.len() { ret true; } + else { ret value.starts_with(val + "-"); } + } + none { ret false; } + } + } + } +} + +impl priv_matching_methods for node { + #[doc="Checks if the given css selector, which must describe a single + element with no relational information, describes the given + html element."] + fn matches_element(sel : ~selector) -> bool { + alt *sel { + child(_, _) | descendant(_, _) | sibling(_, _) { ret false; } + element(tag, attrs) { + alt self.rd { |n| copy *n.kind } { + nk_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; + } + nk_text(str) { /*fall through, currently unsupported*/ } + } + } + } + + ret false; //If we got this far it was because something was + //unsupported. + } + + #[doc = "Checks if a generic css selector matches a given html element"] + fn matches_selector(sel : ~selector) -> bool { + alt *sel { + element(str, atts) { ret self.matches_element(sel); } + child(sel1, sel2) { + alt self.rd { |n| n.tree.parent } { + some(parent) { + ret self.matches_element(sel2) && + parent.matches_selector(sel1); + } + none { ret false; } + } + } + descendant(sel1, sel2) { + if !self.matches_element(sel2) { + ret false; + } + + //loop over all ancestors to check if they are the person + //we should be descended from. + let mut cur_parent = alt self.rd { |n| n.tree.parent } { + some(parent) { parent } + none { ret false; } + }; + + loop { + if cur_parent.matches_selector(sel1) { ret true; } + + cur_parent = alt cur_parent.rd { |n| n.tree.parent } { + some(parent) { parent } + none { ret false; } + }; + } + } + sibling(sel1, sel2) { + if !self.matches_element(sel2) { ret false; } + + // loop over this node's previous siblings to see if they + // match + alt self.rd { |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.rd { |n| n.tree.prev_sibling } { + some(sib) { sib } + none { break; } + }; + } + } + none { } + } + + // check the rest of the siblings + alt self.rd { |n| n.tree.next_sibling } { + some(sib) { + let mut cur_sib = sib; + loop { + if cur_sib.matches_selector(sel1) { ret true; } + + cur_sib = alt cur_sib.rd { |n| n.tree.next_sibling } { + some(sib) { sib } + none { break; } + }; + } + } + none { } + } + + ret false; + } + } + } +} + +impl matching_methods for node { + #[doc="Compare an html element to a list of css rules and update its + style according to the rules matching it."] + fn match_css_style(styles : stylesheet) -> computed_style { + let node_kind = self.rd { |n| copy *n.kind }; + let style = + @default_style_for_node_kind(node_kind); + + // Loop over each rule, see if our node matches what is + // described in the rule. If it matches, update its style. + // As we don't currently have priorities of style information, + // the latest rule takes precedence so we can just overwrite + // style information. + for styles.each { |sty| + let (selectors, decls) <- *(copy sty); + for selectors.each { |sel| + if self.matches_selector(sel) { + #debug("Matched selector {%?} with node {%?}", + *sel, node_kind); + for decls.each { |decl| + update_style(style, decl); + } + } + } + } + + #debug["Changed the style to: %?", *style]; + + ret copy *(style); + } +} + +mod test { + import dom::base::{node_scope, methods, nk_element, attr, es_div, + es_img, es_unknown, es_head, wr_tree_ops}; + import dvec::{dvec, extensions}; + import io::println; + + fn new_node_from_attr(scope : node_scope, name : str, val : str) -> node { + let elmt = dom::base::element("div", ~es_div); + let attr = ~attr(name, val); + elmt.attrs.push(copy attr); + ret scope.new_node(nk_element(elmt)); + } + + #[test] + fn test_match_pipe1() { + let scope = node_scope(); + let node = new_node_from_attr(scope, "lang", "en-us"); + + let sel = element("*", [starts_with("lang", "en")]); + + assert node.matches_selector(~sel); + } + + #[test] + fn test_match_pipe2() { + let scope = node_scope(); + let node = new_node_from_attr(scope, "lang", "en"); + + let sel = element("*", [starts_with("lang", "en")]); + + assert node.matches_selector(~sel); + } + + #[test] + fn test_not_match_pipe() { + let scope = node_scope(); + let node = new_node_from_attr(scope, "lang", "english"); + + let sel = element("*", [starts_with("lang", "en")]); + + assert !node.matches_selector(~sel); + } + + #[test] + fn test_match_includes() { + let scope = node_scope(); + let node = new_node_from_attr(scope, "mad", "hatter cobler cooper"); + + let sel = element("div", [includes("mad", "hatter")]); + + assert node.matches_selector(~sel); + } + + #[test] + fn test_match_exists() { + let scope = node_scope(); + let node = new_node_from_attr(scope, "mad", "hatter cobler cooper"); + + let sel1 = element("div", [exists("mad")]); + let sel2 = element("div", [exists("hatter")]); + + assert node.matches_selector(~sel1); + assert !node.matches_selector(~sel2); + } + + #[test] + fn test_match_exact() { + let scope = node_scope(); + let node1 = new_node_from_attr(scope, "mad", "hatter cobler cooper"); + let node2 = new_node_from_attr(scope, "mad", "hatter"); + + let sel = element("div", [exact("mad", "hatter")]); + + assert !node1.matches_selector(~copy sel); + assert node2.matches_selector(~sel); + } + + #[test] + fn match_tree() { + let scope = node_scope(); + + let root = new_node_from_attr(scope, "class", "blue"); + let child1 = new_node_from_attr(scope, "id", "green"); + let child2 = new_node_from_attr(scope, "flag", "black"); + let gchild = new_node_from_attr(scope, "flag", "grey"); + let ggchild = new_node_from_attr(scope, "flag", "white"); + let gggchild = new_node_from_attr(scope, "flag", "purple"); + + scope.add_child(root, child1); + scope.add_child(root, child2); + scope.add_child(child2, gchild); + scope.add_child(gchild, ggchild); + scope.add_child(ggchild, gggchild); + + let sel1 = descendant(~element("*", [exact("class", "blue")]), + ~element("*", [])); + + assert !root.matches_selector(~copy sel1); + assert child1.matches_selector(~copy sel1); + assert child2.matches_selector(~copy sel1); + assert gchild.matches_selector(~copy sel1); + assert ggchild.matches_selector(~copy sel1); + assert gggchild.matches_selector(~sel1); + + let sel2 = descendant(~child(~element("*", [exact("class", "blue")]), + ~element("*", [])), + ~element("div", [exists("flag")])); + + assert !root.matches_selector(~copy sel2); + assert !child1.matches_selector(~copy sel2); + assert !child2.matches_selector(~copy sel2); + assert gchild.matches_selector(~copy sel2); + assert ggchild.matches_selector(~copy sel2); + assert gggchild.matches_selector(~sel2); + + let sel3 = sibling(~element("*", []), ~element("*", [])); + + assert !root.matches_selector(~copy sel3); + assert child1.matches_selector(~copy sel3); + assert child2.matches_selector(~copy sel3); + assert !gchild.matches_selector(~copy sel3); + assert !ggchild.matches_selector(~copy sel3); + assert !gggchild.matches_selector(~sel3); + + let sel4 = descendant(~child(~element("*", [exists("class")]), + ~element("*", [])), + ~element("*", [])); + + assert !root.matches_selector(~copy sel4); + assert !child1.matches_selector(~copy sel4); + assert !child2.matches_selector(~copy sel4); + assert gchild.matches_selector(~copy sel4); + assert ggchild.matches_selector(~copy sel4); + assert gggchild.matches_selector(~sel4); + } +} diff --git a/src/servo/layout/style/style.rs b/src/servo/layout/style/style.rs index 814e5c5e21c..515871a5e1f 100644 --- a/src/servo/layout/style/style.rs +++ b/src/servo/layout/style/style.rs @@ -1,35 +1,39 @@ #[doc="High-level interface to CSS selector matching."] +import dom::style::{display_type, di_block, di_inline, di_none, + stylesheet}; import dom::base::{element, es_div, es_head, es_img, nk_element, nk_text}; import dom::base::{node}; import dom::base::node_kind; import dom::rcu::reader_methods; import /*layout::*/base::*; // FIXME: resolve bug requires * +import matching::matching_methods; -enum computed_style = { - mut display: display -}; - -enum display { - di_block, - di_inline, - di_none -} +type computed_style = {mut display : display_type, + mut back_color : uint}; #[doc="Returns the default style for the given node kind."] fn default_style_for_node_kind(kind: node_kind) -> computed_style { alt kind { - nk_text(*) { - computed_style({ mut display: di_inline }) - } - nk_element(element) { - alt *element.subclass { - es_div { computed_style({ mut display: di_block }) } - es_head { computed_style({ mut display: di_none }) } - es_img(*) { computed_style({ mut display: di_inline }) } - es_unknown { computed_style({ mut display: di_inline }) } - } + nk_text(*) { + {mut display: di_inline, + mut back_color : 256u*256u*256u-1u} + } + nk_element(element) { + let r = rand::rng(); + let rand_color = 256u*256u*((r.next() & (255 as u32)) as uint) + + 256u*((r.next() & (255 as u32)) as uint) + + ((r.next() & (255 as u32)) as uint); + + alt *element.subclass { + es_div { {mut display : di_block, + mut back_color : rand_color} } + es_head { {mut display : di_none, mut back_color : rand_color} } + es_img(*) { {mut display : di_inline, mut back_color : rand_color} } + es_unknown { {mut display : di_inline, mut back_color : + rand_color} } } + } } } @@ -41,14 +45,13 @@ impl style_priv for node { the node (the reader-auxiliary box in the RCU model) and populates it with the computed style. "] - fn recompute_style() { - let default_style: computed_style = - default_style_for_node_kind(self.rd { |n| copy *n.kind }); + fn recompute_style(styles : stylesheet) { + let style = self.match_css_style(styles); #debug("recomputing style; parent node:"); let the_layout_data = @layout_data({ - mut computed_style: default_style, + mut computed_style: style, mut box: none }); @@ -78,12 +81,14 @@ impl style_methods for node { 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 computed style. + + TODO: compute the style of multiple nodes in parallel. "] - fn recompute_style_for_subtree() { - self.recompute_style(); + fn recompute_style_for_subtree(styles : stylesheet) { + self.recompute_style(styles); for ntree.each_child(self) { |kid| - kid.recompute_style_for_subtree(); + kid.recompute_style_for_subtree(styles); } } } diff --git a/src/servo/parser/css_builder.rs b/src/servo/parser/css_builder.rs index cc74afb5f5d..01dd83fe951 100644 --- a/src/servo/parser/css_builder.rs +++ b/src/servo/parser/css_builder.rs @@ -21,6 +21,7 @@ impl methods for token_reader { } fn unget(tok : token) { + assert self.lookahead.is_none(); self.lookahead = some(tok); } } @@ -85,7 +86,7 @@ fn parse_color(color : str) -> uint { i += 1u; - while i < color_vec.len() && color_vec[i] != ',' as u8 { + while i < color_vec.len() && color_vec[i] != ')' as u8 { blue_vec += [color_vec[i]]; i += 1u; } @@ -220,8 +221,9 @@ fn parse_rule(reader : token_reader) -> option<~rule> { } "display" { alt val { - "inline" { desc_list += [display(inline)]; } - "block" { desc_list += [display(block)]; } + "inline" { desc_list += [display(di_inline)]; } + "block" { desc_list += [display(di_block)]; } + "none" { desc_list += [display(di_none)]; } _ { #debug["Recieved unknown display value '%s'", val]; } } diff --git a/src/servo/parser/lexer.rs b/src/servo/parser/lexer.rs index e68a5ef3695..8d621cca668 100644 --- a/src/servo/parser/lexer.rs +++ b/src/servo/parser/lexer.rs @@ -493,7 +493,7 @@ fn spawn_html_parser_task(filename: str) -> port { ret result_port; } -fn spawn_css_parser_task(filename: str) -> port { +fn spawn_css_lexer_task(filename: str) -> port { let result_port = port(); let result_chan = chan(result_port); task::spawn {|| diff --git a/src/servo/servo.rc b/src/servo/servo.rc index 2ea68868145..3427cb56528 100755 --- a/src/servo/servo.rc +++ b/src/servo/servo.rc @@ -37,6 +37,7 @@ mod layout { mod style { mod apply; mod style; + mod matching; } mod base; diff --git a/src/test/test.css b/src/test/test.css index f2094480d9e..5b3146bf0b0 100644 --- a/src/test/test.css +++ b/src/test/test.css @@ -3,3 +3,8 @@ p img {color : rgb(0,255,0); display : block } p.blue > p.green + p.red { background-color : blue ;color : green } img[class] .pastoral *[lang|=en] { display:inline} .book > #novel + *[type=novella] p { color : blue; color : white } +* {background-color : red} +* * * * {background-color : black} +div div {background-color : green} +* * div {background-color : blue} +div div div {background-color : rgb(100,100,100)} diff --git a/src/test/test_bg_color.css b/src/test/test_bg_color.css new file mode 100644 index 00000000000..a987a0264b2 --- /dev/null +++ b/src/test/test_bg_color.css @@ -0,0 +1,16 @@ +.green {background-color : green} +.blue {background-color : blue} +.red {background-color : red} +.white {background-color : white} +.black {background-color : black} +.brown {background-color : rgb(200,100,0)} +.gray {background-color : rgb(100,100,100)} +.lightgray {background-color : rgb(200,200,200)} +.darkgray {background-color : rgb(50,50,50)} +.cyan {background-color : rgb(0,255,255)} +.maroon {background-color : rgb(100,0,20)} +.pink {background-color : rgb(255,0,255)} +.orange {background-color : rgb(255,175,0)} +.violet {background-color : rgb(100,0,150)} +.darkgreen {background-color : rgb(0,100,0)} +.darkblue {background-color : rgb(0,0,100)} diff --git a/src/test/test_bg_color.html b/src/test/test_bg_color.html new file mode 100644 index 00000000000..8a13f04ae27 --- /dev/null +++ b/src/test/test_bg_color.html @@ -0,0 +1,12 @@ +
+
+
+ + +
+
+ + +
+
+