servo: Merge #10749 - Prepare related files to make it easier to split up the Mako template (from servo:split-mako); r=nox
authorSimon Sapin <simon.sapin@exyr.org>
Thu, 21 Apr 2016 10:08:39 +0500
changeset 338582 75af6f5e3e2d5876694d389cfcaf04de2120ab6e
parent 338581 05236dcfca50666486bf1db3a2d6023bcda84a5e
child 338583 73046b7a5b8924fa5cc2c6185932b6c58f7335dd
push id31307
push usergszorc@mozilla.com
push dateSat, 04 Feb 2017 00:59:06 +0000
treeherdermozilla-central@94079d43835f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnox
servo: Merge #10749 - Prepare related files to make it easier to split up the Mako template (from servo:split-mako); r=nox https://github.com/servo/servo/pull/10586#issuecomment-211490049 r? @nox Source-Repo: https://github.com/servo/servo Source-Revision: 3bfa4cc7414fea760ce5c503bfbcf25262acb9d7
servo/components/style/Mako-0.9.1.zip
servo/components/style/build.rs
servo/components/style/generate_properties_rs.py
servo/components/style/list_properties.py
servo/components/style/properties.html.mako
servo/components/style/properties.mako.rs
servo/components/style/properties/Mako-0.9.1.zip
servo/components/style/properties/build.py
servo/components/style/properties/data.py
servo/components/style/properties/properties.html.mako
servo/components/style/properties/properties.mako.rs
servo/etc/ci/upload_docs.sh
servo/ports/geckolib/build.rs
servo/ports/geckolib/properties.mako.rs
servo/python/servo/testing_commands.py
--- a/servo/components/style/build.rs
+++ b/servo/components/style/build.rs
@@ -1,17 +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 std::env;
-use std::fs::File;
-use std::io::Write;
 use std::path::Path;
