Bug 1434130 part 11 - Add some attributes for SpecifiedValueInfo to help deriving more from types. r?emilio draft
authorXidorn Quan <me@upsuper.org>
Thu, 26 Apr 2018 21:02:24 +1000
changeset 788777 2af95ecd7e1f48bf54a93573a68a9d3f29393e42
parent 788776 524796e355b2221343b96a77c7a00abd2eb41af7
child 788778 b638793227b9f6317b540670227d68ad90a76173
push id108088
push userxquan@mozilla.com
push dateFri, 27 Apr 2018 00:40:56 +0000
reviewersemilio
bugs1434130
milestone61.0a1
Bug 1434130 part 11 - Add some attributes for SpecifiedValueInfo to help deriving more from types. r?emilio MozReview-Commit-ID: IyohSTbUO31
servo/components/style/values/generics/basic_shape.rs
servo/components/style/values/generics/border.rs
servo/components/style/values/generics/counters.rs
servo/components/style/values/generics/effects.rs
servo/components/style/values/generics/font.rs
servo/components/style/values/generics/grid.rs
servo/components/style/values/generics/image.rs
servo/components/style/values/generics/transform.rs
servo/components/style/values/specified/box.rs
servo/components/style/values/specified/font.rs
servo/components/style/values/specified/inherited_box.rs
servo/components/style/values/specified/list.rs
servo/components/style/values/specified/mod.rs
servo/components/style/values/specified/position.rs
servo/components/style/values/specified/transform.rs
servo/components/style_derive/lib.rs
servo/components/style_derive/specified_value_info.rs
servo/components/style_traits/specified_value_info.rs
--- a/servo/components/style/values/generics/basic_shape.rs
+++ b/servo/components/style/values/generics/basic_shape.rs
@@ -64,34 +64,37 @@ pub enum BasicShape<H, V, LengthOrPercen
     Inset(#[css(field_bound)] InsetRect<LengthOrPercentage>),
     Circle(#[css(field_bound)] Circle<H, V, LengthOrPercentage>),
     Ellipse(#[css(field_bound)] Ellipse<H, V, LengthOrPercentage>),
     Polygon(Polygon<LengthOrPercentage>),
 }
 
 /// <https://drafts.csswg.org/css-shapes/#funcdef-inset>
 #[allow(missing_docs)]
+#[css(function = "inset")]
 #[derive(Animate, Clone, ComputeSquaredDistance, Debug, MallocSizeOf, PartialEq,
          SpecifiedValueInfo, ToComputedValue)]
 pub struct InsetRect<LengthOrPercentage> {
     pub rect: Rect<LengthOrPercentage>,
     pub round: Option<BorderRadius<LengthOrPercentage>>,
 }
 
 /// <https://drafts.csswg.org/css-shapes/#funcdef-circle>
 #[allow(missing_docs)]
+#[css(function)]
 #[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf,
          PartialEq, SpecifiedValueInfo, ToComputedValue)]
 pub struct Circle<H, V, LengthOrPercentage> {
     pub position: Position<H, V>,
     pub radius: ShapeRadius<LengthOrPercentage>,
 }
 
 /// <https://drafts.csswg.org/css-shapes/#funcdef-ellipse>
 #[allow(missing_docs)]
+#[css(function)]
 #[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf,
          PartialEq, SpecifiedValueInfo, ToComputedValue)]
 pub struct Ellipse<H, V, LengthOrPercentage> {
     pub position: Position<H, V>,
     pub semiaxis_x: ShapeRadius<LengthOrPercentage>,
     pub semiaxis_y: ShapeRadius<LengthOrPercentage>,
 }
 
@@ -105,16 +108,17 @@ pub enum ShapeRadius<LengthOrPercentage>
     ClosestSide,
     #[animation(error)]
     FarthestSide,
 }
 
 /// A generic type for representing the `polygon()` function
 ///
 /// <https://drafts.csswg.org/css-shapes/#funcdef-polygon>
+#[css(function)]
 #[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo,
          ToComputedValue)]
 pub struct Polygon<LengthOrPercentage> {
     /// The filling rule for a polygon.
     pub fill: FillRule,
     /// A collection of (x, y) coordinates to draw the polygon.
     pub coordinates: Vec<(LengthOrPercentage, LengthOrPercentage)>,
 }
