# 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