Source code

Revision control

Copy as Markdown

Other Tools

# 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 string
import sys
SERVO_PROPS = mozpath.join(buildconfig.topsrcdir, "servo", "components", "style", "properties")
sys.path.insert(0, SERVO_PROPS)
import data
def props_and_deps():
properties = data.PropertiesData(engine="gecko")
# Add all relevant files into the dependencies of the generated file.
return (
properties,
set([
mozpath.join(SERVO_PROPS, f) for f in ["data.py", "counted_unknown_properties.py", "longhands.toml", "shorthands.toml"]
])
)
def gen_css_prop_list_header(output):
properties, deps = props_and_deps()
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/. */
#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_, flags_, pref_) /* nothing */
#define DEFINED_CSS_PROP_ALIAS
#endif
"""
)
MACRO_NAMES = {
"longhand": "CSS_PROP_LONGHAND",
"shorthand": "CSS_PROP_SHORTHAND",
"alias": "CSS_PROP_ALIAS",
}
for prop in properties.all_properties_and_aliases():
flags = " | ".join(
"CSSPropFlags::{}".format(flag)
for flag in cpp_flags(prop)
)
if not flags:
flags = "CSSPropFlags(0)"
pref = '"' + (prop.gecko_pref or "") + '"'
method = prop.idl_method
if prop.type() == "alias":
params = [prop.name, prop.ident, prop.original.ident, method, flags, pref]
else:
params = [prop.name, prop.ident, method, flags, pref]
output.write("{}({})\n".format(MACRO_NAMES[prop.type()], ", ".join(params)))
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
"""
)
return deps
# 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 [LegacyNullToEmptyString] UTF8String %s;\n" % (
", ".join(extendedAttrs),
propName,
)
def idl_attribute(p):
prop = p.idl_method
# Generate a name with camelCase spelling of property-name (or capitalized,
# for Moz-prefixed properties):
if not prop.startswith("Moz"):
prop = prop[0].lower() + prop[1:]
return prop
def gen_webidl(output, ruleType, interfaceName, bindingTemplate, pref=None):
properties, deps = props_and_deps()
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/. */
[Exposed=Window"""
)
if pref:
output.write(', Pref="' + pref + '"')
output.write(
"""]
interface """
+ interfaceName
+ " : CSSStyleDeclaration {\n"
)
for p in properties.all_properties_and_aliases():
# Skip properties which aren't valid in style rules.
if ruleType not in p.rule_types_allowed_names():
continue
pref = p.gecko_pref
propId = p.ident
if p.type() == "alias":
if p.idl_method == "MozAppearance":
# Hide MozAppearance from CSSStyleProperties to prevent outdated
# special casing against Gecko. (Bug 1977489)
pref = "layout.css.moz-appearance.webidl.enabled"
elif p.gecko_pref == p.original.gecko_pref:
# We already added this as a BindingAlias for the original prop.
continue
propId = p.original.ident
extendedAttrs = [
"BindingTemplate=(%s, eCSSProperty_%s)" % (bindingTemplate, propId),
"CEReactions",
"SetterThrows",
"SetterNeedsSubjectPrincipal=NonSystem",
]
if pref:
assert not is_internal(p)
# backdrop-filter is a special case where we want WebIDL to check a
# function instead of checking the pref directly.
if p.name == "backdrop-filter":
extendedAttrs.append('Func="nsCSSProps::IsBackdropFilterAvailable"')
else:
extendedAttrs.append('Pref="%s"' % pref)
elif p.explicitly_enabled_in_chrome():
extendedAttrs.append("ChromeOnly")
elif is_internal(p):
continue
def add_extra_accessors(p):
# webkit properties get a camelcase "webkitFoo" accessor
# as well as a capitalized "WebkitFoo" alias (added here).
if p.idl_method.startswith("Webkit"):
extendedAttrs.append('BindingAlias="%s"' % p.idl_method)
prop = idl_attribute(p)
# Per spec, what's actually supposed to happen here is that we're supposed
# to have properties for:
#
# 1) Each supported CSS property name, camelCased.
# 2) Each supported name that contains or starts with dashes,
# without any changes to the name.
# 3) cssFloat
#
# 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 != p.name:
extendedAttrs.append('BindingAlias="%s"' % p.name)
return prop
prop = add_extra_accessors(p)
if p.type() != "alias":
for a in p.aliases:
if p.gecko_pref == a.gecko_pref:
newProp = add_extra_accessors(a)
extendedAttrs.append('BindingAlias="%s"' % newProp)
output.write(generateLine(prop, extendedAttrs))
output.write("};")
return deps
def gen_style_properties_webidl(output):
return gen_webidl(output, "style", "CSSStyleProperties", "CSS2Property")
def gen_page_descriptors_webidl(output):
return gen_webidl(output, "page", "CSSPageDescriptors", "CSSPageDescriptor")
def gen_position_try_descriptors_webidl(output):
return gen_webidl(output, "position-try", "CSSPositionTryDescriptors",
"CSSPositionTryDescriptor",
"layout.css.anchor-positioning.enabled")
class PropertyWrapper(object):
__slots__ = ["index", "prop", "idlname"]
def __init__(self, index, prop):
self.index = index
self.prop = prop
self.idlname = None if is_internal(prop) else idl_attribute(prop)
def __getattr__(self, name):
return getattr(self.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.
return not prop.gecko_pref and not prop.enabled_in_content()
# TODO(emilio): Get this to zero.
LONGHANDS_NOT_SERIALIZED_WITH_SERVO = set([
# These resolve auto to zero in a few cases, but not all.
"max-height",
"max-width",
"min-height",
"min-width",
# resistfingerprinting stuff.
"-moz-osx-font-smoothing",
# Layout dependent.
"width",
"height",
"grid-template-rows",
"grid-template-columns",
"perspective-origin",
"transform-origin",
"transform",
"-webkit-transform",
"top",
"right",
"bottom",
"left",
"margin-top",
"margin-right",
"margin-bottom",
"margin-left",
"padding-top",
"padding-right",
"padding-bottom",
"padding-left",
])
def serialized_by_servo(prop):
if prop.type() == "alias":
return True # Doesn't matter, we resolve the alias early
return prop.name not in LONGHANDS_NOT_SERIALIZED_WITH_SERVO
def exposed_on_getcs(prop):
if "style" not in prop.rule_types_allowed_names():
return False
if is_internal(prop):
return False
return True
def cpp_flags(prop):
RUST_TO_CPP_FLAGS = {
"CAN_ANIMATE_ON_COMPOSITOR": "CanAnimateOnCompositor",
"AFFECTS_LAYOUT": "AffectsLayout",
"AFFECTS_PAINT": "AffectsPaint",
"AFFECTS_OVERFLOW": "AffectsOverflow",
}
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")
if prop.enabled_in == "":
result.append("Inaccessible")
for (k, v) in RUST_TO_CPP_FLAGS.items():
if k in prop.flags:
result.append(v)
if serialized_by_servo(prop):
result.append("SerializedByServo")
if prop.type() == "longhand" and prop.logical:
result.append("IsLogical")
return result
def gen_ns_css_props(output):
raw_properties, deps = props_and_deps()
output.write(
"""/* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT */
/* processed file that defines CSS property tables that can't be generated
with the pre-processor, designed to be #included in nsCSSProps.cpp */
"""
)
properties = [
PropertyWrapper(i, p)
for i, p in enumerate(raw_properties.longhands + raw_properties.shorthands)
if p.type() != "alias"
]
# Generate kIDLNameTable
output.write(
"const char* const nsCSSProps::" "kIDLNameTable[eCSSProperty_COUNT] = {\n"
)
for p in properties:
if p.idlname is None:
output.write(" nullptr, // {}\n".format(p.name))
else:
output.write(' "{}",\n'.format(p.idlname))
output.write("};\n\n")
# Generate kIDLNameSortPositionTable
ps = sorted(properties, key=lambda p: p.idlname if p.idlname else "")
ps = [(p, position) for position, p in enumerate(ps)]
ps.sort(key=lambda item: item[0].index)
output.write(
"const int32_t nsCSSProps::"
"kIDLNameSortPositionTable[eCSSProperty_COUNT] = {\n"
)
for p, position in ps:
output.write(" {},\n".format(position))
output.write("};\n\n")
# Generate preferences table
output.write(
"const nsCSSProps::PropertyPref " "nsCSSProps::kPropertyPrefTable[] = {\n"
)
for p in raw_properties.all_properties_and_aliases():
if not p.gecko_pref:
continue
if p.type() != "alias":
prop_id = "eCSSProperty_" + p.ident
else:
prop_id = "eCSSPropertyAlias_" + p.ident
output.write(' {{ {}, "{}" }},\n'.format(prop_id, p.gecko_pref))
output.write(" { eCSSProperty_UNKNOWN, nullptr },\n")
output.write("};\n\n")
# Generate shorthand subprop tables
names = []
for p in properties:
if p.type() != "shorthand":
continue
name = "g{}SubpropTable".format(p.idl_method)
names.append(name)
output.write(f"static const NonCustomCSSPropertyId {name}[] = {{\n")
for subprop in p.sub_properties:
output.write(f" eCSSProperty_{subprop.ident},\n")
output.write(" eCSSProperty_UNKNOWN\n")
output.write("};\n\n")
output.write("const NonCustomCSSPropertyId* const\n")
output.write(
"nsCSSProps::kSubpropertyTable["
"eCSSProperty_COUNT - eCSSProperty_COUNT_no_shorthands"
"] = {\n"
)
for name in names:
output.write(" {},\n".format(name))
output.write("};\n\n")
# Generate assertions
msg = (
"GenerateCSSPropsGenerated.py did not list properties "
"in NonCustomCSSPropertyId order"
)
for p in properties:
output.write(
'static_assert(eCSSProperty_{} == {}, "{}");\n'.format(p.ident, p.index, msg)
)
return deps
def gen_counted_unknown_properties(output, prop_file):
properties, deps = props_and_deps()
output.write("/* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT */\n\n")
for prop in properties.counted_unknown_properties:
output.write(
"COUNTED_UNKNOWN_PROPERTY({}, {})\n".format(prop.name, prop.ident)
)
return deps
def gen_compositor_animatable_properties(output):
properties, deps = props_and_deps()
output.write(
"""/* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT */
#ifndef COMPOSITOR_ANIMATABLE_PROPERTY_LIST
#define COMPOSITOR_ANIMATABLE_PROPERTY_LIST { \\
"""
)
count = 0
for p in properties.longhands:
if "CAN_ANIMATE_ON_COMPOSITOR" in p.flags:
output.write(" eCSSProperty_{}, \\\n".format(p.ident))
count += 1
output.write("}\n")
output.write("#endif /* COMPOSITOR_ANIMATABLE_PROPERTY_LIST */\n")
output.write("\n")
output.write("#ifndef COMPOSITOR_ANIMATABLE_PROPERTY_LIST_LENGTH\n")
output.write(
"#define COMPOSITOR_ANIMATABLE_PROPERTY_LIST_LENGTH {}\n".format(count)
)
output.write("#endif /* COMPOSITOR_ANIMATABLE_PROPERTY_LIST_LENGTH */\n")
return deps
def gen_non_custom_css_property_id(output, template):
properties, deps = props_and_deps()
with open(template, "r") as f:
template = string.Template(f.read())
property_ids = []
for prop in properties.longhands:
property_ids.append("eCSSProperty_{}".format(prop.ident))
for prop in properties.shorthands:
property_ids.append("eCSSProperty_{}".format(prop.ident))
for alias in properties.all_aliases():
property_ids.append("eCSSPropertyAlias_{}".format(alias.ident))
longhand_count = property_ids[len(properties.longhands)]
shorthand_count = property_ids[len(properties.longhands) + len(properties.shorthands)]
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_first": property_ids[0],
"longhand_count": longhand_count,
"shorthand_count": shorthand_count,
}
)
)
return deps
# This builds the list of CSS properties that we expect in the
# property-database file for testing.
def gen_css_properties_js(output):
# Don't print the properties that aren't accepted by the parser
# TODO(emilio): Pretty sure we can automate this more.
inaccessible_properties = set([
"-x-cols",
"-x-lang",
"-x-span",
"-x-text-scale",
"-moz-default-appearance",
"-moz-theme",
"-moz-inert",
"-moz-script-level", # parsed by UA sheets only
"-moz-math-variant",
"-moz-math-display", # parsed by UA sheets only
"-moz-top-layer", # parsed by UA sheets only
"-moz-min-font-size-ratio", # parsed by UA sheets only
"-moz-box-collapse", # chrome-only internal properties
"-moz-subtree-hidden-only-visually", # chrome-only internal properties
"-moz-user-focus", # chrome-only internal properties
"-moz-window-input-region-margin", # chrome-only internal properties
"-moz-window-dragging", # chrome-only internal properties
"-moz-window-opacity", # chrome-only internal properties
"-moz-window-transform", # chrome-only internal properties
"-moz-window-shadow", # chrome-only internal properties
])
properties, deps = props_and_deps()
def print_array(name, props):
first = True
output.write(f"var {name} = [\n");
for prop in props:
if prop.name in inaccessible_properties:
continue
if "style" not in prop.rule_types_allowed_names():
continue
if not first:
output.write(",\n")
first = False
output.write(f"\t{{ name: \"{prop.name}\", prop: \"{idl_attribute(prop)}\"")
if prop.gecko_pref:
output.write(f", pref: \"{prop.gecko_pref}\"")
output.write(" }")
output.write("\n];\n")
output.write("/* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT */\n\n")
print_array("gLonghandProperties", properties.longhands)
print_array("gShorthandProperties", properties.shorthands)
return deps
COMPUTED_STYLE_INC = """/* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT */
/* processed file that defines entries for nsComputedDOMStyle, designed
to be #included in nsComputedDOMStyle.cpp */
static constexpr size_t kEntryIndices[eCSSProperty_COUNT] = {{
{indices}
}};
{asserts}
static constexpr Entry kEntries[eCSSProperty_COUNT] = {{
{entries}
}};
"""
def gen_computed_style(output):
properties, deps = props_and_deps()
def order_key(p):
# Put prefixed properties after normal properties.
# The spec is unclear about this, and Blink doesn't have any sensible
# order at all, so it probably doesn't matter a lot. But originally
# Gecko put then later so we do so as well. See w3c/csswg-drafts#2827.
order = p.name.startswith("-")
return (order, p.name)
def has_cpp_getter(p):
if not exposed_on_getcs(p):
return False
if serialized_by_servo(p):
return False
if p.type() == "longhand" and p.logical:
return False
return True
def getter_entry(p):
if has_cpp_getter(p):
return "DoGet" + p.idl_method
# Put a dummy getter here instead of nullptr because MSVC seems
# to have bug which ruins the table when we put nullptr for
# pointer-to-member-function. See bug 1471426.
return "DummyGetter"
entries = []
indices = []
asserts = []
index_map = {}
non_aliases = properties.longhands + properties.shorthands
for i, p in enumerate(sorted(non_aliases, key=order_key)):
can_be_exposed = "true" if exposed_on_getcs(p) else "false"
entries.append(
"{{ eCSSProperty_{}, {}, &nsComputedDOMStyle::{}}}".format(
p.ident, can_be_exposed, getter_entry(p)
)
)
index_map[p.ident] = i
i += 1
for i, p in enumerate(non_aliases):
indices.append(str(index_map[p.ident]))
asserts.append(
'static_assert(size_t(eCSSProperty_{}) == {}, "");'.format(p.ident, i)
)
assert len(indices) == len(entries)
output.write(
COMPUTED_STYLE_INC.format(
indices=", ".join(indices),
entries=",\n ".join(entries),
asserts="\n".join(asserts),
)
)
return deps