--- a/servo/components/style/values/generics/border.rs
+++ b/servo/components/style/values/generics/border.rs
@@ -23,16 +23,17 @@ pub enum BorderImageSideWidth<LengthOrPe
 
 /// A generic value for the `border-image-slice` property.
 #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo,
          ToComputedValue)]
 pub struct BorderImageSlice<NumberOrPercentage> {
     /// The offsets.
     pub offsets: Rect<NumberOrPercentage>,
     /// Whether to fill the middle part.
+    #[value_info(represents_keyword)]
     pub fill: bool,
 }
 
 /// A generic value for the `border-*-radius` longhand properties.
 #[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf,
          PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss)]
 pub struct BorderCornerRadius<L>(#[css(field_bound)] pub Size<L>);
 
--- a/servo/components/style/values/generics/counters.rs
+++ b/servo/components/style/values/generics/counters.rs
@@ -54,17 +54,17 @@ impl<I> Deref for CounterReset<I> {
     }
 }
 
 /// A generic value for lists of counters.
 ///
 /// Keyword `none` is represented by an empty vector.
 #[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo,
          ToComputedValue)]
-pub struct Counters<I>(Box<[(CustomIdent, I)]>);
+pub struct Counters<I>(#[css(if_empty = "none")] Box<[(CustomIdent, I)]>);
 
 impl<I> Default for Counters<I> {
     #[inline]
     fn default() -> Self {
         Counters(vec![].into_boxed_slice())
     }
 }
 
--- a/servo/components/style/values/generics/effects.rs
+++ b/servo/components/style/values/generics/effects.rs
@@ -14,16 +14,17 @@ use values::specified::url::SpecifiedUrl
          ToAnimatedValue, ToAnimatedZero)]
 pub struct BoxShadow<Color, SizeLength, BlurShapeLength, ShapeLength> {
     /// The base shadow.
     pub base: SimpleShadow<Color, SizeLength, BlurShapeLength>,
     /// The spread radius.
     pub spread: ShapeLength,
     /// Whether this is an inset box shadow.
     #[animation(constant)]
+    #[value_info(represents_keyword)]
     pub inset: bool,
 }
 
 /// A generic value for a single `filter`.
 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
 #[derive(Clone, ComputeSquaredDistance, Debug, MallocSizeOf, PartialEq,
          SpecifiedValueInfo, ToAnimatedValue, ToComputedValue, ToCss)]
 pub enum Filter<Angle, Factor, Length, DropShadow> {
--- a/servo/components/style/values/generics/font.rs
+++ b/servo/components/style/values/generics/font.rs
@@ -226,10 +226,11 @@ impl Default for KeywordSize {
 #[allow(missing_docs)]
 #[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf,
          PartialEq, SpecifiedValueInfo, ToAnimatedValue, ToAnimatedZero)]
 pub enum FontStyle<Angle> {
     #[animation(error)]
     Normal,
     #[animation(error)]
     Italic,
+    #[value_info(starts_with_keyword)]
     Oblique(Angle),
 }
--- a/servo/components/style/values/generics/grid.rs
+++ b/servo/components/style/values/generics/grid.rs
@@ -193,20 +193,22 @@ impl<L> TrackBreadth<L> {
 #[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo)]
 pub enum TrackSize<L> {
     /// A flexible `<track-breadth>`
     Breadth(TrackBreadth<L>),
     /// A `minmax` function for a range over an inflexible `<track-breadth>`
     /// and a flexible `<track-breadth>`
     ///
     /// <https://drafts.csswg.org/css-grid/#valdef-grid-template-columns-minmax>
+    #[css(function)]
     Minmax(TrackBreadth<L>, TrackBreadth<L>),
     /// A `fit-content` function.
     ///
     /// <https://drafts.csswg.org/css-grid/#valdef-grid-template-columns-fit-content>
+    #[css(function)]
     FitContent(L),
 }
 
 impl<L> TrackSize<L> {
     /// Check whether this is a `<fixed-size>`
     ///
     /// <https://drafts.csswg.org/css-grid/#typedef-fixed-size>
     pub fn is_fixed(&self) -> bool {
@@ -341,18 +343,17 @@ where
     }
 
     Ok(())
 }
 
 /// The initial argument of the `repeat` function.
 ///
 /// <https://drafts.csswg.org/css-grid/#typedef-track-repeat>
-#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo,
-         ToComputedValue, ToCss)]
+#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss)]
 pub enum RepeatCount<Integer> {
     /// A positive integer. This is allowed only for `<track-repeat>` and `<fixed-repeat>`
     Number(Integer),
     /// An `<auto-fill>` keyword allowed only for `<auto-repeat>`
     AutoFill,
     /// An `<auto-fit>` keyword allowed only for `<auto-repeat>`
     AutoFit,
 }
