Bug 1452542 part 5 - Generate property list from Servo data. r?emilio,froydnj draft
authorXidorn Quan <me@upsuper.org>
Mon, 16 Apr 2018 14:08:20 +1000
changeset 784861 dd8953d0b3d0ada24f6d8064377895c17a1635ab
parent 784860 9267dbef13e9e9c792292af7fef8811a5817f1d4
child 784862 6e61821560e87f3c23ea9ee57085bb25ec9e10b8
push id107060
push userxquan@mozilla.com
push dateThu, 19 Apr 2018 08:17:21 +0000
reviewersemilio, froydnj
bugs1452542
milestone61.0a1
Bug 1452542 part 5 - Generate property list from Servo data. r?emilio,froydnj With this change, we first generate a data file ServoCSSPropList.py from Servo data, and then use this data to generate ServoCSSPropList.h. This change itself serves as a checkpoint with a runtime check that all information generated from Servo side matches what we have in the Gecko side. Following patches will start replacing uses of nsCSSPropList.h with either the data file or the header file. The reason that it generates data file rather than header directly is that, many users of PythonCSSProps.h invokes C++ preprocessor manually to extract data from nsCSSPropList.h without passing in search paths, so it is non-trivial to replace the use of nsCSSPropList.h there with a generated header. Generating a Python data file would hopefully simplify those users rather than adding more complexity to them. I also thought about generating JSON rather than plain Python file, but JSON doesn't allow trailing comma in array, which makes it less pretty to generate via mako template. MozReview-Commit-ID: CwK2oL88r6F
layout/style/GenerateServoCSSPropList.py
layout/style/ServoCSSPropList.mako.py
layout/style/moz.build
layout/style/nsCSSProps.cpp
servo/components/style/properties/build.py
servo/components/style/properties/data.py
new file mode 100644
--- /dev/null
+++ b/layout/style/GenerateServoCSSPropList.py
@@ -0,0 +1,110 @@
+# 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 subprocess
+import string
+import sys
+
+SERVO_BASE = mozpath.join(buildconfig.topsrcdir, "servo")
+SERVO_PROP_BASE = mozpath.join(SERVO_BASE, "components", "style", "properties")
+
+
+def generate_data(output, template):
+    output.write("# THIS IS AN AUTOGENERATED FILE.  DO NOT EDIT\n\n")
+    output.write(subprocess.check_output([
+        sys.executable,
+        mozpath.join(SERVO_PROP_BASE, "build.py"),
+        "gecko", "geckolib", template
+    ], universal_newlines=True))
+
+    # Add all relevant files into the dependencies of the generated file.
+    DEP_EXTS = [".py", ".rs", ".zip"]
+    deps = set()
+    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())
+
+    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/. */
+
+#define CSS_PROP_DOMPROP_PREFIXED(name_) \\
+  CSS_PROP_PUBLIC_OR_PRIVATE(Moz ## name_, name_)
+
+#ifndef CSS_PROP_LONGHAND
+#define CSS_PROP_LONGHAND(name_, id_, method_, flags_, pref_) /* nothing */
+#define DEFINED_CSS_PROP_LONGHAND
+#endif
+
+#ifndef CSS_PROP_SHORTHAND
+#define CSS_PROP_SHORTHAND(name_, id_, method_, flags_, pref_) /* nothing */
+#define DEFINED_CSS_PROP_SHORTHAND
+#endif
+
+#ifndef CSS_PROP_ALIAS
+#define CSS_PROP_ALIAS(name_, aliasid_, id_, method_, pref_) /* nothing */
+#define DEFINED_CSS_PROP_ALIAS
+#endif
+
+""")
+
+    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 = "CSS_PROPERTY_INTERNAL" in flags
+        pref = '"' + pref + '"'
+        if proptype == "alias":
+            params = [name, id[0], id[1], method, pref]
+        else:
+            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)
+            else:
+                flags = "0"
+            params = [name, id, method, flags, pref]
+
+        if is_internal:
+            output.write("#ifndef CSS_PROP_LIST_EXCLUDE_INTERNAL\n")
+        output.write("{}({})\n".format(MACRO_NAMES[proptype], ", ".join(params)))
+        if is_internal:
+            output.write("#endif\n")
+
+    output.write("""
+#ifdef DEFINED_CSS_PROP_ALIAS
+#undef CSS_PROP_ALIAS
+#undef DEFINED_CSS_PROP_ALIAS
+#endif
+
+#ifdef DEFINED_CSS_PROP_SHORTHAND
+#undef CSS_PROP_SHORTHAND
+#undef DEFINED_CSS_PROP_SHORTHAND
+#endif
+
+#ifdef DEFINED_CSS_PROP_LONGHAND
+#undef CSS_PROP_LONGHAND
+#undef DEFINED_CSS_PROP_LONGHAND
+#endif
+
+#undef CSS_PROP_DOMPROP_PREFIXED
+""")
new file mode 100644
--- /dev/null
+++ b/layout/style/ServoCSSPropList.mako.py
@@ -0,0 +1,87 @@
+# 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/.
+
+<%!
+# 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
+
+# See bug 1454823 for situation of internal flag.
+def is_internal(prop):
+    # A property which is not controlled by pref and not enabled in
+    # content by default is an internal property.
+    if not prop.gecko_pref and not prop.enabled_in_content():
+        return True
+    # There are some special cases we may want to remove eventually.
+    OTHER_INTERNALS = [
+        "-moz-context-properties",
+        "-moz-control-character-visibility",
+        "-moz-window-opacity",
+        "-moz-window-transform",
+        "-moz-window-transform-origin",
+    ]
+    return prop.name in OTHER_INTERNALS
+
+def flags(prop):
+    result = []
+    if prop.explicitly_enabled_in_chrome():
+        result.append("CSS_PROPERTY_ENABLED_IN_UA_SHEETS_AND_CHROME")
+    elif prop.explicitly_enabled_in_ua_sheets():
+        result.append("CSS_PROPERTY_ENABLED_IN_UA_SHEETS")
+    if is_internal(prop):
+        result.append("CSS_PROPERTY_INTERNAL")
+    if prop.enabled_in == "":
+        result.append("CSS_PROPERTY_PARSE_INACCESSIBLE")
+    return ", ".join('"{}"'.format(flag) for flag in result)
+
+def pref(prop):
+    if prop.gecko_pref:
+        return '"' + prop.gecko_pref + '"'
+    return '""'
+%>
+
+[
+    % 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",
+    ),
+    % endfor
+
+    % for prop in sorted(data.shorthands, key=order_key):
+    (
+        "${prop.name}",
+        "${prop.camel_case}",
+        "${prop.ident}",
+        [${flags(prop)}],
+        ${pref(prop)},
+        "shorthand",
+    ),
+    % 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",
+    ),
+    % endfor
+]
--- a/layout/style/moz.build
+++ b/layout/style/moz.build
@@ -58,16 +58,17 @@ EXPORTS += [
     'nsStyleStructInlines.h',
     'nsStyleStructList.h',
     'nsStyleTransformMatrix.h',
     'nsStyleUtil.h',
     'nsTimingFunction.h',
 ]
 
 EXPORTS.mozilla += [
+    '!ServoCSSPropList.h',
     'AnimationCollection.h',
     'BindingStyleRule.h',
     'CachedInheritingStyles.h',
     'ComputedStyle.h',
     'ComputedStyleInlines.h',
     'CSSEnabledState.h',
     'DeclarationBlock.h',
     'DeclarationBlockInlines.h',
@@ -269,20 +270,38 @@ RESOURCE_FILES += [
 CONTENT_ACCESSIBLE_FILES += [
     'ImageDocument.css',
     'res/plaintext.css',
     'res/viewsource.css',
     'TopLevelImageDocument.css',
     'TopLevelVideoDocument.css',
 ]
 
+GENERATED_FILES += [
+    'ServoCSSPropList.h',
+    'ServoCSSPropList.py',
+]
+
+servo_props = GENERATED_FILES['ServoCSSPropList.h']
+servo_props.script = 'GenerateServoCSSPropList.py:generate_header'
+servo_props.inputs = [
+    '!ServoCSSPropList.py',
+]
+
+servo_props = GENERATED_FILES['ServoCSSPropList.py']
+servo_props.script = 'GenerateServoCSSPropList.py:generate_data'
+servo_props.inputs = [
+    'ServoCSSPropList.mako.py',
+]
+
 if CONFIG['COMPILE_ENVIRONMENT']:
     GENERATED_FILES += [
         'nsCSSPropsGenerated.inc',
     ]
+
     css_props = GENERATED_FILES['nsCSSPropsGenerated.inc']
     css_props.script = 'GenerateCSSPropsGenerated.py:generate'
     css_props.inputs = [
         'nsCSSPropsGenerated.inc.in',
         'PythonCSSProps.h',
     ]
 
     CONFIGURE_SUBST_FILES += [
--- a/layout/style/nsCSSProps.cpp
+++ b/layout/style/nsCSSProps.cpp
@@ -142,16 +142,104 @@ CreateStaticTable(const char* const aRaw
     nsAutoCString temp(aRawTable[index]);
     MOZ_ASSERT(-1 == temp.FindChar('_'),
                "underscore char in case insensitive name table");
   }
 #endif
   return table;
 }
 
+#ifdef DEBUG
+static void
+CheckServoCSSPropList()
+{
+  struct PropData {
+    nsCSSPropertyID mID;
+    uint32_t mFlags;
+    const char* mPref;
+  };
+  const PropData sGeckoProps[eCSSProperty_COUNT_with_aliases] = {
+#define CSS_PROP(name_, id_, method_, flags_, pref_, ...) \
+    { eCSSProperty_##id_, flags_, pref_ },
+#include "nsCSSPropList.h"
+#undef CSS_PROP
+
+#define CSS_PROP_SHORTHAND(name_, id_, method_, flags_, pref_) \
+    { eCSSProperty_##id_, flags_, pref_ },
+#include "nsCSSPropList.h"
+#undef CSS_PROP_SHORTHAND
+
+#define CSS_PROP_ALIAS(aliasname_, aliasid_, propid_, aliasmethod_, pref_) \
+    { eCSSPropertyAlias_##aliasid_, 0, pref_ },
+#include "nsCSSPropAliasList.h"
+#undef CSS_PROP_ALIAS
+  };
+  const PropData sServoProps[eCSSProperty_COUNT_with_aliases] = {
+#define CSS_PROP_LONGHAND(name_, id_, method_, flags_, pref_) \
+    { eCSSProperty_##id_, flags_, pref_ },
+#define CSS_PROP_SHORTHAND(name_, id_, method_, flags_, pref_) \
+    { eCSSProperty_##id_, flags_, pref_ },
+#define CSS_PROP_ALIAS(name_, aliasid_, id_, method_, pref_) \
+    { eCSSPropertyAlias_##aliasid_, 0, pref_ },
+#include "mozilla/ServoCSSPropList.h"
+#undef CSS_PROP_ALIAS
+#undef CSS_PROP_SHORTHAND
+#undef CSS_PROP_LONGHAND
+  };
+
+  const uint32_t kServoFlags =
+    CSS_PROPERTY_ENABLED_MASK | CSS_PROPERTY_INTERNAL |
+    CSS_PROPERTY_PARSE_INACCESSIBLE;
+  bool mismatch = false;
+  for (size_t i = 0; i < eCSSProperty_COUNT_with_aliases; i++) {
+    auto& geckoData = sGeckoProps[i];
+    auto& servoData = sServoProps[i];
+    const char* name = nsCSSProps::GetStringValue(geckoData.mID).get();
+    if (geckoData.mID != servoData.mID) {
+      printf_stderr("Order mismatches: gecko: %s, servo: %s\n",
+                    name, nsCSSProps::GetStringValue(servoData.mID).get());
+      mismatch = true;
+      continue;
+    }
+    if ((geckoData.mFlags & kServoFlags) != servoData.mFlags) {
+      printf_stderr("Enabled flags of %s mismatch\n", name);
+      mismatch = true;
+    }
+    if (strcmp(geckoData.mPref, servoData.mPref) != 0) {
+      printf_stderr("Pref of %s mismatches\n", name);
+      mismatch = true;
+    }
+  }
+
+  const nsCSSPropertyID sGeckoAliases[eCSSAliasCount] = {
+#define CSS_PROP_ALIAS(aliasname_, aliasid_, propid_, aliasmethod_, pref_) \
+    eCSSProperty_##propid_,
+#include "nsCSSPropAliasList.h"
+#undef CSS_PROP_ALIAS
+  };
+  const nsCSSPropertyID sServoAliases[eCSSAliasCount] = {
+#define CSS_PROP_ALIAS(aliasname_, aliasid_, propid_, aliasmethod_, pref_) \
+    eCSSProperty_##propid_,
+#include "mozilla/ServoCSSPropList.h"
+#undef CSS_PROP_ALIAS
+  };
+  for (size_t i = 0; i < eCSSAliasCount; i++) {
+    if (sGeckoAliases[i] == sServoAliases[i]) {
+      continue;
+    }
+    nsCSSPropertyID aliasid = nsCSSPropertyID(eCSSProperty_COUNT + i);
+    printf_stderr("Original property of alias %s mismatches\n",
+                  nsCSSProps::GetStringValue(aliasid).get());
+    mismatch = true;
+  }
+
+  MOZ_ASSERT(!mismatch);
+}
+#endif
+
 void
 nsCSSProps::AddRefTable(void)
 {
   if (0 == gPropertyTableRefCount++) {
     MOZ_ASSERT(!gPropertyTable, "pre existing array!");
     MOZ_ASSERT(!gFontDescTable, "pre existing array!");
     MOZ_ASSERT(!gCounterDescTable, "pre existing array!");
     MOZ_ASSERT(!gPropertyIDLNameTable, "pre existing array!");
@@ -166,16 +254,20 @@ nsCSSProps::AddRefTable(void)
     for (nsCSSPropertyID p = nsCSSPropertyID(0);
          size_t(p) < ArrayLength(kIDLNameTable);
          p = nsCSSPropertyID(p + 1)) {
       if (kIDLNameTable[p]) {
         gPropertyIDLNameTable->Put(nsDependentCString(kIDLNameTable[p]), p);
       }
     }
 
+#ifdef DEBUG
+    CheckServoCSSPropList();
+#endif
+
     static bool prefObserversInited = false;
     if (!prefObserversInited) {
       prefObserversInited = true;
 
       #define OBSERVE_PROP(pref_, id_)                                        \
         if (pref_[0]) {                                                       \
           Preferences::AddBoolVarCache(&gPropertyEnabled[id_],                \
                                        pref_);                                \
--- a/servo/components/style/properties/build.py
+++ b/servo/components/style/properties/build.py
@@ -16,17 +16,17 @@ from mako.lookup import TemplateLookup
 from mako.template import Template
 
 import data
 
 RE_PYTHON_ADDR = re.compile(r'<.+? object at 0x[0-9a-fA-F]+>')
 
 
 def main():
-    usage = "Usage: %s [ servo | gecko ] [ style-crate | html ]" % sys.argv[0]
+    usage = "Usage: %s [ servo | gecko ] [ style-crate | geckolib <template> | 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)
 
@@ -34,16 +34,22 @@ def main():
     template = os.path.join(BASE, "properties.mako.rs")
     rust = render(template, product=product, data=properties, __file__=template)
     if output == "style-crate":
         write(os.environ["OUT_DIR"], "properties.rs", rust)
         if product == "gecko":
             template = os.path.join(BASE, "gecko.mako.rs")
             rust = render(template, data=properties)
             write(os.environ["OUT_DIR"], "gecko_properties.rs", rust)
+    elif output == "geckolib":
+        if len(sys.argv) < 4:
+            abort(usage)
+        template = sys.argv[3]
+        header = render(template, data=properties)
+        sys.stdout.write(header)
     elif output == "html":
         write_html(properties)
 
 
 def abort(message):
     sys.stderr.write(message + b"\n")
     sys.exit(1)
 
--- a/servo/components/style/properties/data.py
+++ b/servo/components/style/properties/data.py
@@ -380,16 +380,17 @@ class Shorthand(object):
         return "nsCSSPropertyID::eCSSProperty_%s" % self.ident
 
 
 class Alias(object):
     def __init__(self, name, original, gecko_pref):
         self.name = name
         self.ident = to_rust_ident(name)
         self.camel_case = to_camel_case(self.ident)
+        self.original = original
         self.enabled_in = original.enabled_in
         self.servo_pref = original.servo_pref
         self.gecko_pref = gecko_pref
         self.allowed_in_page_rule = original.allowed_in_page_rule
         self.allowed_in_keyframe_block = original.allowed_in_keyframe_block
 
     def experimental(self, product):
         if product == "gecko":