Bug 1452542 part 5 - Generate property list from Servo data. r=emilio,froydnj
authorXidorn Quan <me@upsuper.org>
Mon, 16 Apr 2018 14:08:20 +1000
changeset 414454 4dbf4e6f1b2fca9e4ab322928c0c7f2e4d0d808e
parent 414453 647d4e66d67a90e16209b011a29dbf3c8a562bc6
child 414455 cd54075da3d00eec5acd3710981052289b19e548
push id33870
push usercsabou@mozilla.com
push dateThu, 19 Apr 2018 22:28:45 +0000
treeherdermozilla-central@5c983ad5a421 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio, froydnj
bugs1452542
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 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":