@@ -379,16 +380,17 @@ impl Parse for RepeatCount<specified::In
 }
 
 /// The structure containing `<line-names>` and `<track-size>` values.
 ///
 /// It can also hold `repeat()` function parameters, which expands into the respective
 /// values in its computed form.
 #[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo,
          ToComputedValue)]
+#[css(function = "repeat")]
 pub struct TrackRepeat<L, I> {
     /// The number of times for the value to be repeated (could also be `auto-fit` or `auto-fill`)
     pub count: RepeatCount<I>,
     /// `<line-names>` accompanying `<track_size>` values.
     ///
     /// If there's no `<line-names>`, then it's represented by an empty vector.
     /// For N `<track-size>` values, there will be N+1 `<line-names>`, and so this vector's
     /// length is always one value more than that of the `<track-size>`.
@@ -476,18 +478,17 @@ pub enum TrackListValue<LengthOrPercenta
     TrackSize(TrackSize<LengthOrPercentage>),
     /// A <track-repeat> value.
     TrackRepeat(TrackRepeat<LengthOrPercentage, Integer>),
 }
 
 /// The type of a `<track-list>` as determined during parsing.
 ///
 /// <https://drafts.csswg.org/css-grid/#typedef-track-list>
-#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo,
-         ToComputedValue)]
+#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue)]
 pub enum TrackListType {
     /// [`<auto-track-list>`](https://drafts.csswg.org/css-grid/#typedef-auto-track-list)
     ///
     /// If this type exists, then the value at the index in `line_names` field in `TrackList`
     /// has the `<line-names>?` list that comes before `<auto-repeat>`. If it's a specified value,
     /// then the `repeat()` function (that follows the line names list) is also at the given index
     /// in `values` field. On the contrary, if it's a computed value, then the `repeat()` function
     /// is in the `auto_repeat` field.
@@ -505,16 +506,17 @@ pub enum TrackListType {
 ///
 /// <https://drafts.csswg.org/css-grid/#typedef-track-list>
 #[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo)]
 pub struct TrackList<LengthOrPercentage, Integer> {
     /// The type of this `<track-list>` (auto, explicit or general).
     ///
     /// In order to avoid parsing the same value multiple times, this does a single traversal
     /// and arrives at the type of value it has parsed (or bails out gracefully with an error).
+    #[css(skip)]
     pub list_type: TrackListType,
     /// A vector of `<track-size> | <track-repeat>` values.
     pub values: Vec<TrackListValue<LengthOrPercentage, Integer>>,
     /// `<line-names>` accompanying `<track-size> | <track-repeat>` values.
     ///
     /// If there's no `<line-names>`, then it's represented by an empty vector.
     /// For N values, there will be N+1 `<line-names>`, and so this vector's
     /// length is always one value more than that of the `<track-size>`.
--- a/servo/components/style/values/generics/image.rs
+++ b/servo/components/style/values/generics/image.rs
@@ -21,16 +21,17 @@ pub enum Image<Gradient, MozImageRect, I
     /// A `<url()>` image.
     Url(ImageUrl),
     /// A `<gradient>` image.  Gradients are rather large, and not nearly as
     /// common as urls, so we box them here to keep the size of this enum sane.
     Gradient(Box<Gradient>),
     /// A `-moz-image-rect` image.  Also fairly large and rare.
     Rect(Box<MozImageRect>),
     /// A `-moz-element(# <element-id>)`
+    #[css(function = "-moz-element")]
     Element(Atom),
     /// A paint worklet image.
     /// <https://drafts.css-houdini.org/css-paint-api/>
     #[cfg(feature = "servo")]
     PaintWorklet(PaintWorklet),
 }
 
 /// A CSS gradient.
--- a/servo/components/style/values/generics/transform.rs
+++ b/servo/components/style/values/generics/transform.rs
@@ -75,40 +75,44 @@ pub struct TransformOrigin<H, V, Depth> 
     pub vertical: V,
     /// The depth.
     pub depth: Depth,
 }
 
 /// A generic timing function.
 ///
 /// <https://drafts.csswg.org/css-timing-1/#single-timing-function-production>
-#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToCss)]
+#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo,
+         ToCss)]
+#[value_info(ty = "TIMING_FUNCTION")]
 pub enum TimingFunction<Integer, Number> {
     /// `linear | ease | ease-in | ease-out | ease-in-out`
     Keyword(TimingKeyword),
     /// `cubic-bezier(<number>, <number>, <number>, <number>)`
     #[allow(missing_docs)]
     #[css(comma, function)]
     CubicBezier {
         x1: Number,
         y1: Number,
         x2: Number,
         y2: Number,
     },
     /// `step-start | step-end | steps(<integer>, [ start | end ]?)`
     #[css(comma, function)]
+    #[value_info(other_values = "step-start,step-end")]
     Steps(Integer, #[css(skip_if = "is_end")] StepPosition),
     /// `frames(<integer>)`
     #[css(comma, function)]
     Frames(Integer),
 }
 
 #[allow(missing_docs)]
 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
-#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToComputedValue, ToCss)]
+#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq,
+         SpecifiedValueInfo, ToComputedValue, ToCss)]
 pub enum TimingKeyword {
     Linear,
     Ease,
     EaseIn,
     EaseOut,
     EaseInOut,
 }
 
