diff options
Diffstat (limited to '')
-rw-r--r-- | servo/components/style/properties/data.py | 1083 |
1 files changed, 1083 insertions, 0 deletions
diff --git a/servo/components/style/properties/data.py b/servo/components/style/properties/data.py new file mode 100644 index 0000000000..093f1cb75f --- /dev/null +++ b/servo/components/style/properties/data.py @@ -0,0 +1,1083 @@ +# 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/. + +import re +from counted_unknown_properties import COUNTED_UNKNOWN_PROPERTIES + +# It is important that the order of these physical / logical variants matches +# the order of the enum variants in logical_geometry.rs +PHYSICAL_SIDES = ["top", "right", "bottom", "left"] +PHYSICAL_CORNERS = ["top-left", "top-right", "bottom-right", "bottom-left"] +PHYSICAL_AXES = ["y", "x"] +PHYSICAL_SIZES = ["height", "width"] +LOGICAL_SIDES = ["block-start", "block-end", "inline-start", "inline-end"] +LOGICAL_CORNERS = ["start-start", "start-end", "end-start", "end-end"] +LOGICAL_SIZES = ["block-size", "inline-size"] +LOGICAL_AXES = ["block", "inline"] + +# bool is True when logical +ALL_SIDES = [(side, False) for side in PHYSICAL_SIDES] + [ + (side, True) for side in LOGICAL_SIDES +] +ALL_SIZES = [(size, False) for size in PHYSICAL_SIZES] + [ + (size, True) for size in LOGICAL_SIZES +] +ALL_CORNERS = [(corner, False) for corner in PHYSICAL_CORNERS] + [ + (corner, True) for corner in LOGICAL_CORNERS +] +ALL_AXES = [(axis, False) for axis in PHYSICAL_AXES] + [ + (axis, True) for axis in LOGICAL_AXES +] + +SYSTEM_FONT_LONGHANDS = """font_family font_size font_style + font_stretch font_weight""".split() + +PRIORITARY_PROPERTIES = set( + [ + # The writing-mode group has the most priority of all property groups, as + # sizes like font-size can depend on it. + "writing-mode", + "direction", + "text-orientation", + # The fonts and colors group has the second priority, as all other lengths + # and colors depend on them. + # + # There are some interdependencies between these, but we fix them up in + # Cascade::fixup_font_stuff. + # Needed to properly compute the zoomed font-size. + "-x-text-scale", + # Needed to do font-size computation in a language-dependent way. + "-x-lang", + # Needed for ruby to respect language-dependent min-font-size + # preferences properly, see bug 1165538. + "-moz-min-font-size-ratio", + # font-size depends on math-depth's computed value. + "math-depth", + # Needed to compute the first available font and its used size, + # in order to compute font-relative units correctly. + "font-size", + "font-size-adjust", + "font-weight", + "font-stretch", + "font-style", + "font-family", + # color-scheme affects how system colors resolve. + "color-scheme", + # forced-color-adjust affects whether colors are adjusted. + "forced-color-adjust", + # Zoom affects all absolute lengths. + "zoom", + # Line height lengths depend on this. + "line-height", + ] +) + +VISITED_DEPENDENT_PROPERTIES = set( + [ + "column-rule-color", + "text-emphasis-color", + "-webkit-text-fill-color", + "-webkit-text-stroke-color", + "text-decoration-color", + "fill", + "stroke", + "caret-color", + "background-color", + "border-top-color", + "border-right-color", + "border-bottom-color", + "border-left-color", + "border-block-start-color", + "border-inline-end-color", + "border-block-end-color", + "border-inline-start-color", + "outline-color", + "color", + ] +) + +# Bitfield values for all rule types which can have property declarations. +STYLE_RULE = 1 << 0 +PAGE_RULE = 1 << 1 +KEYFRAME_RULE = 1 << 2 + +ALL_RULES = STYLE_RULE | PAGE_RULE | KEYFRAME_RULE +DEFAULT_RULES = STYLE_RULE | KEYFRAME_RULE +DEFAULT_RULES_AND_PAGE = DEFAULT_RULES | PAGE_RULE +DEFAULT_RULES_EXCEPT_KEYFRAME = STYLE_RULE + +# Rule name to value dict +RULE_VALUES = { + "Style": STYLE_RULE, + "Page": PAGE_RULE, + "Keyframe": KEYFRAME_RULE, +} + + +def rule_values_from_arg(that): + if isinstance(that, int): + return that + mask = 0 + for rule in that.split(): + mask |= RULE_VALUES[rule] + return mask + + +def maybe_moz_logical_alias(engine, side, prop): + if engine == "gecko" and side[1]: + axis, dir = side[0].split("-") + if axis == "inline": + return prop % dir + return None + + +def to_rust_ident(name): + name = name.replace("-", "_") + if name in ["static", "super", "box", "move"]: # Rust keywords + name += "_" + return name + + +def to_snake_case(ident): + return re.sub("([A-Z]+)", lambda m: "_" + m.group(1).lower(), ident).strip("_") + + +def to_camel_case(ident): + return re.sub( + "(^|_|-)([a-z0-9])", lambda m: m.group(2).upper(), ident.strip("_").strip("-") + ) + + +def to_camel_case_lower(ident): + camel = to_camel_case(ident) + return camel[0].lower() + camel[1:] + + +# https://drafts.csswg.org/cssom/#css-property-to-idl-attribute +def to_idl_name(ident): + return re.sub("-([a-z])", lambda m: m.group(1).upper(), ident) + + +def parse_aliases(value): + aliases = {} + for pair in value.split(): + [a, v] = pair.split("=") + aliases[a] = v + return aliases + + +class Keyword(object): + def __init__( + self, + name, + values, + gecko_constant_prefix=None, + gecko_enum_prefix=None, + custom_consts=None, + extra_gecko_values=None, + extra_servo_2013_values=None, + extra_servo_2020_values=None, + gecko_aliases=None, + servo_2013_aliases=None, + servo_2020_aliases=None, + gecko_strip_moz_prefix=None, + gecko_inexhaustive=None, + ): + self.name = name + self.values = values.split() + if gecko_constant_prefix and gecko_enum_prefix: + raise TypeError( + "Only one of gecko_constant_prefix and gecko_enum_prefix " + "can be specified" + ) + self.gecko_constant_prefix = ( + gecko_constant_prefix or "NS_STYLE_" + self.name.upper().replace("-", "_") + ) + self.gecko_enum_prefix = gecko_enum_prefix + self.extra_gecko_values = (extra_gecko_values or "").split() + self.extra_servo_2013_values = (extra_servo_2013_values or "").split() + self.extra_servo_2020_values = (extra_servo_2020_values or "").split() + self.gecko_aliases = parse_aliases(gecko_aliases or "") + self.servo_2013_aliases = parse_aliases(servo_2013_aliases or "") + self.servo_2020_aliases = parse_aliases(servo_2020_aliases or "") + self.consts_map = {} if custom_consts is None else custom_consts + self.gecko_strip_moz_prefix = ( + True if gecko_strip_moz_prefix is None else gecko_strip_moz_prefix + ) + self.gecko_inexhaustive = gecko_inexhaustive or (gecko_enum_prefix is None) + + def values_for(self, engine): + if engine == "gecko": + return self.values + self.extra_gecko_values + elif engine == "servo-2013": + return self.values + self.extra_servo_2013_values + elif engine == "servo-2020": + return self.values + self.extra_servo_2020_values + else: + raise Exception("Bad engine: " + engine) + + def aliases_for(self, engine): + if engine == "gecko": + return self.gecko_aliases + elif engine == "servo-2013": + return self.servo_2013_aliases + elif engine == "servo-2020": + return self.servo_2020_aliases + else: + raise Exception("Bad engine: " + engine) + + def gecko_constant(self, value): + moz_stripped = ( + value.replace("-moz-", "") + if self.gecko_strip_moz_prefix + else value.replace("-moz-", "moz-") + ) + mapped = self.consts_map.get(value) + if self.gecko_enum_prefix: + parts = moz_stripped.replace("-", "_").split("_") + parts = mapped if mapped else [p.title() for p in parts] + return self.gecko_enum_prefix + "::" + "".join(parts) + else: + suffix = mapped if mapped else moz_stripped.replace("-", "_") + return self.gecko_constant_prefix + "_" + suffix.upper() + + def needs_cast(self): + return self.gecko_enum_prefix is None + + def maybe_cast(self, type_str): + return "as " + type_str if self.needs_cast() else "" + + def casted_constant_name(self, value, cast_type): + if cast_type is None: + raise TypeError("We should specify the cast_type.") + + if self.gecko_enum_prefix is None: + return cast_type.upper() + "_" + self.gecko_constant(value) + else: + return ( + cast_type.upper() + + "_" + + self.gecko_constant(value).upper().replace("::", "_") + ) + + +def arg_to_bool(arg): + if isinstance(arg, bool): + return arg + assert arg in ["True", "False"], "Unexpected value for boolean arguement: " + repr( + arg + ) + return arg == "True" + + +def parse_property_aliases(alias_list): + result = [] + if alias_list: + for alias in alias_list.split(): + (name, _, pref) = alias.partition(":") + result.append((name, pref)) + return result + + +def to_phys(name, logical, physical): + return name.replace(logical, physical).replace("inset-", "") + + +class Property(object): + def __init__( + self, + name, + spec, + servo_2013_pref, + servo_2020_pref, + gecko_pref, + enabled_in, + rule_types_allowed, + aliases, + extra_prefixes, + flags, + ): + self.name = name + if not spec: + raise TypeError("Spec should be specified for " + name) + self.spec = spec + self.ident = to_rust_ident(name) + self.camel_case = to_camel_case(self.ident) + self.servo_2013_pref = servo_2013_pref + self.servo_2020_pref = servo_2020_pref + self.gecko_pref = gecko_pref + self.rule_types_allowed = rule_values_from_arg(rule_types_allowed) + # For enabled_in, the setup is as follows: + # It needs to be one of the four values: ["", "ua", "chrome", "content"] + # * "chrome" implies "ua", and implies that they're explicitly + # enabled. + # * "" implies the property will never be parsed. + # * "content" implies the property is accessible unconditionally, + # modulo a pref, set via servo_pref / gecko_pref. + assert enabled_in in ("", "ua", "chrome", "content") + self.enabled_in = enabled_in + self.aliases = parse_property_aliases(aliases) + self.extra_prefixes = parse_property_aliases(extra_prefixes) + self.flags = flags.split() if flags else [] + + def rule_types_allowed_names(self): + for name in RULE_VALUES: + if self.rule_types_allowed & RULE_VALUES[name] != 0: + yield name + + def experimental(self, engine): + if engine == "gecko": + return bool(self.gecko_pref) + elif engine == "servo-2013": + return bool(self.servo_2013_pref) + elif engine == "servo-2020": + return bool(self.servo_2020_pref) + else: + raise Exception("Bad engine: " + engine) + + def explicitly_enabled_in_ua_sheets(self): + return self.enabled_in in ("ua", "chrome") + + def explicitly_enabled_in_chrome(self): + return self.enabled_in == "chrome" + + def enabled_in_content(self): + return self.enabled_in == "content" + + def is_visited_dependent(self): + return self.name in VISITED_DEPENDENT_PROPERTIES + + def is_prioritary(self): + return self.name in PRIORITARY_PROPERTIES + + def nscsspropertyid(self): + return "nsCSSPropertyID::eCSSProperty_" + self.ident + + +class Longhand(Property): + def __init__( + self, + style_struct, + name, + spec=None, + animation_value_type=None, + keyword=None, + predefined_type=None, + servo_2013_pref=None, + servo_2020_pref=None, + gecko_pref=None, + enabled_in="content", + need_index=False, + gecko_ffi_name=None, + has_effect_on_gecko_scrollbars=None, + rule_types_allowed=DEFAULT_RULES, + cast_type="u8", + logical=False, + logical_group=None, + aliases=None, + extra_prefixes=None, + boxed=False, + flags=None, + allow_quirks="No", + ignored_when_colors_disabled=False, + simple_vector_bindings=False, + vector=False, + servo_restyle_damage="repaint", + affects=None, + ): + Property.__init__( + self, + name=name, + spec=spec, + servo_2013_pref=servo_2013_pref, + servo_2020_pref=servo_2020_pref, + gecko_pref=gecko_pref, + enabled_in=enabled_in, + rule_types_allowed=rule_types_allowed, + aliases=aliases, + extra_prefixes=extra_prefixes, + flags=flags, + ) + + self.affects = affects + self.flags += self.affects_flags() + + self.keyword = keyword + self.predefined_type = predefined_type + self.style_struct = style_struct + self.has_effect_on_gecko_scrollbars = has_effect_on_gecko_scrollbars + assert ( + has_effect_on_gecko_scrollbars in [None, False, True] + and not style_struct.inherited + or (gecko_pref is None and enabled_in != "") + == (has_effect_on_gecko_scrollbars is None) + ), ( + "Property " + + name + + ": has_effect_on_gecko_scrollbars must be " + + "specified, and must have a value of True or False, iff a " + + "property is inherited and is behind a Gecko pref or internal" + ) + self.need_index = need_index + self.gecko_ffi_name = gecko_ffi_name or "m" + self.camel_case + self.cast_type = cast_type + self.logical = arg_to_bool(logical) + self.logical_group = logical_group + if self.logical: + assert logical_group, "Property " + name + " must have a logical group" + + self.boxed = arg_to_bool(boxed) + self.allow_quirks = allow_quirks + self.ignored_when_colors_disabled = ignored_when_colors_disabled + self.is_vector = vector + self.simple_vector_bindings = simple_vector_bindings + + # This is done like this since just a plain bool argument seemed like + # really random. + if animation_value_type is None: + raise TypeError( + "animation_value_type should be specified for (" + name + ")" + ) + self.animation_value_type = animation_value_type + + self.animatable = animation_value_type != "none" + self.is_animatable_with_computed_value = ( + animation_value_type == "ComputedValue" + or animation_value_type == "discrete" + ) + + # See compute_damage for the various values this can take + self.servo_restyle_damage = servo_restyle_damage + + def affects_flags(self): + # Layout is the stronger hint. This property animation affects layout + # or frame construction. `display` or `width` are examples that should + # use this. + if self.affects == "layout": + return ["AFFECTS_LAYOUT"] + # This property doesn't affect layout, but affects overflow. + # `transform` and co. are examples of this. + if self.affects == "overflow": + return ["AFFECTS_OVERFLOW"] + # This property affects the rendered output but doesn't affect layout. + # `opacity`, `color`, or `z-index` are examples of this. + if self.affects == "paint": + return ["AFFECTS_PAINT"] + # This property doesn't affect rendering in any way. + # `user-select` is an example of this. + assert self.affects == "", ( + "Property " + + self.name + + ': affects must be specified and be one of ["layout", "overflow", "paint", ""], see Longhand.affects_flags for documentation' + ) + return [] + + @staticmethod + def type(): + return "longhand" + + # For a given logical property, return the kind of mapping we need to + # perform, and which logical value we represent, in a tuple. + def logical_mapping_data(self, data): + if not self.logical: + return [] + # Sizes and axes are basically the same for mapping, we just need + # slightly different replacements (block-size -> height, etc rather + # than -x/-y) below. + for [ty, logical_items, physical_items] in [ + ["Side", LOGICAL_SIDES, PHYSICAL_SIDES], + ["Corner", LOGICAL_CORNERS, PHYSICAL_CORNERS], + ["Axis", LOGICAL_SIZES, PHYSICAL_SIZES], + ["Axis", LOGICAL_AXES, PHYSICAL_AXES], + ]: + candidate = [s for s in logical_items if s in self.name] + if candidate: + assert len(candidate) == 1 + return [ty, candidate[0], logical_items, physical_items] + assert False, "Don't know how to deal with " + self.name + + def logical_mapping_kind(self, data): + assert self.logical + [kind, item, _, _] = self.logical_mapping_data(data) + return "LogicalMappingKind::{}(Logical{}::{})".format( + kind, kind, to_camel_case(item.replace("-size", "")) + ) + + # For a given logical property return all the physical property names + # corresponding to it. + def all_physical_mapped_properties(self, data): + if not self.logical: + return [] + [_, logical_side, _, physical_items] = self.logical_mapping_data(data) + return [ + data.longhands_by_name[to_phys(self.name, logical_side, physical_side)] + for physical_side in physical_items + ] + + def may_be_disabled_in(self, shorthand, engine): + if engine == "gecko": + return self.gecko_pref and self.gecko_pref != shorthand.gecko_pref + elif engine == "servo-2013": + return ( + self.servo_2013_pref + and self.servo_2013_pref != shorthand.servo_2013_pref + ) + elif engine == "servo-2020": + return ( + self.servo_2020_pref + and self.servo_2020_pref != shorthand.servo_2020_pref + ) + else: + raise Exception("Bad engine: " + engine) + + def base_type(self): + if self.predefined_type and not self.is_vector: + return "crate::values::specified::{}".format(self.predefined_type) + return "longhands::{}::SpecifiedValue".format(self.ident) + + def specified_type(self): + if self.predefined_type and not self.is_vector: + ty = "crate::values::specified::{}".format(self.predefined_type) + else: + ty = "longhands::{}::SpecifiedValue".format(self.ident) + if self.boxed: + ty = "Box<{}>".format(ty) + return ty + + def specified_is_copy(self): + if self.is_vector or self.boxed: + return False + if self.predefined_type: + return self.predefined_type in { + "AlignContent", + "AlignItems", + "AlignSelf", + "Appearance", + "AnimationComposition", + "AnimationDirection", + "AnimationFillMode", + "AnimationPlayState", + "AspectRatio", + "BaselineSource", + "BreakBetween", + "BreakWithin", + "BackgroundRepeat", + "BorderImageRepeat", + "BorderStyle", + "table::CaptionSide", + "Clear", + "ColumnCount", + "Contain", + "ContentVisibility", + "ContainerType", + "Display", + "FillRule", + "Float", + "FontLanguageOverride", + "FontSizeAdjust", + "FontStretch", + "FontStyle", + "FontSynthesis", + "FontVariantEastAsian", + "FontVariantLigatures", + "FontVariantNumeric", + "FontWeight", + "GreaterThanOrEqualToOneNumber", + "GridAutoFlow", + "ImageRendering", + "InitialLetter", + "Integer", + "JustifyContent", + "JustifyItems", + "JustifySelf", + "LineBreak", + "LineClamp", + "MasonryAutoFlow", + "ui::MozTheme", + "BoolInteger", + "text::MozControlCharacterVisibility", + "MathDepth", + "MozScriptMinSize", + "MozScriptSizeMultiplier", + "TransformBox", + "TextDecorationSkipInk", + "NonNegativeNumber", + "OffsetRotate", + "Opacity", + "OutlineStyle", + "Overflow", + "OverflowAnchor", + "OverflowClipBox", + "OverflowWrap", + "OverscrollBehavior", + "PageOrientation", + "Percentage", + "PrintColorAdjust", + "ForcedColorAdjust", + "Resize", + "RubyPosition", + "SVGOpacity", + "SVGPaintOrder", + "ScrollbarGutter", + "ScrollSnapAlign", + "ScrollSnapAxis", + "ScrollSnapStop", + "ScrollSnapStrictness", + "ScrollSnapType", + "TextAlign", + "TextAlignLast", + "TextDecorationLine", + "TextEmphasisPosition", + "TextJustify", + "TextTransform", + "TextUnderlinePosition", + "TouchAction", + "TransformStyle", + "UserSelect", + "WordBreak", + "XSpan", + "XTextScale", + "ZIndex", + "Zoom", + } + if self.name == "overflow-y": + return True + return bool(self.keyword) + + def animated_type(self): + assert self.animatable + computed = "<{} as ToComputedValue>::ComputedValue".format(self.base_type()) + if self.is_animatable_with_computed_value: + return computed + return "<{} as ToAnimatedValue>::AnimatedValue".format(computed) + + +class Shorthand(Property): + def __init__( + self, + name, + sub_properties, + spec=None, + servo_2013_pref=None, + servo_2020_pref=None, + gecko_pref=None, + enabled_in="content", + rule_types_allowed=DEFAULT_RULES, + aliases=None, + extra_prefixes=None, + flags=None, + ): + Property.__init__( + self, + name=name, + spec=spec, + servo_2013_pref=servo_2013_pref, + servo_2020_pref=servo_2020_pref, + gecko_pref=gecko_pref, + enabled_in=enabled_in, + rule_types_allowed=rule_types_allowed, + aliases=aliases, + extra_prefixes=extra_prefixes, + flags=flags, + ) + self.sub_properties = sub_properties + + def get_animatable(self): + for sub in self.sub_properties: + if sub.animatable: + return True + return False + + animatable = property(get_animatable) + + @staticmethod + def type(): + return "shorthand" + + +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.animatable = original.animatable + self.servo_2013_pref = original.servo_2013_pref + self.servo_2020_pref = original.servo_2020_pref + self.gecko_pref = gecko_pref + self.rule_types_allowed = original.rule_types_allowed + self.flags = original.flags + + @staticmethod + def type(): + return "alias" + + def rule_types_allowed_names(self): + for name in RULE_VALUES: + if self.rule_types_allowed & RULE_VALUES[name] != 0: + yield name + + def experimental(self, engine): + if engine == "gecko": + return bool(self.gecko_pref) + elif engine == "servo-2013": + return bool(self.servo_2013_pref) + elif engine == "servo-2020": + return bool(self.servo_2020_pref) + else: + raise Exception("Bad engine: " + engine) + + def explicitly_enabled_in_ua_sheets(self): + return self.enabled_in in ["ua", "chrome"] + + def explicitly_enabled_in_chrome(self): + return self.enabled_in == "chrome" + + def enabled_in_content(self): + return self.enabled_in == "content" + + def nscsspropertyid(self): + return "nsCSSPropertyID::eCSSPropertyAlias_%s" % self.ident + + +class Method(object): + def __init__(self, name, return_type=None, arg_types=None, is_mut=False): + self.name = name + self.return_type = return_type + self.arg_types = arg_types or [] + self.is_mut = is_mut + + def arg_list(self): + args = ["_: " + x for x in self.arg_types] + args = ["&mut self" if self.is_mut else "&self"] + args + return ", ".join(args) + + def signature(self): + sig = "fn %s(%s)" % (self.name, self.arg_list()) + if self.return_type: + sig = sig + " -> " + self.return_type + return sig + + def declare(self): + return self.signature() + ";" + + def stub(self): + return self.signature() + "{ unimplemented!() }" + + +class StyleStruct(object): + def __init__(self, name, inherited, gecko_name=None, additional_methods=None): + self.gecko_struct_name = "Gecko" + name + self.name = name + self.name_lower = to_snake_case(name) + self.ident = to_rust_ident(self.name_lower) + self.longhands = [] + self.inherited = inherited + self.gecko_name = gecko_name or name + self.gecko_ffi_name = "nsStyle" + self.gecko_name + self.additional_methods = additional_methods or [] + self.document_dependent = self.gecko_name in ["Font", "Visibility", "Text"] + + +class PropertiesData(object): + def __init__(self, engine): + self.engine = engine + self.style_structs = [] + self.current_style_struct = None + self.longhands = [] + self.longhands_by_name = {} + self.longhands_by_logical_group = {} + self.longhand_aliases = [] + self.shorthands = [] + self.shorthands_by_name = {} + self.shorthand_aliases = [] + self.counted_unknown_properties = [ + CountedUnknownProperty(p) for p in COUNTED_UNKNOWN_PROPERTIES + ] + + def new_style_struct(self, *args, **kwargs): + style_struct = StyleStruct(*args, **kwargs) + self.style_structs.append(style_struct) + self.current_style_struct = style_struct + + def active_style_structs(self): + return [s for s in self.style_structs if s.additional_methods or s.longhands] + + def add_prefixed_aliases(self, property): + # FIXME Servo's DOM architecture doesn't support vendor-prefixed properties. + # See servo/servo#14941. + if self.engine == "gecko": + for prefix, pref in property.extra_prefixes: + property.aliases.append(("-%s-%s" % (prefix, property.name), pref)) + + def declare_longhand(self, name, engines=None, **kwargs): + engines = engines.split() + if self.engine not in engines: + return + + longhand = Longhand(self.current_style_struct, name, **kwargs) + self.add_prefixed_aliases(longhand) + longhand.aliases = [Alias(xp[0], longhand, xp[1]) for xp in longhand.aliases] + self.longhand_aliases += longhand.aliases + self.current_style_struct.longhands.append(longhand) + self.longhands.append(longhand) + self.longhands_by_name[name] = longhand + if longhand.logical_group: + self.longhands_by_logical_group.setdefault( + longhand.logical_group, [] + ).append(longhand) + + return longhand + + def declare_shorthand(self, name, sub_properties, engines, *args, **kwargs): + engines = engines.split() + if self.engine not in engines: + return + + sub_properties = [self.longhands_by_name[s] for s in sub_properties] + shorthand = Shorthand(name, sub_properties, *args, **kwargs) + self.add_prefixed_aliases(shorthand) + shorthand.aliases = [Alias(xp[0], shorthand, xp[1]) for xp in shorthand.aliases] + self.shorthand_aliases += shorthand.aliases + self.shorthands.append(shorthand) + self.shorthands_by_name[name] = shorthand + return shorthand + + def shorthands_except_all(self): + return [s for s in self.shorthands if s.name != "all"] + + def all_aliases(self): + return self.longhand_aliases + self.shorthand_aliases + + +def _add_logical_props(data, props): + groups = set() + for prop in props: + if prop not in data.longhands_by_name: + assert data.engine in ["servo-2013", "servo-2020"] + continue + prop = data.longhands_by_name[prop] + if prop.logical_group: + groups.add(prop.logical_group) + for group in groups: + for prop in data.longhands_by_logical_group[group]: + props.add(prop.name) + + +# These are probably Gecko bugs and should be supported per spec. +def _remove_common_first_line_and_first_letter_properties(props, engine): + if engine == "gecko": + props.remove("tab-size") + props.remove("hyphens") + props.remove("line-break") + props.remove("text-align-last") + props.remove("text-emphasis-position") + props.remove("text-emphasis-style") + props.remove("text-emphasis-color") + + props.remove("overflow-wrap") + props.remove("text-align") + props.remove("text-justify") + props.remove("white-space-collapse") + props.remove("text-wrap-mode") + props.remove("text-wrap-style") + props.remove("word-break") + props.remove("text-indent") + + +class PropertyRestrictions: + @staticmethod + def logical_group(data, group): + return [p.name for p in data.longhands_by_logical_group[group]] + + @staticmethod + def shorthand(data, shorthand): + if shorthand not in data.shorthands_by_name: + return [] + return [p.name for p in data.shorthands_by_name[shorthand].sub_properties] + + @staticmethod + def spec(data, spec_path): + return [p.name for p in data.longhands if spec_path in p.spec] + + # https://svgwg.org/svg2-draft/propidx.html + @staticmethod + def svg_text_properties(): + props = set( + [ + "fill", + "fill-opacity", + "fill-rule", + "paint-order", + "stroke", + "stroke-dasharray", + "stroke-dashoffset", + "stroke-linecap", + "stroke-linejoin", + "stroke-miterlimit", + "stroke-opacity", + "stroke-width", + "text-rendering", + "vector-effect", + ] + ) + return props + + @staticmethod + def webkit_text_properties(): + props = set( + [ + # Kinda like css-text? + "-webkit-text-stroke-width", + "-webkit-text-fill-color", + "-webkit-text-stroke-color", + ] + ) + return props + + # https://drafts.csswg.org/css-pseudo/#first-letter-styling + @staticmethod + def first_letter(data): + props = set( + [ + "color", + "opacity", + "float", + "initial-letter", + # Kinda like css-fonts? + "-moz-osx-font-smoothing", + "vertical-align", + # Will become shorthand of vertical-align (Bug 1830771) + "baseline-source", + "line-height", + # Kinda like css-backgrounds? + "background-blend-mode", + ] + + PropertyRestrictions.shorthand(data, "padding") + + PropertyRestrictions.shorthand(data, "margin") + + PropertyRestrictions.spec(data, "css-fonts") + + PropertyRestrictions.spec(data, "css-backgrounds") + + PropertyRestrictions.spec(data, "css-text") + + PropertyRestrictions.spec(data, "css-shapes") + + PropertyRestrictions.spec(data, "css-text-decor") + ) + props = props.union(PropertyRestrictions.svg_text_properties()) + props = props.union(PropertyRestrictions.webkit_text_properties()) + + _add_logical_props(data, props) + + _remove_common_first_line_and_first_letter_properties(props, data.engine) + return props + + # https://drafts.csswg.org/css-pseudo/#first-line-styling + @staticmethod + def first_line(data): + props = set( + [ + # Per spec. + "color", + "opacity", + # Kinda like css-fonts? + "-moz-osx-font-smoothing", + "vertical-align", + # Will become shorthand of vertical-align (Bug 1830771) + "baseline-source", + "line-height", + # Kinda like css-backgrounds? + "background-blend-mode", + ] + + PropertyRestrictions.spec(data, "css-fonts") + + PropertyRestrictions.spec(data, "css-backgrounds") + + PropertyRestrictions.spec(data, "css-text") + + PropertyRestrictions.spec(data, "css-text-decor") + ) + props = props.union(PropertyRestrictions.svg_text_properties()) + props = props.union(PropertyRestrictions.webkit_text_properties()) + + # These are probably Gecko bugs and should be supported per spec. + for prop in PropertyRestrictions.shorthand(data, "border"): + props.remove(prop) + for prop in PropertyRestrictions.shorthand(data, "border-radius"): + props.remove(prop) + props.remove("box-shadow") + + _remove_common_first_line_and_first_letter_properties(props, data.engine) + return props + + # https://drafts.csswg.org/css-pseudo/#placeholder + # + # The spec says that placeholder and first-line have the same restrictions, + # but that's not true in Gecko and we also allow a handful other properties + # for ::placeholder. + @staticmethod + def placeholder(data): + props = PropertyRestrictions.first_line(data) + props.add("opacity") + props.add("text-overflow") + props.add("text-align") + props.add("text-justify") + for p in PropertyRestrictions.shorthand(data, "text-wrap"): + props.add(p) + for p in PropertyRestrictions.shorthand(data, "white-space"): + props.add(p) + # ::placeholder can't be SVG text + props -= PropertyRestrictions.svg_text_properties() + + return props + + # https://drafts.csswg.org/css-pseudo/#marker-pseudo + @staticmethod + def marker(data): + return set( + [ + "color", + "text-combine-upright", + "text-transform", + "unicode-bidi", + "direction", + "content", + "line-height", + "-moz-osx-font-smoothing", + ] + + PropertyRestrictions.shorthand(data, "text-wrap") + + PropertyRestrictions.shorthand(data, "white-space") + + PropertyRestrictions.spec(data, "css-fonts") + + PropertyRestrictions.spec(data, "css-animations") + + PropertyRestrictions.spec(data, "css-transitions") + ) + + # https://www.w3.org/TR/webvtt1/#the-cue-pseudo-element + @staticmethod + def cue(data): + return set( + [ + "color", + "opacity", + "visibility", + "text-shadow", + "text-combine-upright", + "ruby-position", + # XXX Should these really apply to cue? + "-moz-osx-font-smoothing", + # FIXME(emilio): background-blend-mode should be part of the + # background shorthand, and get reset, per + # https://drafts.fxtf.org/compositing/#background-blend-mode + "background-blend-mode", + ] + + PropertyRestrictions.shorthand(data, "text-decoration") + + PropertyRestrictions.shorthand(data, "text-wrap") + + PropertyRestrictions.shorthand(data, "white-space") + + PropertyRestrictions.shorthand(data, "background") + + PropertyRestrictions.shorthand(data, "outline") + + PropertyRestrictions.shorthand(data, "font") + + PropertyRestrictions.shorthand(data, "font-synthesis") + ) + + +class CountedUnknownProperty: + def __init__(self, name): + self.name = name + self.ident = to_rust_ident(name) + self.camel_case = to_camel_case(self.ident) |