servo: Merge #14535 - Introduce a PropertyId enum and use it instead of strings of property names (from servo:property-id); r=mbrubeck
authorSimon Sapin <simon.sapin@exyr.org>
Sat, 10 Dec 2016 01:16:26 -0800
changeset 340377 daaa89ca8da2cf8fa5142c69ba50bcf12b306204
parent 340376 b5b63ec333ddcb40ac2c321e7e9dda9c79a81bc0
child 340378 be1f64d5b3475a3f7a959929fe9c507dcaa442a1
push id86548
push userkwierso@gmail.com
push dateSat, 04 Feb 2017 01:35:21 +0000
treeherdermozilla-inbound@e7b96d015d03 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmbrubeck
servo: Merge #14535 - Introduce a PropertyId enum and use it instead of strings of property names (from servo:property-id); r=mbrubeck <!-- Please describe your changes on the following line: --> * `LonghandId` and `ShorthandId` are C-like enums * `Atom` is used for the name of custom properties. * `PropertyDeclarationId` is the identifier for `PropertyDeclaration`, after parsing and shorthand expansion. (Longhand or custom property.) * `PropertyId` represents any CSS property, e.g. in CSSOM. (Longhand, shorthand, or custom.) CC @upsuper --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [ ] These changes fix #__ (github issue number if applicable). <!-- Either: --> - [ ] There are tests for these changes OR - [ ] These changes do not require tests because _____ <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: 6dd4b4822fa788694153ee61a04dd9a5dfb748ec
servo/Cargo.lock
servo/components/atoms/static_atoms.txt
servo/components/layout/query.rs
servo/components/layout_thread/lib.rs
servo/components/script/dom/bindings/str.rs
servo/components/script/dom/cssstyledeclaration.rs
servo/components/script/dom/window.rs
servo/components/script_layout_interface/message.rs
servo/components/script_layout_interface/rpc.rs
servo/components/style/Cargo.toml
servo/components/style/build.rs
servo/components/style/gecko_string_cache/mod.rs
servo/components/style/keyframes.rs
servo/components/style/lib.rs
servo/components/style/properties/build.py
servo/components/style/properties/declaration_block.rs
servo/components/style/properties/helpers.mako.rs
servo/components/style/properties/properties.mako.rs
servo/components/style/properties/shorthand/serialize.mako.rs
servo/components/style/str.rs
servo/ports/geckolib/glue.rs
servo/tests/unit/style/Cargo.toml
servo/tests/unit/style/lib.rs
servo/tests/unit/style/properties/serialization.rs
servo/tests/unit/style/str.rs
servo/tests/unit/style/stylesheets.rs
servo/tests/unit/style/stylist.rs
--- a/servo/Cargo.lock
+++ b/servo/Cargo.lock
@@ -2605,16 +2605,18 @@ dependencies = [
  "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "nsstring_vendor 0.1.0",
  "num-integer 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
  "num_cpus 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "ordered-float 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "owning_ref 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "parking_lot 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "phf 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)",
+ "phf_codegen 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)",
  "plugins 0.0.1",
  "quickersort 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
  "rayon 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
  "selectors 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.8.18 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 0.8.18 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2631,16 +2633,17 @@ dependencies = [
 [[package]]
 name = "style_tests"
 version = "0.0.1"
 dependencies = [
  "app_units 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "html5ever-atoms 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "owning_ref 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "parking_lot 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
  "selectors 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_atoms 0.0.1",
  "servo_url 0.0.1",
  "style 0.0.1",
  "style_traits 0.0.1",
--- a/servo/components/atoms/static_atoms.txt
+++ b/servo/components/atoms/static_atoms.txt
@@ -27,31 +27,16 @@ date
 month
 week
 time
 datetime-local
 number
 
 dir
 
-bottom
-top
-left
-right
-width
-height
-margin-bottom
-margin-top
-margin-left
-margin-right
-padding-bottom
-padding-top
-padding-left
-padding-right
-
 DOMContentLoaded
 select
 input
 load
 loadstart
 loadend
 progress
 transitionend
--- a/servo/components/layout/query.rs
+++ b/servo/components/layout/query.rs
@@ -19,26 +19,25 @@ use script_layout_interface::rpc::{Conte
 use script_layout_interface::rpc::{HitTestResponse, LayoutRPC};
 use script_layout_interface::rpc::{MarginStyleResponse, NodeGeometryResponse};
 use script_layout_interface::rpc::{NodeOverflowResponse, OffsetParentResponse};
 use script_layout_interface::rpc::{NodeScrollRootIdResponse, ResolvedStyleResponse};
 use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
 use script_traits::LayoutMsg as ConstellationMsg;
 use script_traits::UntrustedNodeAddress;
 use sequential;
-use servo_atoms::Atom;
 use std::cmp::{min, max};
 use std::ops::Deref;
 use std::sync::{Arc, Mutex};
 use style::computed_values;
 use style::context::StyleContext;
 use style::dom::TElement;
 use style::logical_geometry::{WritingMode, BlockFlowDirection, InlineBaseDirection};
+use style::properties::{style_structs, PropertyId, PropertyDeclarationId, LonghandId};
 use style::properties::longhands::{display, position};
-use style::properties::style_structs;
 use style::selector_parser::PseudoElement;
 use style::stylist::Stylist;
 use style_traits::ToCss;
 use style_traits::cursor::Cursor;
 use wrapper::{LayoutNodeHelpers, LayoutNodeLayoutData};
 
 /// Mutable data belonging to the LayoutThread.
 ///
@@ -70,17 +69,17 @@ pub struct LayoutThreadData {
 
     /// A pair of overflow property in x and y
     pub overflow_response: NodeOverflowResponse,
 
     /// A queued response for the scroll {top, left, width, height} of a node in pixels.
     pub scroll_area_response: Rect<i32>,
 
     /// A queued response for the resolved style property of an element.
-    pub resolved_style_response: Option<String>,
+    pub resolved_style_response: String,
 
     /// A queued response for the offset parent/rect of a node.
     pub offset_parent_response: OffsetParentResponse,
 
     /// A queued response for the offset parent/rect of a node.
     pub margin_style_response: MarginStyleResponse,
 
     /// Scroll offsets of stacking contexts. This will only be populated if WebRender is in use.
@@ -623,18 +622,18 @@ pub fn process_node_scroll_area_request<
     }
 }
 
 /// Return the resolved value of property for a given (pseudo)element.
 /// https://drafts.csswg.org/cssom/#resolved-value
 pub fn process_resolved_style_request<'a, N, C>(requested_node: N,
                                                 style_context: &'a C,
                                                 pseudo: &Option<PseudoElement>,
-                                                property: &Atom,
-                                                layout_root: &mut Flow) -> Option<String>
+                                                property: &PropertyId,
+                                                layout_root: &mut Flow) -> String
     where N: LayoutNode,
           C: StyleContext<'a>
 {
     use style::traversal::{clear_descendant_data, style_element_in_display_none_subtree};
     let element = requested_node.as_element().unwrap();
 
     // We call process_resolved_style_request after performing a whole-document
     // traversal, so the only reason we wouldn't have an up-to-date style here
@@ -662,23 +661,35 @@ pub fn process_resolved_style_request<'a
         _ => Some(layout_el)
     };
 
     let layout_el = match layout_el {
         None => {
             // The pseudo doesn't exist, return nothing.  Chrome seems to query
             // the element itself in this case, Firefox uses the resolved value.
             // https://www.w3.org/Bugs/Public/show_bug.cgi?id=29006
-            return None;
+            return String::new();
         }
         Some(layout_el) => layout_el
     };
 
     let style = &*layout_el.resolved_style();
 
+    let longhand_id = match *property {
+        PropertyId::Longhand(id) => id,
+
+        // Firefox returns blank strings for the computed value of shorthands,
+        // so this should be web-compatible.
+        PropertyId::Shorthand(_) => return String::new(),
+
+        PropertyId::Custom(ref name) => {
+            return style.computed_value_to_string(PropertyDeclarationId::Custom(name))
+        }
+    };
+
     // Clear any temporarily-resolved data to maintain our invariants. See the comment
     // at the top of this function.
     display_none_root.map(|r| clear_descendant_data(r, &|e| e.as_node().clear_data()));
 
     let positioned = match style.get_box().position {
         position::computed_value::T::relative |
         /*position::computed_value::T::sticky |*/
         position::computed_value::T::fixed |
@@ -692,89 +703,86 @@ pub fn process_resolved_style_request<'a
     // (Chrome seems to think they never apply and always returns resolved values).
     // There are probably other quirks.
     let applies = true;
 
     fn used_value_for_position_property<N: LayoutNode>(
             layout_el: <N::ConcreteThreadSafeLayoutNode as ThreadSafeLayoutNode>::ConcreteThreadSafeLayoutElement,
             layout_root: &mut Flow,
             requested_node: N,
-            property: &Atom) -> Option<String> {
+            longhand_id: LonghandId) -> String {
         let maybe_data = layout_el.borrow_layout_data();
         let position = maybe_data.map_or(Point2D::zero(), |data| {
             match (*data).flow_construction_result {
                 ConstructionResult::Flow(ref flow_ref, _) =>
                     flow::base(flow_ref.deref()).stacking_relative_position,
                 // TODO(dzbarsky) search parents until we find node with a flow ref.
                 // https://github.com/servo/servo/issues/8307
                 _ => Point2D::zero()
             }
         });
-        let property = match *property {
-            atom!("bottom") => PositionProperty::Bottom,
-            atom!("top") => PositionProperty::Top,
-            atom!("left") => PositionProperty::Left,
-            atom!("right") => PositionProperty::Right,
-            atom!("width") => PositionProperty::Width,
-            atom!("height") => PositionProperty::Height,
+        let property = match longhand_id {
+            LonghandId::Bottom => PositionProperty::Bottom,
+            LonghandId::Top => PositionProperty::Top,
+            LonghandId::Left => PositionProperty::Left,
+            LonghandId::Right => PositionProperty::Right,
+            LonghandId::Width => PositionProperty::Width,
+            LonghandId::Height => PositionProperty::Height,
             _ => unreachable!()
         };
         let mut iterator =
             PositionRetrievingFragmentBorderBoxIterator::new(requested_node.opaque(),
                                                              property,
                                                              position);
         sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root,
                                                                     &mut iterator);
-        iterator.result.map(|r| r.to_css_string())
+        iterator.result.map(|r| r.to_css_string()).unwrap_or(String::new())
     }
 
     // TODO: we will return neither the computed nor used value for margin and padding.
-    // Firefox returns blank strings for the computed value of shorthands,
-    // so this should be web-compatible.
-    match *property {
-        atom!("margin-bottom") | atom!("margin-top") |
-        atom!("margin-left") | atom!("margin-right") |
-        atom!("padding-bottom") | atom!("padding-top") |
-        atom!("padding-left") | atom!("padding-right")
+    match longhand_id {
+        LonghandId::MarginBottom | LonghandId::MarginTop |
+        LonghandId::MarginLeft | LonghandId::MarginRight |
+        LonghandId::PaddingBottom | LonghandId::PaddingTop |
+        LonghandId::PaddingLeft | LonghandId::PaddingRight
         if applies && style.get_box().display != display::computed_value::T::none => {
-            let (margin_padding, side) = match *property {
-                atom!("margin-bottom") => (MarginPadding::Margin, Side::Bottom),
-                atom!("margin-top") => (MarginPadding::Margin, Side::Top),
-                atom!("margin-left") => (MarginPadding::Margin, Side::Left),
-                atom!("margin-right") => (MarginPadding::Margin, Side::Right),
-                atom!("padding-bottom") => (MarginPadding::Padding, Side::Bottom),
-                atom!("padding-top") => (MarginPadding::Padding, Side::Top),
-                atom!("padding-left") => (MarginPadding::Padding, Side::Left),
-                atom!("padding-right") => (MarginPadding::Padding, Side::Right),
+            let (margin_padding, side) = match longhand_id {
+                LonghandId::MarginBottom => (MarginPadding::Margin, Side::Bottom),
+                LonghandId::MarginTop => (MarginPadding::Margin, Side::Top),
+                LonghandId::MarginLeft => (MarginPadding::Margin, Side::Left),
+                LonghandId::MarginRight => (MarginPadding::Margin, Side::Right),
+                LonghandId::PaddingBottom => (MarginPadding::Padding, Side::Bottom),
+                LonghandId::PaddingTop => (MarginPadding::Padding, Side::Top),
+                LonghandId::PaddingLeft => (MarginPadding::Padding, Side::Left),
+                LonghandId::PaddingRight => (MarginPadding::Padding, Side::Right),
                 _ => unreachable!()
             };
             let mut iterator =
                 MarginRetrievingFragmentBorderBoxIterator::new(requested_node.opaque(),
                                                                side,
                                                                margin_padding,
                                                                style.writing_mode);
             sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root,
                                                                         &mut iterator);
-            iterator.result.map(|r| r.to_css_string())
+            iterator.result.map(|r| r.to_css_string()).unwrap_or(String::new())
         },
 
-        atom!("bottom") | atom!("top") | atom!("right") |
-        atom!("left")
+        LonghandId::Bottom | LonghandId::Top | LonghandId::Right | LonghandId::Left
         if applies && positioned && style.get_box().display !=
                 display::computed_value::T::none => {
-            used_value_for_position_property(layout_el, layout_root, requested_node, property)
+            used_value_for_position_property(layout_el, layout_root, requested_node, longhand_id)
         }
-        atom!("width") | atom!("height")
+        LonghandId::Width | LonghandId::Height
         if applies && style.get_box().display !=
                 display::computed_value::T::none => {
-            used_value_for_position_property(layout_el, layout_root, requested_node, property)
+            used_value_for_position_property(layout_el, layout_root, requested_node, longhand_id)
         }
         // FIXME: implement used value computation for line-height
-        ref property => {
-            style.computed_value_to_string(&*property).ok()
+        _ => {
+            style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id))
         }
     }
 }
 
 pub fn process_offset_parent_query<N: LayoutNode>(requested_node: N, layout_root: &mut Flow)
         -> OffsetParentResponse {
     let mut iterator = ParentOffsetBorderBoxIterator::new(requested_node.opaque());
     sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, &mut iterator);