--- a/servo/components/style/values/specified/box.rs
+++ b/servo/components/style/values/specified/box.rs
@@ -4,17 +4,17 @@
 
 //! Specified types for box properties.
 
 use Atom;
 use cssparser::Parser;
 use parser::{Parse, ParserContext};
 use selectors::parser::SelectorParseErrorKind;
 use std::fmt::{self, Write};
-use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss};
+use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
 use values::{CustomIdent, KeyframesName};
 use values::generics::box_::AnimationIterationCount as GenericAnimationIterationCount;
 use values::generics::box_::Perspective as GenericPerspective;
 use values::generics::box_::VerticalAlign as GenericVerticalAlign;
 use values::specified::{AllowQuirks, Number};
 use values::specified::length::{LengthOrPercentage, NonNegativeLength};
 
 #[allow(missing_docs)]
@@ -293,16 +293,17 @@ impl AnimationIterationCount {
     pub fn one() -> Self {
         GenericAnimationIterationCount::Number(Number::new(1.0))
     }
 }
 
 /// A value for the `animation-name` property.
 #[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, SpecifiedValueInfo,
          ToComputedValue)]
+#[value_info(other_values = "none")]
 pub struct AnimationName(pub Option<KeyframesName>);
 
 impl AnimationName {
     /// Get the name of the animation as an `Atom`.
     pub fn as_atom(&self) -> Option<&Atom> {
         self.0.as_ref().map(|n| n.as_atom())
     }
 
@@ -415,18 +416,19 @@ impl Parse for WillChange {
         Ok(WillChange::AnimateableFeatures(
             custom_idents.into_boxed_slice(),
         ))
     }
 }
 
 bitflags! {
     #[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
-    #[derive(ToComputedValue)]
+    #[derive(SpecifiedValueInfo, ToComputedValue)]
     /// These constants match Gecko's `NS_STYLE_TOUCH_ACTION_*` constants.
+    #[value_info(other_values = "auto,none,manipulation,pan-x,pan-y")]
     pub struct TouchAction: u8 {
         /// `none` variant
         const TOUCH_ACTION_NONE = 1 << 0;
         /// `auto` variant
         const TOUCH_ACTION_AUTO = 1 << 1;
         /// `pan-x` variant
         const TOUCH_ACTION_PAN_X = 1 << 2;
         /// `pan-y` variant
@@ -488,18 +490,16 @@ impl Parse for TouchAction {
                 } else {
                     Ok(TouchAction::TOUCH_ACTION_PAN_Y)
                 }
             },
         }
     }
 }
 
