Bug 1434130 part 11 - Add some attributes for SpecifiedValueInfo to help deriving more from types. r=emilio
authorXidorn Quan <me@upsuper.org>
Sun, 29 Apr 2018 09:03:31 +1000
changeset 472303 b9e4fda9a50f2cc7ab722df3e9549a9b4e653970
parent 472302 83df94ad2416a8f603acdbdc9fccdfccea90ffba
child 472304 1baf959951783147c5d1a0321eb6577a0f6c1e09
push id1728
push userjlund@mozilla.com
push dateMon, 18 Jun 2018 21:12:27 +0000
treeherdermozilla-release@c296fde26f5f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
bugs1434130
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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.