-use std::process::{Command, Stdio, exit};
+use std::process::{Command, exit};
 
 #[cfg(windows)]
 fn find_python() -> String {
     if Command::new("python27.exe").arg("--version").output().is_ok() {
         return "python27.exe".to_owned();
     }
 
     if Command::new("python.exe").arg("--version").output().is_ok() {
@@ -26,30 +24,21 @@ fn find_python() -> String {
     if Command::new("python2.7").arg("--version").output().unwrap().status.success() {
         "python2.7"
     } else {
         "python"
     }.to_owned()
 }
 
 fn main() {
-    let python = match env::var("PYTHON") {
-        Ok(python_path) => python_path,
-        Err(_) => find_python(),
-    };
-    let style = Path::new(file!()).parent().unwrap();
-    let mako = style.join("Mako-0.9.1.zip");
-    let template = style.join("properties.mako.rs");
+    let python = env::var("PYTHON").ok().unwrap_or_else(find_python);
+    let script = Path::new(file!()).parent().unwrap().join("properties").join("build.py");
     let product = if cfg!(feature = "gecko") { "gecko" } else { "servo" };
-    let result = Command::new(python)
-        .env("PYTHONPATH", &mako)
-        .env("TEMPLATE", &template)
-        .env("PRODUCT", product)
-        .arg("generate_properties_rs.py")
-        .stderr(Stdio::inherit())
-        .output()
+    let status = Command::new(python)
+        .arg(&script)
+        .arg(product)
+        .arg("style-crate")
+        .status()
         .unwrap();
-    if !result.status.success() {
+    if !status.success() {
         exit(1)
     }
-    let out = env::var("OUT_DIR").unwrap();
-    File::create(&Path::new(&out).join("properties.rs")).unwrap().write_all(&result.stdout).unwrap();
 }
deleted file mode 100644
--- a/servo/components/style/generate_properties_rs.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-import os
-import sys
-
-from mako import exceptions
-from mako.template import Template
-
-try:
-    template = Template(open(os.environ['TEMPLATE'], 'rb').read(),
-                        input_encoding='utf8')
-    print(template.render(PRODUCT=os.environ['PRODUCT']).encode('utf8'))
-except:
-    sys.stderr.write(exceptions.text_error_template().render().encode('utf8'))
-    sys.exit(1)
deleted file mode 100644
--- a/servo/components/style/list_properties.py
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/usr/bin/env python
-
-# 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/.
-
-import os.path
-import sys
-import json
-
-style = os.path.dirname(__file__)
-sys.path.insert(0, os.path.join(style, "Mako-0.9.1.zip"))
-from mako.template import Template
-
-template = Template(filename=os.path.join(style, "properties.mako.rs"), input_encoding='utf8')
-template.render(PRODUCT='servo')
-properties = dict(
-    (p.name, {
-        "flag": p.experimental,
-        "shorthand": hasattr(p, "sub_properties")
-    })
-    for p in template.module.LONGHANDS + template.module.SHORTHANDS
-)
-
-json_dump = json.dumps(properties, indent=4)
-
-#
-# Resolve path to doc directory and write CSS properties and JSON.
-#
-servo_doc_path = os.path.abspath(os.path.join(style, '../', '../', 'target', 'doc', 'servo'))
-
-# Ensure ./target/doc/servo exists
-if not os.path.exists(servo_doc_path):
-    os.makedirs(servo_doc_path)
-
-with open(os.path.join(servo_doc_path, 'css-properties.json'), "w") as out_file:
-    out_file.write(json_dump)
-
-html_template = Template(filename=os.path.join(style, "properties.html.mako"), input_encoding='utf8')
-with open(os.path.join(servo_doc_path, 'css-properties.html'), "w") as out_file:
-    out_file.write(html_template.render(properties=properties))
rename from servo/components/style/Mako-0.9.1.zip
rename to servo/components/style/properties/Mako-0.9.1.zip
new file mode 100644
--- /dev/null
+++ b/servo/components/style/properties/build.py
@@ -0,0 +1,80 @@
+# 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/.
+
+import json
+import os.path
+import sys
+
+BASE = os.path.dirname(__file__)
+sys.path.insert(0, os.path.join(BASE, "Mako-0.9.1.zip"))
+
+from mako import exceptions
+from mako.template import Template
+
+import data
+
+
+def main():
+    usage = "Usage: %s [ servo | gecko ] [ style-crate | geckolib | html ]" % sys.argv[0]
+    if len(sys.argv) < 3:
+        abort(usage)
+    product = sys.argv[1]
+    output = sys.argv[2]
+    if product not in ["servo", "gecko"] or output not in ["style-crate", "geckolib", "html"]:
+        abort(usage)
+
+    properties = data.PropertiesData(product=product)
+    rust = render(os.path.join(BASE, "properties.mako.rs"), product=product, data=properties)
+    if output == "style-crate":
+        write(os.environ["OUT_DIR"], "properties.rs", rust)
+    if output == "geckolib":
+        template = os.path.join(BASE, "..", "..", "..", "ports", "geckolib", "properties.mako.rs")
+        rust = render(template, data=properties)
+        write(os.environ["OUT_DIR"], "properties.rs", rust)
+    elif output == "html":
+        write_html(properties)
+
+
+def abort(message):
+    sys.stderr.write(message + b"\n")
+    sys.exit(1)
+
+
+def render(filename, **context):
+    try:
+        template = Template(open(filename, "rb").read(),
+                            input_encoding="utf8",
+                            strict_undefined=True,
+                            filename=filename)
+        # Uncomment to debug generated Python code:
+        # write("/tmp", "mako_%s.py" % os.path.basename(filename), template.code)
+        return template.render(**context).encode("utf8")
+    except:
+        # Uncomment to see a traceback in generated Python code:
+        # raise
+        abort(exceptions.text_error_template().render().encode("utf8"))
+
+
+def write(directory, filename, content):
+    if not os.path.exists(directory):
+        os.makedirs(directory)
+    open(os.path.join(directory, filename), "wb").write(content)
+
+
+def write_html(properties):
+    properties = dict(
+        (p.name, {
+            "flag": p.experimental,
+            "shorthand": hasattr(p, "sub_properties")
+        })
+        for p in properties.longhands + properties.shorthands
+    )
+    doc_servo = os.path.join(BASE, "..", "..", "..", "target", "doc", "servo")
+    html = render(os.path.join(BASE, "properties.html.mako"), properties=properties)
+    write(doc_servo, "css-properties.html", html)
+    write(doc_servo, "css-properties.json", json.dumps(properties, indent=4))
+
+
+if __name__ == "__main__":
+    main()
new file mode 100644
--- /dev/null
+++ b/servo/components/style/properties/data.py
@@ -0,0 +1,156 @@
+# 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/.
+
+import re
+
+
+def to_rust_ident(name):
+    name = name.replace("-", "_")
+    if name in ["static", "super", "box", "move"]:  # Rust keywords
+        name += "_"
+    return name
+
+
+def to_camel_case(ident):
+    return re.sub("_([a-z])", lambda m: m.group(1).upper(), ident.strip("_").capitalize())
+
+
+class Keyword(object):
+    def __init__(self, name, values, gecko_constant_prefix=None,
+                 extra_gecko_values=None, extra_servo_values=None):
+        self.name = name
+        self.values = values
+        self.gecko_constant_prefix = gecko_constant_prefix or \
+            "NS_STYLE_" + self.name.upper().replace("-", "_")
+        self.extra_gecko_values = (extra_gecko_values or "").split()
+        self.extra_servo_values = (extra_servo_values or "").split()
+
+    def gecko_values(self):
+        return self.values + self.extra_gecko_values
+
+    def servo_values(self):
+        return self.values + self.extra_servo_values
+
+    def values_for(self, product):
+        if product == "gecko":
+            return self.gecko_values()
+        elif product == "servo":
+            return self.servo_values()
+        else:
+            raise Exception("Bad product: " + product)
+
+    def gecko_constant(self, value):
+        return self.gecko_constant_prefix + "_" + value.upper().replace("-", "_")
+
+
+class Longhand(object):
+    def __init__(self, style_struct, name, derived_from=None, keyword=None,
+                 custom_cascade=False, experimental=False, internal=False,
+                 gecko_ffi_name=None):
+        self.name = name
+        self.keyword = keyword
+        self.ident = to_rust_ident(name)
+        self.camel_case = to_camel_case(self.ident)
+        self.style_struct = style_struct
+        self.experimental = ("layout.%s.enabled" % name) if experimental else None
+        self.custom_cascade = custom_cascade
+        self.internal = internal
+        self.gecko_ffi_name = gecko_ffi_name or "m" + self.camel_case
+        self.derived_from = (derived_from or "").split()
+
+
+class Shorthand(object):
+    def __init__(self, name, sub_properties, experimental=False, internal=False):
+        self.name = name
+        self.ident = to_rust_ident(name)
+        self.camel_case = to_camel_case(self.ident)
+        self.derived_from = None
+        self.experimental = ("layout.%s.enabled" % name) if experimental else None
+        self.sub_properties = sub_properties
+        self.internal = internal
+
+
+class Method(object):
+    def __init__(self, name, return_type=None, arg_types=None, is_mut=False):
+        self.name = name
+        self.return_type = return_type
+        self.arg_types = arg_types or []
+        self.is_mut = is_mut
+
+    def arg_list(self):
+        args = ["_: " + x for x in self.arg_types]
+        args = ["&mut self" if self.is_mut else "&self"] + args
+        return ", ".join(args)
+
+    def signature(self):
+        sig = "fn %s(%s)" % (self.name, self.arg_list())
+        if self.return_type:
+            sig = sig + " -> " + self.return_type
+        return sig
+
+    def declare(self):
+        return self.signature() + ";"
+
+    def stub(self):
+        return self.signature() + "{ unimplemented!() }"
+
+
+class StyleStruct(object):
+    def __init__(self, name, inherited, gecko_ffi_name=None, additional_methods=None):
+        self.servo_struct_name = "Servo" + name
+        self.gecko_struct_name = "Gecko" + name
+        self.trait_name = name
+        self.trait_name_lower = name.lower()
+        self.ident = to_rust_ident(self.trait_name_lower)
+        self.longhands = []
+        self.inherited = inherited
+        self.gecko_ffi_name = gecko_ffi_name
+        self.additional_methods = additional_methods or []
+
+
+class PropertiesData(object):
+    def __init__(self, product):
+        self.product = product
+        self.style_structs = []
+        self.current_style_struct = None
+        self.longhands = []
+        self.longhands_by_name = {}
+        self.derived_longhands = {}
+        self.shorthands = []
+
+    def new_style_struct(self, *args, **kwargs):
+        style_struct = StyleStruct(*args, **kwargs)
+        self.style_structs.append(style_struct)
+        self.current_style_struct = style_struct
+
+    def active_style_structs(self):
+        return [s for s in self.style_structs if s.additional_methods or s.longhands]
+
+    def switch_to_style_struct(self, name):
+        for style_struct in self.style_structs:
+            if style_struct.trait_name == name:
+                self.current_style_struct = style_struct
+                return
+        raise Exception("Failed to find the struct named " + name)
+
+    def declare_longhand(self, name, products="gecko servo", **kwargs):
+        products = products.split()
+        if self.product not in products:
+            return
+
+        longand = Longhand(self.current_style_struct, name, **kwargs)
+        self.current_style_struct.longhands.append(longand)
+        self.longhands.append(longand)
+        self.longhands_by_name[name] = longand
+
+        for name in longand.derived_from:
+            self.derived_longhands.setdefault(name, []).append(longand)
+
+        return longand
+
+    def declare_shorthand(self, name, sub_properties, *args, **kwargs):
+        sub_properties = [self.longhands_by_name[s] for s in sub_properties]
+        shorthand = Shorthand(name, sub_properties, *args, **kwargs)
+        self.shorthands.append(shorthand)
+        return shorthand
rename from servo/components/style/properties.html.mako
rename to servo/components/style/properties/properties.html.mako
rename from servo/components/style/properties.mako.rs
rename to servo/components/style/properties/properties.mako.rs
--- a/servo/components/style/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -32,183 +32,42 @@ use selectors::matching::DeclarationBloc
 use stylesheets::Origin;
 use values::AuExtensionMethods;
 use values::computed::{self, TContext, ToComputedValue};
 use values::specified::BorderStyle;
 
 use self::property_bit_field::PropertyBitField;
 
 <%!
-
-import re
-
-def to_rust_ident(name):
-    name = name.replace("-", "_")
-    if name in ["static", "super", "box", "move"]:  # Rust keywords
-        name += "_"
-    return name
-
-def to_camel_case(ident):
-    return re.sub("_([a-z])", lambda m: m.group(1).upper(), ident.strip("_").capitalize())
-
-class Keyword(object):
-    def __init__(self, name, values, gecko_constant_prefix=None,
-                 extra_gecko_values=None, extra_servo_values=None, **kwargs):
-        self.name = name
-        self.values = values
-        self.gecko_constant_prefix = gecko_constant_prefix or "NS_STYLE_" + self.name.upper().replace("-", "_")
-        self.extra_gecko_values = (extra_gecko_values or "").split()
-        self.extra_servo_values = (extra_servo_values or "").split()
-    def gecko_values(self):
-        return self.values + self.extra_gecko_values
-    def servo_values(self):
-        return self.values + self.extra_servo_values
-    def values_for(self, product):
-        if product == "gecko":
-            return self.gecko_values()
-        elif product == "servo":
-            return self.servo_values()
-        else:
-            raise Exception("Bad product: " + product)
-    def gecko_constant(self, value):
-        return self.gecko_constant_prefix + "_" + value.upper().replace("-", "_")
-
-class Longhand(object):
-    def __init__(self, name, derived_from=None, keyword=None,
-                 custom_cascade=False, experimental=False, internal=False,
-                 gecko_ffi_name=None, **kwargs):
-        self.name = name
-        self.keyword = keyword
-        self.ident = to_rust_ident(name)
-        self.camel_case = to_camel_case(self.ident)
-        self.style_struct = THIS_STYLE_STRUCT
-        self.experimental = ("layout.%s.enabled" % name) if experimental else None
-        self.custom_cascade = custom_cascade
-        self.internal = internal
-        self.gecko_ffi_name = gecko_ffi_name or "m" + self.camel_case
-        self.derived_from = (derived_from or "").split()
-
-class Shorthand(object):
-    def __init__(self, name, sub_properties, experimental=False, internal=False):
-        self.name = name
-        self.ident = to_rust_ident(name)
-        self.camel_case = to_camel_case(self.ident)
-        self.derived_from = None
-        self.experimental = ("layout.%s.enabled" % name) if experimental else None
-        self.sub_properties = [LONGHANDS_BY_NAME[s] for s in sub_properties]
-        self.internal = internal
-
-class Method(object):
-    def __init__(self, name, return_type=None, arg_types=None, is_mut=False):
-        self.name = name
-        self.return_type = return_type
-        self.arg_types = arg_types or []
-        self.is_mut = is_mut
-    def arg_list(self):
-        args = ["_: " + x for x in self.arg_types]
-        args = ["&mut self" if self.is_mut else "&self"] + args
-        return ", ".join(args)
-    def signature(self):
-        sig = "fn %s(%s)" % (self.name, self.arg_list())
-        if self.return_type:
-            sig = sig + " -> " + self.return_type
-        return sig
-    def declare(self):
-        return self.signature() + ";"
-    def stub(self):
-        return self.signature() + "{ unimplemented!() }"
-
-class StyleStruct(object):
-    def __init__(self, name, inherited, gecko_ffi_name, additional_methods):
-        self.servo_struct_name = "Servo" + name
-        self.gecko_struct_name = "Gecko" + name
-        self.trait_name = name
-        self.trait_name_lower = name.lower()
-        self.ident = to_rust_ident(self.trait_name_lower)
-        self.longhands = []
-        self.inherited = inherited
-        self.gecko_ffi_name = gecko_ffi_name
-        self.additional_methods = additional_methods or []
-
-STYLE_STRUCTS = []
-THIS_STYLE_STRUCT = None
-LONGHANDS = []
-LONGHANDS_BY_NAME = {}
-DERIVED_LONGHANDS = {}
-SHORTHANDS = []
-CONFIG = {}
-
-def set_product(p):
-    global CONFIG
-    CONFIG['product'] = p
-
-def new_style_struct(name, is_inherited, gecko_name=None, additional_methods=None):
-    global THIS_STYLE_STRUCT
-
-    style_struct = StyleStruct(name, is_inherited, gecko_name, additional_methods)
-    STYLE_STRUCTS.append(style_struct)
-    THIS_STYLE_STRUCT = style_struct
-    return ""
-
-def active_style_structs():
-    return filter(lambda s: s.additional_methods or s.longhands, STYLE_STRUCTS)
-
-def switch_to_style_struct(name):
-    global THIS_STYLE_STRUCT
-
-    for style_struct in STYLE_STRUCTS:
-        if style_struct.trait_name == name:
-            THIS_STYLE_STRUCT = style_struct
-            return ""
-    raise Exception("Failed to find the struct named " + name)
+    from data import Method, Keyword, to_rust_ident
 %>
 
-// Work around Mako's really annoying namespacing setup.
-//
-// The above code runs when the template is loaded, rather than when it's
-// rendered, so it can create global variables, doesn't have access to
-// arguments passed to render(). On the flip side, there are various situations,
-// such as code in the body of a def-used-as-tag, where our python code has
-// access to global variables but not to render() arguments. Hack around this
-// by stashing render arguments in a global.
-<% CONFIG['product'] = PRODUCT %>
-
 pub mod longhands {
     use cssparser::Parser;
     use parser::ParserContext;
     use values::specified;
 
-    <%def name="raw_longhand(name, **kwargs)">
-    <%
-        products = kwargs.pop("products", "gecko servo").split()
-        if not CONFIG["product"] in products:
-            return ""
-
-        property = Longhand(name, **kwargs)
-
-        property.style_struct = THIS_STYLE_STRUCT
-        THIS_STYLE_STRUCT.longhands.append(property)
-        LONGHANDS.append(property)
-        LONGHANDS_BY_NAME[name] = property
-
-        for derived in property.derived_from:
-            DERIVED_LONGHANDS.setdefault(derived, []).append(property)
-    %>
+    <%def name="raw_longhand(*args, **kwargs)">
+        <%
+            property = data.declare_longhand(*args, **kwargs)
+            if property is None:
+                return ""
+        %>
         pub mod ${property.ident} {
             #![allow(unused_imports)]
             % if not property.derived_from:
                 use cssparser::Parser;
                 use parser::ParserContext;
                 use properties::{CSSWideKeyword, DeclaredValue, Shorthand};
             % endif
             use error_reporting::ParseErrorReporter;
             use properties::longhands;
             use properties::property_bit_field::PropertyBitField;
             use properties::{ComputedValues, ServoComputedValues, PropertyDeclaration};
-            use properties::style_struct_traits::${THIS_STYLE_STRUCT.trait_name};
+            use properties::style_struct_traits::${data.current_style_struct.trait_name};
             use properties::style_structs;
             use std::boxed::Box as StdBox;
             use std::collections::HashMap;
             use std::sync::Arc;
             use values::computed::{TContext, ToComputedValue};
             use values::{computed, specified};
             use string_cache::Atom;
             ${caller.body()}
@@ -232,36 +91,37 @@ pub mod longhands {
                     }
                     seen.set_${property.ident}();
                     {
                         let custom_props = context.style().custom_properties();
                         ::properties::substitute_variables_${property.ident}(
                             declared_value, &custom_props, |value| match *value {
                                 DeclaredValue::Value(ref specified_value) => {
                                     let computed = specified_value.to_computed_value(context);
-                                    context.mutate_style().mutate_${THIS_STYLE_STRUCT.trait_name_lower}()
+                                    context.mutate_style().mutate_${data.current_style_struct.trait_name_lower}()
                                                           .set_${property.ident}(computed);
                                 }
                                 DeclaredValue::WithVariables { .. } => unreachable!(),
                                 DeclaredValue::Initial => {
                                     // We assume that it's faster to use copy_*_from rather than
                                     // set_*(get_initial_value());
                                     let initial_struct = C::initial_values()
-                                                          .get_${THIS_STYLE_STRUCT.trait_name_lower}();
-                                    context.mutate_style().mutate_${THIS_STYLE_STRUCT.trait_name_lower}()
+                                                          .get_${data.current_style_struct.trait_name_lower}();
+                                    context.mutate_style().mutate_${data.current_style_struct.trait_name_lower}()
                                                           .copy_${property.ident}_from(initial_struct);
                                 },
                                 DeclaredValue::Inherit => {
                                     // This is a bit slow, but this is rare so it shouldn't
                                     // matter.
                                     //
                                     // FIXME: is it still?
                                     *cacheable = false;
-                                    let inherited_struct = inherited_style.get_${THIS_STYLE_STRUCT.trait_name_lower}();
-                                    context.mutate_style().mutate_${THIS_STYLE_STRUCT.trait_name_lower}()
+                                    let inherited_struct =
+                                        inherited_style.get_${data.current_style_struct.trait_name_lower}();
+                                    context.mutate_style().mutate_${data.current_style_struct.trait_name_lower}()
                                            .copy_${property.ident}_from(inherited_struct);
                                 }
                             }, error_reporter
                         );
                     }
 
                     % if property.custom_cascade:
                         cascade_property_custom(declaration,
@@ -277,17 +137,17 @@ pub mod longhands {
             }
             % if not property.derived_from:
                 pub fn parse_declared(context: &ParserContext, input: &mut Parser)
                                    -> Result<DeclaredValue<SpecifiedValue>, ()> {
                     match input.try(CSSWideKeyword::parse) {
                         Ok(CSSWideKeyword::InheritKeyword) => Ok(DeclaredValue::Inherit),
                         Ok(CSSWideKeyword::InitialKeyword) => Ok(DeclaredValue::Initial),
                         Ok(CSSWideKeyword::UnsetKeyword) => Ok(DeclaredValue::${
-                            "Inherit" if THIS_STYLE_STRUCT.inherited else "Initial"}),
+                            "Inherit" if data.current_style_struct.inherited else "Initial"}),
                         Err(()) => {
                             input.look_for_var_functions();
                             let start = input.position();
                             let specified = parse_specified(context, input);
                             if specified.is_err() {
                                 while let Ok(_) = input.next() {}  // Look for var() after the error.
                             }
                             let var = input.seen_var_functions();
@@ -308,32 +168,37 @@ pub mod longhands {
                 }
             % endif
         }
     </%def>
 
     <%def name="longhand(name, **kwargs)">
         <%call expr="raw_longhand(name, **kwargs)">
             ${caller.body()}
-            % if not LONGHANDS_BY_NAME[name].derived_from:
+            % if not data.longhands_by_name[name].derived_from:
                 pub fn parse_specified(context: &ParserContext, input: &mut Parser)
                                    -> Result<DeclaredValue<SpecifiedValue>, ()> {
                     parse(context, input).map(DeclaredValue::Value)
                 }
             % endif
         </%call>
     </%def>
 
     <%def name="single_keyword_computed(name, values, **kwargs)">
-        <%call expr="longhand(name, keyword=Keyword(name, values.split(), **kwargs), **kwargs)">
+        <%
+            keyword_kwargs = {a: kwargs.pop(a, None) for a in [
+                'gecko_constant_prefix', 'extra_gecko_values', 'extra_servo_values'
+            ]}
+        %>
+        <%call expr="longhand(name, keyword=Keyword(name, values.split(), **keyword_kwargs), **kwargs)">
             pub use self::computed_value::T as SpecifiedValue;
             ${caller.body()}
             pub mod computed_value {
                 define_css_keyword_enum! { T:
-                    % for value in LONGHANDS_BY_NAME[name].keyword.values_for(CONFIG['product']):
+                    % for value in data.longhands_by_name[name].keyword.values_for(product):
                         "${value}" => ${to_rust_ident(value)},
                     % endfor
                 }
             }
             #[inline] pub fn get_initial_value() -> computed_value::T {
                 computed_value::T::${to_rust_ident(values.split()[0])}
             }
             pub fn parse(_context: &ParserContext, input: &mut Parser)
@@ -364,34 +229,34 @@ pub mod longhands {
                 specified::${type}::${parse_method}(input)
             }
         </%self:longhand>
     </%def>
 
 
     // CSS 2.1, Section 8 - Box model
 
-    ${new_style_struct("Margin", is_inherited=False, gecko_name="nsStyleMargin")}
+    <% data.new_style_struct("Margin", inherited=False, gecko_ffi_name="nsStyleMargin") %>
 
     % for side in ["top", "right", "bottom", "left"]:
         ${predefined_type("margin-" + side, "LengthOrPercentageOrAuto",
                           "computed::LengthOrPercentageOrAuto::Length(Au(0))")}
     % endfor
 
-    ${new_style_struct("Padding", is_inherited=False, gecko_name="nsStylePadding")}
+    <% data.new_style_struct("Padding", inherited=False, gecko_ffi_name="nsStylePadding") %>
 
     % for side in ["top", "right", "bottom", "left"]:
         ${predefined_type("padding-" + side, "LengthOrPercentage",
                           "computed::LengthOrPercentage::Length(Au(0))",
                           "parse_non_negative")}
     % endfor
 
-    ${new_style_struct("Border", is_inherited=False, gecko_name="nsStyleBorder",
+    <% data.new_style_struct("Border", inherited=False, gecko_ffi_name="nsStyleBorder",
                        additional_methods=[Method("border_" + side + "_is_none_or_hidden_and_has_nonzero_width",
-                                                  "bool") for side in ["top", "right", "bottom", "left"]])}
+                                                  "bool") for side in ["top", "right", "bottom", "left"]]) %>
 
     % for side in ["top", "right", "bottom", "left"]:
         ${predefined_type("border-%s-color" % side, "CSSColor", "::cssparser::Color::CurrentColor")}
     % endfor
 
     % for side in ["top", "right", "bottom", "left"]:
         ${predefined_type("border-%s-style" % side, "BorderStyle", "specified::BorderStyle::none")}
     % endfor
@@ -436,18 +301,18 @@ pub mod longhands {
 
     // FIXME(#4126): when gfx supports painting it, make this Size2D<LengthOrPercentage>
     % for corner in ["top-left", "top-right", "bottom-right", "bottom-left"]:
         ${predefined_type("border-" + corner + "-radius", "BorderRadiusSize",
                           "computed::BorderRadiusSize::zero()",
                           "parse")}
     % endfor
 
-    ${new_style_struct("Outline", is_inherited=False, gecko_name="nsStyleOutline",
-                       additional_methods=[Method("outline_is_none_or_hidden_and_has_nonzero_width", "bool")])}
+    <% data.new_style_struct("Outline", inherited=False, gecko_ffi_name="nsStyleOutline",
+                       additional_methods=[Method("outline_is_none_or_hidden_and_has_nonzero_width", "bool")]) %>
 
     // TODO(pcwalton): `invert`
     ${predefined_type("outline-color", "CSSColor", "::cssparser::Color::CurrentColor")}
 
     <%self:longhand name="outline-style">
         pub use values::specified::BorderStyle as SpecifiedValue;
         pub fn get_initial_value() -> SpecifiedValue { SpecifiedValue::none }
         pub mod computed_value {
@@ -490,37 +355,37 @@ pub mod longhands {
             fn to_computed_value<Cx: TContext>(&self, context: &Cx) -> computed_value::T {
                 self.0.to_computed_value(context)
             }
         }
     </%self:longhand>
 
     ${predefined_type("outline-offset", "Length", "Au(0)")}
 
-    ${new_style_struct("Position", is_inherited=False, gecko_name="nsStylePosition")}
+    <% data.new_style_struct("Position", inherited=False, gecko_ffi_name="nsStylePosition") %>
 
     % for side in ["top", "right", "bottom", "left"]:
         ${predefined_type(side, "LengthOrPercentageOrAuto",
                           "computed::LengthOrPercentageOrAuto::Auto")}
     % endfor
 
     // CSS 2.1, Section 9 - Visual formatting model
 
-    ${new_style_struct("Box", is_inherited=False, gecko_name="nsStyleDisplay",
+    <% data.new_style_struct("Box", inherited=False, gecko_ffi_name="nsStyleDisplay",
                        additional_methods=[Method("clone_display",
                                                   "longhands::display::computed_value::T"),
                                            Method("clone_position",
                                                   "longhands::position::computed_value::T"),
                                            Method("is_floated", "bool"),
                                            Method("overflow_x_is_visible", "bool"),
                                            Method("overflow_y_is_visible", "bool"),
-                                           Method("transition_count", "usize")])}
+                                           Method("transition_count", "usize")]) %>
 
     // TODO(SimonSapin): don't parse `inline-table`, since we don't support it
-    <%self:longhand name="display" custom_cascade="${CONFIG['product'] == 'servo'}">
+    <%self:longhand name="display" custom_cascade="${product == 'servo'}">
         <%
             values = """inline block inline-block
                 table inline-table table-row-group table-header-group table-footer-group
                 table-row table-column-group table-column table-cell table-caption
                 list-item flex
                 none
             """.split()
             experimental_values = set("flex".split())
@@ -567,17 +432,17 @@ pub mod longhands {
                     },
                 % endfor
                 _ => Err(())
             }
         }
 
         impl ComputedValueAsSpecified for SpecifiedValue {}
 
-        % if CONFIG["product"] == "servo":
+        % if product == "servo":
             fn cascade_property_custom<C: ComputedValues>(
                                        _declaration: &PropertyDeclaration,
                                        _inherited_style: &C,
                                        context: &mut computed::Context<C>,
                                        _seen: &mut PropertyBitField,
                                        _cacheable: &mut bool,
                                        _error_reporter: &mut StdBox<ParseErrorReporter + Send>) {
                 longhands::_servo_display_for_hypothetical_box::derive_from_display(context);
@@ -621,17 +486,17 @@ pub mod longhands {
         #[inline]
         pub fn derive_from_display<Cx: TContext>(context: &mut Cx) {
             let d = context.style().get_box().clone_display();
             context.mutate_style().mutate_box().set__servo_display_for_hypothetical_box(d);
         }
 
     </%self:longhand>
 
-    ${switch_to_style_struct("Position")}
+    <% data.switch_to_style_struct("Position") %>
 
     <%self:longhand name="z-index">
         use values::computed::ComputedValueAsSpecified;
 
         impl ComputedValueAsSpecified for SpecifiedValue {}
         pub type SpecifiedValue = computed_value::T;
         pub mod computed_value {
             use cssparser::ToCss;
@@ -669,58 +534,58 @@ pub mod longhands {
             if input.try(|input| input.expect_ident_matching("auto")).is_ok() {
                 Ok(computed_value::T::Auto)
             } else {
                 specified::parse_integer(input).map(computed_value::T::Number)
             }
         }
     </%self:longhand>
 
-    ${new_style_struct("InheritedBox", is_inherited=True, gecko_name="nsStyleVisibility",
+    <% data.new_style_struct("InheritedBox", inherited=True, gecko_ffi_name="nsStyleVisibility",
                        additional_methods=[Method("clone_direction",
                                                   "longhands::direction::computed_value::T"),
                                            Method("clone_writing_mode",
                                                   "longhands::writing_mode::computed_value::T"),
                                            Method("clone_text_orientation",
-                                                  "longhands::text_orientation::computed_value::T")])}
+                                                  "longhands::text_orientation::computed_value::T")]) %>
 
     ${single_keyword("direction", "ltr rtl")}
 
     // CSS 2.1, Section 10 - Visual formatting model details
 
-    ${switch_to_style_struct("Box")}
+    <% data.switch_to_style_struct("Box") %>
 
     ${predefined_type("width", "LengthOrPercentageOrAuto",
                       "computed::LengthOrPercentageOrAuto::Auto",
                       "parse_non_negative")}
 
     ${predefined_type("height", "LengthOrPercentageOrAuto",
                       "computed::LengthOrPercentageOrAuto::Auto",
                       "parse_non_negative")}
 
-    ${switch_to_style_struct("Position")}
+    <% data.switch_to_style_struct("Position") %>
 
     ${predefined_type("min-width", "LengthOrPercentage",
                       "computed::LengthOrPercentage::Length(Au(0))",
                       "parse_non_negative")}
     ${predefined_type("max-width", "LengthOrPercentageOrNone",
                       "computed::LengthOrPercentageOrNone::None",
                       "parse_non_negative")}
 
     ${predefined_type("min-height", "LengthOrPercentage",
                       "computed::LengthOrPercentage::Length(Au(0))",
                       "parse_non_negative")}
     ${predefined_type("max-height", "LengthOrPercentageOrNone",
                       "computed::LengthOrPercentageOrNone::None",
                       "parse_non_negative")}
 
-    ${new_style_struct("InheritedText", is_inherited=True, gecko_name="nsStyleText",
+    <% data.new_style_struct("InheritedText", inherited=True, gecko_ffi_name="nsStyleText",
                        additional_methods=([Method("clone__servo_text_decorations_in_effect",
                                                   "longhands::_servo_text_decorations_in_effect::computed_value::T")]
-                                           if CONFIG["product"] == "servo" else []))}
+                                           if product == "servo" else [])) %>
 
     <%self:longhand name="line-height">
         use cssparser::ToCss;
         use std::fmt;
         use values::AuExtensionMethods;
         use values::CSSFloat;
 
         #[derive(Debug, Clone, PartialEq, Copy, HeapSizeOf)]
@@ -804,17 +669,17 @@ pub mod longhands {
                             }
                         }
                     }
                 }
             }
         }
     </%self:longhand>
 