-impl SpecifiedValueInfo for TouchAction {}
-
 #[cfg(feature = "gecko")]
 impl_bitflags_conversions!(TouchAction);
 
 /// Asserts that all touch-action matches its NS_STYLE_TOUCH_ACTION_* value.
 #[cfg(feature = "gecko")]
 #[inline]
 pub fn assert_touch_action_matches() {
     use gecko_bindings::structs;
@@ -517,17 +517,18 @@ pub fn assert_touch_action_matches() {
         NS_STYLE_TOUCH_ACTION_AUTO => TouchAction::TOUCH_ACTION_AUTO,
         NS_STYLE_TOUCH_ACTION_PAN_X => TouchAction::TOUCH_ACTION_PAN_X,
         NS_STYLE_TOUCH_ACTION_PAN_Y => TouchAction::TOUCH_ACTION_PAN_Y,
         NS_STYLE_TOUCH_ACTION_MANIPULATION => TouchAction::TOUCH_ACTION_MANIPULATION,
     }
 }
 
 bitflags! {
-    #[derive(MallocSizeOf, ToComputedValue)]
+    #[derive(MallocSizeOf, ToComputedValue, SpecifiedValueInfo)]
+    #[value_info(other_values = "none,strict,layout,style,paint")]
     /// Constants for contain: https://drafts.csswg.org/css-contain/#contain-property
     pub struct Contain: u8 {
         /// `layout` variant, turns on layout containment
         const LAYOUT = 0x01;
         /// `style` variant, turns on style containment
         const STYLE = 0x02;
         /// `paint` variant, turns on paint containment
         const PAINT = 0x04;
@@ -600,18 +601,16 @@ impl Parse for Contain {
         if !result.is_empty() {
             Ok(result)
         } else {
             Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
         }
     }
 }
 
-impl SpecifiedValueInfo for Contain {}
-
 /// A specified value for the `perspective` property.
 pub type Perspective = GenericPerspective<NonNegativeLength>;
 
 impl Parse for Perspective {
     fn parse<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>,
     ) -> Result<Self, ParseError<'i>> {
--- a/servo/components/style/values/specified/font.rs
+++ b/servo/components/style/values/specified/font.rs
@@ -1942,19 +1942,21 @@ impl Parse for FontFeatureSettings {
 #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo,
          ToComputedValue)]
 /// Whether user agents are allowed to synthesize bold or oblique font faces
 /// when a font family lacks bold or italic faces
 pub struct FontSynthesis {
     /// If a `font-weight` is requested that the font family does not contain,
     /// the user agent may synthesize the requested weight from the weights
     /// that do exist in the font family.
+    #[value_info(represents_keyword)]
     pub weight: bool,
     /// If a font-style is requested that the font family does not contain,
     /// the user agent may synthesize the requested style from the normal face in the font family.
+    #[value_info(represents_keyword)]
     pub style: bool,
 }
 
 impl FontSynthesis {
     #[inline]
     /// Get the default value of font-synthesis
     pub fn get_initial_value() -> Self {
         FontSynthesis {
--- a/servo/components/style/values/specified/inherited_box.rs
+++ b/servo/components/style/values/specified/inherited_box.rs
@@ -16,16 +16,17 @@ use values::specified::Angle;
 /// The specified value of the `image-orientation` property.
 /// https://drafts.csswg.org/css-images/#propdef-image-orientation
 #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo)]
 pub struct ImageOrientation {
     /// The angle specified, if any
     pub angle: Option<Angle>,
 
     /// Whether or not "flip" was specified
+    #[value_info(other_values = "flip,from-image")]
     pub flipped: bool,
 }
 
 impl ToCss for ImageOrientation {
     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
     where
         W: Write,
     {
--- a/servo/components/style/values/specified/list.rs
+++ b/servo/components/style/values/specified/list.rs
@@ -73,19 +73,20 @@ impl Parse for ListStyleType {
         ))
     }
 }
 
 /// Specified and computed `quote` property.
 ///
 /// FIXME(emilio): It's a shame that this allocates all the time it's computed,
 /// probably should just be refcounted.
+/// FIXME This can probably derive ToCss.
 #[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo,
          ToComputedValue)]