--- a/servo/components/layout_thread/lib.rs
+++ b/servo/components/layout_thread/lib.rs
@@ -461,17 +461,17 @@ impl LayoutThread {
                     stylist: stylist,
                     content_box_response: Rect::zero(),
                     content_boxes_response: Vec::new(),
                     client_rect_response: Rect::zero(),
                     hit_test_response: (None, false),
                     scroll_root_id_response: None,
                     scroll_area_response: Rect::zero(),
                     overflow_response: NodeOverflowResponse(None),
-                    resolved_style_response: None,
+                    resolved_style_response: String::new(),
                     offset_parent_response: OffsetParentResponse::empty(),
                     margin_style_response: MarginStyleResponse::empty(),
                     stacking_context_scroll_offsets: HashMap::new(),
                 })),
             error_reporter: CSSErrorReporter {
                 pipelineid: id,
                 script_chan: Arc::new(Mutex::new(script_chan)),
             },
@@ -1013,17 +1013,17 @@ impl LayoutThread {
                     },
                     ReflowQueryType::NodeOverflowQuery(_) => {
                         rw_data.overflow_response = NodeOverflowResponse(None);
                     },
                     ReflowQueryType::NodeScrollRootIdQuery(_) => {
                         rw_data.scroll_root_id_response = None;
                     },
                     ReflowQueryType::ResolvedStyleQuery(_, _, _) => {
-                        rw_data.resolved_style_response = None;
+                        rw_data.resolved_style_response = String::new();
                     },
                     ReflowQueryType::OffsetParentQuery(_) => {
                         rw_data.offset_parent_response = OffsetParentResponse::empty();
                     },
                     ReflowQueryType::MarginStyleQuery(_) => {
                         rw_data.margin_style_response = MarginStyleResponse::empty();
                     },
                     ReflowQueryType::NoQuery => {}
--- a/servo/components/script/dom/bindings/str.rs
+++ b/servo/components/script/dom/bindings/str.rs
@@ -281,13 +281,19 @@ impl From<DOMString> for String {
 }
 
 impl Into<Vec<u8>> for DOMString {
     fn into(self) -> Vec<u8> {
         self.0.into()
     }
 }
 
+impl<'a> Into<Cow<'a, str>> for DOMString {
+    fn into(self) -> Cow<'a, str> {
+        self.0.into()
+    }
+}
+
 impl Extend<char> for DOMString {
     fn extend<I>(&mut self, iterable: I) where I: IntoIterator<Item=char> {
         self.0.extend(iterable)
     }
 }