-    ${switch_to_style_struct("Box")}
+    <% data.switch_to_style_struct("Box") %>
 
     <%self:longhand name="vertical-align">
         use cssparser::ToCss;
         use std::fmt;
 
         <% vertical_align_keywords = (
             "baseline sub super top text-top middle bottom text-bottom".split()) %>
         #[allow(non_camel_case_types)]
@@ -957,25 +822,25 @@ pub mod longhands {
     // Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-type-y
     ${single_keyword("scroll-snap-type-y", "none mandatory proximity",
                      products="gecko", gecko_constant_prefix="NS_STYLE_SCROLL_SNAP_TYPE")}
 
     // Compositing and Blending Level 1
     // http://www.w3.org/TR/compositing-1/
     ${single_keyword("isolation", "auto isolate", products="gecko")}
 
-    ${switch_to_style_struct("InheritedBox")}
+    <% data.switch_to_style_struct("InheritedBox") %>
 
     // TODO: collapse. Well, do tables first.
     ${single_keyword("visibility", "visible hidden", extra_gecko_values="collapse",
                                                      gecko_ffi_name="mVisible")}
 
     // CSS 2.1, Section 12 - Generated content, automatic numbering, and lists
 
-    ${new_style_struct("Counters", is_inherited=False, gecko_name="nsStyleContent")}
+    <% data.new_style_struct("Counters", inherited=False, gecko_ffi_name="nsStyleContent") %>
 
     <%self:longhand name="content">
         use cssparser::Token;
         use std::ascii::AsciiExt;
         use values::computed::ComputedValueAsSpecified;
 
         use super::list_style_type;
 
@@ -1130,17 +995,17 @@ pub mod longhands {
             if !content.is_empty() {
                 Ok(SpecifiedValue::Content(content))
             } else {
                 Err(())
             }
         }
     </%self:longhand>
 
-    ${new_style_struct("List", is_inherited=True, gecko_name="nsStyleList")}
+    <% data.new_style_struct("List", inherited=True, gecko_ffi_name="nsStyleList") %>
 
     ${single_keyword("list-style-position", "outside inside")}
 
     // TODO(pcwalton): Implement the full set of counter styles per CSS-COUNTER-STYLES [1] 6.1:
     //
     //     decimal-leading-zero, armenian, upper-armenian, lower-armenian, georgian, lower-roman,
     //     upper-roman
     //
@@ -1278,17 +1143,17 @@ pub mod longhands {
             if !quotes.is_empty() {
                 Ok(SpecifiedValue(quotes))
             } else {
                 Err(())
             }
         }
     </%self:longhand>
 
-    ${switch_to_style_struct("Counters")}
+    <% data.switch_to_style_struct("Counters") %>
 
     <%self:longhand name="counter-increment">
         use std::fmt;
         use super::content;
         use values::computed::ComputedValueAsSpecified;
 
         use cssparser::{ToCss, Token, serialize_identifier};
         use std::borrow::{Cow, ToOwned};
@@ -1360,26 +1225,26 @@ pub mod longhands {
 
         pub fn parse(_: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
             parse_common(0, input)
         }
     </%self:longhand>
 
     // CSS 2.1, Section 13 - Paged media
 
-    ${switch_to_style_struct("Box")}
+    <% data.switch_to_style_struct("Box") %>
 
     ${single_keyword("page-break-after", "auto always avoid left right", products="gecko")}
     ${single_keyword("page-break-before", "auto always avoid left right", products="gecko")}
     ${single_keyword("page-break-inside", "auto avoid",
                      products="gecko", gecko_ffi_name="mBreakInside", gecko_constant_prefix="NS_STYLE_PAGE_BREAK")}
 
     // CSS 2.1, Section 14 - Colors and Backgrounds
 
-    ${new_style_struct("Background", is_inherited=False, gecko_name="nsStyleBackground")}
+    <% data.new_style_struct("Background", inherited=False, gecko_ffi_name="nsStyleBackground") %>
     ${predefined_type(
         "background-color", "CSSColor",
         "::cssparser::Color::RGBA(::cssparser::RGBA { red: 0., green: 0., blue: 0., alpha: 0. }) /* transparent */")}
 
     <%self:longhand name="background-image">
         use cssparser::ToCss;
         use std::fmt;
         use values::specified::Image;
@@ -1691,19 +1556,19 @@ pub mod longhands {
 
             Ok(SpecifiedValue::Explicit(SpecifiedExplicitSize {
                 width: width,
                 height: height,
             }))
         }
     </%self:longhand>
 
-    ${new_style_struct("Color", is_inherited=True, gecko_name="nsStyleColor",
+    <% data.new_style_struct("Color", inherited=True, gecko_ffi_name="nsStyleColor",
                        additional_methods=[Method("clone_color",
-                                                  "longhands::color::computed_value::T")])}
+                                                  "longhands::color::computed_value::T")]) %>
 
     <%self:raw_longhand name="color">
         use cssparser::Color as CSSParserColor;
         use cssparser::RGBA;
         use values::specified::{CSSColor, CSSRGBA};
 
         impl ToComputedValue for SpecifiedValue {
             type ComputedValue = computed_value::T;
@@ -1733,22 +1598,22 @@ pub mod longhands {
                 parsed: rgba,
                 authored: value.authored,
             }))
         }
     </%self:raw_longhand>
 
     // CSS 2.1, Section 15 - Fonts
 
