Bug 1454591 part 1 - Generate more structured data in ServoCSSPropList.py. r?heycam draft
authorXidorn Quan <me@upsuper.org>
Fri, 04 May 2018 13:44:51 +1000
changeset 791363 3d002f7790b76aaa3cea373430206c7629825e47
parent 790065 502c2f271bee2e3126fe641d0c99842de3cd86c6
child 791364 3304d221aa8378873137f1500f982e45b9f643c0
push id108797
push userxquan@mozilla.com
push dateFri, 04 May 2018 07:07:28 +0000
reviewersheycam
bugs1454591
milestone61.0a1
Bug 1454591 part 1 - Generate more structured data in ServoCSSPropList.py. r?heycam This patch changes ServoCSSPropList.py to use classes for properties. This allows extending the data in the file without needing to update all users of this file. Sorting in GenerateCSSPropsGenerated.py is removed because the data file has the right order already. MozReview-Commit-ID: D74bItCfpPH
devtools/shared/css/generated/mach_commands.py
dom/bindings/GenerateCSS2PropertiesWebIDL.py
layout/style/GenerateCSSPropertyID.py
layout/style/GenerateCSSPropsGenerated.py
layout/style/GenerateServoCSSPropList.py
layout/style/ServoCSSPropList.mako.py
--- a/devtools/shared/css/generated/mach_commands.py
+++ b/devtools/shared/css/generated/mach_commands.py
@@ -5,16 +5,17 @@
 """
 This script implements the `mach devtools-css-db` command. It runs an xpcshell
 script that uses InspectorUtils to query the CSS properties used by the browser.
 This information is used to generate the properties-db.js file.
 """
 
 import json
 import os
+import runpy
 import sys
 import string
 import subprocess
 from mozbuild import shellutil
 from mozbuild.base import (
     MozbuildObject,
     MachCommandBase,
 )
@@ -48,25 +49,22 @@ class MachCommands(MachCommandBase):
             'cssProperties': stringify(db['cssProperties']),
             'pseudoElements': stringify(db['pseudoElements'])})
 
     def get_preferences(self):
         """Get all of the preferences associated with enabling and disabling a property."""
         # The data takes the following form:
         # [ (name, prop, id, flags, pref, proptype), ... ]
         dataPath = resolve_path(self.topobjdir, 'layout/style/ServoCSSPropList.py')
-        with open(dataPath, "r") as f:
-            data = eval(f.read())
+        data = runpy.run_path(dataPath)['data']
 
         # Map this list
-        # (name, prop, id, flags, pref, proptype) => (name, pref)
         preferences = [
-            (name, pref)
-            for name, prop, id, flags, pref, proptype in data
-            if 'CSSPropFlags::Internal' not in flags and pref]
+            (p.name, p.pref) for p in data
+            if 'CSSPropFlags::Internal' not in p.flags and p.pref]
 
         return preferences
 
     def get_properties_db_from_xpcshell(self):
         """Generate the static css properties db for devtools from an xpcshell script."""
         build = MozbuildObject.from_environment()
 
         # Get the paths
--- a/dom/bindings/GenerateCSS2PropertiesWebIDL.py
+++ b/dom/bindings/GenerateCSS2PropertiesWebIDL.py
@@ -1,35 +1,37 @@
 # 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 sys
 import string
 import argparse
+import runpy
 
 # Generates a line of WebIDL with the given spelling of the property name
 # (whether camelCase, _underscorePrefixed, etc.) and the given array of
 # extended attributes.
 def generateLine(propName, extendedAttrs):
     return "  [%s] attribute DOMString %s;\n" % (", ".join(extendedAttrs),
                                                  propName)
 def generate(output, idlFilename, dataFile):
-    with open(dataFile, "r") as f:
-        propList = eval(f.read())
+    propList = runpy.run_path(dataFile)["data"]
     props = ""
-    for name, prop, id, flags, pref, proptype in propList:
-        if "CSSPropFlags::Internal" in flags:
+    for p in propList:
+        if "CSSPropFlags::Internal" in p.flags:
             continue
         # Unfortunately, even some of the getters here are fallible
         # (e.g. on nsComputedDOMStyle).
         extendedAttrs = ["CEReactions", "Throws", "TreatNullAs=EmptyString",
                          "SetterNeedsSubjectPrincipal=NonSystem"]
-        if pref is not "":
-            extendedAttrs.append('Pref="%s"' % pref)
+        if p.pref is not "":
+            extendedAttrs.append('Pref="%s"' % p.pref)
+
+        prop = p.method
 
         # webkit properties get a camelcase "webkitFoo" accessor
         # as well as a capitalized "WebkitFoo" alias (added here).
         if (prop.startswith("Webkit")):
             extendedAttrs.append('BindingAlias="%s"' % prop)
 
         # Generate a name with camelCase spelling of property-name (or capitalized,
         # for Moz-prefixed properties):