--- a/servo/components/script/dom/cssstyledeclaration.rs
+++ b/servo/components/script/dom/cssstyledeclaration.rs
@@ -7,22 +7,21 @@ use dom::bindings::error::{Error, ErrorR
 use dom::bindings::inheritance::Castable;
 use dom::bindings::js::{JS, Root};
 use dom::bindings::reflector::{Reflector, reflect_dom_object};
 use dom::bindings::str::DOMString;
 use dom::element::Element;
 use dom::node::{Node, NodeDamage, window_from_node};
 use dom::window::Window;
 use parking_lot::RwLock;
-use servo_atoms::Atom;
 use std::ascii::AsciiExt;
 use std::sync::Arc;
 use style::parser::ParserContextExtraData;
-use style::properties::{Shorthand, Importance, PropertyDeclarationBlock};
-use style::properties::{is_supported_property, parse_one_declaration, parse_style_attribute};
+use style::properties::{Importance, PropertyDeclarationBlock, PropertyId, LonghandId, ShorthandId};
+use style::properties::{parse_one_declaration, parse_style_attribute};
 use style::selector_parser::PseudoElement;
 use style_traits::ToCss;
 
 // http://dev.w3.org/csswg/cssom/#the-cssstyledeclaration-interface
 #[dom_struct]
 pub struct CSSStyleDeclaration {
     reflector_: Reflector,
     owner: JS<Element>,
@@ -32,23 +31,23 @@ pub struct CSSStyleDeclaration {
 
 #[derive(PartialEq, HeapSizeOf)]
 pub enum CSSModificationAccess {
     ReadWrite,
     Readonly,
 }
 
 macro_rules! css_properties(
-    ( $([$getter:ident, $setter:ident, $cssprop:expr]),* ) => (
+    ( $([$getter:ident, $setter:ident, $id:expr],)* ) => (
         $(
             fn $getter(&self) -> DOMString {
-                self.GetPropertyValue(DOMString::from($cssprop))
+                self.get_property_value($id)
             }
             fn $setter(&self, value: DOMString) -> ErrorResult {
-                self.SetPropertyValue(DOMString::from($cssprop), value)
+                self.set_property($id, value, DOMString::new())
             }
         )*
     );
 );
 
 impl CSSStyleDeclaration {
     pub fn new_inherited(owner: &Element,
                          pseudo: Option<PseudoElement>,
@@ -69,131 +68,83 @@ impl CSSStyleDeclaration {
                -> Root<CSSStyleDeclaration> {
         reflect_dom_object(box CSSStyleDeclaration::new_inherited(owner,
                                                                   pseudo,
                                                                   modification_access),
                            global,
                            CSSStyleDeclarationBinding::Wrap)
     }
 
-    fn get_computed_style(&self, property: &Atom) -> Option<DOMString> {
+    fn get_computed_style(&self, property: PropertyId) -> DOMString {
         let node = self.owner.upcast::<Node>();
         if !node.is_in_doc() {
             // TODO: Node should be matched against the style rules of this window.
             // Firefox is currently the only browser to implement this.
-            return None;
+            return DOMString::new();
         }
         let addr = node.to_trusted_node_address();
         window_from_node(&*self.owner).resolved_style_query(addr, self.pseudo.clone(), property)
     }
-}
 
-impl CSSStyleDeclarationMethods for CSSStyleDeclaration {
-    // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-length
-    fn Length(&self) -> u32 {
-        let elem = self.owner.upcast::<Element>();
-        let len = match *elem.style_attribute().borrow() {
-            Some(ref lock) => lock.read().declarations.len(),
-            None => 0,
-        };
-        len as u32
-    }
-
-    // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-item
-    fn Item(&self, index: u32) -> DOMString {
-        self.IndexedGetter(index).unwrap_or_default()
-    }
-
-    // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-getpropertyvalue
-    fn GetPropertyValue(&self, mut property: DOMString) -> DOMString {
+    fn get_property_value(&self, id: PropertyId) -> DOMString {
         if self.readonly {
             // Readonly style declarations are used for getComputedStyle.
-            property.make_ascii_lowercase();
-            let property = Atom::from(property);
-            return self.get_computed_style(&property).unwrap_or(DOMString::new());
+            return self.get_computed_style(id);
         }
 
         let style_attribute = self.owner.style_attribute().borrow();
         let style_attribute = if let Some(ref lock) = *style_attribute {
             lock.read()
         } else {
             // No style attribute is like an empty style attribute: no matching declaration.
             return DOMString::new()
         };
 
         let mut string = String::new();
-        style_attribute.property_value_to_css(&property, &mut string).unwrap();
+        style_attribute.property_value_to_css(&id, &mut string).unwrap();
         DOMString::from(string)
     }
 
-    // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-getpropertypriority
-    fn GetPropertyPriority(&self, property: DOMString) -> DOMString {
-        let style_attribute = self.owner.style_attribute().borrow();
-        let style_attribute = if let Some(ref lock) = *style_attribute {
-            lock.read()
-        } else {
-            // No style attribute is like an empty style attribute: no matching declaration.
-            return DOMString::new()
-        };
-
-        if style_attribute.property_priority(&property).important() {
-            DOMString::from("important")
-        } else {
-            // Step 4
-            DOMString::new()
-        }
-    }
-
-    // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-setproperty
-    fn SetProperty(&self,
-                   property: DOMString,
-                   value: DOMString,
-                   priority: DOMString)
-                   -> ErrorResult {
+    fn set_property(&self, id: PropertyId, value: DOMString, priority: DOMString) -> ErrorResult {
         // Step 1
         if self.readonly {
             return Err(Error::NoModificationAllowed);
         }
 
-        // Step 3
-        if !is_supported_property(&property) {
-            return Ok(());
-        }
-
         let mut style_attribute = self.owner.style_attribute().borrow_mut();
 
         if value.is_empty() {
             // Step 4
             let empty;
             {
                 let mut style_attribute = if let Some(ref lock) = *style_attribute {
                     lock.write()
                 } else {
                     // No style attribute is like an empty style attribute: nothing to remove.
                     return Ok(())
                 };
 
-                style_attribute.remove_property(&property);
+                style_attribute.remove_property(&id);
                 empty = style_attribute.declarations.is_empty()
             }
             if empty {
                 *style_attribute = None;
             }
         } else {
             // Step 5
             let importance = match &*priority {
                 "" => Importance::Normal,
                 p if p.eq_ignore_ascii_case("important") => Importance::Important,
                 _ => return Ok(()),
             };
 
             // Step 6
             let window = window_from_node(&*self.owner);
             let declarations =
-                parse_one_declaration(&property, &value, &window.get_url(), window.css_error_reporter(),
+                parse_one_declaration(id, &value, &window.get_url(), window.css_error_reporter(),
                                       ParserContextExtraData::default());
 
             // Step 7
             let declarations = if let Ok(declarations) = declarations {
                 declarations
             } else {
                 return Ok(());
             };
@@ -223,45 +174,114 @@ impl CSSStyleDeclarationMethods for CSSS
                 }
             }
         }
 
         let node = self.owner.upcast::<Node>();
         node.dirty(NodeDamage::NodeStyleDamaged);
         Ok(())
     }
+}
+
+impl CSSStyleDeclarationMethods for CSSStyleDeclaration {
+    // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-length
+    fn Length(&self) -> u32 {
+        let elem = self.owner.upcast::<Element>();
+        let len = match *elem.style_attribute().borrow() {
+            Some(ref lock) => lock.read().declarations.len(),
+            None => 0,
+        };
+        len as u32
+    }
+
+    // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-item
+    fn Item(&self, index: u32) -> DOMString {
+        self.IndexedGetter(index).unwrap_or_default()
+    }
+
+    // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-getpropertyvalue
+    fn GetPropertyValue(&self, property: DOMString) -> DOMString {
+        let id = if let Ok(id) = PropertyId::parse(property.into()) {
+            id
+        } else {
+            // Unkwown property
+            return DOMString::new()
+        };
+        self.get_property_value(id)
+    }
+
+    // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-getpropertypriority
+    fn GetPropertyPriority(&self, property: DOMString) -> DOMString {
+        let id = if let Ok(id) = PropertyId::parse(property.into()) {
+            id
+        } else {
+            // Unkwown property
+            return DOMString::new()
+        };
+
+        let style_attribute = self.owner.style_attribute().borrow();
+        let style_attribute = if let Some(ref lock) = *style_attribute {
+            lock.read()
+        } else {
+            // No style attribute is like an empty style attribute: no matching declaration.
+            return DOMString::new()
+        };
+
+        if style_attribute.property_priority(&id).important() {
+            DOMString::from("important")
+        } else {
+            // Step 4
+            DOMString::new()
+        }
+    }
+
+    // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-setproperty
+    fn SetProperty(&self,
+                   property: DOMString,
+                   value: DOMString,
+                   priority: DOMString)
+                   -> ErrorResult {
+        // Step 3
+        let id = if let Ok(id) = PropertyId::parse(property.into()) {
+            id
+        } else {
+            // Unkwown property
+            return Ok(())
+        };
+        self.set_property(id, value, priority)
+    }
 
     // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-setpropertypriority
     fn SetPropertyPriority(&self, property: DOMString, priority: DOMString) -> ErrorResult {
         // Step 1
         if self.readonly {
             return Err(Error::NoModificationAllowed);
         }
 
         // Step 2 & 3
-        if !is_supported_property(&property) {
-            return Ok(());
-        }
+        let id = if let Ok(id) = PropertyId::parse(property.into()) {
+            id
+        } else {
+            // Unkwown property
+            return Ok(())
+        };
 
         // Step 4
         let importance = match &*priority {
             "" => Importance::Normal,
             p if p.eq_ignore_ascii_case("important") => Importance::Important,
             _ => return Ok(()),
         };
 
         let style_attribute = self.owner.style_attribute().borrow();
         if let Some(ref lock) = *style_attribute {
             let mut style_attribute = lock.write();
 
             // Step 5 & 6
-            match Shorthand::from_name(&property) {
-                Some(shorthand) => style_attribute.set_importance(shorthand.longhands(), importance),
-                None => style_attribute.set_importance(&[&*property], importance),
-            }
+            style_attribute.set_importance(&id, importance);
 
             self.owner.set_style_attr(style_attribute.to_css_string());
             let node = self.owner.upcast::<Node>();
             node.dirty(NodeDamage::NodeStyleDamaged);
         }
         Ok(())
     }
 
@@ -272,32 +292,39 @@ impl CSSStyleDeclarationMethods for CSSS
 
     // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-removeproperty
     fn RemoveProperty(&self, property: DOMString) -> Fallible<DOMString> {
         // Step 1
         if self.readonly {
             return Err(Error::NoModificationAllowed);
         }
 
+        let id = if let Ok(id) = PropertyId::parse(property.into()) {
+            id
+        } else {
+            // Unkwown property, cannot be there to remove.
+            return Ok(DOMString::new())
+        };
+
         let mut style_attribute = self.owner.style_attribute().borrow_mut();
         let mut string = String::new();
         let empty;
         {
             let mut style_attribute = if let Some(ref lock) = *style_attribute {
                 lock.write()
             } else {
                 // No style attribute is like an empty style attribute: nothing to remove.
                 return Ok(DOMString::new())
             };
 
             // Step 3
-            style_attribute.property_value_to_css(&property, &mut string).unwrap();
+            style_attribute.property_value_to_css(&id, &mut string).unwrap();
 
             // Step 4 & 5
-            style_attribute.remove_property(&property);
+            style_attribute.remove_property(&id);
             self.owner.set_style_attr(style_attribute.to_css_string());
             empty = style_attribute.declarations.is_empty()
         }
         if empty {
             *style_attribute = None;
         }
 
         let node = self.owner.upcast::<Node>();
--- a/servo/components/script/dom/window.rs
+++ b/servo/components/script/dom/window.rs
@@ -87,16 +87,17 @@ use std::io::{Write, stderr, stdout};
 use std::rc::Rc;
 use std::sync::{Arc, Mutex};
 use std::sync::atomic::{AtomicBool, Ordering};
 use std::sync::mpsc::{Sender, channel};
 use std::sync::mpsc::TryRecvError::{Disconnected, Empty};
 use style::context::ReflowGoal;
 use style::error_reporting::ParseErrorReporter;
 use style::media_queries;
+use style::properties::PropertyId;
 use style::properties::longhands::overflow_x;
 use style::selector_parser::PseudoElement;
 use style::str::HTML_SPACE_CHARACTERS;
 use task_source::dom_manipulation::DOMManipulationTaskSource;
 use task_source::file_reading::FileReadingTaskSource;
 use task_source::history_traversal::HistoryTraversalTaskSource;
 use task_source::networking::NetworkingTaskSource;
 use task_source::user_interaction::UserInteractionTaskSource;
@@ -1290,26 +1291,26 @@ impl Window {
         self.perform_a_scroll(x_.to_f32().unwrap_or(0.0f32),
                               y_.to_f32().unwrap_or(0.0f32),
                               scroll_root_id,
                               behavior,
                               None);
     }
 
     pub fn resolved_style_query(&self,
-                            element: TrustedNodeAddress,
-                            pseudo: Option<PseudoElement>,
-                            property: &Atom) -> Option<DOMString> {
+                                element: TrustedNodeAddress,
+                                pseudo: Option<PseudoElement>,
+                                property: PropertyId) -> DOMString {
         if !self.reflow(ReflowGoal::ForScriptQuery,
-                        ReflowQueryType::ResolvedStyleQuery(element, pseudo, property.clone()),
+                        ReflowQueryType::ResolvedStyleQuery(element, pseudo, property),
                         ReflowReason::Query) {
-            return None;
+            return DOMString::new();
         }
         let ResolvedStyleResponse(resolved) = self.layout_rpc.resolved_style();
-        resolved.map(DOMString::from)
+        DOMString::from(resolved)
     }
 
     pub fn offset_parent_query(&self, node: TrustedNodeAddress) -> (Option<Root<Element>>, Rect<Au>) {
         if !self.reflow(ReflowGoal::ForScriptQuery,
                         ReflowQueryType::OffsetParentQuery(node),
                         ReflowReason::Query) {
             return (None, Rect::zero());
         }
--- a/servo/components/script_layout_interface/message.rs
+++ b/servo/components/script_layout_interface/message.rs
@@ -9,21 +9,21 @@ use euclid::rect::Rect;
 use gfx_traits::Epoch;
 use ipc_channel::ipc::{IpcReceiver, IpcSender};
 use msg::constellation_msg::PipelineId;
 use net_traits::image_cache_thread::ImageCacheThread;
 use profile_traits::mem::ReportsChan;
 use rpc::LayoutRPC;
 use script_traits::{ConstellationControlMsg, LayoutControlMsg};
 use script_traits::{LayoutMsg as ConstellationMsg, StackingContextScrollState, WindowSizeData};
-use servo_atoms::Atom;
 use servo_url::ServoUrl;
 use std::sync::Arc;
 use std::sync::mpsc::{Receiver, Sender};
 use style::context::ReflowGoal;
+use style::properties::PropertyId;
 use style::selector_parser::PseudoElement;
 use style::stylesheets::Stylesheet;
 
 /// Asynchronous messages that script can send to layout.
 pub enum Msg {
     /// Adds the given stylesheet to the document.
     AddStylesheet(Arc<Stylesheet>),
 
@@ -92,17 +92,17 @@ pub enum ReflowQueryType {
     NoQuery,
     ContentBoxQuery(TrustedNodeAddress),
     ContentBoxesQuery(TrustedNodeAddress),
     NodeOverflowQuery(TrustedNodeAddress),
     HitTestQuery(Point2D<f32>, Point2D<f32>, bool),
     NodeScrollRootIdQuery(TrustedNodeAddress),
     NodeGeometryQuery(TrustedNodeAddress),
     NodeScrollGeometryQuery(TrustedNodeAddress),
-    ResolvedStyleQuery(TrustedNodeAddress, Option<PseudoElement>, Atom),
+    ResolvedStyleQuery(TrustedNodeAddress, Option<PseudoElement>, PropertyId),
     OffsetParentQuery(TrustedNodeAddress),
     MarginStyleQuery(TrustedNodeAddress),
 }
 
 /// Information needed for a reflow.
 pub struct Reflow {
     /// The goal of reflow: either to render to the screen or to flush layout info for script.
     pub goal: ReflowGoal,
--- a/servo/components/script_layout_interface/rpc.rs
+++ b/servo/components/script_layout_interface/rpc.rs
@@ -52,17 +52,17 @@ pub struct NodeGeometryResponse {
 pub struct NodeOverflowResponse(pub Option<Point2D<overflow_x::computed_value::T>>);
 
 pub struct NodeScrollRootIdResponse(pub ScrollRootId);
 
 pub struct HitTestResponse {
     pub node_address: Option<UntrustedNodeAddress>,
 }
 
-pub struct ResolvedStyleResponse(pub Option<String>);
+pub struct ResolvedStyleResponse(pub String);
 
 #[derive(Clone)]
 pub struct OffsetParentResponse {
     pub node_address: Option<UntrustedNodeAddress>,
     pub rect: Rect<Au>,
 }
 
 impl OffsetParentResponse {
--- a/servo/components/style/Cargo.toml
+++ b/servo/components/style/Cargo.toml
@@ -35,16 +35,17 @@ lazy_static = "0.2"
 log = "0.3.5"
 matches = "0.1"
 nsstring_vendor = {path = "gecko_bindings/nsstring_vendor", optional = true}
 num-integer = "0.1.32"
 num-traits = "0.1.32"
 ordered-float = "0.2.2"
 owning_ref = "0.2.2"
 parking_lot = "0.3.3"
+phf = "0.7.20"
 quickersort = "2.0.0"
 rand = "0.3"
 rayon = "0.5"
 rustc-serialize = "0.3"
 selectors = "0.15"
 serde = {version = "0.8", optional = true}
 serde_derive = {version = "0.8", optional = true}
 servo_atoms = {path = "../atoms", optional = true}
@@ -59,9 +60,10 @@ plugins = {path = "../plugins", optional
 [dependencies.num_cpus]
 optional = true
 version = "1.0"
 
 [target.'cfg(windows)'.dependencies]
 kernel32-sys = "0.2"
 
 [build-dependencies]
+phf_codegen = "0.7.20"
 walkdir = "0.1"
--- a/servo/components/style/build.rs
+++ b/servo/components/style/build.rs
@@ -1,15 +1,18 @@
 /* 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/. */
 
 extern crate walkdir;
+extern crate phf_codegen;
 
 use std::env;
+use std::fs::File;
+use std::io::{BufWriter, BufReader, BufRead, Write};
 use std::path::Path;
 use std::process::{Command, exit};
 use walkdir::WalkDir;
 
 #[cfg(windows)]
 fn find_python() -> String {
     if Command::new("python2.7.exe").arg("--version").output().is_ok() {
         return "python2.7.exe".to_owned();
@@ -56,9 +59,26 @@ fn main() {
         .arg(product)
         .arg("style-crate")
         .arg(if cfg!(feature = "testing") { "testing" } else { "regular" })
         .status()
         .unwrap();
     if !status.success() {
         exit(1)
     }
+
+    let path = Path::new(&env::var("OUT_DIR").unwrap()).join("static_ids.rs");
+    let static_ids = Path::new(&env::var("OUT_DIR").unwrap()).join("static_ids.txt");
+    let mut file = BufWriter::new(File::create(&path).unwrap());
+    let static_ids = BufReader::new(File::open(&static_ids).unwrap());
+
+    write!(&mut file, "static STATIC_IDS: ::phf::Map<&'static str, StaticId> = ").unwrap();
+    let mut map = phf_codegen::Map::new();
+    for result in static_ids.lines() {
+        let line = result.unwrap();
+        let mut split = line.split('\t');
+        let key = split.next().unwrap().to_owned();
+        let value = split.next().unwrap();
+        map.entry(key, value);
+    }
+    map.build(&mut file).unwrap();
+    write!(&mut file, ";\n").unwrap();
 }
--- a/servo/components/style/gecko_string_cache/mod.rs
+++ b/servo/components/style/gecko_string_cache/mod.rs
@@ -4,20 +4,19 @@
 
 #![allow(unsafe_code)]
 
 use gecko_bindings::bindings::Gecko_AddRefAtom;
 use gecko_bindings::bindings::Gecko_Atomize;
 use gecko_bindings::bindings::Gecko_ReleaseAtom;
 use gecko_bindings::structs::nsIAtom;
 use heapsize::HeapSizeOf;
-use std::ascii::AsciiExt;
 use std::borrow::{Cow, Borrow};
 use std::char::{self, DecodeUtf16};
-use std::fmt;
+use std::fmt::{self, Write};
 use std::hash::{Hash, Hasher};
 use std::iter::Cloned;
 use std::mem;
 use std::ops::Deref;
 use std::slice;
 
 #[macro_use]
 #[allow(improper_ctypes, non_camel_case_types)]
@@ -107,24 +106,16 @@ impl WeakAtom {
     {
         // FIXME(bholley): We should measure whether it makes more sense to
         // cache the UTF-8 version in the Gecko atom table somehow.
         let owned = String::from_utf16(self.as_slice()).unwrap();
         cb(&owned)
     }
 
     #[inline]
-    pub fn eq_str_ignore_ascii_case(&self, s: &str) -> bool {
-        self.chars().map(|r| match r {
-            Ok(c) => c.to_ascii_lowercase() as u32,
-            Err(e) => e.unpaired_surrogate() as u32,
-        }).eq(s.chars().map(|c| c.to_ascii_lowercase() as u32))
-    }
-
-    #[inline]
     pub fn to_string(&self) -> String {
         String::from_utf16(self.as_slice()).unwrap()
     }
 
     #[inline]
     pub fn is_static(&self) -> bool {
         unsafe {
             (*self.as_ptr()).mIsStatic() != 0
@@ -149,17 +140,17 @@ impl fmt::Debug for WeakAtom {
     fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
         write!(w, "Gecko WeakAtom({:p}, {})", self, self)
     }
 }
 
 impl fmt::Display for WeakAtom {
     fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
         for c in self.chars() {
-            try!(write!(w, "{}", c.unwrap_or(char::REPLACEMENT_CHARACTER)))
+            try!(w.write_char(c.unwrap_or(char::REPLACEMENT_CHARACTER)))
         }
         Ok(())
     }
 }
 
 impl Atom {
     pub unsafe fn with<F>(ptr: *mut nsIAtom, callback: &mut F) where F: FnMut(&Atom) {
         let atom = Atom(WeakAtom::new(ptr));
--- a/servo/components/style/keyframes.rs
+++ b/servo/components/style/keyframes.rs
@@ -1,17 +1,17 @@
 /* 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::{AtRuleParser, Parser, QualifiedRuleParser, RuleListParser};
 use cssparser::{DeclarationListParser, DeclarationParser, parse_one_rule};
 use parking_lot::RwLock;
 use parser::{ParserContext, ParserContextExtraData, log_css_error};
-use properties::{Importance, PropertyDeclaration, PropertyDeclarationBlock};
+use properties::{Importance, PropertyDeclaration, PropertyDeclarationBlock, PropertyId};
 use properties::PropertyDeclarationParseResult;
 use properties::animated_properties::TransitionProperty;
 use std::fmt;
 use std::sync::Arc;
 use style_traits::ToCss;
 use stylesheets::{MemoryHoleReporter, Stylesheet};
 
 /// A number from 0 to 1, indicating the percentage of the animation where
@@ -338,16 +338,17 @@ impl<'a, 'b> AtRuleParser for KeyframeDe
     type Prelude = ();
     type AtRule = Vec<PropertyDeclaration>;
 }
 
 impl<'a, 'b> DeclarationParser for KeyframeDeclarationParser<'a, 'b> {
     type Declaration = Vec<PropertyDeclaration>;
 
     fn parse_value(&mut self, name: &str, input: &mut Parser) -> Result<Vec<PropertyDeclaration>, ()> {
+        let id = try!(PropertyId::parse(name.into()));
         let mut results = Vec::new();
-        match PropertyDeclaration::parse(name, self.context, input, &mut results, true) {
+        match PropertyDeclaration::parse(id, self.context, input, &mut results, true) {
             PropertyDeclarationParseResult::ValidOrIgnoredDeclaration => {}
             _ => return Err(())
         }
         Ok(results)
     }
 }
--- a/servo/components/style/lib.rs
+++ b/servo/components/style/lib.rs
@@ -33,17 +33,17 @@
 // gecko atom!() macro work. When Rust 1.14 is released [1], we can uncomment
 // the commented-out attributes in regen_atoms.py and go back to denying unsafe
 // code by default.
 //
 // [1] https://github.com/rust-lang/rust/issues/15701#issuecomment-251900615
 //#![deny(unsafe_code)]
 #![allow(unused_unsafe)]
 
-#![recursion_limit = "500"]  // For match_ignore_ascii_case in PropertyDeclaration::parse
+#![recursion_limit = "500"]  // For define_css_keyword_enum! in -moz-appearance
 
 extern crate app_units;
 #[allow(unused_extern_crates)]
 #[macro_use]
 extern crate bitflags;
 #[macro_use] #[no_link]
 extern crate cfg_if;
 extern crate core;
@@ -66,16 +66,17 @@ extern crate log;
 extern crate matches;
 #[cfg(feature = "gecko")] extern crate nsstring_vendor as nsstring;
 extern crate num_integer;
 extern crate num_traits;
 #[cfg(feature = "gecko")] extern crate num_cpus;
 extern crate ordered_float;
 extern crate owning_ref;
 extern crate parking_lot;
+extern crate phf;
 extern crate quickersort;
 extern crate rayon;
 extern crate rustc_serialize;
 extern crate selectors;
 #[cfg(feature = "servo")]
 extern crate serde;
 #[cfg(feature = "servo")] #[macro_use] extern crate serde_derive;
 #[cfg(feature = "servo")] #[macro_use] extern crate servo_atoms;
--- a/servo/components/style/properties/build.py
+++ b/servo/components/style/properties/build.py
@@ -28,16 +28,17 @@ def main():
     if product not in ["servo", "gecko"] or output not in ["style-crate", "geckolib", "html"]:
         abort(usage)
 
     properties = data.PropertiesData(product=product, testing=testing)
     template = os.path.join(BASE, "properties.mako.rs")
     rust = render(template, product=product, data=properties, __file__=template)
     if output == "style-crate":
         write(os.environ["OUT_DIR"], "properties.rs", rust)
+        write(os.environ["OUT_DIR"], "static_ids.txt", static_ids(properties))
         if product == "gecko":
             template = os.path.join(BASE, "gecko.mako.rs")
             rust = render(template, data=properties)
             write(os.environ["OUT_DIR"], "gecko_properties.rs", rust)
     elif output == "html":
         write_html(properties)
 
 
@@ -64,16 +65,25 @@ def render(filename, **context):
 
 
 def write(directory, filename, content):
     if not os.path.exists(directory):
         os.makedirs(directory)
     open(os.path.join(directory, filename), "wb").write(content)
 
 
+def static_ids(properties):
+    return '\n'.join(
+        "%s\tStaticId::%s(%sId::%s)" % (p.name, kind, kind, p.camel_case)
+        for kind, props in [("Longhand", properties.longhands),
+                            ("Shorthand", properties.shorthands)]
+        for p in props
+    )
+
+
 def write_html(properties):
     properties = dict(
         (p.name, {
             "flag": p.experimental,
             "shorthand": hasattr(p, "sub_properties")
         })
         for p in properties.longhands + properties.shorthands
     )
--- a/servo/components/style/properties/declaration_block.rs
+++ b/servo/components/style/properties/declaration_block.rs
@@ -2,17 +2,16 @@
  * 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::{DeclarationListParser, parse_important};
 use cssparser::{Parser, AtRuleParser, DeclarationParser, Delimiter};
 use error_reporting::ParseErrorReporter;
 use parser::{ParserContext, ParserContextExtraData, log_css_error};
 use servo_url::ServoUrl;
-use std::ascii::AsciiExt;
 use std::boxed::Box as StdBox;
 use std::fmt;
 use style_traits::ToCss;
 use stylesheets::Origin;
 use super::*;
 
 
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
@@ -60,100 +59,104 @@ impl PropertyDeclarationBlock {
     ///
     /// This is based on the `important_count` counter,
     /// which should be maintained whenever `declarations` is changed.
     // FIXME: make fields private and maintain it here in methods?
     pub fn any_normal(&self) -> bool {
         self.declarations.len() > self.important_count as usize
     }
 
-    pub fn get(&self, property_name: &str) -> Option< &(PropertyDeclaration, Importance)> {
-        self.declarations.iter().find(|&&(ref decl, _)| decl.matches(property_name))
+    pub fn get(&self, property: PropertyDeclarationId) -> Option< &(PropertyDeclaration, Importance)> {
+        self.declarations.iter().find(|&&(ref decl, _)| decl.id() == property)
     }
 
     /// Find the value of the given property in this block and serialize it
     ///
     /// https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-getpropertyvalue
-    pub fn property_value_to_css<W>(&self, property_name: &str, dest: &mut W) -> fmt::Result
+    pub fn property_value_to_css<W>(&self, property: &PropertyId, dest: &mut W) -> fmt::Result
     where W: fmt::Write {
-        // Step 1
-        let property = property_name.to_ascii_lowercase();
+        // Step 1: done when parsing a string to PropertyId
 
         // Step 2
-        if let Some(shorthand) = Shorthand::from_name(&property) {
-            // Step 2.1
-            let mut list = Vec::new();
-            let mut important_count = 0;
+        match property.as_shorthand() {
+            Ok(shorthand) => {
+                // Step 2.1
+                let mut list = Vec::new();
+                let mut important_count = 0;
 
-            // Step 2.2
-            for longhand in shorthand.longhands() {
-                // Step 2.2.1
-                let declaration = self.get(longhand);
+                // Step 2.2
+                for &longhand in shorthand.longhands() {
+                    // Step 2.2.1
+                    let declaration = self.get(PropertyDeclarationId::Longhand(longhand));
 
-                // Step 2.2.2 & 2.2.3
-                match declaration {
-                    Some(&(ref declaration, importance)) => {
-                        list.push(declaration);
-                        if importance.important() {
-                            important_count += 1;
-                        }
-                    },
-                    None => return Ok(()),
+                    // Step 2.2.2 & 2.2.3
+                    match declaration {
+                        Some(&(ref declaration, importance)) => {
+                            list.push(declaration);
+                            if importance.important() {
+                                important_count += 1;
+                            }
+                        },
+                        None => return Ok(()),
+                    }
+                }
+
+                // Step 3.3.2.4
+                // If there is one or more longhand with important, and one or more
+                // without important, we don't serialize it as a shorthand.
+                if important_count > 0 && important_count != list.len() {
+                    return Ok(());
+                }
+
+                // Step 2.3
+                // We don't print !important when serializing individual properties,
+                // so we treat this as a normal-importance property
+                let importance = Importance::Normal;
+                let appendable_value = shorthand.get_shorthand_appendable_value(list).unwrap();
+                append_declaration_value(dest, appendable_value, importance)
+            }
+            Err(longhand_or_custom) => {
+                if let Some(&(ref value, _importance)) = self.get(longhand_or_custom) {
+                    // Step 3
+                    value.to_css(dest)
+                } else {
+                    // Step 4
+                    Ok(())
                 }
             }
-
-            // Step 3.3.2.4
-            // If there is one or more longhand with important, and one or more
-            // without important, we don't serialize it as a shorthand.
-            if important_count > 0 && important_count != list.len() {
-                return Ok(());
-            }
-
-            // Step 2.3
-            // We don't print !important when serializing individual properties,
-            // so we treat this as a normal-importance property
-            let importance = Importance::Normal;
-            let appendable_value = shorthand.get_shorthand_appendable_value(list).unwrap();
-            return append_declaration_value(dest, appendable_value, importance)
-        }
-
-        if let Some(&(ref value, _importance)) = self.get(property_name) {
-            // Step 3
-            value.to_css(dest)
-        } else {
-            // Step 4
-            Ok(())
         }
     }
 
     /// https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-getpropertypriority
-    pub fn property_priority(&self, property_name: &str) -> Importance {
-        // Step 1
-        let property = property_name.to_ascii_lowercase();
+    pub fn property_priority(&self, property: &PropertyId) -> Importance {
+        // Step 1: done when parsing a string to PropertyId
 
         // Step 2
-        if let Some(shorthand) = Shorthand::from_name(&property) {
-            // Step 2.1 & 2.2 & 2.3
-            if shorthand.longhands().iter().all(|l| {
-                self.get(l).map_or(false, |&(_, importance)| importance.important())
-            }) {
-                Importance::Important
-            } else {
-                Importance::Normal
+        match property.as_shorthand() {
+            Ok(shorthand) => {
+                // Step 2.1 & 2.2 & 2.3
+                if shorthand.longhands().iter().all(|&l| {
+                    self.get(PropertyDeclarationId::Longhand(l))
+                        .map_or(false, |&(_, importance)| importance.important())
+                }) {
+                    Importance::Important
+                } else {
+                    Importance::Normal
+                }
             }
-        } else {
-            // Step 3
-            self.get(&property).map_or(Importance::Normal, |&(_, importance)| importance)
+            Err(longhand_or_custom) => {
+                // Step 3
+                self.get(longhand_or_custom).map_or(Importance::Normal, |&(_, importance)| importance)
+            }
         }
     }
 
-    pub fn set_parsed_declaration(&mut self, declaration: PropertyDeclaration,
-                                  importance: Importance) {
+    pub fn set_parsed_declaration(&mut self, declaration: PropertyDeclaration, importance: Importance) {
         for slot in &mut *self.declarations {
-            if slot.0.name() == declaration.name() {
+            if slot.0.id() == declaration.id() {
                 match (slot.1, importance) {
                     (Importance::Normal, Importance::Important) => {
                         self.important_count += 1;
                     }
                     (Importance::Important, Importance::Normal) => {
                         self.important_count -= 1;
                     }
                     _ => {}
@@ -164,72 +167,62 @@ impl PropertyDeclarationBlock {
         }
 
         self.declarations.push((declaration, importance));
         if importance.important() {
             self.important_count += 1;
         }
     }
 
-    pub fn set_importance(&mut self, property_names: &[&str], new_importance: Importance) {
+    pub fn set_importance(&mut self, property: &PropertyId, new_importance: Importance) {
         for &mut (ref declaration, ref mut importance) in &mut self.declarations {
-            if property_names.iter().any(|p| declaration.matches(p)) {
+            if declaration.id().is_or_is_longhand_of(property) {
                 match (*importance, new_importance) {
                     (Importance::Normal, Importance::Important) => {
                         self.important_count += 1;
                     }
                     (Importance::Important, Importance::Normal) => {
                         self.important_count -= 1;
                     }
                     _ => {}
                 }
                 *importance = new_importance;
             }
         }
     }
 
     /// https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-removeproperty
-    pub fn remove_property(&mut self, property_name: &str) {
-        // Step 2
-        let property = property_name.to_ascii_lowercase();
-
-        match Shorthand::from_name(&property) {
-            // Step 4
-            Some(shorthand) => self.remove_longhands(shorthand.longhands()),
-            // Step 5
-            None => self.remove_longhands(&[&*property]),
-        }
-    }
-
-    fn remove_longhands(&mut self, names: &[&str]) {
+    pub fn remove_property(&mut self, property: &PropertyId) {
         let important_count = &mut self.important_count;
         self.declarations.retain(|&(ref declaration, importance)| {
-            let retain = !names.iter().any(|n| declaration.matches(n));
-            if !retain && importance.important() {
+            let remove = declaration.id().is_or_is_longhand_of(property);
+            if remove && importance.important() {
                 *important_count -= 1
             }
-            retain
+            !remove
         })
     }
 
     /// Take a declaration block known to contain a single property and serialize it.
-    pub fn single_value_to_css<W>(&self, property_name: &str, dest: &mut W) -> fmt::Result
+    pub fn single_value_to_css<W>(&self, property: &PropertyId, dest: &mut W) -> fmt::Result
     where W: fmt::Write {
-        match self.declarations.len() {
-            0 => Err(fmt::Error),
-            1 if self.declarations[0].0.name().eq_str_ignore_ascii_case(property_name) => {
-                self.declarations[0].0.to_css(dest)
+        match property.as_shorthand() {
+            Err(_longhand_or_custom) => {
+                if self.declarations.len() == 1 {
+                    self.declarations[0].0.to_css(dest)
+                } else {
+                    Err(fmt::Error)
+                }
             }
-            _ => {
+            Ok(shorthand) => {
                 // we use this function because a closure won't be `Clone`
                 fn get_declaration(dec: &(PropertyDeclaration, Importance))
                     -> &PropertyDeclaration {
                     &dec.0
                 }
-                let shorthand = try!(Shorthand::from_name(property_name).ok_or(fmt::Error));
                 if !self.declarations.iter().all(|decl| decl.0.shorthands().contains(&shorthand)) {
                     return Err(fmt::Error)
                 }
                 let iter = self.declarations.iter().map(get_declaration as fn(_) -> _);
                 match shorthand.get_shorthand_appendable_value(iter) {
                     Some(AppendableValue::Css(css)) => dest.write_str(css),
                     Some(AppendableValue::DeclarationsForShorthand(_, decls)) => {
                         shorthand.longhands_to_css(decls, dest)
@@ -249,42 +242,41 @@ impl ToCss for PropertyDeclarationBlock 
         // Step 1 -> dest = result list
 
         // Step 2
         let mut already_serialized = Vec::new();
 
         // Step 3
         for &(ref declaration, importance) in &*self.declarations {
             // Step 3.1
-            let property = declaration.name();
+            let property = declaration.id();
 
             // Step 3.2
             if already_serialized.contains(&property) {
                 continue;
             }
 
             // Step 3.3
             let shorthands = declaration.shorthands();
             if !shorthands.is_empty() {
                 // Step 3.3.1
                 let mut longhands = self.declarations.iter()
-                    .filter(|d| !already_serialized.contains(&d.0.name()))
+                    .filter(|d| !already_serialized.contains(&d.0.id()))
                     .collect::<Vec<_>>();
 
                 // Step 3.3.2
-                for shorthand in shorthands {
+                for &shorthand in shorthands {
                     let properties = shorthand.longhands();
 
                     // Substep 2 & 3
                     let mut current_longhands = Vec::new();
                     let mut important_count = 0;
 
                     for &&(ref longhand, longhand_importance) in longhands.iter() {
-                        let longhand_name = longhand.name();
-                        if properties.iter().any(|p| &longhand_name == *p) {
+                        if longhand.id().is_longhand_of(shorthand) {
                             current_longhands.push(longhand);
                             if longhand_importance.important() {
                                 important_count += 1;
                             }
                         }
                     }
 
                     // Substep 1
@@ -320,17 +312,17 @@ impl ToCss for PropertyDeclarationBlock 
 
                     // Substep 6
                     if !was_serialized {
                         continue;
                     }
 
                     for current_longhand in current_longhands {
                         // Substep 9
-                        already_serialized.push(current_longhand.name());
+                        already_serialized.push(current_longhand.id());
                         let index_to_remove = longhands.iter().position(|l| l.0 == *current_longhand);
                         if let Some(index) = index_to_remove {
                             // Substep 10
                             longhands.remove(index);
                         }
                      }
                  }
             }
@@ -343,36 +335,36 @@ impl ToCss for PropertyDeclarationBlock 
             use std::iter::Cloned;
             use std::slice;
 
             // Steps 3.3.5, 3.3.6 & 3.3.7
             // Need to specify an iterator type here even though it’s unused to work around
             // "error: unable to infer enough type information about `_`;
             //  type annotations or generic parameter binding required [E0282]"
             // Use the same type as earlier call to reuse generated code.
-            try!(append_serialization::<W, Cloned<slice::Iter< &PropertyDeclaration>>>(
+            try!(append_serialization::<W, Cloned<slice::Iter< &PropertyDeclaration>>, _>(
                 dest,
-                &property.to_string(),
+                &property,
                 AppendableValue::Declaration(declaration),
                 importance,
                 &mut is_first_serialization));
 
             // Step 3.3.8
             already_serialized.push(property);
         }
 
         // Step 4
         Ok(())
     }
 }
 
 pub enum AppendableValue<'a, I>
 where I: Iterator<Item=&'a PropertyDeclaration> {
     Declaration(&'a PropertyDeclaration),
-    DeclarationsForShorthand(Shorthand, I),
+    DeclarationsForShorthand(ShorthandId, I),
     Css(&'a str)
 }
 
 fn handle_first_serialization<W>(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, " "));
     } else {
@@ -402,32 +394,35 @@ pub fn append_declaration_value<'a, W, I
 
   if importance.important() {
       try!(write!(dest, " !important"));
   }
 
   Ok(())
 }
 
-pub fn append_serialization<'a, W, I>(dest: &mut W,
-                                  property_name: &str,
+pub fn append_serialization<'a, W, I, N>(dest: &mut W,
+                                  property_name: &N,
                                   appendable_value: AppendableValue<'a, I>,
                                   importance: Importance,
                                   is_first_serialization: &mut bool)
                                   -> fmt::Result
-                                  where W: fmt::Write, I: Iterator<Item=&'a PropertyDeclaration> {
+                                  where W: fmt::Write,
+                                        I: Iterator<Item=&'a PropertyDeclaration>,
+                                        N: ToCss {
     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, importance);
     }
 
-    try!(write!(dest, "{}:", property_name));
+    try!(property_name.to_css(dest));
+    try!(dest.write_char(':'));
 
     // for normal parsed values, add a space between key: and value
     match &appendable_value {
         &AppendableValue::Css(_) => {
             try!(write!(dest, " "))
         },
         &AppendableValue::Declaration(decl) => {
             if !decl.value_is_unparsed() {
@@ -446,25 +441,25 @@ pub fn parse_style_attribute(input: &str
                              base_url: &ServoUrl,
                              error_reporter: StdBox<ParseErrorReporter + Send>,
                              extra_data: ParserContextExtraData)
                              -> PropertyDeclarationBlock {
     let context = ParserContext::new_with_extra_data(Origin::Author, base_url, error_reporter, extra_data);
     parse_property_declaration_list(&context, &mut Parser::new(input))
 }
 
-pub fn parse_one_declaration(name: &str,
+pub fn parse_one_declaration(id: PropertyId,
                              input: &str,
                              base_url: &ServoUrl,
                              error_reporter: StdBox<ParseErrorReporter + Send>,
                              extra_data: ParserContextExtraData)
                              -> Result<Vec<PropertyDeclaration>, ()> {
     let context = ParserContext::new_with_extra_data(Origin::Author, base_url, error_reporter, extra_data);
     let mut results = vec![];
-    match PropertyDeclaration::parse(name, &context, &mut Parser::new(input), &mut results, false) {
+    match PropertyDeclaration::parse(id, &context, &mut Parser::new(input), &mut results, false) {
         PropertyDeclarationParseResult::ValidOrIgnoredDeclaration => Ok(results),
         _ => Err(())
     }
 }
 
 struct PropertyDeclarationParser<'a, 'b: 'a> {
     context: &'a ParserContext<'b>,
 }
@@ -477,19 +472,20 @@ impl<'a, 'b> AtRuleParser for PropertyDe
 }
 
 
 impl<'a, 'b> DeclarationParser for PropertyDeclarationParser<'a, 'b> {
     type Declaration = (Vec<PropertyDeclaration>, Importance);
 
     fn parse_value(&mut self, name: &str, input: &mut Parser)
                    -> Result<(Vec<PropertyDeclaration>, Importance), ()> {
+        let id = try!(PropertyId::parse(name.into()));
         let mut results = vec![];
         try!(input.parse_until_before(Delimiter::Bang, |input| {
-            match PropertyDeclaration::parse(name, self.context, input, &mut results, false) {
+            match PropertyDeclaration::parse(id, self.context, input, &mut results, false) {
                 PropertyDeclarationParseResult::ValidOrIgnoredDeclaration => Ok(()),
                 _ => Err(())
             }
         }));
         let importance = match input.try(parse_important) {
             Ok(()) => Importance::Important,
             Err(()) => Importance::Normal,
         };
--- a/servo/components/style/properties/helpers.mako.rs
+++ b/servo/components/style/properties/helpers.mako.rs
@@ -63,17 +63,17 @@
                     let &SpecifiedValue(ref vec) = self;
                     vec.iter().any(|ref x| x.has_viewport_percentage())
                 }
             }
 
             pub mod single_value {
                 use cssparser::Parser;
                 use parser::{Parse, ParserContext, ParserContextExtraData};
-                use properties::{CSSWideKeyword, DeclaredValue, Shorthand};
+                use properties::{CSSWideKeyword, DeclaredValue, ShorthandId};
                 use values::computed::{Context, ToComputedValue};
                 use values::{computed, specified};
                 ${caller.body()}
             }
             pub mod computed_value {
                 pub use super::single_value::computed_value as single_value;
                 pub use self::single_value::T as SingleComputedValue;
                 #[derive(Debug, Clone, PartialEq)]
@@ -177,17 +177,17 @@
         if property is None:
             return ""
     %>
     pub mod ${property.ident} {
         #![allow(unused_imports)]
         % if not property.derived_from:
             use cssparser::Parser;
             use parser::{Parse, ParserContext, ParserContextExtraData};
-            use properties::{CSSWideKeyword, DeclaredValue, Shorthand};
+            use properties::{CSSWideKeyword, DeclaredValue, ShorthandId};
         % endif
         use values::{Auto, Either, None_, Normal};
         use cascade_info::CascadeInfo;
         use error_reporting::ParseErrorReporter;
         use properties::longhands;
         use properties::property_bit_field::PropertyBitField;
         use properties::{ComputedValues, PropertyDeclaration};
         use properties::style_structs;
@@ -375,17 +375,17 @@
     shorthand = data.declare_shorthand(name, sub_properties.split(), experimental=experimental,
                                        **kwargs)
 %>
     % if shorthand:
     pub mod ${shorthand.ident} {
         #[allow(unused_imports)]
         use cssparser::Parser;
         use parser::ParserContext;
-        use properties::{longhands, PropertyDeclaration, DeclaredValue, Shorthand};
+        use properties::{longhands, PropertyDeclaration, DeclaredValue, ShorthandId};
         use std::fmt;
         use style_traits::ToCss;
 
         pub struct Longhands {
             % for sub_property in shorthand.sub_properties:
                 pub ${sub_property.ident}:
                     Option<longhands::${sub_property.ident}::SpecifiedValue>,
             % endfor
@@ -495,17 +495,17 @@
                 let (first_token_type, css) = try!(
                     ::custom_properties::parse_non_custom_with_var(input));
                 % for sub_property in shorthand.sub_properties:
                     declarations.push(PropertyDeclaration::${sub_property.camel_case}(
                         DeclaredValue::WithVariables {
                             css: css.clone().into_owned(),
                             first_token_type: first_token_type,
                             base_url: context.base_url.clone(),
-                            from_shorthand: Some(Shorthand::${shorthand.camel_case}),
+                            from_shorthand: Some(ShorthandId::${shorthand.camel_case}),
                         }
                     ));
                 % endfor
                 Ok(())
             } else {
                 Err(())
             }
         }
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -5,46 +5,52 @@
 // This file is a Mako template: http://www.makotemplates.org/
 
 // 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> or Vec< &Foo> achieves the desired result of Vec<&Foo>.
 
 <%namespace name="helpers" file="/helpers.mako.rs" />
 
-use std::ascii::AsciiExt;
+use std::borrow::Cow;
 use std::boxed::Box as StdBox;
 use std::collections::HashSet;
 use std::fmt::{self, Write};
 use std::sync::Arc;
 
-use Atom;
 use app_units::Au;
 #[cfg(feature = "servo")] use cssparser::{Color as CSSParserColor, RGBA};
 use cssparser::{Parser, TokenSerializationType};
 use error_reporting::ParseErrorReporter;
 #[cfg(feature = "servo")] use euclid::side_offsets::SideOffsets2D;
 use euclid::size::Size2D;
 use computed_values;
 use font_metrics::FontMetricsProvider;
+#[cfg(feature = "gecko")] use gecko_bindings::structs::nsCSSPropertyID;
 #[cfg(feature = "servo")] use logical_geometry::{LogicalMargin, PhysicalSide};
 use logical_geometry::WritingMode;
 use parser::{Parse, ParserContext, ParserContextExtraData};
 use servo_url::ServoUrl;
 use style_traits::ToCss;
 use stylesheets::Origin;
 #[cfg(feature = "servo")] use values::Either;
 use values::{HasViewportPercentage, computed};
 use cascade_info::CascadeInfo;
 use rule_tree::StrongRuleNode;
 #[cfg(feature = "servo")] use values::specified::BorderStyle;
 
 use self::property_bit_field::PropertyBitField;
 pub use self::declaration_block::*;
 
+#[cfg(feature = "gecko")]
+#[macro_export]
+macro_rules! property_name {
+    ($s: tt) => { atom!($s) }
+}
+
 <%!
     from data import Method, Keyword, to_rust_ident
     import os.path
 %>
 
 #[path="${repr(os.path.join(os.path.dirname(__file__), 'declaration_block.rs'))[1:-1]}"]
 pub mod declaration_block;
 
@@ -237,17 +243,17 @@ mod property_bit_field {
         }
 
         #[allow(non_snake_case)]
         #[inline(never)]
         fn substitute_variables_${property.ident}_slow<F>(
                 css: &String,
                 first_token_type: TokenSerializationType,
                 base_url: &ServoUrl,
-                from_shorthand: Option<Shorthand>,
+                from_shorthand: Option<ShorthandId>,
                 custom_properties: &Option<Arc<::custom_properties::ComputedValuesMap>>,
                 f: F,
                 error_reporter: &mut StdBox<ParseErrorReporter + Send>,
                 extra_data: ParserContextExtraData)
                 where F: FnOnce(&DeclaredValue<longhands::${property.ident}::SpecifiedValue>) {
             f(&
                 ::custom_properties::substitute(css, first_token_type, custom_properties)
                 .and_then(|css| {
@@ -260,17 +266,17 @@ mod property_bit_field {
                         extra_data);
                     Parser::new(&css).parse_entirely(|input| {
                         match from_shorthand {
                             None => {
                                 longhands::${property.ident}::parse_specified(&context, input)
                             }
                             % for shorthand in data.shorthands:
                                 % if property in shorthand.sub_properties:
-                                    Some(Shorthand::${shorthand.camel_case}) => {
+                                    Some(ShorthandId::${shorthand.camel_case}) => {
                                         shorthands::${shorthand.ident}::parse_value(&context, input)
                                         .map(|result| match result.${property.ident} {
                                             Some(value) => DeclaredValue::Value(value),
                                             None => DeclaredValue::Initial,
                                         })
                                     }
                                 % endif
                             % endfor
@@ -373,60 +379,75 @@ impl Parse for CSSWideKeyword {
             "unset" => Ok(CSSWideKeyword::UnsetKeyword),
             _ => Err(())
         }
     }
 }
 
 #[derive(Clone, Copy, Eq, PartialEq, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-pub enum Shorthand {
+pub enum LonghandId {
+    % for i, property in enumerate(data.longhands):
+        ${property.camel_case} = ${i},
+    % endfor
+}
+
+impl LonghandId {
+    pub fn name(&self) -> &'static str {
+        match *self {
+            % for property in data.longhands:
+                LonghandId::${property.camel_case} => "${property.name}",
+            % endfor
+        }
+    }
+}
+
+#[derive(Clone, Copy, Eq, PartialEq, Debug)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+pub enum ShorthandId {
     % for property in data.shorthands:
         ${property.camel_case},
     % endfor
 }
 
-impl Shorthand {
-    pub fn from_name(name: &str) -> Option<Shorthand> {
-        match_ignore_ascii_case! { name,
-            % for property in data.shorthands:
-                "${property.name}" => Some(Shorthand::${property.camel_case}),
-            % endfor
-            _ => None
-        }
+impl ToCss for ShorthandId {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        dest.write_str(self.name())
     }
+}
 
+impl ShorthandId {
     pub fn name(&self) -> &'static str {
         match *self {
             % for property in data.shorthands:
-                Shorthand::${property.camel_case} => "${property.name}",
+                ShorthandId::${property.camel_case} => "${property.name}",
             % endfor
         }
     }
 
-    pub fn longhands(&self) -> &'static [&'static str] {
+    pub fn longhands(&self) -> &'static [LonghandId] {
         % for property in data.shorthands:
-            static ${property.ident.upper()}: &'static [&'static str] = &[
+            static ${property.ident.upper()}: &'static [LonghandId] = &[
                 % for sub in property.sub_properties:
-                    "${sub.name}",
+                    LonghandId::${sub.camel_case},
                 % endfor
             ];
         % endfor
         match *self {
             % for property in data.shorthands:
-                Shorthand::${property.camel_case} => ${property.ident.upper()},
+                ShorthandId::${property.camel_case} => ${property.ident.upper()},
             % endfor
         }
     }
 
     pub fn longhands_to_css<'a, W, I>(&self, declarations: I, dest: &mut W) -> fmt::Result
         where W: fmt::Write, I: Iterator<Item=&'a PropertyDeclaration> {
         match *self {
             % for property in data.shorthands:
-                Shorthand::${property.camel_case} => {
+                ShorthandId::${property.camel_case} => {
                     match shorthands::${property.ident}::LonghandsToSerialize::from_iter(declarations) {
                         Ok(longhands) => longhands.to_css(dest),
                         Err(_) => Err(fmt::Error)
                     }
                 },
             % endfor
         }
     }
@@ -438,21 +459,19 @@ impl Shorthand {
                                                    declarations: I,
                                                    is_first_serialization: &mut bool,
                                                    importance: Importance)
                                                    -> Result<bool, fmt::Error>
     where W: Write, I: IntoIterator<Item=&'a PropertyDeclaration>, I::IntoIter: Clone {
         match self.get_shorthand_appendable_value(declarations) {
             None => Ok(false),
             Some(appendable_value) => {
-                let property_name = self.name();
-
                 append_serialization(
                     dest,
-                    property_name,
+                    &self,
                     appendable_value,
                     importance,
                     is_first_serialization
                 ).and_then(|_| Ok(true))
             }
         }
     }
 
@@ -491,17 +510,17 @@ impl Shorthand {
 #[derive(Clone, PartialEq, Eq, Debug)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum DeclaredValue<T> {
     Value(T),
     WithVariables {
         css: String,
         first_token_type: TokenSerializationType,
         base_url: ServoUrl,
-        from_shorthand: Option<Shorthand>,
+        from_shorthand: Option<ShorthandId>,
     },
     Initial,
     Inherit,
     // There is no Unset variant here.
     // The 'unset' keyword is represented as either Initial or Inherit,
     // depending on whether the property is inherited.
 }
 
@@ -530,16 +549,143 @@ impl<T: ToCss> ToCss for DeclaredValue<T
             DeclaredValue::Initial => dest.write_str("initial"),
             DeclaredValue::Inherit => dest.write_str("inherit"),
         }
     }
 }
 
 #[derive(PartialEq, Clone)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+pub enum PropertyDeclarationId<'a> {
+    Longhand(LonghandId),
+    Custom(&'a ::custom_properties::Name),
+}
+
+impl<'a> ToCss for PropertyDeclarationId<'a> {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        match *self {
+            PropertyDeclarationId::Longhand(id) => dest.write_str(id.name()),
+            PropertyDeclarationId::Custom(name) => write!(dest, "--{}", name),
+        }
+    }
+}
+
+impl<'a> PropertyDeclarationId<'a> {
+    pub fn is_or_is_longhand_of(&self, other: &PropertyId) -> bool {
+        match *self {
+            PropertyDeclarationId::Longhand(id) => {
+                match *other {
+                    PropertyId::Longhand(other_id) => id == other_id,
+                    PropertyId::Shorthand(shorthand) => shorthand.longhands().contains(&id),
+                    PropertyId::Custom(_) => false,
+                }
+            }
+            PropertyDeclarationId::Custom(name) => {
+                matches!(*other, PropertyId::Custom(ref other_name) if name == other_name)
+            }
+        }
+    }
+
+    pub fn is_longhand_of(&self, shorthand: ShorthandId) -> bool {
+        match *self {
+            PropertyDeclarationId::Longhand(ref id) => shorthand.longhands().contains(id),
+            _ => false,
+        }
+    }
+}
+
+#[derive(Eq, PartialEq, Clone)]
+pub enum PropertyId {
+    Longhand(LonghandId),
+    Shorthand(ShorthandId),
+    Custom(::custom_properties::Name),
+}
+
+impl fmt::Debug for PropertyId {
+    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        self.to_css(formatter)
+    }
+}
+
+impl ToCss for PropertyId {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        match *self {
+            PropertyId::Longhand(id) => dest.write_str(id.name()),
+            PropertyId::Shorthand(id) => dest.write_str(id.name()),
+            PropertyId::Custom(ref name) => write!(dest, "--{}", name),
+        }
+    }
+}
+
+// FIXME(https://github.com/rust-lang/rust/issues/33156): remove this enum and use PropertyId
+// when stable Rust allows destructors in statics.
+enum StaticId {
+    Longhand(LonghandId),
+    Shorthand(ShorthandId),
+}
+include!(concat!(env!("OUT_DIR"), "/static_ids.rs"));
+
+impl PropertyId {
+    /// Returns Err(()) for unknown non-custom properties
+    pub fn parse(s: Cow<str>) -> Result<Self, ()> {
+        if let Ok(name) = ::custom_properties::parse_name(&s) {
+            return Ok(PropertyId::Custom(::custom_properties::Name::from(name)))
+        }
+
+        let lower_case = ::str::cow_into_ascii_lowercase(s);
+        match STATIC_IDS.get(&*lower_case) {
+            Some(&StaticId::Longhand(id)) => Ok(PropertyId::Longhand(id)),
+            Some(&StaticId::Shorthand(id)) => Ok(PropertyId::Shorthand(id)),
+            None => Err(()),
+        }
+    }
+
+    #[cfg(feature = "gecko")]
+    #[allow(non_upper_case_globals)]
+    pub fn from_nscsspropertyid(id: nsCSSPropertyID) -> Result<Self, ()> {
+        use gecko_bindings::structs::*;
+        <%
+            def to_nscsspropertyid(ident):
+                if ident == "word_wrap":
+                    return "nsCSSPropertyID_eCSSPropertyAlias_WordWrap"
+
+                if ident == "float":
+                    ident = "float_"
+                elif "outline_radius" in ident:
+                    ident = ident.replace("right", "Right").replace("left", "Left")
+                elif ident.startswith("_moz_"):
+                    ident = ident[len("_moz_"):]
+                return "nsCSSPropertyID::eCSSProperty_" + ident
+        %>
+        match id {
+            % for property in data.longhands:
+                ${to_nscsspropertyid(property.ident)} => {
+                    Ok(PropertyId::Longhand(LonghandId::${property.camel_case}))
+                }
+            % endfor
+            % for property in data.shorthands:
+                ${to_nscsspropertyid(property.ident)} => {
+                    Ok(PropertyId::Shorthand(ShorthandId::${property.camel_case}))
+                }
+            % endfor
+            _ => Err(())
+        }
+    }
+
+    pub fn as_shorthand(&self) -> Result<ShorthandId, PropertyDeclarationId> {
+        match *self {
+            PropertyId::Shorthand(id) => Ok(id),
+            PropertyId::Longhand(id) => Err(PropertyDeclarationId::Longhand(id)),
+            PropertyId::Custom(ref name) => Err(PropertyDeclarationId::Custom(name)),
+        }
+    }
+}
+
+#[derive(PartialEq, Clone)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum PropertyDeclaration {
     % for property in data.longhands:
         ${property.camel_case}(DeclaredValue<longhands::${property.ident}::SpecifiedValue>),
     % endfor
     Custom(::custom_properties::Name, DeclaredValue<::custom_properties::SpecifiedValue>),
 }
 
 impl HasViewportPercentage for PropertyDeclaration {
@@ -561,61 +707,20 @@ impl HasViewportPercentage for PropertyD
 pub enum PropertyDeclarationParseResult {
     UnknownProperty,
     ExperimentalProperty,
     InvalidValue,
     AnimationPropertyInKeyframeBlock,
     ValidOrIgnoredDeclaration,
 }
 
-#[derive(Eq, PartialEq, Clone)]
-pub enum PropertyDeclarationName {
-    Longhand(&'static str),
-    Custom(::custom_properties::Name),
-    Internal
-}
-
-impl PropertyDeclarationName {
-    pub fn eq_str_ignore_ascii_case(&self, other: &str) -> bool {
-        match *self {
-            PropertyDeclarationName::Longhand(s) => s.eq_ignore_ascii_case(other),
-            PropertyDeclarationName::Custom(ref n) => n.eq_str_ignore_ascii_case(other),
-            PropertyDeclarationName::Internal => false
-        }
-    }
-}
-
-impl PartialEq<str> for PropertyDeclarationName {
-    fn eq(&self, other: &str) -> bool {
-        match *self {
-            PropertyDeclarationName::Longhand(n) => n == other,
-            PropertyDeclarationName::Custom(ref n) => {
-                n.with_str(|s| ::custom_properties::parse_name(other) == Ok(s))
-            }
-            PropertyDeclarationName::Internal => false,
-        }
-    }
-}
-
-impl fmt::Display for PropertyDeclarationName {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match *self {
-            PropertyDeclarationName::Longhand(n) => f.write_str(n),
-            PropertyDeclarationName::Custom(ref n) => {
-                try!(f.write_str("--"));
-                n.with_str(|s| f.write_str(s))
-            }
-            PropertyDeclarationName::Internal => Ok(()),
-        }
-    }
-}
-
 impl fmt::Debug for PropertyDeclaration {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        try!(write!(f, "{}: ", self.name()));
+        try!(self.id().to_css(f));
+        try!(f.write_str(": "));
         match *self {
             % for property in data.longhands:
                 % if not property.derived_from:
                     PropertyDeclaration::${property.camel_case}(ref value) => value.to_css(f),
                 % endif
             % endfor
             PropertyDeclaration::Custom(_, ref value) => value.to_css(f),
             % if any(property.derived_from for property in data.longhands):
@@ -638,55 +743,30 @@ impl ToCss for PropertyDeclaration {
             % if any(property.derived_from for property in data.longhands):
                 _ => Err(fmt::Error),
             % endif
         }
     }
 }
 
 impl PropertyDeclaration {
-    pub fn name(&self) -> PropertyDeclarationName {
+    pub fn id(&self) -> PropertyDeclarationId {
         match *self {
             % for property in data.longhands:
-                PropertyDeclaration::${property.camel_case}(..) =>
-                % if not property.derived_from:
-                    PropertyDeclarationName::Longhand("${property.name}"),
-                % else:
-                    PropertyDeclarationName::Internal,
-                % endif
+                PropertyDeclaration::${property.camel_case}(..) => {
+                    PropertyDeclarationId::Longhand(LonghandId::${property.camel_case})
+                }
             % endfor
             PropertyDeclaration::Custom(ref name, _) => {
-                PropertyDeclarationName::Custom(name.clone())
+                PropertyDeclarationId::Custom(name)
             }
         }
     }
 
-    #[inline]
-    pub fn discriminant_value(&self) -> usize {
-        match *self {
-            % for i, property in enumerate(data.longhands):
-                PropertyDeclaration::${property.camel_case}(..) => ${i},
-            % endfor
-            PropertyDeclaration::Custom(..) => ${len(data.longhands)}
-        }
-    }
-
-    pub fn value(&self) -> String {
-        let mut value = String::new();
-        if let Err(_) = self.to_css(&mut value) {
-            panic!("unsupported property declaration: {}", self.name());
-        }
-
-        value
-    }
-
-    /// If this is a pending-substitution value from the given shorthand, return that value
-    // Extra space here because < seems to be removed by Mako when immediately followed by &.
-    //                                                                          ↓
-    pub fn with_variables_from_shorthand(&self, shorthand: Shorthand) -> Option< &str> {
+    pub fn with_variables_from_shorthand(&self, shorthand: ShorthandId) -> Option< &str> {
         match *self {
             % for property in data.longhands:
                 PropertyDeclaration::${property.camel_case}(ref value) => match *value {
                     DeclaredValue::WithVariables { ref css, from_shorthand: Some(s), .. }
                     if s == shorthand => {
                         Some(&**css)
                     }
                     _ => None
@@ -722,59 +802,44 @@ impl PropertyDeclaration {
               PropertyDeclaration::${property.camel_case}(ref value) => {
                   matches!(*value, DeclaredValue::WithVariables { .. })
               },
           % endfor
           PropertyDeclaration::Custom(..) => true
       }
     }
 
-    pub fn matches(&self, name: &str) -> bool {
-        match *self {
-            % for property in data.longhands:
-                PropertyDeclaration::${property.camel_case}(..) =>
-                % if not property.derived_from:
-                    name.eq_ignore_ascii_case("${property.name}"),
-                % else:
-                    false,
-                % endif
-            % endfor
-            PropertyDeclaration::Custom(ref declaration_name, _) => {
-                declaration_name.with_str(|s| ::custom_properties::parse_name(name) == Ok(s))
-            }
-        }
-    }
-
     /// The `in_keyframe_block` parameter controls this:
     ///
     /// https://drafts.csswg.org/css-animations/#keyframes
     /// > The <declaration-list> inside of <keyframe-block> accepts any CSS property
     /// > except those defined in this specification,
     /// > but does accept the `animation-play-state` property and interprets it specially.
-    pub fn parse(name: &str, context: &ParserContext, input: &mut Parser,
+    pub fn parse(id: PropertyId, context: &ParserContext, input: &mut Parser,
                  result_list: &mut Vec<PropertyDeclaration>,
                  in_keyframe_block: bool)
                  -> PropertyDeclarationParseResult {
-        if let Ok(name) = ::custom_properties::parse_name(name) {
-            let value = match input.try(|i| CSSWideKeyword::parse(context, i)) {
-                Ok(CSSWideKeyword::UnsetKeyword) |  // Custom properties are alawys inherited
-                Ok(CSSWideKeyword::InheritKeyword) => DeclaredValue::Inherit,
-                Ok(CSSWideKeyword::InitialKeyword) => DeclaredValue::Initial,
-                Err(()) => match ::custom_properties::SpecifiedValue::parse(context, input) {
-                    Ok(value) => DeclaredValue::Value(value),
-                    Err(()) => return PropertyDeclarationParseResult::InvalidValue,
-                }
-            };
-            result_list.push(PropertyDeclaration::Custom(Atom::from(name), value));
-            return PropertyDeclarationParseResult::ValidOrIgnoredDeclaration;
-        }
-        match_ignore_ascii_case! { name,
+        match id {
+            PropertyId::Custom(name) => {
+                let value = match input.try(|i| CSSWideKeyword::parse(context, i)) {
+                    Ok(CSSWideKeyword::UnsetKeyword) |  // Custom properties are alawys inherited
+                    Ok(CSSWideKeyword::InheritKeyword) => DeclaredValue::Inherit,
+                    Ok(CSSWideKeyword::InitialKeyword) => DeclaredValue::Initial,
+                    Err(()) => match ::custom_properties::SpecifiedValue::parse(context, input) {
+                        Ok(value) => DeclaredValue::Value(value),
+                        Err(()) => return PropertyDeclarationParseResult::InvalidValue,
+                    }
+                };
+                result_list.push(PropertyDeclaration::Custom(name, value));
+                return PropertyDeclarationParseResult::ValidOrIgnoredDeclaration;
+            }
+            PropertyId::Longhand(id) => match id {
             % for property in data.longhands:
-                % if not property.derived_from:
-                    "${property.name}" => {
+                LonghandId::${property.camel_case} => {
+                    % if not property.derived_from:
                         % if not property.allowed_in_keyframe_block:
                             if in_keyframe_block {
                                 return PropertyDeclarationParseResult::AnimationPropertyInKeyframeBlock
                             }
                         % endif
                         % if property.internal:
                             if context.stylesheet_origin != Origin::UserAgent {
                                 return PropertyDeclarationParseResult::UnknownProperty
@@ -788,23 +853,25 @@ impl PropertyDeclaration {
                         % endif
                         match longhands::${property.ident}::parse_declared(context, input) {
                             Ok(value) => {
                                 result_list.push(PropertyDeclaration::${property.camel_case}(value));
                                 PropertyDeclarationParseResult::ValidOrIgnoredDeclaration
                             },
                             Err(()) => PropertyDeclarationParseResult::InvalidValue,
                         }
-                    },
-                % else:
-                    "${property.name}" => PropertyDeclarationParseResult::UnknownProperty,
-                % endif
+                    % else:
+                        PropertyDeclarationParseResult::UnknownProperty
+                    % endif
+                }
             % endfor
+            },
+            PropertyId::Shorthand(id) => match id {
             % for shorthand in data.shorthands:
-                "${shorthand.name}" => {
+                ShorthandId::${shorthand.camel_case} => {
                     % if not shorthand.allowed_in_keyframe_block:
                         if in_keyframe_block {
                             return PropertyDeclarationParseResult::AnimationPropertyInKeyframeBlock
                         }
                     % endif
                     % if shorthand.internal:
                         if context.stylesheet_origin != Origin::UserAgent {
                             return PropertyDeclarationParseResult::UnknownProperty
@@ -841,48 +908,42 @@ impl PropertyDeclaration {
                             % endfor
                             PropertyDeclarationParseResult::ValidOrIgnoredDeclaration
                         },
                         Err(()) => match shorthands::${shorthand.ident}::parse(context, input, result_list) {
                             Ok(()) => PropertyDeclarationParseResult::ValidOrIgnoredDeclaration,
                             Err(()) => PropertyDeclarationParseResult::InvalidValue,
                         }
                     }
-                },
+                }
             % endfor
-
-            _ => {
-                if cfg!(all(debug_assertions, feature = "gecko")) && !name.starts_with('-') {
-                    println!("stylo: Unimplemented property setter: {}", name);
-                }
-                PropertyDeclarationParseResult::UnknownProperty
             }
         }
     }
 
-    pub fn shorthands(&self) -> &'static [Shorthand] {
+    pub fn shorthands(&self) -> &'static [ShorthandId] {
         // first generate longhand to shorthands lookup map
         <%
             longhand_to_shorthand_map = {}
             for shorthand in data.shorthands:
                 for sub_property in shorthand.sub_properties:
                     if sub_property.ident not in longhand_to_shorthand_map:
                         longhand_to_shorthand_map[sub_property.ident] = []
 
                     longhand_to_shorthand_map[sub_property.ident].append(shorthand.camel_case)
 
             for shorthand_list in longhand_to_shorthand_map.itervalues():
                 shorthand_list.sort()
         %>
 
         // based on lookup results for each longhand, create result arrays
         % for property in data.longhands:
-            static ${property.ident.upper()}: &'static [Shorthand] = &[
+            static ${property.ident.upper()}: &'static [ShorthandId] = &[
                 % for shorthand in longhand_to_shorthand_map.get(property.ident, []):
-                    Shorthand::${shorthand},
+                    ShorthandId::${shorthand},
                 % endfor
             ];
         % endfor
 
         match *self {
             % for property in data.longhands:
                 PropertyDeclaration::${property.camel_case}(_) => ${property.ident.upper()},
             % endfor
@@ -1316,28 +1377,31 @@ impl ComputedValues {
                 }
             }
         }
 
         // Neither perspective nor transform present
         false
     }
 
-    pub fn computed_value_to_string(&self, name: &str) -> Result<String, ()> {
-        match name {
+    pub fn computed_value_to_string(&self, property: PropertyDeclarationId) -> String {
+        match property {
             % for style_struct in data.active_style_structs():
                 % for longhand in style_struct.longhands:
-                "${longhand.name}" => Ok(self.${style_struct.ident}.${longhand.ident}.to_css_string()),
+                    PropertyDeclarationId::Longhand(LonghandId::${longhand.camel_case}) => {
+                        self.${style_struct.ident}.${longhand.ident}.to_css_string()
+                    }
                 % endfor
             % endfor
-            _ => {
-                let name = try!(::custom_properties::parse_name(name));
-                let map = try!(self.custom_properties.as_ref().ok_or(()));
-                let value = try!(map.get(&Atom::from(name)).ok_or(()));
-                Ok(value.to_css_string())
+            PropertyDeclarationId::Custom(name) => {
+                self.custom_properties
+                    .as_ref()
+                    .and_then(|map| map.get(name))
+                    .map(|value| value.to_css_string())
+                    .unwrap_or(String::new())
             }
         }
     }
 }
 
 
 /// Return a WritingMode bitflags from the relevant CSS properties.
 pub fn get_writing_mode(inheritedbox_style: &style_structs::InheritedBox) -> WritingMode {
@@ -1571,19 +1635,21 @@ pub fn apply_declarations<'a, F, I>(view
     // We could (and used to) use a pattern match here, but that bloats this
     // function to over 100K of compiled code!
     //
     // To improve i-cache behavior, we outline the individual functions and use
     // virtual dispatch instead.
     ComputedValues::do_cascade_property(|cascade_property| {
         % for category_to_cascade_now in ["early", "other"]:
             for declaration in iter_declarations() {
-                if let PropertyDeclaration::Custom(..) = *declaration {
-                    continue
-                }
+                let longhand_id = match declaration.id() {
+                    PropertyDeclarationId::Longhand(id) => id,
+                    PropertyDeclarationId::Custom(..) => continue,
+                };
+
                 // The computed value of some properties depends on the
                 // (sometimes computed) value of *other* properties.
                 //
                 // So we classify properties into "early" and "other", such that
                 // the only dependencies can be from "other" to "early".
                 //
                 // We iterate applicable_declarations twice, first cascading
                 // "early" properties then "other".
@@ -1605,17 +1671,17 @@ pub fn apply_declarations<'a, F, I>(view
                     % if category_to_cascade_now == "early":
                         !
                     % endif
                     is_early_property
                 {
                     continue
                 }
 
-                let discriminant = declaration.discriminant_value();
+                let discriminant = longhand_id as usize;
                 (cascade_property[discriminant])(declaration,
                                                  inherited_style,
                                                  &mut context,
                                                  &mut seen,
                                                  &mut cacheable,
                                                  &mut cascade_info,
                                                  &mut error_reporter);
             }
@@ -1943,41 +2009,31 @@ pub fn modify_style_for_text(style: &mut
 pub fn modify_style_for_inline_absolute_hypothetical_fragment(style: &mut Arc<ComputedValues>) {
     if style.get_effects().clip.0.is_some() {
         let mut style = Arc::make_mut(style);
         let effects_style = Arc::make_mut(&mut style.effects);
         effects_style.clip.0 = None
     }
 }
 
-// FIXME: https://github.com/w3c/csswg-drafts/issues/580
-pub fn is_supported_property(property: &str) -> bool {
-    match_ignore_ascii_case! { property,
-        % for property in data.shorthands + data.longhands:
-            "${property.name}" => true,
-        % endfor
-        _ => property.starts_with("--")
-    }
-}
-
 #[macro_export]
 macro_rules! css_properties_accessors {
     ($macro_name: ident) => {
         $macro_name! {
-            % for property in data.shorthands + data.longhands:
-                % if not property.derived_from and not property.internal:
-                    % if '-' in property.name:
-                        [${property.ident.capitalize()}, Set${property.ident.capitalize()}, "${property.name}"],
+            % for kind, props in [("Longhand", data.longhands), ("Shorthand", data.shorthands)]:
+                % for property in props:
+                    % if not property.derived_from and not property.internal:
+                        % if '-' in property.name:
+                            [${property.ident.capitalize()}, Set${property.ident.capitalize()},
+                             PropertyId::${kind}(${kind}Id::${property.camel_case})],
+                        % endif
+                        [${property.camel_case}, Set${property.camel_case},
+                         PropertyId::${kind}(${kind}Id::${property.camel_case})],
                     % endif
-                    % if property != data.longhands[-1]:
-                        [${property.camel_case}, Set${property.camel_case}, "${property.name}"],
-                    % else:
-                        [${property.camel_case}, Set${property.camel_case}, "${property.name}"]
-                    % endif
-                % endif
+                % endfor
             % endfor
         }
     }
 }
 
 
 macro_rules! longhand_properties_idents {
     ($macro_name: ident) => {
--- a/servo/components/style/properties/shorthand/serialize.mako.rs
+++ b/servo/components/style/properties/shorthand/serialize.mako.rs
@@ -1,13 +1,13 @@
 /* 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 properties::{AppendableValue, DeclaredValue, PropertyDeclaration, Shorthand};
+use properties::{AppendableValue, DeclaredValue, PropertyDeclaration, ShorthandId};
 use style_traits::ToCss;
 use values::specified::{BorderStyle, CSSColor};
 use std::fmt;
 
 pub fn serialize_four_sides<W, I>(dest: &mut W, top: &I, right: &I, bottom: &I, left: &I)
     -> fmt::Result where W: fmt::Write, I: ToCss + PartialEq {
 
     if left == right {
@@ -83,15 +83,15 @@ fn serialize_directional_border<W, I>(de
         _ => Ok(())
     }
 }
 
 
 pub fn is_overflow_shorthand<'a, I>(appendable_value: &AppendableValue<'a, I>) -> bool
                                     where I: Iterator<Item=&'a PropertyDeclaration> {
     if let AppendableValue::DeclarationsForShorthand(shorthand, _) = *appendable_value {
-        if let Shorthand::Overflow = shorthand {
+        if let ShorthandId::Overflow = shorthand {
             return true;
         }
     }
 
     false
 }
--- a/servo/components/style/str.rs
+++ b/servo/components/style/str.rs
@@ -1,13 +1,15 @@
 /* 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 num_traits::ToPrimitive;
+use std::ascii::AsciiExt;
+use std::borrow::Cow;
 use std::convert::AsRef;
 use std::iter::{Filter, Peekable};
 use std::str::Split;
 
 pub type StaticCharVec = &'static [char];
 pub type StaticStringVec = &'static [&'static str];
 
 /// A "space character" according to:
@@ -120,8 +122,18 @@ pub fn str_join<I, T>(strs: I, join: &st
     where I: IntoIterator<Item=T>, T: AsRef<str>,
 {
     strs.into_iter().enumerate().fold(String::new(), |mut acc, (i, s)| {
         if i > 0 { acc.push_str(join); }
         acc.push_str(s.as_ref());
         acc
     })
 }
+
+/// Like AsciiExt::to_ascii_lowercase, but avoids allocating when the input is already lower-case.
+pub fn cow_into_ascii_lowercase<'a, S: Into<Cow<'a, str>>>(s: S) -> Cow<'a, str> {
+    let mut cow = s.into();
+    match cow.bytes().position(|byte| byte >= b'A' && byte <= b'Z') {
+        Some(first_uppercase) => cow.to_mut()[first_uppercase..].make_ascii_lowercase(),
+        None => {}
+    }
+    cow
+}
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -44,17 +44,17 @@ use style::gecko_bindings::structs::{Thr
 use style::gecko_bindings::structs::{nsRestyleHint, nsChangeHint};
 use style::gecko_bindings::structs::nsresult;
 use style::gecko_bindings::sugar::ownership::{FFIArcHelpers, HasArcFFI, HasBoxFFI};
 use style::gecko_bindings::sugar::ownership::{HasSimpleFFI, Strong};
 use style::gecko_bindings::sugar::refptr::{GeckoArcPrincipal, GeckoArcURI};
 use style::parallel;
 use style::parser::{ParserContext, ParserContextExtraData};
 use style::properties::{CascadeFlags, ComputedValues, Importance, PropertyDeclaration};
-use style::properties::{PropertyDeclarationParseResult, PropertyDeclarationBlock};
+use style::properties::{PropertyDeclarationParseResult, PropertyDeclarationBlock, PropertyId};
 use style::properties::{apply_declarations, parse_one_declaration};
 use style::restyle_hints::RestyleHint;
 use style::selector_parser::PseudoElementCascadeType;
 use style::sequential;
 use style::string_cache::Atom;
 use style::stylesheets::{CssRule, CssRules, Origin, Stylesheet, StyleRule};
 use style::thread_state;
 use style::timer::Timer;
@@ -529,31 +529,36 @@ pub extern "C" fn Servo_StyleSet_Drop(da
 
 #[no_mangle]
 pub extern "C" fn Servo_ParseProperty(property: *const nsACString, value: *const nsACString,
                                       base_url: *const nsACString, base: *mut ThreadSafeURIHolder,
                                       referrer: *mut ThreadSafeURIHolder,
                                       principal: *mut ThreadSafePrincipalHolder)
                                       -> RawServoDeclarationBlockStrong {
     let name = unsafe { property.as_ref().unwrap().as_str_unchecked() };
+    let id = if let Ok(id) = PropertyId::parse(name.into()) {
+        id
+    } else {
+        return RawServoDeclarationBlockStrong::null()
+    };
     let value = unsafe { value.as_ref().unwrap().as_str_unchecked() };
     let base_str = unsafe { base_url.as_ref().unwrap().as_str_unchecked() };
     let base_url = ServoUrl::parse(base_str).unwrap();
     let extra_data = unsafe { ParserContextExtraData {
         base: Some(GeckoArcURI::new(base)),
         referrer: Some(GeckoArcURI::new(referrer)),
         principal: Some(GeckoArcPrincipal::new(principal)),
     }};
 
     let context = ParserContext::new_with_extra_data(Origin::Author, &base_url,
                                                      Box::new(StdoutErrorReporter),
                                                      extra_data);
 
     let mut results = vec![];
-    match PropertyDeclaration::parse(name, &context, &mut Parser::new(value),
+    match PropertyDeclaration::parse(id, &context, &mut Parser::new(value),
                                      &mut results, false) {
         PropertyDeclarationParseResult::ValidOrIgnoredDeclaration => {},
         _ => return RawServoDeclarationBlockStrong::null(),
     }
 
     let results = results.into_iter().map(|r| (r, Importance::Normal)).collect();
 
     Arc::new(RwLock::new(PropertyDeclarationBlock {
@@ -606,17 +611,17 @@ pub extern "C" fn Servo_DeclarationBlock
 
 #[no_mangle]
 pub extern "C" fn Servo_DeclarationBlock_SerializeOneValue(
     declarations: RawServoDeclarationBlockBorrowed,
     property: *mut nsIAtom, is_custom: bool,
     buffer: *mut nsAString)
 {
     let declarations = RwLock::<PropertyDeclarationBlock>::as_arc(&declarations);
-    let property = get_property_name_from_atom(property, is_custom);
+    let property = get_property_id_from_atom(property, is_custom);
     let mut string = String::new();
     let rv = declarations.read().single_value_to_css(&property, &mut string);
     debug_assert!(rv.is_ok());
 
     write!(unsafe { &mut *buffer }, "{}", string).expect("Failed to copy string");
 }
 
 #[no_mangle]
@@ -626,92 +631,95 @@ pub extern "C" fn Servo_DeclarationBlock
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_DeclarationBlock_GetNthProperty(declarations: RawServoDeclarationBlockBorrowed,
                                                         index: u32, result: *mut nsAString) -> bool {
     let declarations = RwLock::<PropertyDeclarationBlock>::as_arc(&declarations);
     if let Some(&(ref decl, _)) = declarations.read().declarations.get(index as usize) {
         let result = unsafe { result.as_mut().unwrap() };
-        write!(result, "{}", decl.name()).unwrap();
+        decl.id().to_css(result).unwrap();
         true
     } else {
         false
     }
 }
 
-// FIXME Methods of PropertyDeclarationBlock should take atoms directly.
-// This function is just a temporary workaround before that finishes.
-fn get_property_name_from_atom(atom: *mut nsIAtom, is_custom: bool) -> String {
+fn get_property_id_from_atom(atom: *mut nsIAtom, is_custom: bool) -> PropertyId {
     let atom = Atom::from(atom);
     if !is_custom {
-        atom.to_string()
+        // FIXME: can we do this mapping without going through a UTF-8 string?
+        // Maybe even from nsCSSPropertyID directly?
+        PropertyId::parse(atom.to_string().into()).expect("got unknown property name from Gecko")
     } else {
-        let mut result = String::with_capacity(atom.len() as usize + 2);
-        write!(result, "--{}", atom).unwrap();
-        result
+        PropertyId::Custom(atom)
     }
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_DeclarationBlock_GetPropertyValue(declarations: RawServoDeclarationBlockBorrowed,
                                                           property: *mut nsIAtom, is_custom: bool,
                                                           value: *mut nsAString) {
     let declarations = RwLock::<PropertyDeclarationBlock>::as_arc(&declarations);
-    let property = get_property_name_from_atom(property, is_custom);
+    let property = get_property_id_from_atom(property, is_custom);
     declarations.read().property_value_to_css(&property, unsafe { value.as_mut().unwrap() }).unwrap();
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_DeclarationBlock_GetPropertyIsImportant(declarations: RawServoDeclarationBlockBorrowed,
                                                                 property: *mut nsIAtom, is_custom: bool) -> bool {
     let declarations = RwLock::<PropertyDeclarationBlock>::as_arc(&declarations);
-    let property = get_property_name_from_atom(property, is_custom);
+    let property = get_property_id_from_atom(property, is_custom);
     declarations.read().property_priority(&property).important()
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_DeclarationBlock_SetProperty(declarations: RawServoDeclarationBlockBorrowed,
                                                      property: *mut nsIAtom, is_custom: bool,
                                                      value: *mut nsACString, is_important: bool) -> bool {
-    let property = get_property_name_from_atom(property, is_custom);
+    let property = get_property_id_from_atom(property, is_custom);
     let value = unsafe { value.as_ref().unwrap().as_str_unchecked() };
     // FIXME Needs real URL and ParserContextExtraData.
     let base_url = &*DUMMY_BASE_URL;
     let extra_data = ParserContextExtraData::default();
-    if let Ok(decls) = parse_one_declaration(&property, value, &base_url,
+    if let Ok(decls) = parse_one_declaration(property, value, &base_url,
                                              Box::new(StdoutErrorReporter), extra_data) {
         let mut declarations = RwLock::<PropertyDeclarationBlock>::as_arc(&declarations).write();
         let importance = if is_important { Importance::Important } else { Importance::Normal };
         for decl in decls.into_iter() {
             declarations.set_parsed_declaration(decl, importance);
         }
         true
     } else {
         false
     }
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_DeclarationBlock_RemoveProperty(declarations: RawServoDeclarationBlockBorrowed,
                                                         property: *mut nsIAtom, is_custom: bool) {
     let declarations = RwLock::<PropertyDeclarationBlock>::as_arc(&declarations);
-    let property = get_property_name_from_atom(property, is_custom);
+    let property = get_property_id_from_atom(property, is_custom);
     declarations.write().remove_property(&property);
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_CSSSupports(property: *const nsACString, value: *const nsACString) -> bool {
     let property = unsafe { property.as_ref().unwrap().as_str_unchecked() };
+    let id =  if let Ok(id) = PropertyId::parse(property.into()) {
+        id
+    } else {
+        return false
+    };
     let value = unsafe { value.as_ref().unwrap().as_str_unchecked() };
 
     let base_url = &*DUMMY_BASE_URL;
     let extra_data = ParserContextExtraData::default();
 
-    match parse_one_declaration(&property, &value, &base_url, Box::new(StdoutErrorReporter), extra_data) {
+    match parse_one_declaration(id, &value, &base_url, Box::new(StdoutErrorReporter), extra_data) {
         Ok(decls) => !decls.is_empty(),
         Err(()) => false,
     }
 }
 
 /// Only safe to call on the main thread, with exclusive access to the element and
 /// its ancestors.
 unsafe fn maybe_restyle<'a>(data: &'a mut AtomicRefMut<ElementData>, element: GeckoElement)
--- a/servo/tests/unit/style/Cargo.toml
+++ b/servo/tests/unit/style/Cargo.toml
@@ -11,16 +11,17 @@ doctest = false
 
 [features]
 testing = ["style/testing"]
 
 [dependencies]
 app_units = "0.3"
 cssparser = {version = "0.7", features = ["heap_size"]}
 euclid = "0.10.1"
+matches = "0.1"
 owning_ref = "0.2.2"
 parking_lot = "0.3"
 rustc-serialize = "0.3"
 selectors = "0.15"
 html5ever-atoms = "0.1"
 servo_atoms = {path = "../../../components/atoms"}
 style = {path = "../../../components/style"}
 style_traits = {path = "../../../components/style_traits"}
--- a/servo/tests/unit/style/lib.rs
+++ b/servo/tests/unit/style/lib.rs
@@ -5,16 +5,17 @@
 #![cfg(test)]
 #![feature(core_intrinsics)]
 #![feature(plugin)]
 
 extern crate app_units;
 extern crate cssparser;
 extern crate euclid;
 #[macro_use] extern crate html5ever_atoms;
+#[macro_use] #[allow(unused_extern_crates)] extern crate matches;
 extern crate owning_ref;
 extern crate parking_lot;
 extern crate rustc_serialize;
 extern crate selectors;
 #[macro_use] extern crate servo_atoms;
 extern crate servo_url;
 extern crate style;
 extern crate style_traits;
--- a/servo/tests/unit/style/properties/serialization.rs
+++ b/servo/tests/unit/style/properties/serialization.rs
@@ -1,15 +1,15 @@
 /* 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 std::sync::Arc;
 pub use style::computed_values::display::T::inline_block;
-pub use style::properties::{DeclaredValue, PropertyDeclaration, PropertyDeclarationBlock, Importance};
+pub use style::properties::{DeclaredValue, PropertyDeclaration, PropertyDeclarationBlock, Importance, PropertyId};
 pub use style::values::specified::{BorderStyle, BorderWidth, 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::url::{UrlExtraData, SpecifiedUrl};
 pub use style_traits::ToCss;
 
 #[test]
@@ -1022,17 +1022,18 @@ mod shorthand_serialization {
 
             let block = PropertyDeclarationBlock {
                 declarations: declarations,
                 important_count: 0
             };
 
             let mut s = String::new();
 
-            let x = block.single_value_to_css("scroll-snap-type", &mut s);
+            let id = PropertyId::parse("scroll-snap-type".into()).unwrap();
+            let x = block.single_value_to_css(&id, &mut s);
 
             assert_eq!(x.is_ok(), true);
             assert_eq!(s, "");
         }
 
         #[test]
         fn should_serialize_to_single_value_if_sub_types_are_equal() {
             let declarations = vec![
@@ -1044,15 +1045,16 @@ mod shorthand_serialization {
 
             let block = PropertyDeclarationBlock {
                 declarations: declarations,
                 important_count: 0
             };
 
             let mut s = String::new();
 
-            let x = block.single_value_to_css("scroll-snap-type", &mut s);
+            let id = PropertyId::parse("scroll-snap-type".into()).unwrap();
+            let x = block.single_value_to_css(&id, &mut s);
 
             assert_eq!(x.is_ok(), true);
             assert_eq!(s, "mandatory");
         }
     }
 }
--- a/servo/tests/unit/style/str.rs
+++ b/servo/tests/unit/style/str.rs
@@ -1,13 +1,14 @@
 /* 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 style::str::{split_html_space_chars, str_join};
+use std::borrow::Cow;
+use style::str::{split_html_space_chars, str_join, cow_into_ascii_lowercase};
 
 #[test]
 pub fn split_html_space_chars_whitespace() {
     assert!(split_html_space_chars("").collect::<Vec<_>>().is_empty());
     assert!(split_html_space_chars("\u{0020}\u{0009}\u{000a}\u{000c}\u{000d}").collect::<Vec<_>>().is_empty());
 }
 
 #[test]
@@ -28,8 +29,18 @@ pub fn test_str_join_one() {
 
 #[test]
 pub fn test_str_join_many() {
     let slice = ["", "alpha", "", "beta", "gamma", ""];
     let actual = str_join(&slice, "-");
     let expected = "-alpha--beta-gamma-";
     assert_eq!(actual, expected);
 }
+
+#[test]
+pub fn test_cow_into_ascii_lowercase() {
+    assert!(matches!(cow_into_ascii_lowercase("abc.d"), Cow::Borrowed("abc.d")));
+    let string = String::from("abc.d");
+    assert!(matches!(cow_into_ascii_lowercase(string), Cow::Owned(ref s) if s == "abc.d"));
+    assert!(matches!(cow_into_ascii_lowercase("Abc.d"), Cow::Owned(ref s) if s == "abc.d"));
+    assert!(matches!(cow_into_ascii_lowercase("aBC.D"), Cow::Owned(ref s) if s == "abc.d"));
+    assert!(matches!(cow_into_ascii_lowercase("abc.D"), Cow::Owned(ref s) if s == "abc.d"));
+}
--- a/servo/tests/unit/style/stylesheets.rs
+++ b/servo/tests/unit/style/stylesheets.rs
@@ -11,18 +11,18 @@ use servo_atoms::Atom;
 use servo_url::ServoUrl;
 use std::borrow::ToOwned;
 use std::sync::Arc;
 use std::sync::Mutex;
 use std::sync::atomic::AtomicBool;
 use style::error_reporting::ParseErrorReporter;
 use style::keyframes::{Keyframe, KeyframeSelector, KeyframePercentage};
 use style::parser::ParserContextExtraData;
+use style::properties::Importance;
 use style::properties::{PropertyDeclaration, PropertyDeclarationBlock, DeclaredValue, longhands};
-use style::properties::Importance;
 use style::properties::longhands::animation_play_state;
 use style::stylesheets::{Origin, Namespaces};
 use style::stylesheets::{Stylesheet, NamespaceRule, CssRule, CssRules, StyleRule, KeyframesRule};
 use style::values::specified::{LengthOrPercentageOrAuto, Percentage};
 
 #[test]
 fn test_parse_stylesheet() {
     let css = r"
--- a/servo/tests/unit/style/stylist.rs
+++ b/servo/tests/unit/style/stylist.rs
@@ -96,17 +96,17 @@ fn test_get_local_name() {
     check(3, Some(("ImG", "img")));
 }
 
 #[test]
 fn test_insert() {
     let rules_list = get_mock_rules(&[".intro.foo", "#top"]);
     let mut selector_map = SelectorMap::new();
     selector_map.insert(rules_list[1][0].clone());
-    assert_eq!(1, selector_map.id_hash.get(&atom!("top")).unwrap()[0].source_order);
+    assert_eq!(1, selector_map.id_hash.get(&Atom::from("top")).unwrap()[0].source_order);
     selector_map.insert(rules_list[0][0].clone());
     assert_eq!(0, selector_map.class_hash.get(&Atom::from("intro")).unwrap()[0].source_order);
     assert!(selector_map.class_hash.get(&Atom::from("foo")).is_none());
 }
 
 #[test]
 fn test_get_universal_rules() {
     thread_state::initialize(thread_state::LAYOUT);