-    ${new_style_struct("Font", is_inherited=True, gecko_name="nsStyleFont",
+    <% data.new_style_struct("Font", inherited=True, gecko_ffi_name="nsStyleFont",
                        additional_methods=[Method("clone_font_size",
                                                   "longhands::font_size::computed_value::T"),
                                            Method("clone_font_weight",
                                                   "longhands::font_weight::computed_value::T"),
-                                           Method("compute_font_hash", is_mut=True)])}
+                                           Method("compute_font_hash", is_mut=True)]) %>
 
     <%self:longhand name="font-family">
         use self::computed_value::FontFamily;
         use values::computed::ComputedValueAsSpecified;
         pub use self::computed_value::T as SpecifiedValue;
 
         const SERIF: &'static str = "serif";
         const SANS_SERIF: &'static str = "sans-serif";
@@ -2044,17 +1909,17 @@ pub mod longhands {
     ${single_keyword("font-stretch",
                      "normal ultra-condensed extra-condensed condensed semi-condensed semi-expanded \
                      expanded extra-expanded ultra-expanded")}
 
     ${single_keyword("font-kerning", "auto none normal", products="gecko")}
 
     // CSS 2.1, Section 16 - Text
 
-    ${switch_to_style_struct("InheritedText")}
+    <% data.switch_to_style_struct("InheritedText") %>
 
     <%self:longhand name="text-align">
         pub use self::computed_value::T as SpecifiedValue;
         use values::computed::ComputedValueAsSpecified;
         impl ComputedValueAsSpecified for SpecifiedValue {}
         pub mod computed_value {
             macro_rules! define_text_align {
                 ( $( $name: ident ( $string: expr ) => $discriminant: expr, )+ ) => {
@@ -2236,26 +2101,26 @@ pub mod longhands {
 
     // TODO(pcwalton): Support `word-break: keep-all` once we have better CJK support.
     ${single_keyword("word-break", "normal break-all", extra_gecko_values="keep-all",
                                                        gecko_constant_prefix="NS_STYLE_WORDBREAK")}
 
     // TODO(pcwalton): Support `text-justify: distribute`.
     ${single_keyword("text-justify", "auto none inter-word", products="servo")}
 
-    ${new_style_struct("Text", is_inherited=False, gecko_name="nsStyleTextReset",
+    <% data.new_style_struct("Text", inherited=False, gecko_ffi_name="nsStyleTextReset",
                        additional_methods=[Method("has_underline", "bool"),
                                            Method("has_overline", "bool"),
-                                           Method("has_line_through", "bool")])}
+                                           Method("has_line_through", "bool")]) %>
 
     ${single_keyword("text-overflow", "clip ellipsis")}
 
     ${single_keyword("unicode-bidi", "normal embed isolate bidi-override isolate-override plaintext")}
 
-    <%self:longhand name="text-decoration" custom_cascade="${CONFIG['product'] == 'servo'}">
+    <%self:longhand name="text-decoration" custom_cascade="${product == 'servo'}">
         use cssparser::ToCss;
         use std::fmt;
         use values::computed::ComputedValueAsSpecified;
 
         impl ComputedValueAsSpecified for SpecifiedValue {}
 
         #[derive(PartialEq, Eq, Copy, Clone, Debug, HeapSizeOf)]
         pub struct SpecifiedValue {
@@ -2320,33 +2185,33 @@ pub mod longhands {
                     "blink" => if blink { return Err(()) }
                                else { empty = false; blink = true },
                     _ => break
                 }
             }
             if !empty { Ok(result) } else { Err(()) }
         }
 
-        % if CONFIG["product"] == "servo":
+        % if product == "servo":
             fn cascade_property_custom<C: ComputedValues>(
                                        _declaration: &PropertyDeclaration,
                                        _inherited_style: &C,
                                        context: &mut computed::Context<C>,
                                        _seen: &mut PropertyBitField,
                                        _cacheable: &mut bool,
                                        _error_reporter: &mut StdBox<ParseErrorReporter + Send>) {
                     longhands::_servo_text_decorations_in_effect::derive_from_text_decoration(context);
             }
         % endif
     </%self:longhand>
 
     ${single_keyword("text-decoration-style", "-moz-none solid double dotted dashed wavy",
                      products="gecko")}
 
-    ${switch_to_style_struct("InheritedText")}
+    <% data.switch_to_style_struct("InheritedText") %>
 
     <%self:longhand name="-servo-text-decorations-in-effect"
                     derived_from="display text-decoration" products="servo">
         use cssparser::{RGBA, ToCss};
         use std::fmt;
 
         use values::computed::ComputedValueAsSpecified;
         use properties::style_struct_traits::{Box, Color, Text};
@@ -2474,21 +2339,21 @@ pub mod longhands {
 
     // CSS Ruby Layout Module Level 1
     // https://www.w3.org/TR/css-ruby-1/
     ${single_keyword("ruby-align", "start center space-between space-around", products="gecko")}
 
     ${single_keyword("ruby-position", "over under", products="gecko")}
 
     // CSS 2.1, Section 17 - Tables
-    ${new_style_struct("Table", is_inherited=False, gecko_name="nsStyleTable")}
+    <% data.new_style_struct("Table", inherited=False, gecko_ffi_name="nsStyleTable") %>
 
     ${single_keyword("table-layout", "auto fixed", gecko_ffi_name="mLayoutStrategy")}
 
-    ${new_style_struct("InheritedTable", is_inherited=True, gecko_name="nsStyleTableBorder")}
+    <% data.new_style_struct("InheritedTable", inherited=True, gecko_ffi_name="nsStyleTableBorder") %>
 
     ${single_keyword("border-collapse", "separate collapse", gecko_constant_prefix="NS_STYLE_BORDER")}
 
     ${single_keyword("empty-cells", "show hide", gecko_constant_prefix="NS_STYLE_TABLE_EMPTY_CELLS")}
 
     ${single_keyword("caption-side", "top bottom", extra_gecko_values="right left top-outside bottom-outside")}
 
     <%self:longhand name="border-spacing">
@@ -2577,45 +2442,45 @@ pub mod longhands {
                 }
                 (None, Some(_)) => panic!("shouldn't happen"),
             }
         }
     </%self:longhand>
 
     // CSS Fragmentation Module Level 3
     // https://www.w3.org/TR/css-break-3/
-    ${switch_to_style_struct("Border")}
+    <% data.switch_to_style_struct("Border") %>
 
     ${single_keyword("box-decoration-break", "slice clone", products="gecko")}
 
     // CSS Writing Modes Level 3
     // http://dev.w3.org/csswg/css-writing-modes/
-    ${switch_to_style_struct("InheritedBox")}
+    <% data.switch_to_style_struct("InheritedBox") %>
 
     ${single_keyword("writing-mode", "horizontal-tb vertical-rl vertical-lr", experimental=True)}
 
     // FIXME(SimonSapin): Add 'mixed' and 'upright' (needs vertical text support)
     // FIXME(SimonSapin): initial (first) value should be 'mixed', when that's implemented
     ${single_keyword("text-orientation", "sideways sideways-left sideways-right", experimental=True)}
 
     // CSS Color Module Level 4
     // https://drafts.csswg.org/css-color/
     ${single_keyword("color-adjust", "economy exact", products="gecko")}
 
     // CSS Basic User Interface Module Level 3
     // http://dev.w3.org/csswg/css-ui/
-    ${switch_to_style_struct("Box")}
+    <% data.switch_to_style_struct("Box") %>
 
     ${single_keyword("resize", "none both horizontal vertical", products="gecko")}
 
-    ${switch_to_style_struct("Position")}
+    <% data.switch_to_style_struct("Position") %>
 
     ${single_keyword("box-sizing", "content-box border-box")}
 
-    ${new_style_struct("Pointing", is_inherited=True, gecko_name="nsStyleUserInterface")}
+    <% data.new_style_struct("Pointing", inherited=True, gecko_ffi_name="nsStyleUserInterface") %>
 
     <%self:longhand name="cursor">
         pub use self::computed_value::T as SpecifiedValue;
         use values::computed::ComputedValueAsSpecified;
 
         impl ComputedValueAsSpecified for SpecifiedValue {}
 
         pub mod computed_value {
@@ -2657,17 +2522,17 @@ pub mod longhands {
     </%self:longhand>
 
     // NB: `pointer-events: auto` (and use of `pointer-events` in anything that isn't SVG, in fact)
     // is nonstandard, slated for CSS4-UI.
     // TODO(pcwalton): SVG-only values.
     ${single_keyword("pointer-events", "auto none")}
 
 
-    ${new_style_struct("Column", is_inherited=False, gecko_name="nsStyleColumn")}
+    <% data.new_style_struct("Column", inherited=False, gecko_ffi_name="nsStyleColumn") %>
 
     <%self:longhand name="column-width" experimental="True">
         use cssparser::ToCss;
         use std::fmt;
         use values::AuExtensionMethods;
 
         #[derive(Debug, Clone, Copy, PartialEq, HeapSizeOf)]
         pub enum SpecifiedValue {
@@ -2849,17 +2714,17 @@ pub mod longhands {
                 Ok(SpecifiedValue::Normal)
             } else {
                 specified::Length::parse_non_negative(input).map(SpecifiedValue::Specified)
             }
         }
     </%self:longhand>
 
     // Box-shadow, etc.
-    ${new_style_struct("Effects", is_inherited=False, gecko_name="nsStyleEffects")}
+    <% data.new_style_struct("Effects", inherited=False, gecko_ffi_name="nsStyleEffects") %>
 
     <%self:longhand name="opacity">
         use cssparser::ToCss;
         use std::fmt;
         use values::CSSFloat;
 
         impl ToCss for SpecifiedValue {
             fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
@@ -3253,17 +3118,17 @@ pub mod longhands {
                     left: sides[3].unwrap_or(Length::Absolute(Au(0))),
                 })))
             } else {
                 Err(())
             }
         }
     </%self:longhand>
 