-pub struct Quotes(pub Box<[(Box<str>, Box<str>)]>);
+pub struct Quotes(#[css(if_empty = "none")] pub Box<[(Box<str>, Box<str>)]>);
 
 impl ToCss for Quotes {
     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
     where
         W: Write,
     {
         let mut iter = self.0.iter();
 
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -565,16 +565,17 @@ pub type TrackList = GenericTrackList<Le
 /// The specified value of a `<grid-line>`.
 pub type GridLine = GenericGridLine<Integer>;
 
 /// `<grid-template-rows> | <grid-template-columns>`
 pub type GridTemplateComponent = GenericGridTemplateComponent<LengthOrPercentage, Integer>;
 
 #[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo)]
 /// rect(<top>, <left>, <bottom>, <right>) used by clip and image-region
+#[css(function = "rect")]
 pub struct ClipRect {
     /// <top> (<length> | <auto>)
     pub top: Option<Length>,
     /// <right> (<length> | <auto>)
     pub right: Option<Length>,
     /// <bottom> (<length> | <auto>)
     pub bottom: Option<Length>,
     /// <left> (<length> | <auto>)
@@ -752,17 +753,19 @@ impl AllowQuirks {
     pub fn allowed(self, quirks_mode: QuirksMode) -> bool {
         self == AllowQuirks::Yes && quirks_mode == QuirksMode::Quirks
     }
 }
 
 /// An attr(...) rule
 ///
 /// `[namespace? `|`]? ident`
-#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue)]
+#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo,
+         ToComputedValue)]
+#[css(function)]
 pub struct Attr {
     /// Optional namespace prefix and URL.
     pub namespace: Option<(Prefix, Namespace)>,
     /// Attribute name
     pub attribute: Atom,
 }
 
 impl Parse for Attr {
@@ -847,10 +850,8 @@ impl ToCss for Attr {
         if let Some((ref prefix, ref _url)) = self.namespace {
             serialize_atom_identifier(prefix, dest)?;
             dest.write_str("|")?;
         }
         serialize_atom_identifier(&self.attribute, dest)?;
         dest.write_str(")")
     }
 }
-
-impl SpecifiedValueInfo for Attr {}
--- a/servo/components/style/values/specified/position.rs
+++ b/servo/components/style/values/specified/position.rs
@@ -426,16 +426,17 @@ pub enum AutoFlow {
 #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo,
          ToComputedValue)]
 /// Controls how the auto-placement algorithm works
 /// specifying exactly how auto-placed items get flowed into the grid
 pub struct GridAutoFlow {
     /// Specifiy how auto-placement algorithm fills each `row` or `column` in turn
     pub autoflow: AutoFlow,
     /// Specify use `dense` packing algorithm or not
+    #[value_info(represents_keyword)]
     pub dense: bool,
 }
 
 impl GridAutoFlow {
     #[inline]
     /// Get default `grid-auto-flow` as `row`
     pub fn row() -> GridAutoFlow {
         GridAutoFlow {
--- a/servo/components/style/values/specified/transform.rs
+++ b/servo/components/style/values/specified/transform.rs
@@ -2,17 +2,17 @@
  * 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/. */
 
 //! Specified types for CSS values that are related to transformations.
 
 use cssparser::Parser;
 use parser::{Parse, ParserContext};
 use selectors::parser::SelectorParseErrorKind;
-use style_traits::{CssType, ParseError, SpecifiedValueInfo, StyleParseErrorKind};
+use style_traits::{ParseError, StyleParseErrorKind};
 use values::computed::{Context, LengthOrPercentage as ComputedLengthOrPercentage};
 use values::computed::{Percentage as ComputedPercentage, ToComputedValue};
 use values::computed::transform::TimingFunction as ComputedTimingFunction;
 use values::generics::transform as generic;
 use values::generics::transform::{Matrix, Matrix3D, StepPosition, TimingKeyword};
 use values::specified::{self, Angle, Integer, Length, LengthOrPercentage, Number};
 use values::specified::position::{Side, X, Y};
 
@@ -241,20 +241,16 @@ pub enum OriginComponent<S> {
     Length(LengthOrPercentage),
     /// `<side>`
     Side(S),
 }
 
 /// A specified timing function.
 pub type TimingFunction = generic::TimingFunction<Integer, Number>;
 
-impl SpecifiedValueInfo for TimingFunction {
-    const SUPPORTED_TYPES: u8 = CssType::TIMING_FUNCTION;
-}
-
 impl Parse for TransformOrigin {
     fn parse<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>,
     ) -> Result<Self, ParseError<'i>> {
         let parse_depth = |input: &mut Parser| {
             input
                 .try(|i| Length::parse(context, i))
--- a/servo/components/style_derive/lib.rs
+++ b/servo/components/style_derive/lib.rs
@@ -59,13 +59,13 @@ pub fn derive_to_computed_value(stream: 
 }
 
 #[proc_macro_derive(ToCss, attributes(css))]
 pub fn derive_to_css(stream: TokenStream) -> TokenStream {
     let input = syn::parse(stream).unwrap();
     to_css::derive(input).into()
 }
 
-#[proc_macro_derive(SpecifiedValueInfo, attributes(css))]
+#[proc_macro_derive(SpecifiedValueInfo, attributes(css, value_info))]
 pub fn derive_specified_value_info(stream: TokenStream) -> TokenStream {
     let input = syn::parse(stream).unwrap();
     specified_value_info::derive(input).into()
 }
--- a/servo/components/style_derive/specified_value_info.rs
+++ b/servo/components/style_derive/specified_value_info.rs
@@ -1,56 +1,67 @@
 /* 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 cg;
 use quote::Tokens;
-use syn::{Data, DeriveInput, Fields, Type};
+use syn::{Data, DeriveInput, Fields, Ident,Type};
 use to_css::{CssFieldAttrs, CssInputAttrs, CssVariantAttrs};
 
 pub fn derive(mut input: DeriveInput) -> Tokens {
-    let attrs = cg::parse_input_attrs::<CssInputAttrs>(&input);
+    let css_attrs = cg::parse_input_attrs::<CssInputAttrs>(&input);
     let mut types = vec![];
     let mut values = vec![];
 
     let input_ident = input.ident;
     let input_name = || cg::to_css_identifier(input_ident.as_ref());
-    if let Some(function) = attrs.function {
+    if let Some(function) = css_attrs.function {
         values.push(function.explicit().unwrap_or_else(input_name));
         // If the whole value is wrapped in a function, value types of
         // its fields should not be propagated.
     } else {
         let mut where_clause = input.generics.where_clause.take();
         for param in input.generics.type_params() {
             cg::add_predicate(
                 &mut where_clause,
                 parse_quote!(#param: ::style_traits::SpecifiedValueInfo),
             );
         }
         input.generics.where_clause = where_clause;
 
         match input.data {
             Data::Enum(ref e) => {
                 for v in e.variants.iter() {
-                    let attrs = cg::parse_variant_attrs::<CssVariantAttrs>(&v);
-                    if attrs.skip {
+                    let css_attrs = cg::parse_variant_attrs::<CssVariantAttrs>(&v);
+                    let info_attrs = cg::parse_variant_attrs::<ValueInfoVariantAttrs>(&v);
+                    if css_attrs.skip {
                         continue;
                     }
-                    if let Some(aliases) = attrs.aliases {
+                    if let Some(aliases) = css_attrs.aliases {
                         for alias in aliases.split(",") {
                             values.push(alias.to_string());
                         }
                     }
-                    if let Some(keyword) = attrs.keyword {
+                    if let Some(other_values) = info_attrs.other_values {
+                        for value in other_values.split(",") {
+                            values.push(value.to_string());
+                        }
+                    }
+                    let ident = &v.ident;
+                    let variant_name = || cg::to_css_identifier(ident.as_ref());
+                    if info_attrs.starts_with_keyword {
+                        values.push(variant_name());
+                        continue;
+                    }
+                    if let Some(keyword) = css_attrs.keyword {
                         values.push(keyword);
                         continue;
                     }
-                    let variant_name = || cg::to_css_identifier(v.ident.as_ref());
-                    if let Some(function) = attrs.function {
+                    if let Some(function) = css_attrs.function {
                         values.push(function.explicit().unwrap_or_else(variant_name));
                     } else {
                         if !derive_struct_fields(&v.fields, &mut types, &mut values) {
                             values.push(variant_name());
                         }
                     }
                 }
             }
@@ -58,26 +69,39 @@ pub fn derive(mut input: DeriveInput) ->
                 if !derive_struct_fields(&s.fields, &mut types, &mut values) {
                     values.push(input_name());
                 }
             }
             Data::Union(_) => unreachable!("union is not supported"),
         }
     }
 
+    let info_attrs = cg::parse_input_attrs::<ValueInfoInputAttrs>(&input);
+    if let Some(other_values) = info_attrs.other_values {
+        for value in other_values.split(",") {
+            values.push(value.to_string());
+        }
+    }
+
     let mut types_value = quote!(0);
     types_value.append_all(types.iter().map(|ty| quote! {
         | <#ty as ::style_traits::SpecifiedValueInfo>::SUPPORTED_TYPES
     }));
 
     let mut nested_collects = quote!();
     nested_collects.append_all(types.iter().map(|ty| quote! {
         <#ty as ::style_traits::SpecifiedValueInfo>::collect_completion_keywords(_f);
     }));
 
+    if let Some(ty) = info_attrs.ty {
+        types_value.append_all(quote! {
+            | ::style_traits::CssType::#ty
+        });
+    }
+
     let append_values = if values.is_empty() {
         quote!()
     } else {
         let mut value_list = quote!();
         value_list.append_separated(values.iter(), quote!(,));
         quote! { _f(&[#value_list]); }
     };
 
@@ -105,20 +129,53 @@ fn derive_struct_fields<'a>(
     values: &mut Vec<String>,
 ) -> bool {
     let fields = match *fields {
         Fields::Unit => return false,
         Fields::Named(ref fields) => fields.named.iter(),
         Fields::Unnamed(ref fields) => fields.unnamed.iter(),
     };
     types.extend(fields.filter_map(|field| {
-        let attrs = cg::parse_field_attrs::<CssFieldAttrs>(field);
-        if let Some(if_empty) = attrs.if_empty {
+        let info_attrs = cg::parse_field_attrs::<ValueInfoFieldAttrs>(field);
+        if let Some(other_values) = info_attrs.other_values {
+            for value in other_values.split(",") {
+                values.push(value.to_string());
+            }
+        }
+        if info_attrs.represents_keyword {
+            let ident = field.ident.as_ref()
+                .expect("only named field should use represents_keyword");
+            values.push(cg::to_css_identifier(ident.as_ref()));
+            return None;
+        }
+        let css_attrs = cg::parse_field_attrs::<CssFieldAttrs>(field);
+        if let Some(if_empty) = css_attrs.if_empty {
             values.push(if_empty);
         }
-        if !attrs.skip {
+        if !css_attrs.skip {
             Some(&field.ty)
         } else {
             None
         }
     }));
     true
 }
+
+#[darling(attributes(value_info), default)]
+#[derive(Default, FromDeriveInput)]
+struct ValueInfoInputAttrs {
+    ty: Option<Ident>,
+    other_values: Option<String>,
+}
+
+#[darling(attributes(value_info), default)]
+#[derive(Default, FromVariant)]
+struct ValueInfoVariantAttrs {
+    starts_with_keyword: bool,
+    other_values: Option<String>,
+}
+
+#[darling(attributes(value_info), default)]
+#[derive(Default, FromField)]
+struct ValueInfoFieldAttrs {
+    represents_keyword: bool,
+    other_values: Option<String>,
+}
--- a/servo/components/style_traits/specified_value_info.rs
+++ b/servo/components/style_traits/specified_value_info.rs
@@ -22,16 +22,46 @@ pub mod CssType {
     /// <timing-function>
     pub const TIMING_FUNCTION: u8 = 1 << 2;
 }
 
 /// See SpecifiedValueInfo::collect_completion_keywords.
 pub type KeywordsCollectFn<'a> = &'a mut FnMut(&[&'static str]);
 
 /// Information of values of a given specified value type.
+///
+/// This trait is derivable with `#[derive(SpecifiedValueInfo)]`.
+///
+/// The algorithm traverses the type definition. For `SUPPORTED_TYPES`,
+/// it puts an or'ed value of `SUPPORTED_TYPES` of all types it finds.
+/// For `collect_completion_keywords`, it recursively invokes this
+/// method on types found, and lists all keyword values and function
+/// names following the same rule as `ToCss` in that method.
+///
+/// Some attributes of `ToCss` can affect the behavior, specifically:
+/// * If `#[css(function)]` is found, the content inside the annotated
+///   variant (or the whole type) isn't traversed, only the function
+///   name is listed in `collect_completion_keywords`.
+/// * If `#[css(skip)]` is found, the content inside the variant or
+///   field is ignored.
+/// * Values listed in `#[css(if_empty)]`, `#[css(aliases)]`, and
+///   `#[css(keyword)]` are added into `collect_completion_keywords`.
+///
+/// In addition to `css` attributes, it also has `value_info` helper
+/// attributes, including:
+/// * `#[value_info(ty = "TYPE")]` can be used to specify a constant
+///   from `CssType` to `SUPPORTED_TYPES`.
+/// * `#[value_info(other_values = "value1,value2")]` can be used to
+///   add other values related to a field, variant, or the type itself
+///   into `collect_completion_keywords`.
+/// * `#[value_info(starts_with_keyword)]` can be used on variants to
+///   add the name of a non-unit variant (serialized like `ToCss`) into
+///   `collect_completion_keywords`.
+/// * `#[value_info(represents_keyword)]` can be used on fields into
+///   `collect_completion_keywords`.
 pub trait SpecifiedValueInfo {
     /// Supported CssTypes by the given value type.
     ///
     /// XXX This should be typed CssType when that becomes a bitflags.
     /// Currently we cannot do so since bitflags cannot be used in constant.
     const SUPPORTED_TYPES: u8 = 0;
 
     /// Collect value starting words for the given specified value type.