@@ -47,18 +49,18 @@ def generate(output, idlFilename, dataFi
         # Note that "float" will cause a property called "float" to exist due to (1)
         # in that list.
         #
         # In practice, cssFloat is the only case in which "name" doesn't contain
         # "-" but also doesn't match "prop".  So the generateLine() call will
         # cover (3) and all of (1) except "float".  If we now add an alias
         # for all the cases where "name" doesn't match "prop", that will cover
         # "float" and (2).
-        if prop != name:
-            extendedAttrs.append('BindingAlias="%s"' % name)
+        if prop != p.name:
+            extendedAttrs.append('BindingAlias="%s"' % p.name)
 
         props += generateLine(prop, extendedAttrs)
 
 
     idlFile = open(idlFilename, "r")
     idlTemplate = idlFile.read()
     idlFile.close()
 
--- a/layout/style/GenerateCSSPropertyID.py
+++ b/layout/style/GenerateCSSPropertyID.py
@@ -1,35 +1,35 @@
 # 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 runpy
 import string
 
 def generate(output, template, dataFile):
     with open(template, "r") as f:
         template = string.Template(f.read())
-    with open(dataFile, "r") as f:
-        data = eval(f.read())
+    data = runpy.run_path(dataFile)["data"]
 
     longhand_count = 0
     shorthand_count = 0
     alias_count = 0
     property_ids = []
-    for name, method, id, flags, pref, prototype in data:
-        if prototype != "alias":
-            if prototype == "longhand":
+    for prop in data:
+        if prop.type() != "alias":
+            if prop.type() == "longhand":
                 assert shorthand_count == 0
                 longhand_count += 1
             else:
                 assert alias_count == 0
                 shorthand_count += 1
-            property_ids.append("eCSSProperty_{}".format(id))
+            property_ids.append("eCSSProperty_{}".format(prop.id))
         else:
             alias_count += 1
-            property_ids.append("eCSSPropertyAlias_{}".format(id[0]))
+            property_ids.append("eCSSPropertyAlias_{}".format(prop.alias_id))
 
     output.write("/* THIS IS AN AUTOGENERATED FILE.  DO NOT EDIT */\n\n")
     output.write(template.substitute({
         "property_ids": "\n".join("  {},".format(p) for p in property_ids),
         "longhand_count": property_ids[longhand_count],
         "shorthand_count": property_ids[longhand_count + shorthand_count],
     }))
--- a/layout/style/GenerateCSSPropsGenerated.py
+++ b/layout/style/GenerateCSSPropsGenerated.py
@@ -1,81 +1,72 @@
 # 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 runpy
 import sys
 import string
 import argparse
 
-def get_properties(dataFile):
-    with open(dataFile, "r") as f:
-        properties = eval(f.read())
-    properties = [{"name":p[0], "prop":p[1], "id":p[2],
-                   "flags":p[3], "pref":p[4], "proptype":p[5]}
-                  for (i, p) in enumerate(properties)]
-
-    # Sort the list so that longhand properties are intermingled first,
-    # shorthand properties follow, then aliases appear last.
-    # This matches the order of the nsCSSPropertyID enum.
+class PropertyWrapper(object):
+    __slots__ = ["index", "prop", "idlname"]
 
-    def property_compare(x, y):
-        property_order = {"longhand": 0, "shorthand": 1, "alias": 2}
-        return property_order[x["proptype"]] - property_order[y["proptype"]]
-
-    properties = sorted(properties, cmp=property_compare)
-
-    for i, p in enumerate(properties):
-        p["index"] = i
-
-    # Record each property's IDL name.
-    for p in properties:
-        if "CSSPropFlags::Internal" in p["flags"]:
-            p["idlname"] = None
+    def __init__(self, index, prop):
+        self.index = index
+        self.prop = prop
+        if "CSSPropFlags::Internal" in prop.flags:
+            self.idlname = None
         else:
-            idl_name = p["prop"]
+            idl_name = prop.method
             if not idl_name.startswith("Moz"):
                 idl_name = idl_name[0].lower() + idl_name[1:]
-            p["idlname"] = idl_name
+            self.idlname = idl_name
+
+    def __getattr__(self, name):
+        return getattr(self.prop, name)
 
-    return properties
+
+def get_properties(dataFile):
+    properties = runpy.run_path(dataFile)["data"]
+    return [PropertyWrapper(i, p) for i, p in enumerate(properties)]
 
 def generate_idl_names(properties):
     names = []
     for p in properties:
-        if p["proptype"] is "alias":
+        if p.type() == "alias":
             continue
-        if p["idlname"] is None:
-            names.append("  nullptr,  // %s" % p["name"])
+        if p.idlname is None:
+            names.append("  nullptr,  // %s" % p.name)
         else:
-            names.append('  "%s",' % p["idlname"])
+            names.append('  "%s",' % p.idlname)
     return "\n".join(names)
 
 def generate_assertions(properties):
     def enum(p):
-        if p["proptype"] is "alias":
-            return "eCSSPropertyAlias_%s" % p["id"][0]
+        if p.type() == "alias":
+            return "eCSSPropertyAlias_%s" % p.alias_id
         else:
-            return "eCSSProperty_%s" % p["id"]
+            return "eCSSProperty_%s" % p.id
     msg = ('static_assert(%s == %d, "GenerateCSSPropsGenerated.py did not list '
            'properties in nsCSSPropertyID order");')
-    return "\n".join(map(lambda p: msg % (enum(p), p["index"]), properties))
+    return "\n".join(map(lambda p: msg % (enum(p), p.index), properties))
 
 def generate_idl_name_positions(properties):
     # Skip aliases.
-    ps = filter(lambda p: p["proptype"] is not "alias", properties)
+    ps = filter(lambda p: p.type() != "alias", properties)
 
     # Sort alphabetically by IDL name.
-    ps = sorted(ps, key=lambda p: p["idlname"])
+    ps = sorted(ps, key=lambda p: p.idlname)
 
     # Annotate entries with the sorted position.
     ps = [(p, position) for position, p in enumerate(ps)]
 
     # Sort back to nsCSSPropertyID order.
-    ps = sorted(ps, key=lambda (p, position): p["index"])
+    ps = sorted(ps, key=lambda (p, position): p.index)
 
     return ",\n".join(map(lambda (p, position): "  %d" % position, ps))
 
 def generate(output, cppTemplate, dataFile):
     cppFile = open(cppTemplate, "r")
     cppTemplate = cppFile.read()
     cppFile.close()
 
--- a/layout/style/GenerateServoCSSPropList.py
+++ b/layout/style/GenerateServoCSSPropList.py
@@ -1,15 +1,16 @@
 # 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 buildconfig
 import mozpack.path as mozpath
 import os
+import runpy
 import subprocess
 import string
 import sys
 
 SERVO_BASE = mozpath.join(buildconfig.topsrcdir, "servo")
 SERVO_PROP_BASE = mozpath.join(SERVO_BASE, "components", "style", "properties")
 
 
@@ -27,18 +28,17 @@ def generate_data(output, template):
     for path, dirs, files in os.walk(SERVO_PROP_BASE):
         for file in files:
             if os.path.splitext(file)[1] in DEP_EXTS:
                 deps.add(mozpath.join(path, file))
     return deps
 
 
 def generate_header(output, data):
-    with open(data, "r") as f:
-        data = eval(f.read())
+    data = runpy.run_path(data)["data"]
 
     output.write("""/* THIS IS AN AUTOGENERATED FILE.  DO NOT EDIT */
 
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=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 https://mozilla.org/MPL/2.0/. */
@@ -63,38 +63,39 @@ def generate_header(output, data):
 
 """)
 
     MACRO_NAMES = {
         "longhand": "CSS_PROP_LONGHAND",
         "shorthand": "CSS_PROP_SHORTHAND",
         "alias": "CSS_PROP_ALIAS",
     }
-    for name, method, id, flags, pref, proptype in data:
-        is_internal = "CSSPropFlags::Internal" in flags
-        pref = '"' + pref + '"'
-        if proptype == "alias":
-            params = [name, id[0], id[1], method, pref]
+    for prop in data:
+        is_internal = "CSSPropFlags::Internal" in prop.flags
+        pref = '"' + prop.pref + '"'
+        if prop.type() == "alias":
+            params = [prop.name, prop.alias_id, prop.prop_id, prop.method, pref]
         else:
+            method = prop.method
             if method == "CssFloat":
                 method = "CSS_PROP_PUBLIC_OR_PRIVATE(CssFloat, Float)"
             elif method.startswith("Moz"):
                 method = "CSS_PROP_DOMPROP_PREFIXED({})".format(method[3:])
-            if flags:
-                flags = " | ".join(flags)
+            if prop.flags:
+                flags = " | ".join(prop.flags)
             else:
                 flags = "CSSPropFlags(0)"
-            params = [name, id, method, flags, pref]
+            params = [prop.name, prop.id, method, flags, pref]
 
-        is_component_of_all = not is_internal and name not in ["direction", "unicode-bidi"]
+        is_component_of_all = not is_internal and prop.name not in ["direction", "unicode-bidi"]
         if not is_component_of_all:
             output.write("#ifndef CSS_PROP_LIST_ONLY_COMPONENTS_OF_ALL_SHORTHAND\n")
         if is_internal:
             output.write("#ifndef CSS_PROP_LIST_EXCLUDE_INTERNAL\n")
-        output.write("{}({})\n".format(MACRO_NAMES[proptype], ", ".join(params)))
+        output.write("{}({})\n".format(MACRO_NAMES[prop.type()], ", ".join(params)))
         if is_internal:
             output.write("#endif\n")
         if not is_component_of_all:
             output.write("#endif\n")
 
     output.write("""
 #ifdef DEFINED_CSS_PROP_ALIAS
 #undef CSS_PROP_ALIAS
--- a/layout/style/ServoCSSPropList.mako.py
+++ b/layout/style/ServoCSSPropList.mako.py
@@ -1,12 +1,49 @@
 # 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 https://mozilla.org/MPL/2.0/.
 
+def _assign_slots(obj, args):
+    for i, attr in enumerate(obj.__slots__):
+        setattr(obj, attr, args[i])
+
+
+class Longhand(object):
+    __slots__ = ["name", "method", "id", "flags", "pref"]
+
+    def __init__(self, *args):
+        _assign_slots(self, args)
+
+    @staticmethod
+    def type():
+        return "longhand"
+
+
+class Shorthand(object):
+    __slots__ = ["name", "method", "id", "flags", "pref"]
+
+    def __init__(self, *args):
+        _assign_slots(self, args)
+
+    @staticmethod
+    def type():
+        return "shorthand"
+
+
+class Alias(object):
+    __slots__ = ["name", "method", "alias_id", "prop_id", "flags", "pref"]
+
+    def __init__(self, *args):
+        _assign_slots(self, args)
+
+    @staticmethod
+    def type():
+        return "alias"
+
 <%!
 # nsCSSPropertyID of longhands and shorthands is ordered alphabetically
 # with vendor prefixes removed. Note that aliases use their alias name
 # as order key directly because they may be duplicate without prefix.
 def order_key(prop):
     if prop.name.startswith("-"):
         return prop.name[prop.name.find("-", 1) + 1:]
     return prop.name
@@ -22,16 +59,23 @@ def is_internal(prop):
         "-moz-context-properties",
         "-moz-control-character-visibility",
         "-moz-window-opacity",
         "-moz-window-transform",
         "-moz-window-transform-origin",
     ]
     return prop.name in OTHER_INTERNALS
 
+def method(prop):
+    if prop.name == "float":
+        return "CssFloat"
+    if prop.name.startswith("-x-"):
+        return prop.camel_case[1:]
+    return prop.camel_case
+
 def flags(prop):
     result = []
     if prop.explicitly_enabled_in_chrome():
         result.append("EnabledInUASheetsAndChrome")
     elif prop.explicitly_enabled_in_ua_sheets():
         result.append("EnabledInUASheets")
     if is_internal(prop):
         result.append("Internal")
@@ -44,48 +88,21 @@ def flags(prop):
     return ", ".join('"CSSPropFlags::{}"'.format(flag) for flag in result)
 
 def pref(prop):
     if prop.gecko_pref:
         return '"' + prop.gecko_pref + '"'
     return '""'
 %>
 
-[
+data = [
     % for prop in sorted(data.longhands, key=order_key):
-    (
-        "${prop.name}",
-        % if prop.name == "float":
-        "CssFloat",
-        % elif prop.name.startswith("-x-"):
-        "${prop.camel_case[1:]}",
-        % else:
-        "${prop.camel_case}",
-        % endif
-        "${prop.ident}",
-        [${flags(prop)}],
-        ${pref(prop)},
-        "longhand",
-    ),
+    Longhand("${prop.name}", "${method(prop)}", "${prop.ident}", [${flags(prop)}], ${pref(prop)}),
     % endfor
 
     % for prop in sorted(data.shorthands, key=order_key):
-    (
-        "${prop.name}",
-        "${prop.camel_case}",
-        "${prop.ident}",
-        [${flags(prop)}],
-        ${pref(prop)},
-        "shorthand",
-    ),
+    Shorthand("${prop.name}", "${prop.camel_case}", "${prop.ident}", [${flags(prop)}], ${pref(prop)}),
     % endfor
 
     % for prop in sorted(data.all_aliases(), key=lambda x: x.name):
-    (
-        "${prop.name}",
-        "${prop.camel_case}",
-        ("${prop.ident}", "${prop.original.ident}"),
-        [],
-        ${pref(prop)},
-        "alias",
-    ),
+    Alias("${prop.name}", "${prop.camel_case}", "${prop.ident}", "${prop.original.ident}", [], ${pref(prop)}),
     % endfor
 ]