-    ${switch_to_style_struct("InheritedText")}
+    <% data.switch_to_style_struct("InheritedText") %>
 
     <%self:longhand name="text-shadow">
         use cssparser::{self, ToCss};
         use std::fmt;
         use values::AuExtensionMethods;
 
         #[derive(Clone, PartialEq, Debug, HeapSizeOf)]
         pub struct SpecifiedValue(Vec<SpecifiedTextShadow>);
@@ -3433,17 +3298,17 @@ pub mod longhands {
                                     .map(|color| color.parsed)
                                     .unwrap_or(cssparser::Color::CurrentColor),
                     }
                 }).collect())
             }
         }
     </%self:longhand>
 
-    ${switch_to_style_struct("Effects")}
+    <% data.switch_to_style_struct("Effects") %>
 
     <%self:longhand name="filter">
         //pub use self::computed_value::T as SpecifiedValue;
         use cssparser::ToCss;
         use std::fmt;
         use values::AuExtensionMethods;
         use values::CSSFloat;
         use values::specified::{Angle, Length};
@@ -4371,21 +4236,21 @@ pub mod longhands {
     ${single_keyword("mix-blend-mode",
                      """normal multiply screen overlay darken lighten color-dodge
                         color-burn hard-light soft-light difference exclusion hue
                         saturation color luminosity""", gecko_constant_prefix="NS_STYLE_BLEND")}
 
     // CSS Image Values and Replaced Content Module Level 3
     // https://drafts.csswg.org/css-images-3/
 
-    ${switch_to_style_struct("Position")}
+    <% data.switch_to_style_struct("Position") %>
 
     ${single_keyword("object-fit", "fill contain cover none scale-down", products="gecko")}
 
-    ${switch_to_style_struct("InheritedBox")}
+    <% data.switch_to_style_struct("InheritedBox") %>
 
     <%self:longhand name="image-rendering">
 
         pub mod computed_value {
             use cssparser::ToCss;
             use std::fmt;
 
             #[derive(Copy, Clone, Debug, PartialEq, HeapSizeOf, Deserialize, Serialize)]
@@ -4432,17 +4297,17 @@ pub mod longhands {
 
             #[inline]
             fn to_computed_value<Cx: TContext>(&self, _: &Cx) -> computed_value::T {
                 *self
             }
         }
     </%self:longhand>
 
-    ${switch_to_style_struct("Box")}
+    <% data.switch_to_style_struct("Box") %>
 
     // TODO(pcwalton): Multiple transitions.
     <%self:longhand name="transition-duration">
         use values::specified::Time;
 
         pub use self::computed_value::T as SpecifiedValue;
         pub use values::specified::Time as SingleSpecifiedValue;
 
@@ -4958,17 +4823,17 @@ pub mod longhands {
         pub use properties::longhands::transition_duration::{computed_value};
         pub use properties::longhands::transition_duration::{get_initial_single_value};
         pub use properties::longhands::transition_duration::{get_initial_value, parse, parse_one};
     </%self:longhand>
 
     // CSS Flexible Box Layout Module Level 1
     // http://www.w3.org/TR/css3-flexbox/
 
-    ${switch_to_style_struct("Position")}
+    <% data.switch_to_style_struct("Position") %>
 
     // Flex container properties
     ${single_keyword("flex-direction", "row row-reverse column column-reverse", experimental=True)}
 
     // https://drafts.csswg.org/css-flexbox/#propdef-order
     <%self:longhand name="order">
         use values::computed::ComputedValueAsSpecified;
 
@@ -4989,17 +4854,17 @@ pub mod longhands {
             specified::parse_integer(input)
         }
     </%self:longhand>
 
     ${single_keyword("flex-wrap", "nowrap wrap wrap-reverse", products="gecko")}
 
     // SVG 1.1 (Second Edition)
     // https://www.w3.org/TR/SVG/
-    ${new_style_struct("SVGInherited", is_inherited=True, gecko_name="nsStyleSVG")}
+    <% data.new_style_struct("SVGInherited", inherited=True, gecko_ffi_name="nsStyleSVG") %>
 
     // Section 10 - Text
 
     ${single_keyword("text-anchor", "start middle end", products="gecko")}
 
     // Section 11 - Painting: Filling, Stroking and Marker Symbols
     ${single_keyword("color-interpolation", "auto sRGB linearRGB", products="gecko")}
 
@@ -5014,17 +4879,17 @@ pub mod longhands {
     ${single_keyword("stroke-linecap", "butt round square", products="gecko")}
 
     ${single_keyword("stroke-linejoin", "miter round bevel", products="gecko")}
 
     // Section 14 - Clipping, Masking and Compositing
     ${single_keyword("clip-rule", "nonzero evenodd",
                      products="gecko", gecko_constant_prefix="NS_STYLE_FILL_RULE")}
 
-    ${new_style_struct("SVG", is_inherited=False, gecko_name="nsStyleSVGReset")}
+    <% data.new_style_struct("SVG", inherited=False, gecko_ffi_name="nsStyleSVGReset") %>
 
     ${single_keyword("dominant-baseline",
                      """auto use-script no-change reset-size ideographic alphabetic hanging
                         mathematical central middle text-after-edge text-before-edge""",
                      products="gecko")}
 
     ${single_keyword("vector-effect", "none non-scaling-stroke", products="gecko")}
 
@@ -5036,18 +4901,17 @@ pub mod longhands {
 
 pub mod shorthands {
     use cssparser::Parser;
     use parser::ParserContext;
     use values::specified;
 
     <%def name="shorthand(name, sub_properties, experimental=False)">
     <%
-        shorthand = Shorthand(name, sub_properties.split(), experimental=experimental)
-        SHORTHANDS.append(shorthand)
+        shorthand = data.declare_shorthand(name, sub_properties.split(), experimental=experimental)
     %>
         pub mod ${shorthand.ident} {
             use cssparser::Parser;
             use parser::ParserContext;
             use properties::{longhands, PropertyDeclaration, DeclaredValue, Shorthand};
 
             pub struct Longhands {
                 % for sub_property in shorthand.sub_properties:
@@ -5749,51 +5613,51 @@ pub mod shorthands {
 }
 
 
 // TODO(SimonSapin): Convert this to a syntax extension rather than a Mako template.
 // Maybe submit for inclusion in libstd?
 mod property_bit_field {
 
     pub struct PropertyBitField {
-        storage: [u32; (${len(LONGHANDS)} - 1 + 32) / 32]
+        storage: [u32; (${len(data.longhands)} - 1 + 32) / 32]
     }
 
     impl PropertyBitField {
         #[inline]
         pub fn new() -> PropertyBitField {
-            PropertyBitField { storage: [0; (${len(LONGHANDS)} - 1 + 32) / 32] }
+            PropertyBitField { storage: [0; (${len(data.longhands)} - 1 + 32) / 32] }
         }
 
         #[inline]
         fn get(&self, bit: usize) -> bool {
             (self.storage[bit / 32] & (1 << (bit % 32))) != 0
         }
         #[inline]
         fn set(&mut self, bit: usize) {
             self.storage[bit / 32] |= 1 << (bit % 32)
         }
-        % for i, property in enumerate(LONGHANDS):
+        % for i, property in enumerate(data.longhands):
             % if not property.derived_from:
                 #[allow(non_snake_case)]
                 #[inline]
                 pub fn get_${property.ident}(&self) -> bool {
                     self.get(${i})
                 }
                 #[allow(non_snake_case)]
                 #[inline]
                 pub fn set_${property.ident}(&mut self) {
                     self.set(${i})
                 }
             % endif
         % endfor
     }
 }
 
-% for property in LONGHANDS:
+% for property in data.longhands:
     % if not property.derived_from:
         #[allow(non_snake_case)]
         fn substitute_variables_${property.ident}<F>(
             value: &DeclaredValue<longhands::${property.ident}::SpecifiedValue>,
             custom_properties: &Option<Arc<::custom_properties::ComputedValuesMap>>,
             f: F,
             error_reporter: &mut StdBox<ParseErrorReporter + Send>)
             where F: FnOnce(&DeclaredValue<longhands::${property.ident}::SpecifiedValue>)
@@ -5833,17 +5697,17 @@ mod property_bit_field {
                     // properties, so whatever...
                     let context = ParserContext::new(
                         ::stylesheets::Origin::Author, base_url, (*error_reporter).clone());
                     Parser::new(&css).parse_entirely(|input| {
                         match from_shorthand {
                             None => {
                                 longhands::${property.ident}::parse_specified(&context, input)
                             }
-                            % for shorthand in SHORTHANDS:
+                            % for shorthand in data.shorthands:
                                 % if property in shorthand.sub_properties:
                                     Some(Shorthand::${shorthand.camel_case}) => {
                                         shorthands::${shorthand.ident}::parse_value(&context, input)
                                         .map(|result| match result.${property.ident} {
                                             Some(value) => DeclaredValue::Value(value),
                                             None => DeclaredValue::Initial,
                                         })
                                     }
@@ -5953,17 +5817,17 @@ pub fn parse_property_declaration_list(c
 /// The input is in source order, output in reverse source order.
 fn deduplicate_property_declarations(declarations: Vec<PropertyDeclaration>)
                                      -> Vec<PropertyDeclaration> {
     let mut deduplicated = vec![];
     let mut seen = PropertyBitField::new();
     let mut seen_custom = Vec::new();
     for declaration in declarations.into_iter().rev() {
         match declaration {
-            % for property in LONGHANDS:
+            % for property in data.longhands:
                 PropertyDeclaration::${property.camel_case}(..) => {
                     % if not property.derived_from:
                         if seen.get_${property.ident}() {
                             continue
                         }
                         seen.set_${property.ident}()
                     % else:
                         unreachable!();
@@ -5998,41 +5862,41 @@ impl CSSWideKeyword {
             "unset" => Ok(CSSWideKeyword::UnsetKeyword),
             _ => Err(())
         }
     }
 }
 
 #[derive(Clone, Copy, Eq, PartialEq, Debug, HeapSizeOf)]
 pub enum Shorthand {
-    % for property in SHORTHANDS:
+    % 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 SHORTHANDS:
+            % for property in data.shorthands:
                 "${property.name}" => Some(Shorthand::${property.camel_case}),
             % endfor
             _ => None
         }
     }
 
     pub fn longhands(&self) -> &'static [&'static str] {
-        % for property in SHORTHANDS:
+        % for property in data.shorthands:
             static ${property.ident.upper()}: &'static [&'static str] = &[
                 % for sub in property.sub_properties:
                     "${sub.name}",
                 % endfor
             ];
         % endfor
         match *self {
-            % for property in SHORTHANDS:
+            % for property in data.shorthands:
                 Shorthand::${property.camel_case} => ${property.ident.upper()},
             % endfor
         }
     }
 }
 
 #[derive(Clone, PartialEq, Eq, Debug, HeapSizeOf)]
 pub enum DeclaredValue<T> {
@@ -6062,17 +5926,17 @@ impl<T: ToCss> ToCss for DeclaredValue<T
             DeclaredValue::Initial => dest.write_str("initial"),
             DeclaredValue::Inherit => dest.write_str("inherit"),
         }
     }
 }
 
 #[derive(PartialEq, Clone, Debug, HeapSizeOf)]
 pub enum PropertyDeclaration {
-    % for property in LONGHANDS:
+    % for property in data.longhands:
         ${property.camel_case}(DeclaredValue<longhands::${property.ident}::SpecifiedValue>),
     % endfor
     Custom(::custom_properties::Name, DeclaredValue<::custom_properties::SpecifiedValue>),
 }
 
 
 #[derive(Eq, PartialEq, Copy, Clone)]
 pub enum PropertyDeclarationParseResult {
@@ -6112,82 +5976,82 @@ impl fmt::Display for PropertyDeclaratio
             PropertyDeclarationName::Internal => Ok(()),
         }
     }
 }
 
 impl PropertyDeclaration {
     pub fn name(&self) -> PropertyDeclarationName {
         match *self {
-            % for property in LONGHANDS:
+            % for property in data.longhands:
                 PropertyDeclaration::${property.camel_case}(..) =>
                 % if not property.derived_from:
                     PropertyDeclarationName::Longhand("${property.name}"),
                 % else:
                     PropertyDeclarationName::Internal,
                 % endif
             % endfor
             PropertyDeclaration::Custom(ref name, _) => {
                 PropertyDeclarationName::Custom(name.clone())
             }
         }
     }
 
     pub fn value(&self) -> String {
         match *self {
-            % for property in LONGHANDS:
+            % for property in data.longhands:
                 PropertyDeclaration::${property.camel_case}
                 % if not property.derived_from:
                     (ref value) => value.to_css_string(),
                 % else:
                     (_) => panic!("unsupported property declaration: ${property.name}"),
                 % endif
             % endfor
             PropertyDeclaration::Custom(_, ref value) => value.to_css_string(),
         }
     }
 
     /// 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> {
         match *self {
-            % for property in LONGHANDS:
+            % 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
                 },
             % endfor
             PropertyDeclaration::Custom(..) => None,
         }
     }
 
     /// Return whether this is a pending-substitution value.
     /// https://drafts.csswg.org/css-variables/#variables-in-shorthands
     pub fn with_variables(&self) -> bool {
         match *self {
-            % for property in LONGHANDS:
+            % for property in data.longhands:
                 PropertyDeclaration::${property.camel_case}(ref value) => match *value {
                     DeclaredValue::WithVariables { .. } => true,
                     _ => false,
                 },
             % endfor
             PropertyDeclaration::Custom(_, ref value) => match *value {
                 DeclaredValue::WithVariables { .. } => true,
                 _ => false,
             }
         }
     }
 
     pub fn matches(&self, name: &str) -> bool {
         match *self {
-            % for property in LONGHANDS:
+            % 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, _) => {
@@ -6207,17 +6071,17 @@ impl PropertyDeclaration {
                     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,
-            % for property in LONGHANDS:
+            % for property in data.longhands:
                 % if not property.derived_from:
                     "${property.name}" => {
                         % if property.internal:
                             if context.stylesheet_origin != Origin::UserAgent {
                                 return PropertyDeclarationParseResult::UnknownProperty
                             }
                         % endif
                         % if property.experimental:
@@ -6233,17 +6097,17 @@ impl PropertyDeclaration {
                             },
                             Err(()) => PropertyDeclarationParseResult::InvalidValue,
                         }
                     },
                 % else:
                     "${property.name}" => PropertyDeclarationParseResult::UnknownProperty,
                 % endif
             % endfor
-            % for shorthand in SHORTHANDS:
+            % for shorthand in data.shorthands:
                 "${shorthand.name}" => {
                     % if shorthand.internal:
                         if context.stylesheet_origin != Origin::UserAgent {
                             return PropertyDeclarationParseResult::UnknownProperty
                         }
                     % endif
                     % if shorthand.experimental:
                         if !::util::prefs::get_pref("${shorthand.experimental}")
@@ -6287,17 +6151,17 @@ impl PropertyDeclaration {
             _ => PropertyDeclarationParseResult::UnknownProperty
         }
     }
 }
 
 pub mod style_struct_traits {
     use super::longhands;
 
-    % for style_struct in active_style_structs():
+    % for style_struct in data.active_style_structs():
         pub trait ${style_struct.trait_name}: Clone {
             % for longhand in style_struct.longhands:
                 #[allow(non_snake_case)]
                 fn set_${longhand.ident}(&mut self, v: longhands::${longhand.ident}::computed_value::T);
                 #[allow(non_snake_case)]
                 fn copy_${longhand.ident}_from(&mut self, other: &Self);
             % endfor
             % for additional in style_struct.additional_methods:
@@ -6308,17 +6172,17 @@ pub mod style_struct_traits {
     % endfor
 }
 
 pub mod style_structs {
     use fnv::FnvHasher;
     use super::longhands;
     use std::hash::{Hash, Hasher};
 
-    % for style_struct in active_style_structs():
+    % for style_struct in data.active_style_structs():
         % if style_struct.trait_name == "Font":
         #[derive(Clone, HeapSizeOf, Debug)]
         % else:
         #[derive(PartialEq, Clone, HeapSizeOf)]
         % endif
         pub struct ${style_struct.servo_struct_name} {
             % for longhand in style_struct.longhands:
                 pub ${longhand.ident}: longhands::${longhand.ident}::computed_value::T,
@@ -6398,17 +6262,17 @@ pub mod style_structs {
                     self.direction.clone()
                 }
                 fn clone_writing_mode(&self) -> longhands::writing_mode::computed_value::T {
                     self.writing_mode.clone()
                 }
                 fn clone_text_orientation(&self) -> longhands::text_orientation::computed_value::T {
                     self.text_orientation.clone()
                 }
-            % elif style_struct.trait_name == "InheritedText" and CONFIG["product"] == "servo":
+            % elif style_struct.trait_name == "InheritedText" and product == "servo":
                 fn clone__servo_text_decorations_in_effect(&self) ->
                     longhands::_servo_text_decorations_in_effect::computed_value::T {
                     self._servo_text_decorations_in_effect.clone()
                 }
             % elif style_struct.trait_name == "Outline":
                 fn outline_is_none_or_hidden_and_has_nonzero_width(&self) -> bool {
                     self.outline_style.none_or_hidden() && self.outline_width != ::app_units::Au(0)
                 }
@@ -6424,41 +6288,41 @@ pub mod style_structs {
                 }
             % endif
         }
 
     % endfor
 }
 
 pub trait ComputedValues : Clone + Send + Sync + 'static {
-    % for style_struct in active_style_structs():
+    % for style_struct in data.active_style_structs():
         type Concrete${style_struct.trait_name}: style_struct_traits::${style_struct.trait_name};
     % endfor
 
         // Temporary bailout case for stuff we haven't made work with the trait
         // yet - panics for non-Servo implementations.
         //
         // Used only for animations. Don't use it in other places.
         fn as_servo<'a>(&'a self) -> &'a ServoComputedValues;
         fn as_servo_mut<'a>(&'a mut self) -> &'a mut ServoComputedValues;
 
         fn new(custom_properties: Option<Arc<::custom_properties::ComputedValuesMap>>,
                shareable: bool,
                writing_mode: WritingMode,
                root_font_size: Au,
-        % for style_struct in active_style_structs():
+        % for style_struct in data.active_style_structs():
                ${style_struct.ident}: Arc<Self::Concrete${style_struct.trait_name}>,
         % endfor
         ) -> Self;
 
         fn initial_values() -> &'static Self;
 
         fn do_cascade_property<F: FnOnce(&Vec<Option<CascadePropertyFn<Self>>>)>(f: F);
 
-    % for style_struct in active_style_structs():
+    % for style_struct in data.active_style_structs():
         fn clone_${style_struct.trait_name_lower}(&self) ->
             Arc<Self::Concrete${style_struct.trait_name}>;
         fn get_${style_struct.trait_name_lower}<'a>(&'a self) ->
             &'a Self::Concrete${style_struct.trait_name};
         fn mutate_${style_struct.trait_name_lower}<'a>(&'a mut self) ->
             &'a mut Self::Concrete${style_struct.trait_name};
     % endfor
 
@@ -6466,59 +6330,59 @@ pub trait ComputedValues : Clone + Send 
     fn root_font_size(&self) -> Au;
     fn set_root_font_size(&mut self, size: Au);
     fn set_writing_mode(&mut self, mode: WritingMode);
     fn is_multicol(&self) -> bool;
 }
 
 #[derive(Clone, HeapSizeOf)]
 pub struct ServoComputedValues {
-    % for style_struct in active_style_structs():
+    % for style_struct in data.active_style_structs():
         ${style_struct.ident}: Arc<style_structs::${style_struct.servo_struct_name}>,
     % endfor
     custom_properties: Option<Arc<::custom_properties::ComputedValuesMap>>,
     shareable: bool,
     pub writing_mode: WritingMode,
     pub root_font_size: Au,
 }
 
 impl ComputedValues for ServoComputedValues {
-    % for style_struct in active_style_structs():
+    % for style_struct in data.active_style_structs():
         type Concrete${style_struct.trait_name} = style_structs::${style_struct.servo_struct_name};
     % endfor
 
         fn as_servo<'a>(&'a self) -> &'a ServoComputedValues { self }
         fn as_servo_mut<'a>(&'a mut self) -> &'a mut ServoComputedValues { self }
 
         fn new(custom_properties: Option<Arc<::custom_properties::ComputedValuesMap>>,
                shareable: bool,
                writing_mode: WritingMode,
                root_font_size: Au,
-            % for style_struct in active_style_structs():
+            % for style_struct in data.active_style_structs():
                ${style_struct.ident}: Arc<style_structs::${style_struct.servo_struct_name}>,
             % endfor
         ) -> Self {
             ServoComputedValues {
                 custom_properties: custom_properties,
                 shareable: shareable,
                 writing_mode: writing_mode,
                 root_font_size: root_font_size,
-            % for style_struct in active_style_structs():
+            % for style_struct in data.active_style_structs():
                 ${style_struct.ident}: ${style_struct.ident},
             % endfor
             }
         }
 
         fn initial_values() -> &'static Self { &*INITIAL_SERVO_VALUES }
 
         fn do_cascade_property<F: FnOnce(&Vec<Option<CascadePropertyFn<Self>>>)>(f: F) {
             CASCADE_PROPERTY.with(|x| f(x));
         }
 
-    % for style_struct in active_style_structs():
+    % for style_struct in data.active_style_structs():
         #[inline]
         fn clone_${style_struct.trait_name_lower}(&self) ->
             Arc<Self::Concrete${style_struct.trait_name}> {
                 self.${style_struct.ident}.clone()
             }
         #[inline]
         fn get_${style_struct.trait_name_lower}<'a>(&'a self) ->
             &'a Self::Concrete${style_struct.trait_name} {
@@ -6710,17 +6574,17 @@ impl ServoComputedValues {
         }
 
         // Neither perspective nor transform present
         false
     }
 
     pub fn computed_value_to_string(&self, name: &str) -> Result<String, ()> {
         match name {
-            % for style_struct in active_style_structs():
+            % 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()),
                 % 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(()));
@@ -6764,17 +6628,17 @@ pub fn get_writing_mode<S: style_struct_
     }
     flags
 }
 
 
 /// The initial values for all style structs as defined by the specification.
 lazy_static! {
     pub static ref INITIAL_SERVO_VALUES: ServoComputedValues = ServoComputedValues {
-        % for style_struct in active_style_structs():
+        % for style_struct in data.active_style_structs():
             ${style_struct.ident}: Arc::new(style_structs::${style_struct.servo_struct_name} {
                 % for longhand in style_struct.longhands:
                     ${longhand.ident}: longhands::${longhand.ident}::get_initial_value(),
                 % endfor
                 % if style_struct.trait_name == "Font":
                     hash: 0,
                 % endif
             }),
@@ -6802,34 +6666,34 @@ fn cascade_with_cached_declarations<C: C
         is_root_element: false,
         viewport_size: viewport_size,
         inherited_style: parent_style,
         style: C::new(
             custom_properties,
             shareable,
             WritingMode::empty(),
             parent_style.root_font_size(),
-            % for style_struct in active_style_structs():
+            % for style_struct in data.active_style_structs():
                 % if style_struct.inherited:
                     parent_style
                 % else:
                     cached_style
                 % endif
                     .clone_${style_struct.trait_name_lower}(),
             % endfor
         ),
     };
     let mut seen = PropertyBitField::new();
     // Declaration blocks are stored in increasing precedence order,
     // we want them in decreasing order here.
     for sub_list in applicable_declarations.iter().rev() {
         // Declarations are already stored in reverse order.
         for declaration in sub_list.declarations.iter() {
             match *declaration {
-                % for style_struct in active_style_structs():
+                % for style_struct in data.active_style_structs():
                     % for property in style_struct.longhands:
                         % if not property.derived_from:
                             PropertyDeclaration::${property.camel_case}(ref
                                     ${'_' if not style_struct.inherited else ''}declared_value)
                                     => {
                                     use properties::style_struct_traits::${style_struct.trait_name};
                                 % if style_struct.inherited:
                                     if seen.get_${property.ident}() {
@@ -6862,18 +6726,18 @@ fn cascade_with_cached_declarations<C: C
                                                 context.mutate_style().mutate_${style_struct.trait_name_lower}()
                                                        .copy_${property.ident}_from(inherited_struct);
                                             }
                                             DeclaredValue::WithVariables { .. } => unreachable!()
                                         }, &mut error_reporter
                                     );
                                 % endif
 
-                                % if property.name in DERIVED_LONGHANDS:
-                                    % for derived in DERIVED_LONGHANDS[property.name]:
+                                % if property.name in data.derived_longhands:
+                                    % for derived in data.derived_longhands[property.name]:
                                             longhands::${derived.ident}
                                                      ::derive_from_${property.ident}(&mut context);
                                     % endfor
                                 % endif
                             }
                         % else:
                             PropertyDeclaration::${property.camel_case}(_) => {
                                 // Do not allow stylesheets to set derived properties.
@@ -6900,17 +6764,17 @@ pub type CascadePropertyFn<C /*: Compute
                      inherited_style: &C,
                      context: &mut computed::Context<C>,
                      seen: &mut PropertyBitField,
                      cacheable: &mut bool,
                      error_reporter: &mut StdBox<ParseErrorReporter + Send>);
 
 pub fn make_cascade_vec<C: ComputedValues>() -> Vec<Option<CascadePropertyFn<C>>> {
     let mut result: Vec<Option<CascadePropertyFn<C>>> = Vec::new();
-    % for style_struct in active_style_structs():
+    % for style_struct in data.active_style_structs():
         % for property in style_struct.longhands:
             let discriminant;
             unsafe {
                 let variant = PropertyDeclaration::${property.camel_case}(intrinsics::uninit());
                 discriminant = intrinsics::discriminant_value(&variant) as usize;
                 mem::forget(variant);
             }
             while result.len() < discriminant + 1 {
@@ -6995,17 +6859,17 @@ pub fn cascade<C: ComputedValues>(
         is_root_element: is_root_element,
         viewport_size: viewport_size,
         inherited_style: inherited_style,
         style: C::new(
             custom_properties,
             shareable,
             WritingMode::empty(),
             inherited_style.root_font_size(),
-            % for style_struct in active_style_structs():
+            % for style_struct in data.active_style_structs():
             % if style_struct.inherited:
             inherited_style
             % else:
             initial_values
             % endif
                 .clone_${style_struct.trait_name_lower}(),
             % endfor
         ),
@@ -7085,17 +6949,17 @@ pub fn cascade<C: ComputedValues>(
             T::table_caption => {
                 Some(T::block)
             }
             _ => None
         };
         if let Some(computed_display) = computed_display {
             let box_ = style.mutate_box();
             box_.set_display(computed_display);
-            % if CONFIG["product"] == "servo":
+            % if product == "servo":
                 box_.set__servo_display_for_hypothetical_box(if is_root_element {
                     computed_display
                 } else {
                     specified_display
                 });
             % endif
         }
     }
@@ -7315,45 +7179,45 @@ pub fn modify_style_for_inline_absolute_
         let mut style = Arc::make_mut(style);
         let effects_style = Arc::make_mut(&mut style.effects);
         effects_style.clip.0 = None
     }
 }
 
 pub fn is_supported_property(property: &str) -> bool {
     match_ignore_ascii_case! { property,
-        % for property in SHORTHANDS + LONGHANDS:
+        % 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 SHORTHANDS + LONGHANDS:
+            % 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}"],
                     % endif
-                    % if property != LONGHANDS[-1]:
+                    % 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
         }
     }
 }
 
 
 macro_rules! longhand_properties_idents {
     ($macro_name: ident) => {
         $macro_name! {
-            % for property in LONGHANDS:
+            % for property in data.longhands:
                 ${property.ident}
             % endfor
         }
     }
 }
--- a/servo/etc/ci/upload_docs.sh
+++ b/servo/etc/ci/upload_docs.sh
@@ -7,12 +7,12 @@
 set -e
 
 cd "$(dirname $0)/../.."
 
 ./mach doc
 # etc/doc.servo.org/index.html overwrites $(mach rust-root)/doc/index.html
 cp etc/doc.servo.org/* target/doc/
 
-python components/style/list_properties.py
+python components/style/properties/build.py servo html
 
 ghp-import -n target/doc
 git push -qf https://${TOKEN}@github.com/servo/doc.servo.org.git gh-pages
--- a/servo/ports/geckolib/build.rs
+++ b/servo/ports/geckolib/build.rs
@@ -1,17 +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 std::env;
-use std::fs::File;
-use std::io::Write;
 use std::path::Path;
-use std::process::{Command, Stdio, exit};
+use std::process::{Command, exit};
 
 #[cfg(windows)]
 fn find_python() -> String {
     if Command::new("python27.exe").arg("--version").output().is_ok() {
         return "python27.exe".to_owned();
     }
 
     if Command::new("python.exe").arg("--version").output().is_ok() {
@@ -26,37 +24,30 @@ fn find_python() -> String {
     if Command::new("python2.7").arg("--version").output().unwrap().status.success() {
         "python2.7"
     } else {
         "python"
     }.to_owned()
 }
 
 fn main() {
-    let python = match env::var("PYTHON") {
-        Ok(python_path) => python_path,
-        Err(_) => find_python(),
-    };
+    let python = env::var("PYTHON").ok().unwrap_or_else(find_python);
 
     // Mako refuses to load templates outside the scope of the current working directory,
     // so we need to run it from the top source directory.
     let geckolib_dir = Path::new(file!()).parent().unwrap();
     let top_dir = geckolib_dir.join("..").join("..");
 
-    let style_template = Path::new("components/style/properties.mako.rs");
-    let geckolib_template = Path::new("ports/geckolib/properties.mako.rs");
-    let mako = Path::new("components/style/Mako-0.9.1.zip");
+    let properties_dir = Path::new("components").join("style").join("properties");
+    println!("cargo:rerun-if-changed={}", top_dir.join(&properties_dir).to_str().unwrap());
+    println!("cargo:rerun-if-changed={}", geckolib_dir.join("properties.mako.rs").to_str().unwrap());
 
-    let result = Command::new(python)
-        .current_dir(top_dir)
-        .env("PYTHONPATH", &mako)
-        .env("STYLE_TEMPLATE", &style_template)
-        .env("GECKOLIB_TEMPLATE", &geckolib_template)
-        .arg("ports/geckolib/generate_properties_rs.py")
-        .stderr(Stdio::inherit())
-        .output()
+    let status = Command::new(python)
+        .current_dir(&top_dir)
+        .arg(&properties_dir.join("build.py"))
+        .arg("gecko")
+        .arg("geckolib")
+        .status()
         .unwrap();
-    if !result.status.success() {
+    if !status.success() {
         exit(1)
     }
-    let out = env::var("OUT_DIR").unwrap();
-    File::create(&Path::new(&out).join("properties.rs")).unwrap().write_all(&result.stdout).unwrap();
 }
--- a/servo/ports/geckolib/properties.mako.rs
+++ b/servo/ports/geckolib/properties.mako.rs
@@ -1,16 +1,20 @@
 /* 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/. */
 
-// STYLE_STRUCTS comes from components/style/properties.mako.rs; see build.rs for more details.
+// `data` comes from components/style/properties.mako.rs; see build.rs for more details.
+
+<%!
+    from data import to_rust_ident
+%>
 
 use app_units::Au;
-% for style_struct in STYLE_STRUCTS:
+% for style_struct in data.style_structs:
 %if style_struct.gecko_ffi_name:
 use gecko_style_structs::${style_struct.gecko_ffi_name};
 use bindings::Gecko_Construct_${style_struct.gecko_ffi_name};
 use bindings::Gecko_CopyConstruct_${style_struct.gecko_ffi_name};
 use bindings::Gecko_Destroy_${style_struct.gecko_ffi_name};
 % endif
 % endfor
 use gecko_style_structs;
@@ -22,61 +26,61 @@ use style::custom_properties::ComputedVa
 use style::logical_geometry::WritingMode;
 use style::properties::{CascadePropertyFn, ServoComputedValues, ComputedValues};
 use style::properties::longhands;
 use style::properties::make_cascade_vec;
 use style::properties::style_struct_traits::*;
 
 #[derive(Clone)]
 pub struct GeckoComputedValues {
-    % for style_struct in STYLE_STRUCTS:
+    % for style_struct in data.style_structs:
     ${style_struct.ident}: Arc<${style_struct.gecko_struct_name}>,
     % endfor
 
     custom_properties: Option<Arc<ComputedValuesMap>>,
     shareable: bool,
     pub writing_mode: WritingMode,
     pub root_font_size: Au,
 }
 
 impl ComputedValues for GeckoComputedValues {
-% for style_struct in STYLE_STRUCTS:
+% for style_struct in data.style_structs:
     type Concrete${style_struct.trait_name} = ${style_struct.gecko_struct_name};
 % endfor
 
     // These will go away, and we will never implement them.
     fn as_servo<'a>(&'a self) -> &'a ServoComputedValues { unimplemented!() }
     fn as_servo_mut<'a>(&'a mut self) -> &'a mut ServoComputedValues { unimplemented!() }
 
     fn new(custom_properties: Option<Arc<ComputedValuesMap>>,
            shareable: bool,
            writing_mode: WritingMode,
            root_font_size: Au,
-            % for style_struct in STYLE_STRUCTS:
+            % for style_struct in data.style_structs:
            ${style_struct.ident}: Arc<${style_struct.gecko_struct_name}>,
             % endfor
     ) -> Self {
         GeckoComputedValues {
             custom_properties: custom_properties,
             shareable: shareable,
             writing_mode: writing_mode,
             root_font_size: root_font_size,
-            % for style_struct in STYLE_STRUCTS:
+            % for style_struct in data.style_structs:
             ${style_struct.ident}: ${style_struct.ident},
             % endfor
         }
     }
 
     fn initial_values() -> &'static Self { &*INITIAL_GECKO_VALUES }
 
     fn do_cascade_property<F: FnOnce(&Vec<Option<CascadePropertyFn<Self>>>)>(f: F) {
         CASCADE_PROPERTY.with(|x| f(x));
     }
 
-    % for style_struct in STYLE_STRUCTS:
+    % for style_struct in data.style_structs:
     #[inline]
     fn clone_${style_struct.trait_name_lower}(&self) -> Arc<Self::Concrete${style_struct.trait_name}> {
         self.${style_struct.ident}.clone()
     }
     #[inline]
     fn get_${style_struct.trait_name_lower}<'a>(&'a self) -> &'a Self::Concrete${style_struct.trait_name} {
         &self.${style_struct.ident}
     }
