From dfaf2e68279b5720f08eab3987b63d13bfa64610 Mon Sep 17 00:00:00 2001 From: David Raifaizen Date: Wed, 17 Aug 2016 12:17:48 -0500 Subject: [PATCH] servo: Merge #12572 - 10933/shorthands (from craftytrickster:10933/shorthands); r=emilio This implements the serialization of nearly all of the css shorthand properties with the following exceptions: 1. font - this may be implemented correctly, but I am not 100% sure 2. border-radius - I do not know how to implement this, since I am not familiar with how the property works 3. background - this is implemented, but I think that the implementation might be a tiny bit off. --- - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes fix #10933 (github issue number if applicable). Also fixes issue #11448 - [X] There are tests for these changes OR - [ ] These changes do not require tests because _____ Source-Repo: https://github.com/servo/servo Source-Revision: b61c45639a264943f8dec61c66b46d40f9d274a1 --HG-- rename : servo/components/script/dom/validation.rs => servo/tests/unit/style/properties/mod.rs --- .../style/properties/helpers.mako.rs | 86 +- .../style/properties/properties.mako.rs | 88 +- .../properties/shorthand/background.mako.rs | 229 +++-- .../style/properties/shorthand/border.mako.rs | 128 ++- .../style/properties/shorthand/box.mako.rs | 390 ++++---- .../style/properties/shorthand/column.mako.rs | 65 +- .../style/properties/shorthand/font.mako.rs | 136 +-- .../shorthand/inherited_text.mako.rs | 15 +- .../style/properties/shorthand/list.mako.rs | 163 ++-- .../properties/shorthand/outline.mako.rs | 118 ++- .../properties/shorthand/position.mako.rs | 133 +-- .../properties/shorthand/serialize.mako.rs | 97 ++ .../style/properties/shorthand/text.mako.rs | 75 +- .../style/values/computed/basic_shape.rs | 30 +- .../style/values/specified/basic_shape.rs | 30 +- servo/tests/unit/style/properties.rs | 114 --- servo/tests/unit/style/properties/mod.rs | 7 + .../unit/style/properties/scaffolding.rs | 53 ++ .../unit/style/properties/serialization.rs | 829 ++++++++++++++++++ servo/tests/unit/style/properties/viewport.rs | 27 + 20 files changed, 2093 insertions(+), 720 deletions(-) create mode 100644 servo/components/style/properties/shorthand/serialize.mako.rs delete mode 100644 servo/tests/unit/style/properties.rs create mode 100644 servo/tests/unit/style/properties/mod.rs create mode 100644 servo/tests/unit/style/properties/scaffolding.rs create mode 100644 servo/tests/unit/style/properties/serialization.rs create mode 100644 servo/tests/unit/style/properties/viewport.rs diff --git a/servo/components/style/properties/helpers.mako.rs b/servo/components/style/properties/helpers.mako.rs index 09ebc9318bca..acb4504eea93 100644 --- a/servo/components/style/properties/helpers.mako.rs +++ b/servo/components/style/properties/helpers.mako.rs @@ -403,9 +403,11 @@ %> % if shorthand: pub mod ${shorthand.ident} { - use cssparser::Parser; + #[allow(unused_imports)] + use cssparser::{Parser, ToCss}; use parser::ParserContext; use properties::{longhands, PropertyDeclaration, DeclaredValue, Shorthand}; + use std::fmt; pub struct Longhands { % for sub_property in shorthand.sub_properties: @@ -414,6 +416,55 @@ % endfor } + /// Represents a serializable set of all of the longhand properties that correspond to a shorthand + pub struct LonghandsToSerialize<'a> { + % for sub_property in shorthand.sub_properties: + pub ${sub_property.ident}: &'a DeclaredValue, + % endfor + } + + impl<'a> LonghandsToSerialize<'a> { + pub fn from_iter>(iter: I) -> Result { + // Define all of the expected variables that correspond to the shorthand + % for sub_property in shorthand.sub_properties: + let mut ${sub_property.ident} = None; + % endfor + + // Attempt to assign the incoming declarations to the expected variables + for longhand in iter { + match *longhand { + % for sub_property in shorthand.sub_properties: + PropertyDeclaration::${sub_property.camel_case}(ref value) => { + ${sub_property.ident} = Some(value) + }, + % endfor + _ => {} + }; + } + + // If any of the expected variables are missing, return an error + match ( + % for sub_property in shorthand.sub_properties: + ${sub_property.ident}, + % endfor + ) { + + ( + % for sub_property in shorthand.sub_properties: + Some(${sub_property.ident}), + % endfor + ) => + Ok(LonghandsToSerialize { + % for sub_property in shorthand.sub_properties: + ${sub_property.ident}: ${sub_property.ident}, + % endfor + }), + _ => Err(()) + } + } + } + + pub fn parse(context: &ParserContext, input: &mut Parser, declarations: &mut Vec) -> Result<(), ()> { @@ -454,10 +505,7 @@ } } - #[allow(unused_variables)] - pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result { - ${caller.body()} - } + ${caller.body()} } % endif @@ -468,12 +516,26 @@ for side in ['top', 'right', 'bottom', 'left'])}"> use super::parse_four_sides; use values::specified; - let _unused = context; - let (top, right, bottom, left) = try!(parse_four_sides(input, ${parser_function})); - Ok(Longhands { - % for side in ["top", "right", "bottom", "left"]: - ${to_rust_ident(sub_property_pattern % side)}: Some(${side}), - % endfor - }) + + pub fn parse_value(_: &ParserContext, input: &mut Parser) -> Result { + let (top, right, bottom, left) = try!(parse_four_sides(input, ${parser_function})); + Ok(Longhands { + % for side in ["top", "right", "bottom", "left"]: + ${to_rust_ident(sub_property_pattern % side)}: Some(${side}), + % endfor + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + super::serialize_four_sides( + dest, + self.${to_rust_ident(sub_property_pattern % 'top')}, + self.${to_rust_ident(sub_property_pattern % 'right')}, + self.${to_rust_ident(sub_property_pattern % 'bottom')}, + self.${to_rust_ident(sub_property_pattern % 'left')} + ) + } + } diff --git a/servo/components/style/properties/properties.mako.rs b/servo/components/style/properties/properties.mako.rs index 986e0bc3e43f..7b19126aeb67 100644 --- a/servo/components/style/properties/properties.mako.rs +++ b/servo/components/style/properties/properties.mako.rs @@ -6,7 +6,7 @@ // Please note that valid Rust syntax may be mangled by the Mako parser. // For example, Vec<&Foo> will be mangled as Vec&Foo>. To work around these issues, the code -// can be escaped. In the above example, Vec<<&Foo> achieves the desired result of Vec<&Foo>. +// can be escaped. In the above example, Vec<<&Foo> or Vec< &Foo> achieves the desired result of Vec<&Foo>. <%namespace name="helpers" file="/helpers.mako.rs" /> @@ -74,8 +74,7 @@ pub mod longhands { } pub mod shorthands { - use cssparser::{Parser, ToCss}; - use std::fmt; + use cssparser::Parser; use parser::ParserContext; use values::specified; @@ -122,33 +121,7 @@ pub mod shorthands { Ok((top, right, bottom, left)) } - /// Serialize a set of top,left,bottom,right values, in -shorthand style, - /// attempting to minimize the output - pub fn serialize_four_sides(sides: (&T, &T, &T, &T), dest: &mut W) -> fmt::Result - where W: fmt::Write, T: ToCss+PartialEq { - if sides.0 == sides.1 && sides.0 == sides.2 && sides.0 == sides.3 { - sides.0.to_css(dest) - } else if sides.0 == sides.2 && sides.1 == sides.3 { - try!(sides.0.to_css(dest)); - try!(dest.write_str(" ")); - sides.1.to_css(dest) - } else if sides.1 == sides.3 { - try!(sides.0.to_css(dest)); - try!(dest.write_str(" ")); - try!(sides.1.to_css(dest)); - try!(dest.write_str(" ")); - sides.2.to_css(dest) - } else { - try!(sides.0.to_css(dest)); - try!(dest.write_str(" ")); - try!(sides.1.to_css(dest)); - try!(dest.write_str(" ")); - try!(sides.2.to_css(dest)); - try!(dest.write_str(" ")); - sides.3.to_css(dest) - } - } - + <%include file="/shorthand/serialize.mako.rs" /> <%include file="/shorthand/background.mako.rs" /> <%include file="/shorthand/border.mako.rs" /> <%include file="/shorthand/box.mako.rs" /> @@ -435,18 +408,14 @@ impl ToCss for PropertyDeclarationBlock { } } -enum AppendableValue<'a, I> +pub enum AppendableValue<'a, I> where I: Iterator { Declaration(&'a PropertyDeclaration), - DeclarationsForShorthand(I), + DeclarationsForShorthand(Shorthand, I), Css(&'a str) } -fn append_property_name(dest: &mut W, - property_name: &str, - is_first_serialization: &mut bool) - -> fmt::Result where W: fmt::Write { - +fn handle_first_serialization(dest: &mut W, is_first_serialization: &mut bool) -> fmt::Result where W: fmt::Write { // after first serialization(key: value;) add whitespace between the pairs if !*is_first_serialization { try!(write!(dest, " ")); @@ -455,7 +424,7 @@ fn append_property_name(dest: &mut W, *is_first_serialization = false; } - write!(dest, "{}", property_name) + Ok(()) } fn append_declaration_value<'a, W, I> @@ -471,15 +440,8 @@ fn append_declaration_value<'a, W, I> AppendableValue::Declaration(decl) => { try!(decl.to_css(dest)); }, - AppendableValue::DeclarationsForShorthand(decls) => { - let mut decls = decls.peekable(); - while let Some(decl) = decls.next() { - try!(decl.to_css(dest)); - - if decls.peek().is_some() { - try!(write!(dest, " ")); - } - } + AppendableValue::DeclarationsForShorthand(shorthand, decls) => { + try!(shorthand.longhands_to_css(decls, dest)); } } @@ -498,8 +460,15 @@ fn append_serialization<'a, W, I>(dest: &mut W, -> fmt::Result where W: fmt::Write, I: Iterator { - try!(append_property_name(dest, property_name, is_first_serialization)); - try!(write!(dest, ":")); + try!(handle_first_serialization(dest, is_first_serialization)); + + // Overflow does not behave like a normal shorthand. When overflow-x and overflow-y are not of equal + // values, they no longer use the shared property name "overflow" and must be handled differently + if shorthands::is_overflow_shorthand(&appendable_value) { + return append_declaration_value(dest, appendable_value, is_important); + } + + write!(dest, "{}:", property_name); // for normal parsed values, add a space between key: and value match &appendable_value { @@ -512,7 +481,7 @@ fn append_serialization<'a, W, I>(dest: &mut W, try!(write!(dest, " ")); } }, - &AppendableValue::DeclarationsForShorthand(_) => try!(write!(dest, " ")) + &AppendableValue::DeclarationsForShorthand(..) => try!(write!(dest, " ")) } try!(append_declaration_value(dest, appendable_value, is_important)); @@ -691,6 +660,20 @@ impl Shorthand { } } + pub fn longhands_to_css<'a, W, I>(&self, declarations: I, dest: &mut W) -> fmt::Result + where W: fmt::Write, I: Iterator { + match *self { + % for property in data.shorthands: + Shorthand::${property.camel_case} => { + match shorthands::${property.ident}::LonghandsToSerialize::from_iter(declarations) { + Ok(longhands) => longhands.to_css(dest), + Err(_) => Err(fmt::Error) + } + }, + % endfor + } + } + /// Serializes possible shorthand value to String. pub fn serialize_shorthand_value_to_string<'a, I>(self, declarations: I, is_important: bool) -> String where I: Iterator + Clone { @@ -747,10 +730,7 @@ impl Shorthand { } if !declarations3.any(|d| d.with_variables()) { - return Some(AppendableValue::DeclarationsForShorthand(declarations)); - // FIXME: this needs property-specific code, which probably should be in style/ - // "as appropriate according to the grammar of shorthand " - // https://drafts.csswg.org/cssom/#serialize-a-css-value + return Some(AppendableValue::DeclarationsForShorthand(self, declarations)); } None diff --git a/servo/components/style/properties/shorthand/background.mako.rs b/servo/components/style/properties/shorthand/background.mako.rs index 4d6e3c8821ad..853e55549373 100644 --- a/servo/components/style/properties/shorthand/background.mako.rs +++ b/servo/components/style/properties/shorthand/background.mako.rs @@ -11,89 +11,176 @@ use properties::longhands::{background_color, background_position, background_repeat, background_attachment}; use properties::longhands::{background_image, background_size, background_origin, background_clip}; - let mut color = None; - let mut image = None; - let mut position = None; - let mut repeat = None; - let mut size = None; - let mut attachment = None; - let mut any = false; - let mut origin = None; - let mut clip = None; + pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result { + let mut color = None; + let mut image = None; + let mut position = None; + let mut repeat = None; + let mut size = None; + let mut attachment = None; + let mut any = false; + let mut origin = None; + let mut clip = None; - loop { - if position.is_none() { - if let Ok(value) = input.try(|input| background_position::parse(context, input)) { - position = Some(value); - any = true; + loop { + if position.is_none() { + if let Ok(value) = input.try(|input| background_position::parse(context, input)) { + position = Some(value); + any = true; - // Parse background size, if applicable. - size = input.try(|input| { - try!(input.expect_delim('/')); - background_size::parse(context, input) - }).ok(); + // Parse background size, if applicable. + size = input.try(|input| { + try!(input.expect_delim('/')); + background_size::parse(context, input) + }).ok(); - continue + continue + } } - } - if color.is_none() { - if let Ok(value) = input.try(|input| background_color::parse(context, input)) { - color = Some(value); - any = true; - continue + if color.is_none() { + if let Ok(value) = input.try(|input| background_color::parse(context, input)) { + color = Some(value); + any = true; + continue + } } - } - if image.is_none() { - if let Ok(value) = input.try(|input| background_image::parse(context, input)) { - image = Some(value); - any = true; - continue + if image.is_none() { + if let Ok(value) = input.try(|input| background_image::parse(context, input)) { + image = Some(value); + any = true; + continue + } } - } - if repeat.is_none() { - if let Ok(value) = input.try(|input| background_repeat::parse(context, input)) { - repeat = Some(value); - any = true; - continue + if repeat.is_none() { + if let Ok(value) = input.try(|input| background_repeat::parse(context, input)) { + repeat = Some(value); + any = true; + continue + } } - } - if attachment.is_none() { - if let Ok(value) = input.try(|input| background_attachment::parse(context, input)) { - attachment = Some(value); - any = true; - continue + if attachment.is_none() { + if let Ok(value) = input.try(|input| background_attachment::parse(context, input)) { + attachment = Some(value); + any = true; + continue + } } - } - if origin.is_none() { - if let Ok(value) = input.try(|input| background_origin::parse(context, input)) { - origin = Some(value); - any = true; - continue + if origin.is_none() { + if let Ok(value) = input.try(|input| background_origin::parse(context, input)) { + origin = Some(value); + any = true; + continue + } } - } - if clip.is_none() { - if let Ok(value) = input.try(|input| background_clip::parse(context, input)) { - clip = Some(value); - any = true; - continue + if clip.is_none() { + if let Ok(value) = input.try(|input| background_clip::parse(context, input)) { + clip = Some(value); + any = true; + continue + } } + break + } + + if any { + Ok(Longhands { + background_color: color, + background_image: image, + background_position: position, + background_repeat: repeat, + background_attachment: attachment, + background_size: size, + background_origin: origin, + background_clip: clip, + }) + } else { + Err(()) } - break } - if any { - Ok(Longhands { - background_color: color, - background_image: image, - background_position: position, - background_repeat: repeat, - background_attachment: attachment, - background_size: size, - background_origin: origin, - background_clip: clip, - }) - } else { - Err(()) + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match *self.background_color { + DeclaredValue::Value(ref color) => { + try!(color.to_css(dest)); + }, + _ => { + try!(write!(dest, "transparent")); + } + }; + + try!(write!(dest, " ")); + + match *self.background_image { + DeclaredValue::Value(ref image) => { + try!(image.to_css(dest)); + }, + _ => { + try!(write!(dest, "none")); + } + }; + + try!(write!(dest, " ")); + + try!(self.background_repeat.to_css(dest)); + + try!(write!(dest, " ")); + + match *self.background_attachment { + DeclaredValue::Value(ref attachment) => { + try!(attachment.to_css(dest)); + }, + _ => { + try!(write!(dest, "scroll")); + } + }; + + try!(write!(dest, " ")); + + try!(self.background_position.to_css(dest)); + + if let DeclaredValue::Value(ref size) = *self.background_size { + try!(write!(dest, " / ")); + try!(size.to_css(dest)); + } + + match (self.background_origin, self.background_clip) { + (&DeclaredValue::Value(ref origin), &DeclaredValue::Value(ref clip)) => { + use properties::longhands::background_origin::computed_value::T as Origin; + use properties::longhands::background_clip::computed_value::T as Clip; + + try!(write!(dest, " ")); + + match (origin, clip) { + (&Origin::padding_box, &Clip::padding_box) => { + try!(origin.to_css(dest)); + }, + (&Origin::border_box, &Clip::border_box) => { + try!(origin.to_css(dest)); + }, + (&Origin::content_box, &Clip::content_box) => { + try!(origin.to_css(dest)); + }, + _ => { + try!(origin.to_css(dest)); + try!(write!(dest, " ")); + try!(clip.to_css(dest)); + } + } + }, + (&DeclaredValue::Value(ref origin), _) => { + try!(write!(dest, " ")); + try!(origin.to_css(dest)); + }, + (_, &DeclaredValue::Value(ref clip)) => { + try!(write!(dest, " ")); + try!(clip.to_css(dest)); + }, + _ => {} + }; + + + Ok(()) + } } - diff --git a/servo/components/style/properties/shorthand/border.mako.rs b/servo/components/style/properties/shorthand/border.mako.rs index 99565b871016..f1d868b26cc4 100644 --- a/servo/components/style/properties/shorthand/border.mako.rs +++ b/servo/components/style/properties/shorthand/border.mako.rs @@ -13,14 +13,39 @@ ${helpers.four_sides_shorthand("border-style", "border-%s-style", for side in ['top', 'right', 'bottom', 'left'])}"> use super::parse_four_sides; use values::specified; - let _unused = context; - let (top, right, bottom, left) = try!(parse_four_sides(input, specified::parse_border_width)); - Ok(Longhands { - % for side in ["top", "right", "bottom", "left"]: - ${to_rust_ident('border-%s-width' % side)}: - Some(longhands::${to_rust_ident('border-%s-width' % side)}::SpecifiedValue(${side})), - % endfor - }) + + pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result { + let _unused = context; + let (top, right, bottom, left) = try!(parse_four_sides(input, specified::parse_border_width)); + Ok(Longhands { + % for side in ["top", "right", "bottom", "left"]: + ${to_rust_ident('border-%s-width' % side)}: + Some(longhands::${to_rust_ident('border-%s-width' % side)}::SpecifiedValue(${side})), + % endfor + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + // extract tuple container values so that the different border widths + // can be compared via partial eq + % for side in ["top", "right", "bottom", "left"]: + let ${side} = match self.border_${side}_width { + &DeclaredValue::Value(ref value) => DeclaredValue::Value(value.0), + &DeclaredValue::WithVariables { + css: ref a, first_token_type: ref b, base_url: ref c, from_shorthand: ref d + } => DeclaredValue::WithVariables { + // WithVariables should not be reachable during serialization + css: a.clone(), first_token_type: b.clone(), base_url: c.clone(), from_shorthand: d.clone() + }, + &DeclaredValue::Initial => DeclaredValue::Initial, + &DeclaredValue::Inherit => DeclaredValue::Inherit, + }; + % endfor + + super::serialize_four_sides(dest, &top, &right, &bottom, &left) + } + } @@ -61,12 +86,13 @@ pub fn parse_border(context: &ParserContext, input: &mut Parser) if any { Ok((color, style, width)) } else { Err(()) } } - % for side in ["top", "right", "bottom", "left"]: <%helpers:shorthand name="border-${side}" sub_properties="${' '.join( 'border-%s-%s' % (side, prop) for prop in ['color', 'style', 'width'] )}"> + + pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result { let (color, style, width) = try!(super::parse_border(context, input)); Ok(Longhands { border_${side}_color: color, @@ -74,6 +100,19 @@ pub fn parse_border(context: &ParserContext, input: &mut Parser) border_${side}_width: width.map(longhands::${to_rust_ident('border-%s-width' % side)}::SpecifiedValue), }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + super::serialize_directional_border( + dest, + self.border_${side}_width, + self.border_${side}_style, + self.border_${side}_color + ) + } + } + % endfor @@ -82,15 +121,32 @@ pub fn parse_border(context: &ParserContext, input: &mut Parser) for side in ['top', 'right', 'bottom', 'left'] for prop in ['color', 'style', 'width'] )}"> - let (color, style, width) = try!(super::parse_border(context, input)); - Ok(Longhands { - % for side in ["top", "right", "bottom", "left"]: - border_${side}_color: color.clone(), - border_${side}_style: style, - border_${side}_width: - width.map(longhands::${to_rust_ident('border-%s-width' % side)}::SpecifiedValue), - % endfor - }) + + pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result { + let (color, style, width) = try!(super::parse_border(context, input)); + Ok(Longhands { + % for side in ["top", "right", "bottom", "left"]: + border_${side}_color: color.clone(), + border_${side}_style: style, + border_${side}_width: + width.map(longhands::${to_rust_ident('border-%s-width' % side)}::SpecifiedValue), + % endfor + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + // If all longhands are all present, then all sides should be the same, + // so we can just one set of color/style/width + super::serialize_directional_border( + dest, + self.border_${side}_width, + self.border_${side}_style, + self.border_${side}_color + ) + } + } + <%helpers:shorthand name="border-radius" sub_properties="${' '.join( @@ -99,13 +155,33 @@ pub fn parse_border(context: &ParserContext, input: &mut Parser) )}"> use values::specified::basic_shape::BorderRadius; - let _ignored = context; + pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result { + let _ignored = context; - let radii = try!(BorderRadius::parse(input)); - Ok(Longhands { - border_top_left_radius: Some(radii.top_left), - border_top_right_radius: Some(radii.top_right), - border_bottom_right_radius: Some(radii.bottom_right), - border_bottom_left_radius: Some(radii.bottom_left), - }) + let radii = try!(BorderRadius::parse(input)); + Ok(Longhands { + border_top_left_radius: Some(radii.top_left), + border_top_right_radius: Some(radii.top_right), + border_bottom_right_radius: Some(radii.bottom_right), + border_bottom_left_radius: Some(radii.bottom_left), + }) + } + + // TODO: I do not understand how border radius works with respect to the slashes /, + // so putting a default generic impl for now + // https://developer.mozilla.org/en-US/docs/Web/CSS/border-radius + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + try!(self.border_top_left_radius.to_css(dest)); + try!(write!(dest, " ")); + + try!(self.border_top_right_radius.to_css(dest)); + try!(write!(dest, " ")); + + try!(self.border_bottom_right_radius.to_css(dest)); + try!(write!(dest, " ")); + + self.border_bottom_left_radius.to_css(dest) + } + } diff --git a/servo/components/style/properties/shorthand/box.mako.rs b/servo/components/style/properties/shorthand/box.mako.rs index 416923ced647..6a502276f4e8 100644 --- a/servo/components/style/properties/shorthand/box.mako.rs +++ b/servo/components/style/properties/shorthand/box.mako.rs @@ -7,11 +7,47 @@ <%helpers:shorthand name="overflow" sub_properties="overflow-x overflow-y"> use properties::longhands::{overflow_x, overflow_y}; - let overflow = try!(overflow_x::parse(context, input)); - Ok(Longhands { - overflow_x: Some(overflow), - overflow_y: Some(overflow_y::SpecifiedValue(overflow)), - }) + pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result { + let overflow = try!(overflow_x::parse(context, input)); + Ok(Longhands { + overflow_x: Some(overflow), + overflow_y: Some(overflow_y::SpecifiedValue(overflow)), + }) + } + + + // Overflow does not behave like a normal shorthand. When overflow-x and overflow-y are not of equal + // values, they no longer use the shared property name "overflow". + // Other shorthands do not include their name in the to_css method + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + let x_and_y_equal = match (self.overflow_x, self.overflow_y) { + (&DeclaredValue::Value(ref x_value), &DeclaredValue::Value(ref y_container)) => { + *x_value == y_container.0 + }, + (&DeclaredValue::WithVariables { .. }, &DeclaredValue::WithVariables { .. }) => true, + (&DeclaredValue::Initial, &DeclaredValue::Initial) => true, + (&DeclaredValue::Inherit, &DeclaredValue::Inherit) => true, + _ => false + }; + + if x_and_y_equal { + try!(write!(dest, "overflow")); + try!(write!(dest, ": ")); + try!(self.overflow_x.to_css(dest)); + } else { + try!(write!(dest, "overflow-x")); + try!(write!(dest, ": ")); + try!(self.overflow_x.to_css(dest)); + try!(write!(dest, "; ")); + + try!(write!(dest, "overflow-y: ")); + try!(self.overflow_y.to_css(dest)); + } + + write!(dest, ";") + } + } macro_rules! try_parse_one { @@ -32,67 +68,84 @@ macro_rules! try_parse_one { use properties::longhands::{transition_delay, transition_duration, transition_property}; use properties::longhands::{transition_timing_function}; - struct SingleTransition { - transition_property: transition_property::SingleSpecifiedValue, - transition_duration: transition_duration::SingleSpecifiedValue, - transition_timing_function: transition_timing_function::SingleSpecifiedValue, - transition_delay: transition_delay::SingleSpecifiedValue, - } - - fn parse_one_transition(input: &mut Parser) -> Result { - let (mut property, mut duration) = (None, None); - let (mut timing_function, mut delay) = (None, None); - loop { - try_parse_one!(input, property, transition_property); - try_parse_one!(input, duration, transition_duration); - try_parse_one!(input, timing_function, transition_timing_function); - try_parse_one!(input, delay, transition_delay); - - break + pub fn parse_value(_: &ParserContext, input: &mut Parser) -> Result { + struct SingleTransition { + transition_property: transition_property::SingleSpecifiedValue, + transition_duration: transition_duration::SingleSpecifiedValue, + transition_timing_function: transition_timing_function::SingleSpecifiedValue, + transition_delay: transition_delay::SingleSpecifiedValue, } - if let Some(property) = property { - Ok(SingleTransition { - transition_property: property, - transition_duration: - duration.unwrap_or_else(transition_duration::get_initial_single_value), - transition_timing_function: - timing_function.unwrap_or_else( - transition_timing_function::get_initial_single_value), - transition_delay: - delay.unwrap_or_else(transition_delay::get_initial_single_value), + fn parse_one_transition(input: &mut Parser) -> Result { + let (mut property, mut duration) = (None, None); + let (mut timing_function, mut delay) = (None, None); + loop { + try_parse_one!(input, property, transition_property); + try_parse_one!(input, duration, transition_duration); + try_parse_one!(input, timing_function, transition_timing_function); + try_parse_one!(input, delay, transition_delay); + + break + } + + if let Some(property) = property { + Ok(SingleTransition { + transition_property: property, + transition_duration: + duration.unwrap_or_else(transition_duration::get_initial_single_value), + transition_timing_function: + timing_function.unwrap_or_else( + transition_timing_function::get_initial_single_value), + transition_delay: + delay.unwrap_or_else(transition_delay::get_initial_single_value), + }) + } else { + Err(()) + } + } + + if input.try(|input| input.expect_ident_matching("none")).is_ok() { + return Ok(Longhands { + transition_property: None, + transition_duration: None, + transition_timing_function: None, + transition_delay: None, }) - } else { - Err(()) } - } - if input.try(|input| input.expect_ident_matching("none")).is_ok() { - return Ok(Longhands { - transition_property: None, - transition_duration: None, - transition_timing_function: None, - transition_delay: None, + let results = try!(input.parse_comma_separated(parse_one_transition)); + let (mut properties, mut durations) = (Vec::new(), Vec::new()); + let (mut timing_functions, mut delays) = (Vec::new(), Vec::new()); + for result in results { + properties.push(result.transition_property); + durations.push(result.transition_duration); + timing_functions.push(result.transition_timing_function); + delays.push(result.transition_delay); + } + + Ok(Longhands { + transition_property: Some(transition_property::SpecifiedValue(properties)), + transition_duration: Some(transition_duration::SpecifiedValue(durations)), + transition_timing_function: + Some(transition_timing_function::SpecifiedValue(timing_functions)), + transition_delay: Some(transition_delay::SpecifiedValue(delays)), }) } - let results = try!(input.parse_comma_separated(parse_one_transition)); - let (mut properties, mut durations) = (Vec::new(), Vec::new()); - let (mut timing_functions, mut delays) = (Vec::new(), Vec::new()); - for result in results { - properties.push(result.transition_property); - durations.push(result.transition_duration); - timing_functions.push(result.transition_timing_function); - delays.push(result.transition_delay); - } + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + try!(self.transition_property.to_css(dest)); + try!(write!(dest, " ")); - Ok(Longhands { - transition_property: Some(transition_property::SpecifiedValue(properties)), - transition_duration: Some(transition_duration::SpecifiedValue(durations)), - transition_timing_function: - Some(transition_timing_function::SpecifiedValue(timing_functions)), - transition_delay: Some(transition_delay::SpecifiedValue(delays)), - }) + try!(self.transition_duration.to_css(dest)); + try!(write!(dest, " ")); + + try!(self.transition_timing_function.to_css(dest)); + try!(write!(dest, " ")); + + self.transition_delay.to_css(dest) + } + } <%helpers:shorthand name="animation" @@ -104,111 +157,142 @@ macro_rules! try_parse_one { use properties::longhands::{animation_delay, animation_iteration_count, animation_direction}; use properties::longhands::{animation_fill_mode, animation_play_state}; - struct SingleAnimation { - animation_name: animation_name::SingleSpecifiedValue, - animation_duration: animation_duration::SingleSpecifiedValue, - animation_timing_function: animation_timing_function::SingleSpecifiedValue, - animation_delay: animation_delay::SingleSpecifiedValue, - animation_iteration_count: animation_iteration_count::SingleSpecifiedValue, - animation_direction: animation_direction::SingleSpecifiedValue, - animation_fill_mode: animation_fill_mode::SingleSpecifiedValue, - animation_play_state: animation_play_state::SingleSpecifiedValue, - } - - fn parse_one_animation(input: &mut Parser) -> Result { - let mut duration = None; - let mut timing_function = None; - let mut delay = None; - let mut iteration_count = None; - let mut direction = None; - let mut fill_mode = None; - let mut play_state = None; - let mut name = None; - - // NB: Name must be the last one here so that keywords valid for other - // longhands are not interpreted as names. - // - // Also, duration must be before delay, see - // https://drafts.csswg.org/css-animations/#typedef-single-animation - loop { - try_parse_one!(input, duration, animation_duration); - try_parse_one!(input, timing_function, animation_timing_function); - try_parse_one!(input, delay, animation_delay); - try_parse_one!(input, iteration_count, animation_iteration_count); - try_parse_one!(input, direction, animation_direction); - try_parse_one!(input, fill_mode, animation_fill_mode); - try_parse_one!(input, play_state, animation_play_state); - try_parse_one!(input, name, animation_name); - - break + pub fn parse_value(_: &ParserContext, input: &mut Parser) -> Result { + struct SingleAnimation { + animation_name: animation_name::SingleSpecifiedValue, + animation_duration: animation_duration::SingleSpecifiedValue, + animation_timing_function: animation_timing_function::SingleSpecifiedValue, + animation_delay: animation_delay::SingleSpecifiedValue, + animation_iteration_count: animation_iteration_count::SingleSpecifiedValue, + animation_direction: animation_direction::SingleSpecifiedValue, + animation_fill_mode: animation_fill_mode::SingleSpecifiedValue, + animation_play_state: animation_play_state::SingleSpecifiedValue, } - if let Some(name) = name { - Ok(SingleAnimation { - animation_name: name, - animation_duration: - duration.unwrap_or_else(animation_duration::get_initial_single_value), - animation_timing_function: - timing_function.unwrap_or_else(animation_timing_function::get_initial_single_value), - animation_delay: - delay.unwrap_or_else(animation_delay::get_initial_single_value), - animation_iteration_count: - iteration_count.unwrap_or_else(animation_iteration_count::get_initial_single_value), - animation_direction: - direction.unwrap_or_else(animation_direction::get_initial_single_value), - animation_fill_mode: - fill_mode.unwrap_or_else(animation_fill_mode::get_initial_single_value), - animation_play_state: - play_state.unwrap_or_else(animation_play_state::get_initial_single_value), + fn parse_one_animation(input: &mut Parser) -> Result { + let mut duration = None; + let mut timing_function = None; + let mut delay = None; + let mut iteration_count = None; + let mut direction = None; + let mut fill_mode = None; + let mut play_state = None; + let mut name = None; + + // NB: Name must be the last one here so that keywords valid for other + // longhands are not interpreted as names. + // + // Also, duration must be before delay, see + // https://drafts.csswg.org/css-animations/#typedef-single-animation + loop { + try_parse_one!(input, duration, animation_duration); + try_parse_one!(input, timing_function, animation_timing_function); + try_parse_one!(input, delay, animation_delay); + try_parse_one!(input, iteration_count, animation_iteration_count); + try_parse_one!(input, direction, animation_direction); + try_parse_one!(input, fill_mode, animation_fill_mode); + try_parse_one!(input, play_state, animation_play_state); + try_parse_one!(input, name, animation_name); + + break + } + + if let Some(name) = name { + Ok(SingleAnimation { + animation_name: name, + animation_duration: + duration.unwrap_or_else(animation_duration::get_initial_single_value), + animation_timing_function: + timing_function.unwrap_or_else(animation_timing_function::get_initial_single_value), + animation_delay: + delay.unwrap_or_else(animation_delay::get_initial_single_value), + animation_iteration_count: + iteration_count.unwrap_or_else(animation_iteration_count::get_initial_single_value), + animation_direction: + direction.unwrap_or_else(animation_direction::get_initial_single_value), + animation_fill_mode: + fill_mode.unwrap_or_else(animation_fill_mode::get_initial_single_value), + animation_play_state: + play_state.unwrap_or_else(animation_play_state::get_initial_single_value), + }) + } else { + Err(()) + } + } + + if input.try(|input| input.expect_ident_matching("none")).is_ok() { + return Ok(Longhands { + animation_name: None, + animation_duration: None, + animation_timing_function: None, + animation_delay: None, + animation_iteration_count: None, + animation_direction: None, + animation_fill_mode: None, + animation_play_state: None, }) - } else { - Err(()) } - } - if input.try(|input| input.expect_ident_matching("none")).is_ok() { - return Ok(Longhands { - animation_name: None, - animation_duration: None, - animation_timing_function: None, - animation_delay: None, - animation_iteration_count: None, - animation_direction: None, - animation_fill_mode: None, - animation_play_state: None, + let results = try!(input.parse_comma_separated(parse_one_animation)); + + let mut names = vec![]; + let mut durations = vec![]; + let mut timing_functions = vec![]; + let mut delays = vec![]; + let mut iteration_counts = vec![]; + let mut directions = vec![]; + let mut fill_modes = vec![]; + let mut play_states = vec![]; + + for result in results.into_iter() { + names.push(result.animation_name); + durations.push(result.animation_duration); + timing_functions.push(result.animation_timing_function); + delays.push(result.animation_delay); + iteration_counts.push(result.animation_iteration_count); + directions.push(result.animation_direction); + fill_modes.push(result.animation_fill_mode); + play_states.push(result.animation_play_state); + } + + Ok(Longhands { + animation_name: Some(animation_name::SpecifiedValue(names)), + animation_duration: Some(animation_duration::SpecifiedValue(durations)), + animation_timing_function: Some(animation_timing_function::SpecifiedValue(timing_functions)), + animation_delay: Some(animation_delay::SpecifiedValue(delays)), + animation_iteration_count: Some(animation_iteration_count::SpecifiedValue(iteration_counts)), + animation_direction: Some(animation_direction::SpecifiedValue(directions)), + animation_fill_mode: Some(animation_fill_mode::SpecifiedValue(fill_modes)), + animation_play_state: Some(animation_play_state::SpecifiedValue(play_states)), }) } - let results = try!(input.parse_comma_separated(parse_one_animation)); + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + try!(self.animation_duration.to_css(dest)); + try!(write!(dest, " ")); - let mut names = vec![]; - let mut durations = vec![]; - let mut timing_functions = vec![]; - let mut delays = vec![]; - let mut iteration_counts = vec![]; - let mut directions = vec![]; - let mut fill_modes = vec![]; - let mut play_states = vec![]; + // FIXME: timing function is displaying the actual mathematical name "cubic-bezier(0.25, 0.1, 0.25, 1)" + // instead of the common name "ease" + try!(self.animation_timing_function.to_css(dest)); + try!(write!(dest, " ")); - for result in results.into_iter() { - names.push(result.animation_name); - durations.push(result.animation_duration); - timing_functions.push(result.animation_timing_function); - delays.push(result.animation_delay); - iteration_counts.push(result.animation_iteration_count); - directions.push(result.animation_direction); - fill_modes.push(result.animation_fill_mode); - play_states.push(result.animation_play_state); + try!(self.animation_delay.to_css(dest)); + try!(write!(dest, " ")); + + try!(self.animation_direction.to_css(dest)); + try!(write!(dest, " ")); + + try!(self.animation_fill_mode.to_css(dest)); + try!(write!(dest, " ")); + + try!(self.animation_iteration_count.to_css(dest)); + try!(write!(dest, " ")); + + try!(self.animation_play_state.to_css(dest)); + try!(write!(dest, " ")); + + self.animation_name.to_css(dest) + } } - - Ok(Longhands { - animation_name: Some(animation_name::SpecifiedValue(names)), - animation_duration: Some(animation_duration::SpecifiedValue(durations)), - animation_timing_function: Some(animation_timing_function::SpecifiedValue(timing_functions)), - animation_delay: Some(animation_delay::SpecifiedValue(delays)), - animation_iteration_count: Some(animation_iteration_count::SpecifiedValue(iteration_counts)), - animation_direction: Some(animation_direction::SpecifiedValue(directions)), - animation_fill_mode: Some(animation_fill_mode::SpecifiedValue(fill_modes)), - animation_play_state: Some(animation_play_state::SpecifiedValue(play_states)), - }) diff --git a/servo/components/style/properties/shorthand/column.mako.rs b/servo/components/style/properties/shorthand/column.mako.rs index 8b3e91628da1..563ef210fdfa 100644 --- a/servo/components/style/properties/shorthand/column.mako.rs +++ b/servo/components/style/properties/shorthand/column.mako.rs @@ -6,41 +6,54 @@ <%helpers:shorthand name="columns" sub_properties="column-count column-width" experimental="True"> use properties::longhands::{column_count, column_width}; - let mut column_count = None; - let mut column_width = None; - let mut autos = 0; - loop { - if input.try(|input| input.expect_ident_matching("auto")).is_ok() { - // Leave the options to None, 'auto' is the initial value. - autos += 1; - continue - } + pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result { - if column_count.is_none() { - if let Ok(value) = input.try(|input| column_count::parse(context, input)) { - column_count = Some(value); + let mut column_count = None; + let mut column_width = None; + let mut autos = 0; + + loop { + if input.try(|input| input.expect_ident_matching("auto")).is_ok() { + // Leave the options to None, 'auto' is the initial value. + autos += 1; continue } - } - if column_width.is_none() { - if let Ok(value) = input.try(|input| column_width::parse(context, input)) { - column_width = Some(value); - continue + if column_count.is_none() { + if let Ok(value) = input.try(|input| column_count::parse(context, input)) { + column_count = Some(value); + continue + } } + + if column_width.is_none() { + if let Ok(value) = input.try(|input| column_width::parse(context, input)) { + column_width = Some(value); + continue + } + } + + break } - break + let values = autos + column_count.iter().len() + column_width.iter().len(); + if values == 0 || values > 2 { + Err(()) + } else { + Ok(Longhands { + column_count: column_count, + column_width: column_width, + }) + } } - let values = autos + column_count.iter().len() + column_width.iter().len(); - if values == 0 || values > 2 { - Err(()) - } else { - Ok(Longhands { - column_count: column_count, - column_width: column_width, - }) + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + try!(self.column_width.to_css(dest)); + try!(write!(dest, " ")); + + self.column_count.to_css(dest) + } } diff --git a/servo/components/style/properties/shorthand/font.mako.rs b/servo/components/style/properties/shorthand/font.mako.rs index 2268f2ff5d21..aae11eb3cf6d 100644 --- a/servo/components/style/properties/shorthand/font.mako.rs +++ b/servo/components/style/properties/shorthand/font.mako.rs @@ -8,59 +8,97 @@ font-size line-height font-family"> use properties::longhands::{font_style, font_variant, font_weight, font_size, line_height, font_family}; - let mut nb_normals = 0; - let mut style = None; - let mut variant = None; - let mut weight = None; - let size; - loop { - // Special-case 'normal' because it is valid in each of - // font-style, font-weight and font-variant. - // Leaves the values to None, 'normal' is the initial value for each of them. - if input.try(|input| input.expect_ident_matching("normal")).is_ok() { - nb_normals += 1; - continue; - } - if style.is_none() { - if let Ok(value) = input.try(|input| font_style::parse(context, input)) { - style = Some(value); - continue + + pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result { + let mut nb_normals = 0; + let mut style = None; + let mut variant = None; + let mut weight = None; + let size; + loop { + // Special-case 'normal' because it is valid in each of + // font-style, font-weight and font-variant. + // Leaves the values to None, 'normal' is the initial value for each of them. + if input.try(|input| input.expect_ident_matching("normal")).is_ok() { + nb_normals += 1; + continue; } - } - if weight.is_none() { - if let Ok(value) = input.try(|input| font_weight::parse(context, input)) { - weight = Some(value); - continue + if style.is_none() { + if let Ok(value) = input.try(|input| font_style::parse(context, input)) { + style = Some(value); + continue + } } - } - if variant.is_none() { - if let Ok(value) = input.try(|input| font_variant::parse(context, input)) { - variant = Some(value); - continue + if weight.is_none() { + if let Ok(value) = input.try(|input| font_weight::parse(context, input)) { + weight = Some(value); + continue + } } + if variant.is_none() { + if let Ok(value) = input.try(|input| font_variant::parse(context, input)) { + variant = Some(value); + continue + } + } + size = Some(try!(font_size::parse(context, input))); + break } - size = Some(try!(font_size::parse(context, input))); - break + #[inline] + fn count(opt: &Option) -> u8 { + if opt.is_some() { 1 } else { 0 } + } + if size.is_none() || (count(&style) + count(&weight) + count(&variant) + nb_normals) > 3 { + return Err(()) + } + let line_height = if input.try(|input| input.expect_delim('/')).is_ok() { + Some(try!(line_height::parse(context, input))) + } else { + None + }; + let family = try!(input.parse_comma_separated(font_family::parse_one_family)); + Ok(Longhands { + font_style: style, + font_variant: variant, + font_weight: weight, + font_size: size, + line_height: line_height, + font_family: Some(font_family::SpecifiedValue(family)) + }) } - #[inline] - fn count(opt: &Option) -> u8 { - if opt.is_some() { 1 } else { 0 } + + // This may be a bit off, unsure, possibly needs changes + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + if let DeclaredValue::Value(ref style) = *self.font_style { + try!(style.to_css(dest)); + try!(write!(dest, " ")); + } + + if let DeclaredValue::Value(ref variant) = *self.font_variant { + try!(variant.to_css(dest)); + try!(write!(dest, " ")); + } + + if let DeclaredValue::Value(ref weight) = *self.font_weight { + try!(weight.to_css(dest)); + try!(write!(dest, " ")); + } + + try!(self.font_size.to_css(dest)); + if let DeclaredValue::Value(ref height) = *self.line_height { + match *height { + line_height::SpecifiedValue::Normal => {}, + _ => { + try!(write!(dest, "/")); + try!(height.to_css(dest)); + } + } + } + + try!(write!(dest, " ")); + + self.font_family.to_css(dest) + } } - if size.is_none() || (count(&style) + count(&weight) + count(&variant) + nb_normals) > 3 { - return Err(()) - } - let line_height = if input.try(|input| input.expect_delim('/')).is_ok() { - Some(try!(line_height::parse(context, input))) - } else { - None - }; - let family = try!(input.parse_comma_separated(font_family::parse_one_family)); - Ok(Longhands { - font_style: style, - font_variant: variant, - font_weight: weight, - font_size: size, - line_height: line_height, - font_family: Some(font_family::SpecifiedValue(family)) - }) diff --git a/servo/components/style/properties/shorthand/inherited_text.mako.rs b/servo/components/style/properties/shorthand/inherited_text.mako.rs index 85aac33930db..e7c19127a152 100644 --- a/servo/components/style/properties/shorthand/inherited_text.mako.rs +++ b/servo/components/style/properties/shorthand/inherited_text.mako.rs @@ -8,7 +8,16 @@ // the `overflow-wrap` property, as if it were a shorthand of `overflow-wrap`." <%helpers:shorthand name="word-wrap" sub_properties="overflow-wrap"> use properties::longhands::overflow_wrap; - Ok(Longhands { - overflow_wrap: Some(try!(overflow_wrap::parse(context, input))), - }) + + pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result { + Ok(Longhands { + overflow_wrap: Some(try!(overflow_wrap::parse(context, input))), + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + self.overflow_wrap.to_css(dest) + } + } diff --git a/servo/components/style/properties/shorthand/list.mako.rs b/servo/components/style/properties/shorthand/list.mako.rs index 2b0f61b3a8c7..6e1abf337fe4 100644 --- a/servo/components/style/properties/shorthand/list.mako.rs +++ b/servo/components/style/properties/shorthand/list.mako.rs @@ -8,85 +8,110 @@ sub_properties="list-style-image list-style-position list-style-type"> use properties::longhands::{list_style_image, list_style_position, list_style_type}; - // `none` is ambiguous until we've finished parsing the shorthands, so we count the number - // of times we see it. - let mut nones = 0u8; - let (mut image, mut position, mut list_style_type, mut any) = (None, None, None, false); - loop { - if input.try(|input| input.expect_ident_matching("none")).is_ok() { - nones = nones + 1; - if nones > 2 { - return Err(()) - } - any = true; - continue - } - - if list_style_type.is_none() { - if let Ok(value) = input.try(|input| list_style_type::parse(context, input)) { - list_style_type = Some(value); + pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result { + // `none` is ambiguous until we've finished parsing the shorthands, so we count the number + // of times we see it. + let mut nones = 0u8; + let (mut image, mut position, mut list_style_type, mut any) = (None, None, None, false); + loop { + if input.try(|input| input.expect_ident_matching("none")).is_ok() { + nones = nones + 1; + if nones > 2 { + return Err(()) + } any = true; continue } + + if list_style_type.is_none() { + if let Ok(value) = input.try(|input| list_style_type::parse(context, input)) { + list_style_type = Some(value); + any = true; + continue + } + } + + if image.is_none() { + if let Ok(value) = input.try(|input| list_style_image::parse(context, input)) { + image = Some(value); + any = true; + continue + } + } + + if position.is_none() { + if let Ok(value) = input.try(|input| list_style_position::parse(context, input)) { + position = Some(value); + any = true; + continue + } + } + break } - if image.is_none() { - if let Ok(value) = input.try(|input| list_style_image::parse(context, input)) { - image = Some(value); - any = true; - continue + // If there are two `none`s, then we can't have a type or image; if there is one `none`, + // then we can't have both a type *and* an image; if there is no `none` then we're fine as + // long as we parsed something. + match (any, nones, list_style_type, image) { + (true, 2, None, None) => { + Ok(Longhands { + list_style_position: position, + list_style_image: Some(list_style_image::SpecifiedValue::None), + list_style_type: Some(list_style_type::SpecifiedValue::none), + }) } - } - - if position.is_none() { - if let Ok(value) = input.try(|input| list_style_position::parse(context, input)) { - position = Some(value); - any = true; - continue + (true, 1, None, Some(image)) => { + Ok(Longhands { + list_style_position: position, + list_style_image: Some(image), + list_style_type: Some(list_style_type::SpecifiedValue::none), + }) } + (true, 1, Some(list_style_type), None) => { + Ok(Longhands { + list_style_position: position, + list_style_image: Some(list_style_image::SpecifiedValue::None), + list_style_type: Some(list_style_type), + }) + } + (true, 1, None, None) => { + Ok(Longhands { + list_style_position: position, + list_style_image: Some(list_style_image::SpecifiedValue::None), + list_style_type: Some(list_style_type::SpecifiedValue::none), + }) + } + (true, 0, list_style_type, image) => { + Ok(Longhands { + list_style_position: position, + list_style_image: image, + list_style_type: list_style_type, + }) + } + _ => Err(()), } - break } - // If there are two `none`s, then we can't have a type or image; if there is one `none`, - // then we can't have both a type *and* an image; if there is no `none` then we're fine as - // long as we parsed something. - match (any, nones, list_style_type, image) { - (true, 2, None, None) => { - Ok(Longhands { - list_style_position: position, - list_style_image: Some(list_style_image::SpecifiedValue::None), - list_style_type: Some(list_style_type::SpecifiedValue::none), - }) + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match *self.list_style_position { + DeclaredValue::Initial => try!(write!(dest, "outside")), + _ => try!(self.list_style_position.to_css(dest)) + } + + try!(write!(dest, " ")); + + match *self.list_style_image { + DeclaredValue::Initial => try!(write!(dest, "none")), + _ => try!(self.list_style_image.to_css(dest)) + }; + + try!(write!(dest, " ")); + + match *self.list_style_type { + DeclaredValue::Initial => write!(dest, "disc"), + _ => self.list_style_type.to_css(dest) + } } - (true, 1, None, Some(image)) => { - Ok(Longhands { - list_style_position: position, - list_style_image: Some(image), - list_style_type: Some(list_style_type::SpecifiedValue::none), - }) - } - (true, 1, Some(list_style_type), None) => { - Ok(Longhands { - list_style_position: position, - list_style_image: Some(list_style_image::SpecifiedValue::None), - list_style_type: Some(list_style_type), - }) - } - (true, 1, None, None) => { - Ok(Longhands { - list_style_position: position, - list_style_image: Some(list_style_image::SpecifiedValue::None), - list_style_type: Some(list_style_type::SpecifiedValue::none), - }) - } - (true, 0, list_style_type, image) => { - Ok(Longhands { - list_style_position: position, - list_style_image: image, - list_style_type: list_style_type, - }) - } - _ => Err(()), } diff --git a/servo/components/style/properties/shorthand/outline.mako.rs b/servo/components/style/properties/shorthand/outline.mako.rs index 09f3e4e3702f..c00294f45df8 100644 --- a/servo/components/style/properties/shorthand/outline.mako.rs +++ b/servo/components/style/properties/shorthand/outline.mako.rs @@ -8,43 +8,65 @@ use properties::longhands::outline_width; use values::specified; - let _unused = context; - let mut color = None; - let mut style = None; - let mut width = None; - let mut any = false; - loop { - if color.is_none() { - if let Ok(value) = input.try(specified::CSSColor::parse) { - color = Some(value); - any = true; - continue + pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result { + let _unused = context; + let mut color = None; + let mut style = None; + let mut width = None; + let mut any = false; + loop { + if color.is_none() { + if let Ok(value) = input.try(specified::CSSColor::parse) { + color = Some(value); + any = true; + continue + } } - } - if style.is_none() { - if let Ok(value) = input.try(specified::BorderStyle::parse) { - style = Some(value); - any = true; - continue + if style.is_none() { + if let Ok(value) = input.try(specified::BorderStyle::parse) { + style = Some(value); + any = true; + continue + } } - } - if width.is_none() { - if let Ok(value) = input.try(|input| outline_width::parse(context, input)) { - width = Some(value); - any = true; - continue + if width.is_none() { + if let Ok(value) = input.try(|input| outline_width::parse(context, input)) { + width = Some(value); + any = true; + continue + } } + break + } + if any { + Ok(Longhands { + outline_color: color, + outline_style: style, + outline_width: width, + }) + } else { + Err(()) } - break } - if any { - Ok(Longhands { - outline_color: color, - outline_style: style, - outline_width: width, - }) - } else { - Err(()) + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + try!(self.outline_width.to_css(dest)); + try!(write!(dest, " ")); + + match *self.outline_style { + DeclaredValue::Initial => try!(write!(dest, "none")), + _ => try!(self.outline_style.to_css(dest)) + }; + + match *self.outline_color { + DeclaredValue::Initial => Ok(()), + _ => { + try!(write!(dest, " ")); + self.outline_color.to_css(dest) + } + } + } } @@ -55,12 +77,30 @@ )}" products="gecko"> use properties::shorthands; - // Re-use border-radius parsing. - shorthands::border_radius::parse_value(context, input).map(|longhands| { - Longhands { - % for corner in ["top_left", "top_right", "bottom_right", "bottom_left"]: - _moz_outline_radius_${corner.replace("_", "")}: longhands.border_${corner}_radius, - % endfor + pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result { + // Re-use border-radius parsing. + shorthands::border_radius::parse_value(context, input).map(|longhands| { + Longhands { + % for corner in ["top_left", "top_right", "bottom_right", "bottom_left"]: + _moz_outline_radius_${corner.replace("_", "")}: longhands.border_${corner}_radius, + % endfor + } + }) + } + + // TODO: Border radius for the radius shorthand is not implemented correctly yet + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + try!(self._moz_outline_radius_topleft.to_css(dest)); + try!(write!(dest, " ")); + + try!(self._moz_outline_radius_topright.to_css(dest)); + try!(write!(dest, " ")); + + try!(self._moz_outline_radius_bottomright.to_css(dest)); + try!(write!(dest, " ")); + + self._moz_outline_radius_bottomleft.to_css(dest) } - }) + } diff --git a/servo/components/style/properties/shorthand/position.mako.rs b/servo/components/style/properties/shorthand/position.mako.rs index 4710afd121ef..9fc33e00f428 100644 --- a/servo/components/style/properties/shorthand/position.mako.rs +++ b/servo/components/style/properties/shorthand/position.mako.rs @@ -9,31 +9,50 @@ experimental="True"> use properties::longhands::{flex_direction, flex_wrap}; - let mut direction = None; - let mut wrap = None; - loop { - if direction.is_none() { - if let Ok(value) = input.try(|input| flex_direction::parse(context, input)) { - direction = Some(value); - continue + pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result { + let mut direction = None; + let mut wrap = None; + loop { + if direction.is_none() { + if let Ok(value) = input.try(|input| flex_direction::parse(context, input)) { + direction = Some(value); + continue + } } - } - if wrap.is_none() { - if let Ok(value) = input.try(|input| flex_wrap::parse(context, input)) { - wrap = Some(value); - continue + if wrap.is_none() { + if let Ok(value) = input.try(|input| flex_wrap::parse(context, input)) { + wrap = Some(value); + continue + } } + break } - break + + if direction.is_none() && wrap.is_none() { + return Err(()) + } + Ok(Longhands { + flex_direction: direction, + flex_wrap: wrap, + }) } - if direction.is_none() && wrap.is_none() { - return Err(()) + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match *self.flex_direction { + DeclaredValue::Initial => try!(write!(dest, "row")), + _ => try!(self.flex_direction.to_css(dest)) + }; + + try!(write!(dest, " ")); + + match *self.flex_wrap { + DeclaredValue::Initial => write!(dest, "nowrap"), + _ => self.flex_wrap.to_css(dest) + } + } } - Ok(Longhands { - flex_direction: direction, - flex_wrap: wrap, - }) // https://drafts.csswg.org/css-flexbox/#flex-property @@ -49,40 +68,54 @@ Ok((grow, shrink)) } - let mut grow = None; - let mut shrink = None; - let mut basis = None; + pub fn parse_value(_: &ParserContext, input: &mut Parser) -> Result { + let mut grow = None; + let mut shrink = None; + let mut basis = None; - if input.try(|input| input.expect_ident_matching("none")).is_ok() { - return Ok(Longhands { - flex_grow: Some(Number(0.0)), - flex_shrink: Some(Number(0.0)), - flex_basis: Some(LengthOrPercentageOrAutoOrContent::Auto) + if input.try(|input| input.expect_ident_matching("none")).is_ok() { + return Ok(Longhands { + flex_grow: Some(Number(0.0)), + flex_shrink: Some(Number(0.0)), + flex_basis: Some(LengthOrPercentageOrAutoOrContent::Auto) + }) + } + loop { + if grow.is_none() { + if let Ok((flex_grow, flex_shrink)) = input.try(parse_flexibility) { + grow = Some(flex_grow); + shrink = flex_shrink; + continue + } + } + if basis.is_none() { + if let Ok(value) = input.try(LengthOrPercentageOrAutoOrContent::parse) { + basis = Some(value); + continue + } + } + break + } + + if grow.is_none() && basis.is_none() { + return Err(()) + } + Ok(Longhands { + flex_grow: grow.or(Some(Number(1.0))), + flex_shrink: shrink.or(Some(Number(1.0))), + flex_basis: basis.or(Some(LengthOrPercentageOrAutoOrContent::Length(Length::Absolute(Au(0))))) }) } - loop { - if grow.is_none() { - if let Ok((flex_grow, flex_shrink)) = input.try(parse_flexibility) { - grow = Some(flex_grow); - shrink = flex_shrink; - continue - } - } - if basis.is_none() { - if let Ok(value) = input.try(LengthOrPercentageOrAutoOrContent::parse) { - basis = Some(value); - continue - } - } - break - } - if grow.is_none() && basis.is_none() { - return Err(()) + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + try!(self.flex_grow.to_css(dest)); + try!(write!(dest, " ")); + + try!(self.flex_shrink.to_css(dest)); + try!(write!(dest, " ")); + + self.flex_basis.to_css(dest) + } } - Ok(Longhands { - flex_grow: grow.or(Some(Number(1.0))), - flex_shrink: shrink.or(Some(Number(1.0))), - flex_basis: basis.or(Some(LengthOrPercentageOrAutoOrContent::Length(Length::Absolute(Au(0))))) - }) diff --git a/servo/components/style/properties/shorthand/serialize.mako.rs b/servo/components/style/properties/shorthand/serialize.mako.rs new file mode 100644 index 000000000000..4fa05dedc45d --- /dev/null +++ b/servo/components/style/properties/shorthand/serialize.mako.rs @@ -0,0 +1,97 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use cssparser::ToCss; +use properties::{AppendableValue, DeclaredValue, PropertyDeclaration, Shorthand}; +use values::specified::{BorderStyle, CSSColor}; +use std::fmt; + +pub fn serialize_four_sides(dest: &mut W, top: &I, right: &I, bottom: &I, left: &I) + -> fmt::Result where W: fmt::Write, I: ToCss + PartialEq { + + if left == right { + let horizontal_value = left; + + if top == bottom { + let vertical_value = top; + + if horizontal_value == vertical_value { + let single_value = horizontal_value; + try!(single_value.to_css(dest)); + } else { + try!(vertical_value.to_css(dest)); + try!(write!(dest, " ")); + + try!(horizontal_value.to_css(dest)); + } + } else { + try!(top.to_css(dest)); + try!(write!(dest, " ")); + + try!(horizontal_value.to_css(dest)); + try!(write!(dest, " ")); + + try!(bottom.to_css(dest)); + } + } else { + try!(top.to_css(dest)); + try!(write!(dest, " ")); + + try!(right.to_css(dest)); + try!(write!(dest, " ")); + + try!(bottom.to_css(dest)); + try!(write!(dest, " ")); + + try!(left.to_css(dest)); + } + + Ok(()) +} + +fn serialize_directional_border(dest: &mut W, + width: &DeclaredValue, + style: &DeclaredValue, + color: &DeclaredValue) + -> fmt::Result where W: fmt::Write, I: ToCss { + match *width { + DeclaredValue::Value(ref width) => { + try!(width.to_css(dest)); + }, + _ => { + try!(write!(dest, "medium")); + } + }; + + try!(write!(dest, " ")); + + match *style { + DeclaredValue::Value(ref style) => { + try!(style.to_css(dest)); + }, + _ => { + try!(write!(dest, "none")); + } + }; + + match *color { + DeclaredValue::Value(ref color) => { + try!(write!(dest, " ")); + color.to_css(dest) + }, + _ => Ok(()) + } +} + + +pub fn is_overflow_shorthand<'a, I>(appendable_value: &AppendableValue<'a, I>) -> bool + where I: Iterator { + if let AppendableValue::DeclarationsForShorthand(shorthand, _) = *appendable_value { + if let Shorthand::Overflow = shorthand { + return true; + } + } + + false +} diff --git a/servo/components/style/properties/shorthand/text.mako.rs b/servo/components/style/properties/shorthand/text.mako.rs index becdb1112311..2bcfbfcd7b99 100644 --- a/servo/components/style/properties/shorthand/text.mako.rs +++ b/servo/components/style/properties/shorthand/text.mako.rs @@ -13,34 +13,61 @@ use properties::longhands::{text_decoration_color, text_decoration_line, text_decoration_style}; use values::specified::CSSColor; - let (mut color, mut line, mut style, mut any) = (None, None, None, false); - loop { - macro_rules! parse_component { - ($value:ident, $module:ident) => ( - if $value.is_none() { - if let Ok(value) = input.try(|input| $module::parse(context, input)) { - $value = Some(value); - any = true; - continue; + pub fn parse_value(context: &ParserContext, input: &mut Parser) -> Result { + let (mut color, mut line, mut style, mut any) = (None, None, None, false); + loop { + macro_rules! parse_component { + ($value:ident, $module:ident) => ( + if $value.is_none() { + if let Ok(value) = input.try(|input| $module::parse(context, input)) { + $value = Some(value); + any = true; + continue; + } } - } - ) + ) + } + + parse_component!(color, text_decoration_color); + parse_component!(line, text_decoration_line); + parse_component!(style, text_decoration_style); + break; } - parse_component!(color, text_decoration_color); - parse_component!(line, text_decoration_line); - parse_component!(style, text_decoration_style); - break; + if !any { + return Err(()); + } + + Ok(Longhands { + text_decoration_color: color.or(Some(CSSColor { parsed: CSSParserColor::CurrentColor, + authored: None })), + text_decoration_line: line.or(Some(text_decoration_line::computed_value::none)), + text_decoration_style: style.or(Some(text_decoration_style::computed_value::T::solid)), + }) } - if !any { - return Err(()); - } + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match *self.text_decoration_line { + DeclaredValue::Value(ref line) => { + try!(line.to_css(dest)); + }, + _ => { + try!(write!(dest, "none")); + } + }; - Ok(Longhands { - text_decoration_color: color.or(Some(CSSColor { parsed: CSSParserColor::CurrentColor, - authored: None })), - text_decoration_line: line.or(Some(text_decoration_line::computed_value::none)), - text_decoration_style: style.or(Some(text_decoration_style::computed_value::T::solid)), - }) + if let DeclaredValue::Value(ref style) = *self.text_decoration_style { + try!(write!(dest, " ")); + try!(style.to_css(dest)); + } + + if let DeclaredValue::Value(ref color) = *self.text_decoration_color { + try!(write!(dest, " ")); + try!(color.to_css(dest)); + } + + Ok(()) + } + } diff --git a/servo/components/style/values/computed/basic_shape.rs b/servo/components/style/values/computed/basic_shape.rs index fe0a338db62a..7431e616665e 100644 --- a/servo/components/style/values/computed/basic_shape.rs +++ b/servo/components/style/values/computed/basic_shape.rs @@ -172,23 +172,23 @@ impl ToCss for BorderRadius { self.top_right.0.width == self.top_right.0.height && self.bottom_right.0.width == self.bottom_right.0.height && self.bottom_left.0.width == self.bottom_left.0.height { - serialize_four_sides((&self.top_left.0.width, - &self.top_right.0.width, - &self.bottom_right.0.width, - &self.bottom_left.0.width), - dest) + serialize_four_sides(dest, + &self.top_left.0.width, + &self.top_right.0.width, + &self.bottom_right.0.width, + &self.bottom_left.0.width) } else { - try!(serialize_four_sides((&self.top_left.0.width, - &self.top_right.0.width, - &self.bottom_right.0.width, - &self.bottom_left.0.width), - dest)); + try!(serialize_four_sides(dest, + &self.top_left.0.width, + &self.top_right.0.width, + &self.bottom_right.0.width, + &self.bottom_left.0.width)); try!(dest.write_str(" / ")); - serialize_four_sides((&self.top_left.0.height, - &self.top_right.0.height, - &self.bottom_right.0.height, - &self.bottom_left.0.height), - dest) + serialize_four_sides(dest, + &self.top_left.0.height, + &self.top_right.0.height, + &self.bottom_right.0.height, + &self.bottom_left.0.height) } } } diff --git a/servo/components/style/values/specified/basic_shape.rs b/servo/components/style/values/specified/basic_shape.rs index 7616bb7bf6dc..b50effd85ae1 100644 --- a/servo/components/style/values/specified/basic_shape.rs +++ b/servo/components/style/values/specified/basic_shape.rs @@ -415,23 +415,23 @@ impl ToCss for BorderRadius { self.top_right.0.width == self.top_right.0.height && self.bottom_right.0.width == self.bottom_right.0.height && self.bottom_left.0.width == self.bottom_left.0.height { - serialize_four_sides((&self.top_left.0.width, - &self.top_right.0.width, - &self.bottom_right.0.width, - &self.bottom_left.0.width), - dest) + serialize_four_sides(dest, + &self.top_left.0.width, + &self.top_right.0.width, + &self.bottom_right.0.width, + &self.bottom_left.0.width) } else { - try!(serialize_four_sides((&self.top_left.0.width, - &self.top_right.0.width, - &self.bottom_right.0.width, - &self.bottom_left.0.width), - dest)); + try!(serialize_four_sides(dest, + &self.top_left.0.width, + &self.top_right.0.width, + &self.bottom_right.0.width, + &self.bottom_left.0.width)); try!(dest.write_str(" / ")); - serialize_four_sides((&self.top_left.0.height, - &self.top_right.0.height, - &self.bottom_right.0.height, - &self.bottom_left.0.height), - dest) + serialize_four_sides(dest, + &self.top_left.0.height, + &self.top_right.0.height, + &self.bottom_right.0.height, + &self.bottom_left.0.height) } } } diff --git a/servo/tests/unit/style/properties.rs b/servo/tests/unit/style/properties.rs deleted file mode 100644 index 2ecae8cc940b..000000000000 --- a/servo/tests/unit/style/properties.rs +++ /dev/null @@ -1,114 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -use app_units::Au; -use cssparser::ToCss; -use rustc_serialize::json::Json; -use std::env; -use std::fs::{File, remove_file}; -use std::path::Path; -use std::process::Command; -use std::sync::Arc; -use style::computed_values::display::T::inline_block; -use style::properties::longhands::border_top_width; -use style::properties::{DeclaredValue, PropertyDeclaration, PropertyDeclarationBlock}; -use style::values::HasViewportPercentage; -use style::values::specified::{Length, LengthOrPercentageOrAuto, LengthOrPercentage, ViewportPercentageLength}; - -#[test] -fn properties_list_json() { - let top = Path::new(file!()).parent().unwrap().join("..").join("..").join(".."); - let json = top.join("target").join("doc").join("servo").join("css-properties.json"); - if json.exists() { - remove_file(&json).unwrap() - } - let python = env::var("PYTHON").ok().unwrap_or_else(find_python); - let script = top.join("components").join("style").join("properties").join("build.py"); - let status = Command::new(python) - .arg(&script) - .arg("servo") - .arg("html") - .status() - .unwrap(); - assert!(status.success()); - let properties = Json::from_reader(&mut File::open(json).unwrap()).unwrap(); - assert!(properties.as_object().unwrap().len() > 100); - assert!(properties.find("margin").is_some()); - assert!(properties.find("margin-top").is_some()); -} - -#[cfg(windows)] -fn find_python() -> String { - if Command::new("python27.exe").arg("--version").output().is_ok() { - return "python27.exe".to_owned(); - } - - if Command::new("python.exe").arg("--version").output().is_ok() { - return "python.exe".to_owned(); - } - - panic!("Can't find python (tried python27.exe and python.exe)! Try fixing PATH or setting the PYTHON env var"); -} - -#[cfg(not(windows))] -fn find_python() -> String { - if Command::new("python2.7").arg("--version").output().unwrap().status.success() { - "python2.7" - } else { - "python" - }.to_owned() -} - -#[test] -fn property_declaration_block_should_serialize_correctly() { - let mut normal = Vec::new(); - let mut important = Vec::new(); - - let length = LengthOrPercentageOrAuto::Length(Length::from_px(70f32)); - let value = DeclaredValue::Value(length); - normal.push(PropertyDeclaration::Width(value)); - - let min_height = LengthOrPercentage::Length(Length::from_px(20f32)); - let value = DeclaredValue::Value(min_height); - normal.push(PropertyDeclaration::MinHeight(value)); - - let value = DeclaredValue::Value(inline_block); - normal.push(PropertyDeclaration::Display(value)); - - let height = LengthOrPercentageOrAuto::Length(Length::from_px(20f32)); - let value = DeclaredValue::Value(height); - important.push(PropertyDeclaration::Height(value)); - - normal.reverse(); - important.reverse(); - let block = PropertyDeclarationBlock { - normal: Arc::new(normal), - important: Arc::new(important) - }; - - let css_string = block.to_css_string(); - - assert_eq!( - css_string, - "width: 70px; min-height: 20px; display: inline-block; height: 20px !important;" - ); -} - -#[test] -fn has_viewport_percentage_for_specified_value() { - //TODO: test all specified value with a HasViewportPercentage impl - let pvw = PropertyDeclaration::BorderTopWidth( - DeclaredValue::Value(border_top_width::SpecifiedValue( - Length::ViewportPercentage(ViewportPercentageLength::Vw(100.)) - )) - ); - assert!(pvw.has_viewport_percentage()); - - let pabs = PropertyDeclaration::BorderTopWidth( - DeclaredValue::Value(border_top_width::SpecifiedValue( - Length::Absolute(Au(100)) - )) - ); - assert!(!pabs.has_viewport_percentage()); -} diff --git a/servo/tests/unit/style/properties/mod.rs b/servo/tests/unit/style/properties/mod.rs new file mode 100644 index 000000000000..639ce9222f77 --- /dev/null +++ b/servo/tests/unit/style/properties/mod.rs @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +mod scaffolding; +mod serialization; +mod viewport; diff --git a/servo/tests/unit/style/properties/scaffolding.rs b/servo/tests/unit/style/properties/scaffolding.rs new file mode 100644 index 000000000000..f4fb064301aa --- /dev/null +++ b/servo/tests/unit/style/properties/scaffolding.rs @@ -0,0 +1,53 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use rustc_serialize::json::Json; +use std::env; +use std::fs::{File, remove_file}; +use std::path::Path; +use std::process::Command; + +#[test] +fn properties_list_json() { + let top = Path::new(file!()).parent().unwrap().join("..").join("..").join("..").join(".."); + let json = top.join("target").join("doc").join("servo").join("css-properties.json"); + if json.exists() { + remove_file(&json).unwrap() + } + let python = env::var("PYTHON").ok().unwrap_or_else(find_python); + let script = top.join("components").join("style").join("properties").join("build.py"); + let status = Command::new(python) + .arg(&script) + .arg("servo") + .arg("html") + .status() + .unwrap(); + assert!(status.success()); + let properties = Json::from_reader(&mut File::open(json).unwrap()).unwrap(); + assert!(properties.as_object().unwrap().len() > 100); + assert!(properties.find("margin").is_some()); + assert!(properties.find("margin-top").is_some()); +} + +#[cfg(windows)] +fn find_python() -> String { + if Command::new("python27.exe").arg("--version").output().is_ok() { + return "python27.exe".to_owned(); + } + + if Command::new("python.exe").arg("--version").output().is_ok() { + return "python.exe".to_owned(); + } + + panic!("Can't find python (tried python27.exe and python.exe)! Try fixing PATH or setting the PYTHON env var"); +} + +#[cfg(not(windows))] +fn find_python() -> String { + if Command::new("python2.7").arg("--version").output().unwrap().status.success() { + "python2.7" + } else { + "python" + }.to_owned() +} diff --git a/servo/tests/unit/style/properties/serialization.rs b/servo/tests/unit/style/properties/serialization.rs new file mode 100644 index 000000000000..7ff65edd959e --- /dev/null +++ b/servo/tests/unit/style/properties/serialization.rs @@ -0,0 +1,829 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +pub use cssparser::ToCss; +pub use std::sync::Arc; +pub use style::computed_values::display::T::inline_block; +pub use style::properties::{DeclaredValue, PropertyDeclaration, PropertyDeclarationBlock}; +pub use style::values::specified::{BorderStyle, CSSColor, Length}; +pub use style::values::specified::{LengthOrPercentage, LengthOrPercentageOrAuto, LengthOrPercentageOrAutoOrContent}; +pub use style::properties::longhands::outline_color::computed_value::T as ComputedColor; +pub use style::values::RGBA; +pub use style::values::specified::UrlExtraData; +pub use url::Url; + +#[test] +fn property_declaration_block_should_serialize_correctly() { + use style::properties::longhands::overflow_x::computed_value::T as OverflowXValue; + use style::properties::longhands::overflow_y::computed_value::T as OverflowYContainer; + + let mut normal = Vec::new(); + let mut important = Vec::new(); + + let length = DeclaredValue::Value(LengthOrPercentageOrAuto::Length(Length::from_px(70f32))); + normal.push(PropertyDeclaration::Width(length)); + + let min_height = DeclaredValue::Value(LengthOrPercentage::Length(Length::from_px(20f32))); + normal.push(PropertyDeclaration::MinHeight(min_height)); + + let value = DeclaredValue::Value(inline_block); + normal.push(PropertyDeclaration::Display(value)); + + let overflow_x = DeclaredValue::Value(OverflowXValue::auto); + normal.push(PropertyDeclaration::OverflowX(overflow_x)); + + let overflow_y = DeclaredValue::Value(OverflowYContainer(OverflowXValue::auto)); + normal.push(PropertyDeclaration::OverflowY(overflow_y)); + + let height = DeclaredValue::Value(LengthOrPercentageOrAuto::Length(Length::from_px(20f32))); + important.push(PropertyDeclaration::Height(height)); + + normal.reverse(); + important.reverse(); + let block = PropertyDeclarationBlock { + normal: Arc::new(normal), + important: Arc::new(important) + }; + + let css_string = block.to_css_string(); + + assert_eq!( + css_string, + "width: 70px; min-height: 20px; display: inline-block; overflow: auto; height: 20px !important;" + ); +} + +mod shorthand_serialization { + pub use super::*; + + pub fn shorthand_properties_to_string(properties: Vec) -> String { + let block = PropertyDeclarationBlock { + normal: Arc::new(properties), + important: Arc::new(Vec::new()) + }; + + block.to_css_string() + } + + // Add Test to show error if a longhand property is missing!!!!!! + + mod overflow { + pub use super::*; + use style::properties::longhands::overflow_x::computed_value::T as OverflowXValue; + use style::properties::longhands::overflow_y::computed_value::T as OverflowYContainer; + + #[test] + fn equal_overflow_properties_should_serialize_to_single_value() { + let mut properties = Vec::new(); + + let overflow_x = DeclaredValue::Value(OverflowXValue::auto); + properties.push(PropertyDeclaration::OverflowX(overflow_x)); + + let overflow_y = DeclaredValue::Value(OverflowYContainer(OverflowXValue::auto)); + properties.push(PropertyDeclaration::OverflowY(overflow_y)); + + let serialization = shorthand_properties_to_string(properties); + assert_eq!(serialization, "overflow: auto;"); + } + + #[test] + fn different_overflow_properties_should_serialize_to_two_values() { + let mut properties = Vec::new(); + + let overflow_x = DeclaredValue::Value(OverflowXValue::scroll); + properties.push(PropertyDeclaration::OverflowX(overflow_x)); + + let overflow_y = DeclaredValue::Value(OverflowYContainer(OverflowXValue::auto)); + properties.push(PropertyDeclaration::OverflowY(overflow_y)); + + let serialization = shorthand_properties_to_string(properties); + assert_eq!(serialization, "overflow-x: scroll; overflow-y: auto;"); + } + } + + mod four_sides_shorthands { + pub use super::*; + + // we can use margin as a base to test out the different combinations + // but afterwards, we only need to to one test per "four sides shorthand" + #[test] + fn all_equal_properties_should_serialize_to_one_value() { + let mut properties = Vec::new(); + + let px_70 = DeclaredValue::Value(LengthOrPercentageOrAuto::Length(Length::from_px(70f32))); + properties.push(PropertyDeclaration::MarginTop(px_70.clone())); + properties.push(PropertyDeclaration::MarginRight(px_70.clone())); + properties.push(PropertyDeclaration::MarginBottom(px_70.clone())); + properties.push(PropertyDeclaration::MarginLeft(px_70)); + + let serialization = shorthand_properties_to_string(properties); + assert_eq!(serialization, "margin: 70px;"); + } + + #[test] + fn equal_vertical_and_equal_horizontal_properties_should_serialize_to_two_value() { + let mut properties = Vec::new(); + + let vertical_px = DeclaredValue::Value(LengthOrPercentageOrAuto::Length(Length::from_px(10f32))); + let horizontal_px = DeclaredValue::Value(LengthOrPercentageOrAuto::Length(Length::from_px(5f32))); + + properties.push(PropertyDeclaration::MarginTop(vertical_px.clone())); + properties.push(PropertyDeclaration::MarginRight(horizontal_px.clone())); + properties.push(PropertyDeclaration::MarginBottom(vertical_px)); + properties.push(PropertyDeclaration::MarginLeft(horizontal_px)); + + let serialization = shorthand_properties_to_string(properties); + assert_eq!(serialization, "margin: 10px 5px;"); + } + + #[test] + fn different_vertical_and_equal_horizontal_properties_should_serialize_to_three_values() { + let mut properties = Vec::new(); + + let top_px = DeclaredValue::Value(LengthOrPercentageOrAuto::Length(Length::from_px(8f32))); + let bottom_px = DeclaredValue::Value(LengthOrPercentageOrAuto::Length(Length::from_px(10f32))); + let horizontal_px = DeclaredValue::Value(LengthOrPercentageOrAuto::Length(Length::from_px(5f32))); + + properties.push(PropertyDeclaration::MarginTop(top_px)); + properties.push(PropertyDeclaration::MarginRight(horizontal_px.clone())); + properties.push(PropertyDeclaration::MarginBottom(bottom_px)); + properties.push(PropertyDeclaration::MarginLeft(horizontal_px)); + + let serialization = shorthand_properties_to_string(properties); + assert_eq!(serialization, "margin: 8px 5px 10px;"); + } + + #[test] + fn different_properties_should_serialize_to_four_values() { + let mut properties = Vec::new(); + + let top_px = DeclaredValue::Value(LengthOrPercentageOrAuto::Length(Length::from_px(8f32))); + let right_px = DeclaredValue::Value(LengthOrPercentageOrAuto::Length(Length::from_px(12f32))); + let bottom_px = DeclaredValue::Value(LengthOrPercentageOrAuto::Length(Length::from_px(10f32))); + let left_px = DeclaredValue::Value(LengthOrPercentageOrAuto::Length(Length::from_px(14f32))); + + properties.push(PropertyDeclaration::MarginTop(top_px)); + properties.push(PropertyDeclaration::MarginRight(right_px)); + properties.push(PropertyDeclaration::MarginBottom(bottom_px)); + properties.push(PropertyDeclaration::MarginLeft(left_px)); + + let serialization = shorthand_properties_to_string(properties); + assert_eq!(serialization, "margin: 8px 12px 10px 14px;"); + } + + #[test] + fn padding_should_serialize_correctly() { + let mut properties = Vec::new(); + + let px_10 = DeclaredValue::Value(LengthOrPercentage::Length(Length::from_px(10f32))); + let px_15 = DeclaredValue::Value(LengthOrPercentage::Length(Length::from_px(15f32))); + properties.push(PropertyDeclaration::PaddingTop(px_10.clone())); + properties.push(PropertyDeclaration::PaddingRight(px_15.clone())); + properties.push(PropertyDeclaration::PaddingBottom(px_10)); + properties.push(PropertyDeclaration::PaddingLeft(px_15)); + + let serialization = shorthand_properties_to_string(properties); + assert_eq!(serialization, "padding: 10px 15px;"); + } + + #[test] + fn border_width_should_serialize_correctly() { + use style::properties::longhands::border_bottom_width::SpecifiedValue as BottomContainer; + use style::properties::longhands::border_left_width::SpecifiedValue as LeftContainer; + use style::properties::longhands::border_right_width::SpecifiedValue as RightContainer; + use style::properties::longhands::border_top_width::SpecifiedValue as TopContainer; + + let mut properties = Vec::new(); + + let top_px = DeclaredValue::Value(TopContainer(Length::from_px(10f32))); + let bottom_px = DeclaredValue::Value(BottomContainer(Length::from_px(10f32))); + + let right_px = DeclaredValue::Value(RightContainer(Length::from_px(15f32))); + let left_px = DeclaredValue::Value(LeftContainer(Length::from_px(15f32))); + + properties.push(PropertyDeclaration::BorderTopWidth(top_px)); + properties.push(PropertyDeclaration::BorderRightWidth(right_px)); + properties.push(PropertyDeclaration::BorderBottomWidth(bottom_px)); + properties.push(PropertyDeclaration::BorderLeftWidth(left_px)); + + let serialization = shorthand_properties_to_string(properties); + assert_eq!(serialization, "border-width: 10px 15px;"); + } + + #[test] + fn border_color_should_serialize_correctly() { + let mut properties = Vec::new(); + + let red = DeclaredValue::Value(CSSColor { + parsed: ComputedColor::RGBA(RGBA { red: 1f32, green: 0f32, blue: 0f32, alpha: 1f32 }), + authored: None + }); + + let blue = DeclaredValue::Value(CSSColor { + parsed: ComputedColor::RGBA(RGBA { red: 0f32, green: 0f32, blue: 1f32, alpha: 1f32 }), + authored: None + }); + + properties.push(PropertyDeclaration::BorderTopColor(blue.clone())); + properties.push(PropertyDeclaration::BorderRightColor(red.clone())); + properties.push(PropertyDeclaration::BorderBottomColor(blue)); + properties.push(PropertyDeclaration::BorderLeftColor(red)); + + let serialization = shorthand_properties_to_string(properties); + + // TODO: Make the rgb test show border-color as blue red instead of below tuples + assert_eq!(serialization, "border-color: rgb(0, 0, 255) rgb(255, 0, 0);"); + } + + #[test] + fn border_style_should_serialize_correctly() { + let mut properties = Vec::new(); + + let solid = DeclaredValue::Value(BorderStyle::solid); + let dotted = DeclaredValue::Value(BorderStyle::dotted); + properties.push(PropertyDeclaration::BorderTopStyle(solid.clone())); + properties.push(PropertyDeclaration::BorderRightStyle(dotted.clone())); + properties.push(PropertyDeclaration::BorderBottomStyle(solid)); + properties.push(PropertyDeclaration::BorderLeftStyle(dotted)); + + let serialization = shorthand_properties_to_string(properties); + assert_eq!(serialization, "border-style: solid dotted;"); + } + } + + + mod border_shorthands { + use style::properties::longhands::border_bottom_width::SpecifiedValue as BottomContainer; + use style::properties::longhands::border_left_width::SpecifiedValue as LeftContainer; + use style::properties::longhands::border_right_width::SpecifiedValue as RightContainer; + use style::properties::longhands::border_top_width::SpecifiedValue as TopContainer; + use super::*; + + // we can use border-top as a base to test out the different combinations + // but afterwards, we only need to to one test per "directional border shorthand" + + #[test] + fn directional_border_should_show_all_properties_when_values_are_set() { + let mut properties = Vec::new(); + + let width = DeclaredValue::Value(TopContainer(Length::from_px(4f32))); + let style = DeclaredValue::Value(BorderStyle::solid); + let color = DeclaredValue::Value(CSSColor { + parsed: ComputedColor::RGBA(RGBA { red: 1f32, green: 0f32, blue: 0f32, alpha: 1f32 }), + authored: None + }); + + properties.push(PropertyDeclaration::BorderTopWidth(width)); + properties.push(PropertyDeclaration::BorderTopStyle(style)); + properties.push(PropertyDeclaration::BorderTopColor(color)); + + let serialization = shorthand_properties_to_string(properties); + assert_eq!(serialization, "border-top: 4px solid rgb(255, 0, 0);"); + } + + #[test] + fn directional_border_with_no_specified_style_will_show_style_as_none() { + let mut properties = Vec::new(); + + let width = DeclaredValue::Value(TopContainer(Length::from_px(4f32))); + let style = DeclaredValue::Initial; + let color = DeclaredValue::Value(CSSColor { + parsed: ComputedColor::RGBA(RGBA { red: 1f32, green: 0f32, blue: 0f32, alpha: 1f32 }), + authored: None + }); + + properties.push(PropertyDeclaration::BorderTopWidth(width)); + properties.push(PropertyDeclaration::BorderTopStyle(style)); + properties.push(PropertyDeclaration::BorderTopColor(color)); + + let serialization = shorthand_properties_to_string(properties); + assert_eq!(serialization, "border-top: 4px none rgb(255, 0, 0);"); + } + + #[test] + fn directional_border_with_no_specified_color_will_not_show_color() { + let mut properties = Vec::new(); + + let width = DeclaredValue::Value(TopContainer(Length::from_px(4f32))); + let style = DeclaredValue::Value(BorderStyle::solid); + let color = DeclaredValue::Initial; + + properties.push(PropertyDeclaration::BorderTopWidth(width)); + properties.push(PropertyDeclaration::BorderTopStyle(style)); + properties.push(PropertyDeclaration::BorderTopColor(color)); + + let serialization = shorthand_properties_to_string(properties); + assert_eq!(serialization, "border-top: 4px solid;"); + } + + #[test] + fn border_right_should_serialize_correctly() { + let mut properties = Vec::new(); + + let width = DeclaredValue::Value(RightContainer(Length::from_px(4f32))); + let style = DeclaredValue::Value(BorderStyle::solid); + let color = DeclaredValue::Initial; + + properties.push(PropertyDeclaration::BorderRightWidth(width)); + properties.push(PropertyDeclaration::BorderRightStyle(style)); + properties.push(PropertyDeclaration::BorderRightColor(color)); + + let serialization = shorthand_properties_to_string(properties); + assert_eq!(serialization, "border-right: 4px solid;"); + } + + #[test] + fn border_bottom_should_serialize_correctly() { + let mut properties = Vec::new(); + + let width = DeclaredValue::Value(BottomContainer(Length::from_px(4f32))); + let style = DeclaredValue::Value(BorderStyle::solid); + let color = DeclaredValue::Initial; + + properties.push(PropertyDeclaration::BorderBottomWidth(width)); + properties.push(PropertyDeclaration::BorderBottomStyle(style)); + properties.push(PropertyDeclaration::BorderBottomColor(color)); + + let serialization = shorthand_properties_to_string(properties); + assert_eq!(serialization, "border-bottom: 4px solid;"); + } + + #[test] + fn border_left_should_serialize_correctly() { + let mut properties = Vec::new(); + + let width = DeclaredValue::Value(LeftContainer(Length::from_px(4f32))); + let style = DeclaredValue::Value(BorderStyle::solid); + let color = DeclaredValue::Initial; + + properties.push(PropertyDeclaration::BorderLeftWidth(width)); + properties.push(PropertyDeclaration::BorderLeftStyle(style)); + properties.push(PropertyDeclaration::BorderLeftColor(color)); + + let serialization = shorthand_properties_to_string(properties); + assert_eq!(serialization, "border-left: 4px solid;"); + } + + #[test] + fn border_should_serialize_correctly() { + let mut properties = Vec::new(); + + let top_width = DeclaredValue::Value(TopContainer(Length::from_px(4f32))); + let top_style = DeclaredValue::Value(BorderStyle::solid); + let top_color = DeclaredValue::Initial; + + properties.push(PropertyDeclaration::BorderTopWidth(top_width)); + properties.push(PropertyDeclaration::BorderTopStyle(top_style)); + properties.push(PropertyDeclaration::BorderTopColor(top_color)); + + let right_width = DeclaredValue::Value(RightContainer(Length::from_px(4f32))); + let right_style = DeclaredValue::Value(BorderStyle::solid); + let right_color = DeclaredValue::Initial; + + properties.push(PropertyDeclaration::BorderRightWidth(right_width)); + properties.push(PropertyDeclaration::BorderRightStyle(right_style)); + properties.push(PropertyDeclaration::BorderRightColor(right_color)); + + let bottom_width = DeclaredValue::Value(BottomContainer(Length::from_px(4f32))); + let bottom_style = DeclaredValue::Value(BorderStyle::solid); + let bottom_color = DeclaredValue::Initial; + + properties.push(PropertyDeclaration::BorderBottomWidth(bottom_width)); + properties.push(PropertyDeclaration::BorderBottomStyle(bottom_style)); + properties.push(PropertyDeclaration::BorderBottomColor(bottom_color)); + + let left_width = DeclaredValue::Value(LeftContainer(Length::from_px(4f32))); + let left_style = DeclaredValue::Value(BorderStyle::solid); + let left_color = DeclaredValue::Initial; + + properties.push(PropertyDeclaration::BorderLeftWidth(left_width)); + properties.push(PropertyDeclaration::BorderLeftStyle(left_style)); + properties.push(PropertyDeclaration::BorderLeftColor(left_color)); + + let serialization = shorthand_properties_to_string(properties); + assert_eq!(serialization, "border: 4px solid;"); + } + } + + mod list_style { + use style::properties::longhands::list_style_image::SpecifiedValue as ListStyleImage; + use style::properties::longhands::list_style_position::computed_value::T as ListStylePosition; + use style::properties::longhands::list_style_type::computed_value::T as ListStyleType; + use super::*; + + #[test] + fn list_style_should_show_all_properties_when_values_are_set() { + let mut properties = Vec::new(); + + let position = DeclaredValue::Value(ListStylePosition::inside); + let image = DeclaredValue::Value(ListStyleImage::Url( + Url::parse("http://servo/test.png").unwrap() + )); + let style_type = DeclaredValue::Value(ListStyleType::disc); + + properties.push(PropertyDeclaration::ListStylePosition(position)); + properties.push(PropertyDeclaration::ListStyleImage(image)); + properties.push(PropertyDeclaration::ListStyleType(style_type)); + + let serialization = shorthand_properties_to_string(properties); + assert_eq!(serialization, "list-style: inside url(\"http://servo/test.png\") disc;"); + } + + #[test] + fn list_style_should_show_all_properties_even_if_only_one_is_set() { + let mut properties = Vec::new(); + + let position = DeclaredValue::Initial; + let image = DeclaredValue::Initial; + let style_type = DeclaredValue::Value(ListStyleType::disc); + + properties.push(PropertyDeclaration::ListStylePosition(position)); + properties.push(PropertyDeclaration::ListStyleImage(image)); + properties.push(PropertyDeclaration::ListStyleType(style_type)); + + let serialization = shorthand_properties_to_string(properties); + assert_eq!(serialization, "list-style: outside none disc;"); + } + } + + #[test] + fn overflow_wrap_should_only_serialize_with_a_single_property() { + use style::properties::longhands::overflow_wrap::computed_value::T as OverflowWrap; + + let value = DeclaredValue::Value(OverflowWrap::break_word); + + let properties = vec![ + PropertyDeclaration::OverflowWrap(value) + ]; + + let serialization = shorthand_properties_to_string(properties); + + // word-wrap is considered an outdated alternative to overflow-wrap, but it is currently + // what servo is using in its naming conventions: + // https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-wrap + assert_eq!(serialization, "word-wrap: break-word;"); + } + + mod outline { + use style::properties::longhands::outline_width::SpecifiedValue as WidthContainer; + use super::*; + + #[test] + fn outline_should_show_all_properties_when_set() { + let mut properties = Vec::new(); + + let width = DeclaredValue::Value(WidthContainer(Length::from_px(4f32))); + let style = DeclaredValue::Value(BorderStyle::solid); + let color = DeclaredValue::Value(CSSColor { + parsed: ComputedColor::RGBA(RGBA { red: 1f32, green: 0f32, blue: 0f32, alpha: 1f32 }), + authored: None + }); + + properties.push(PropertyDeclaration::OutlineWidth(width)); + properties.push(PropertyDeclaration::OutlineStyle(style)); + properties.push(PropertyDeclaration::OutlineColor(color)); + + let serialization = shorthand_properties_to_string(properties); + assert_eq!(serialization, "outline: 4px solid rgb(255, 0, 0);"); + } + + #[test] + fn outline_should_not_show_color_if_not_set() { + let mut properties = Vec::new(); + + let width = DeclaredValue::Value(WidthContainer(Length::from_px(4f32))); + let style = DeclaredValue::Value(BorderStyle::solid); + let color = DeclaredValue::Initial; + + properties.push(PropertyDeclaration::OutlineWidth(width)); + properties.push(PropertyDeclaration::OutlineStyle(style)); + properties.push(PropertyDeclaration::OutlineColor(color)); + + let serialization = shorthand_properties_to_string(properties); + assert_eq!(serialization, "outline: 4px solid;"); + } + + #[test] + fn outline_should_serialize_correctly_when_style_is_not_set() { + let mut properties = Vec::new(); + + let width = DeclaredValue::Value(WidthContainer(Length::from_px(4f32))); + let style = DeclaredValue::Initial; + let color = DeclaredValue::Value(CSSColor { + parsed: ComputedColor::RGBA(RGBA { red: 1f32, green: 0f32, blue: 0f32, alpha: 1f32 }), + authored: None + }); + properties.push(PropertyDeclaration::OutlineWidth(width)); + properties.push(PropertyDeclaration::OutlineStyle(style)); + properties.push(PropertyDeclaration::OutlineColor(color)); + + let serialization = shorthand_properties_to_string(properties); + assert_eq!(serialization, "outline: 4px none rgb(255, 0, 0);"); + } + } + + #[test] + fn columns_should_serialize_correctly() { + use style::properties::longhands::column_count::SpecifiedValue as ColumnCount; + use style::properties::longhands::column_width::SpecifiedValue as ColumnWidth; + + let mut properties = Vec::new(); + + let width = DeclaredValue::Value(ColumnWidth::Auto); + let count = DeclaredValue::Value(ColumnCount::Auto); + + properties.push(PropertyDeclaration::ColumnWidth(width)); + properties.push(PropertyDeclaration::ColumnCount(count)); + + let serialization = shorthand_properties_to_string(properties); + assert_eq!(serialization, "columns: auto auto;"); + } + + #[test] + fn transition_should_serialize_all_available_properties() { + use euclid::point::Point2D; + use style::properties::animated_properties::TransitionProperty; + use style::properties::longhands::transition_duration::computed_value::T as DurationContainer; + use style::properties::longhands::transition_property::computed_value::T as PropertyContainer; + use style::properties::longhands::transition_timing_function::computed_value::T as TimingContainer; + use style::properties::longhands::transition_timing_function::computed_value::TransitionTimingFunction; + use style::values::specified::Time as TimeContainer; + + let property_name = DeclaredValue::Value( + PropertyContainer(vec![TransitionProperty::MarginLeft]) + ); + + let duration = DeclaredValue::Value( + DurationContainer(vec![TimeContainer(3f32)]) + ); + + let delay = DeclaredValue::Value( + DurationContainer(vec![TimeContainer(4f32)]) + ); + + let timing_function = DeclaredValue::Value( + TimingContainer(vec![ + TransitionTimingFunction::CubicBezier(Point2D::new(0f32, 5f32), Point2D::new(5f32, 10f32)) + ]) + ); + + let mut properties = Vec::new(); + + properties.push(PropertyDeclaration::TransitionProperty(property_name)); + properties.push(PropertyDeclaration::TransitionDelay(delay)); + properties.push(PropertyDeclaration::TransitionDuration(duration)); + properties.push(PropertyDeclaration::TransitionTimingFunction(timing_function)); + + let serialization = shorthand_properties_to_string(properties); + assert_eq!(serialization, "transition: margin-left 3s cubic-bezier(0, 5, 5, 10) 4s;"); + } + + #[test] + fn flex_should_serialize_all_available_properties() { + use style::values::specified::Number as NumberContainer; + use style::values::specified::Percentage as PercentageContainer; + + let mut properties = Vec::new(); + + let grow = DeclaredValue::Value(NumberContainer(2f32)); + let shrink = DeclaredValue::Value(NumberContainer(3f32)); + let basis = DeclaredValue::Value( + LengthOrPercentageOrAutoOrContent::Percentage(PercentageContainer(0.5f32)) + ); + + properties.push(PropertyDeclaration::FlexGrow(grow)); + properties.push(PropertyDeclaration::FlexShrink(shrink)); + properties.push(PropertyDeclaration::FlexBasis(basis)); + + let serialization = shorthand_properties_to_string(properties); + assert_eq!(serialization, "flex: 2 3 50%;"); + } + + #[test] + fn flex_flow_should_serialize_all_available_properties() { + use style::properties::longhands::flex_direction::computed_value::T as FlexDirection; + use style::properties::longhands::flex_wrap::computed_value::T as FlexWrap; + + let mut properties = Vec::new(); + + let direction = DeclaredValue::Value(FlexDirection::row); + let wrap = DeclaredValue::Value(FlexWrap::wrap); + + properties.push(PropertyDeclaration::FlexDirection(direction)); + properties.push(PropertyDeclaration::FlexWrap(wrap)); + + let serialization = shorthand_properties_to_string(properties); + assert_eq!(serialization, "flex-flow: row wrap;"); + } + + // TODO: Populate Atom Cache for testing so that the font shorthand can be tested + /* + mod font { + use super::*; + use style::properties::longhands::font_family::computed_value::T as FamilyContainer; + use style::properties::longhands::font_family::computed_value::FontFamily; + use style::properties::longhands::font_style::computed_value::T as FontStyle; + use style::properties::longhands::font_variant::computed_value::T as FontVariant; + use style::properties::longhands::font_weight::SpecifiedValue as FontWeight; + use style::properties::longhands::font_size::SpecifiedValue as FontSizeContainer; + use style::properties::longhands::font_stretch::computed_value::T as FontStretch; + use style::properties::longhands::line_height::SpecifiedValue as LineHeight; + + #[test] + fn font_should_serialize_all_available_properties() { + let mut properties = Vec::new(); + + + let font_family = DeclaredValue::Value( + FamilyContainer(vec![FontFamily::Generic(atom!("serif"))]) + ); + + + let font_style = DeclaredValue::Value(FontStyle::italic); + let font_variant = DeclaredValue::Value(FontVariant::normal); + let font_weight = DeclaredValue::Value(FontWeight::Bolder); + let font_size = DeclaredValue::Value(FontSizeContainer( + LengthOrPercentage::Length(Length::from_px(4f32))) + ); + let font_stretch = DeclaredValue::Value(FontStretch::expanded); + let line_height = DeclaredValue::Value(LineHeight::Number(3f32)); + + properties.push(PropertyDeclaration::FontFamily(font_family)); + properties.push(PropertyDeclaration::FontStyle(font_style)); + properties.push(PropertyDeclaration::FontVariant(font_variant)); + properties.push(PropertyDeclaration::FontWeight(font_weight)); + properties.push(PropertyDeclaration::FontSize(font_size)); + properties.push(PropertyDeclaration::FontStretch(font_stretch)); + properties.push(PropertyDeclaration::LineHeight(line_height)); + + let serialization = shorthand_properties_to_string(properties); + assert_eq!(serialization, "font:;"); + } + } + */ + + // TODO: Populate Atom Cache for testing so that the animation shorthand can be tested + /* + #[test] + fn animation_should_serialize_all_available_properties() { + let mut properties = Vec::new(); + + assert_eq!(serialization, "animation;"); + } + */ + + mod background { + use style::properties::longhands::background_attachment::computed_value::T as Attachment; + use style::properties::longhands::background_clip::computed_value::T as Clip; + use style::properties::longhands::background_image::SpecifiedValue as ImageContainer; + use style::properties::longhands::background_origin::computed_value::T as Origin; + use style::properties::longhands::background_position::SpecifiedValue as PositionContainer; + use style::properties::longhands::background_repeat::computed_value::T as Repeat; + use style::properties::longhands::background_size::SpecifiedExplicitSize; + use style::properties::longhands::background_size::SpecifiedValue as Size; + use style::values::specified::Image; + use style::values::specified::position::Position; + use super::*; + + #[test] + fn background_should_serialize_all_available_properties_when_specified() { + let mut properties = Vec::new(); + + let color = DeclaredValue::Value(CSSColor { + parsed: ComputedColor::RGBA(RGBA { red: 1f32, green: 0f32, blue: 0f32, alpha: 1f32 }), + authored: None + }); + + let position = DeclaredValue::Value(PositionContainer( + Position { + horizontal: LengthOrPercentage::Length(Length::from_px(7f32)), + vertical: LengthOrPercentage::Length(Length::from_px(4f32)) + } + )); + + let repeat = DeclaredValue::Value(Repeat::repeat_x); + let attachment = DeclaredValue::Value(Attachment::scroll); + + let image = DeclaredValue::Value(ImageContainer( + Some(Image::Url(Url::parse("http://servo/test.png").unwrap(), UrlExtraData {})) + )); + + let size = DeclaredValue::Value( + Size::Explicit(SpecifiedExplicitSize { + width: LengthOrPercentageOrAuto::Length(Length::from_px(70f32)), + height: LengthOrPercentageOrAuto::Length(Length::from_px(50f32)) + } + )); + + let origin = DeclaredValue::Value(Origin::border_box); + let clip = DeclaredValue::Value(Clip::padding_box); + + properties.push(PropertyDeclaration::BackgroundColor(color)); + properties.push(PropertyDeclaration::BackgroundPosition(position)); + properties.push(PropertyDeclaration::BackgroundRepeat(repeat)); + properties.push(PropertyDeclaration::BackgroundAttachment(attachment)); + properties.push(PropertyDeclaration::BackgroundImage(image)); + properties.push(PropertyDeclaration::BackgroundSize(size)); + properties.push(PropertyDeclaration::BackgroundOrigin(origin)); + properties.push(PropertyDeclaration::BackgroundClip(clip)); + + let serialization = shorthand_properties_to_string(properties); + + assert_eq!( + serialization, + "background: rgb(255, 0, 0) url(\"http://servo/test.png\") repeat-x \ + scroll 7px 4px / 70px 50px border-box padding-box;" + ); + } + + #[test] + fn background_should_combine_origin_and_clip_properties_when_equal() { + let mut properties = Vec::new(); + + let color = DeclaredValue::Value(CSSColor { + parsed: ComputedColor::RGBA(RGBA { red: 1f32, green: 0f32, blue: 0f32, alpha: 1f32 }), + authored: None + }); + + let position = DeclaredValue::Value(PositionContainer( + Position { + horizontal: LengthOrPercentage::Length(Length::from_px(7f32)), + vertical: LengthOrPercentage::Length(Length::from_px(4f32)) + } + )); + + let repeat = DeclaredValue::Value(Repeat::repeat_x); + let attachment = DeclaredValue::Value(Attachment::scroll); + + let image = DeclaredValue::Value(ImageContainer( + Some(Image::Url(Url::parse("http://servo/test.png").unwrap(), UrlExtraData {})) + )); + + let size = DeclaredValue::Value( + Size::Explicit(SpecifiedExplicitSize { + width: LengthOrPercentageOrAuto::Length(Length::from_px(70f32)), + height: LengthOrPercentageOrAuto::Length(Length::from_px(50f32)) + }) + ); + + let origin = DeclaredValue::Value(Origin::padding_box); + let clip = DeclaredValue::Value(Clip::padding_box); + + properties.push(PropertyDeclaration::BackgroundColor(color)); + properties.push(PropertyDeclaration::BackgroundPosition(position)); + properties.push(PropertyDeclaration::BackgroundRepeat(repeat)); + properties.push(PropertyDeclaration::BackgroundAttachment(attachment)); + properties.push(PropertyDeclaration::BackgroundImage(image)); + properties.push(PropertyDeclaration::BackgroundSize(size)); + properties.push(PropertyDeclaration::BackgroundOrigin(origin)); + properties.push(PropertyDeclaration::BackgroundClip(clip)); + + let serialization = shorthand_properties_to_string(properties); + assert_eq!( + serialization, + "background: rgb(255, 0, 0) url(\"http://servo/test.png\") repeat-x \ + scroll 7px 4px / 70px 50px padding-box;" + ); + } + + #[test] + fn background_should_always_print_color_and_url_and_repeat_and_attachment_and_position() { + let mut properties = Vec::new(); + + let color = DeclaredValue::Value(CSSColor { + parsed: ComputedColor::RGBA(RGBA { red: 1f32, green: 0f32, blue: 0f32, alpha: 1f32 }), + authored: None + }); + + let position = DeclaredValue::Value(PositionContainer( + Position { + horizontal: LengthOrPercentage::Length(Length::from_px(0f32)), + vertical: LengthOrPercentage::Length(Length::from_px(0f32)) + } + )); + + let repeat = DeclaredValue::Value(Repeat::repeat_x); + let attachment = DeclaredValue::Value(Attachment::scroll); + + let image = DeclaredValue::Value(ImageContainer(None)); + + let size = DeclaredValue::Initial; + + let origin = DeclaredValue::Initial; + let clip = DeclaredValue::Initial; + + properties.push(PropertyDeclaration::BackgroundColor(color)); + properties.push(PropertyDeclaration::BackgroundPosition(position)); + properties.push(PropertyDeclaration::BackgroundRepeat(repeat)); + properties.push(PropertyDeclaration::BackgroundAttachment(attachment)); + properties.push(PropertyDeclaration::BackgroundImage(image)); + properties.push(PropertyDeclaration::BackgroundSize(size)); + properties.push(PropertyDeclaration::BackgroundOrigin(origin)); + properties.push(PropertyDeclaration::BackgroundClip(clip)); + + let serialization = shorthand_properties_to_string(properties); + assert_eq!(serialization, "background: rgb(255, 0, 0) none repeat-x scroll 0px 0px;"); + } + } +} diff --git a/servo/tests/unit/style/properties/viewport.rs b/servo/tests/unit/style/properties/viewport.rs new file mode 100644 index 000000000000..585d55b581a3 --- /dev/null +++ b/servo/tests/unit/style/properties/viewport.rs @@ -0,0 +1,27 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use app_units::Au; +use style::properties::longhands::border_top_width; +use style::properties::{DeclaredValue, PropertyDeclaration}; +use style::values::HasViewportPercentage; +use style::values::specified::{Length, ViewportPercentageLength}; + +#[test] +fn has_viewport_percentage_for_specified_value() { + //TODO: test all specified value with a HasViewportPercentage impl + let pvw = PropertyDeclaration::BorderTopWidth( + DeclaredValue::Value(border_top_width::SpecifiedValue( + Length::ViewportPercentage(ViewportPercentageLength::Vw(100.)) + )) + ); + assert!(pvw.has_viewport_percentage()); + + let pabs = PropertyDeclaration::BorderTopWidth( + DeclaredValue::Value(border_top_width::SpecifiedValue( + Length::Absolute(Au(100)) + )) + ); + assert!(!pabs.has_viewport_percentage()); +}