@@ -232,23 +236,23 @@ impl ${style_struct.trait_name} for ${st
     <% additionals = [x for x in style_struct.additional_methods
                       if not (skip_additionals and x.name in skip_additionals)] %>
     % for additional in additionals:
     ${additional.stub()}
     % endfor
 }
 </%def>
 
-<%! MANUAL_STYLE_STRUCTS = [] %>
+<% data.manual_style_structs = [] %>
 <%def name="impl_trait(style_struct_name, skip_longhands=None, skip_additionals=None)">
-<%self:raw_impl_trait style_struct="${next(x for x in STYLE_STRUCTS if x.trait_name == style_struct_name)}"
+<%self:raw_impl_trait style_struct="${next(x for x in data.style_structs if x.trait_name == style_struct_name)}"
                       skip_longhands="${skip_longhands}" skip_additionals="${skip_additionals}">
 ${caller.body()}
 </%self:raw_impl_trait>
-<% MANUAL_STYLE_STRUCTS.append(style_struct_name) %>
+<% data.manual_style_structs.append(style_struct_name) %>
 </%def>
 
 // Proof-of-concept for a style struct with some manually-implemented methods. We add
 // the manually-implemented methods to skip_longhands and skip_additionals, and the
 // infrastructure auto-generates everything not in those lists. This allows us to
 // iteratively implement more and more methods.
 <%self:impl_trait style_struct_name="Border"
                   skip_longhands="${['border-left-color', 'border-left-style']}"
@@ -265,27 +269,27 @@ impl ${style_struct.trait_name} for ${st
     fn copy_border_left_style_from(&mut self, _: &Self) {
         unimplemented!()
     }
     fn border_bottom_is_none_or_hidden_and_has_nonzero_width(&self) -> bool {
         unimplemented!()
     }
 </%self:impl_trait>
 
-% for style_struct in STYLE_STRUCTS:
+% for style_struct in data.style_structs:
 ${declare_style_struct(style_struct)}
 ${impl_style_struct(style_struct)}
-% if not style_struct.trait_name in MANUAL_STYLE_STRUCTS:
+% if not style_struct.trait_name in data.manual_style_structs:
 <%self:raw_impl_trait style_struct="${style_struct}"></%self:raw_impl_trait>
 % endif
 % endfor
 
 lazy_static! {
     pub static ref INITIAL_GECKO_VALUES: GeckoComputedValues = GeckoComputedValues {
-        % for style_struct in STYLE_STRUCTS:
+        % for style_struct in data.style_structs:
            ${style_struct.ident}: ${style_struct.gecko_struct_name}::initial(),
         % endfor
         custom_properties: None,
         shareable: true,
         writing_mode: WritingMode::empty(),
         root_font_size: longhands::font_size::get_initial_value(),
     };
 }
--- a/servo/python/servo/testing_commands.py
+++ b/servo/python/servo/testing_commands.py
@@ -136,30 +136,17 @@ class MachCommands(CommandBase):
 
     @Command('test-unit',
              description='Run unit tests',
              category='testing')
     @CommandArgument('--package', '-p', default=None, help="Specific package to test")
     @CommandArgument('test_name', nargs=argparse.REMAINDER,
                      help="Only run tests that match this pattern or file path")
     def test_unit(self, test_name=None, package=None):
-        subprocess.check_output([
-            sys.executable,
-            path.join(self.context.topdir, "components", "style", "list_properties.py")
-        ])
-
-        this_file = os.path.dirname(__file__)
-        servo_doc_path = os.path.abspath(os.path.join(this_file, '../', '../', 'target', 'doc', 'servo'))
-
-        with open(os.path.join(servo_doc_path, 'css-properties.json'), 'r') as property_file:
-            properties = json.loads(property_file.read())
-
-        assert len(properties) >= 100
-        assert "margin-top" in properties
-        assert "margin" in properties
+        check_css_properties_json(self.context.topdir)
 
         if test_name is None:
             test_name = []
 
         self.ensure_bootstrapped()
 
         if package:
             packages = {package}
@@ -665,8 +652,28 @@ testing/web-platform/mozilla/tests for S
             if kwargs["release"]:
                 args.append("--release")
             args.append(test_path)
             wpt_kwargs = vars(p.parse_args(args))
             self.context.commands.dispatch("test-wpt", self.context, **wpt_kwargs)
 
         if editor:
             proc.wait()
+
+
+def check_css_properties_json(topdir):
+    print("Testing generation of css-properties.json...")
+    filename = path.join(topdir, "target", "doc", "servo", "css-properties.json")
+
+    if path.exists(filename):
+        os.remove(filename)
+    subprocess.check_call([
+        sys.executable,
+        path.join(topdir, "components", "style", "properties", "build.py"),
+        "servo",
+        "html",
+    ])
+    properties = json.load(open(filename))
+
+    assert len(properties) >= 100
+    assert "margin-top" in properties
+    assert "margin" in properties
+    print("OK")