mirror of
https://github.com/servo/servo.git
synced 2025-07-03 05:23:38 +01:00
|animation_type| was renamed in 94fb839fdd
, but the commit missed renaming
one place. It means that some of logical properties might have been
accidentally animatable. Logical properties should be animatable but we
haven't yet implemented the proper Animate trait for logical properties.
486 lines
18 KiB
Python
486 lines
18 KiB
Python
# 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 re
|
|
|
|
PHYSICAL_SIDES = ["top", "left", "bottom", "right"]
|
|
LOGICAL_SIDES = ["block-start", "block-end", "inline-start", "inline-end"]
|
|
PHYSICAL_SIZES = ["width", "height"]
|
|
LOGICAL_SIZES = ["block-size", "inline-size"]
|
|
|
|
# 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]
|
|
|
|
SYSTEM_FONT_LONGHANDS = """font_family font_size font_style
|
|
font_variant_caps font_stretch font_kerning
|
|
font_variant_position font_weight
|
|
font_size_adjust font_variant_alternates
|
|
font_variant_ligatures font_variant_east_asian
|
|
font_variant_numeric font_language_override
|
|
font_feature_settings font_variation_settings
|
|
font_optical_sizing""".split()
|
|
|
|
|
|
def maybe_moz_logical_alias(product, side, prop):
|
|
if product == "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_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_values=None,
|
|
aliases=None,
|
|
extra_gecko_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_values = (extra_servo_values or "").split()
|
|
self.aliases = parse_aliases(aliases or "")
|
|
self.extra_gecko_aliases = parse_aliases(extra_gecko_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 gecko_values(self):
|
|
return self.values + self.extra_gecko_values
|
|
|
|
def servo_values(self):
|
|
return self.values + self.extra_servo_values
|
|
|
|
def gecko_aliases(self):
|
|
aliases = self.aliases.copy()
|
|
aliases.update(self.extra_gecko_aliases)
|
|
return aliases
|
|
|
|
def values_for(self, product):
|
|
if product == "gecko":
|
|
return self.gecko_values()
|
|
elif product == "servo":
|
|
return self.servo_values()
|
|
else:
|
|
raise Exception("Bad product: " + product)
|
|
|
|
def aliases_for(self, product):
|
|
if product == "gecko":
|
|
return self.gecko_aliases()
|
|
elif product == "servo":
|
|
return self.aliases
|
|
else:
|
|
raise Exception("Bad product: " + product)
|
|
|
|
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"
|
|
|
|
|
|
class Longhand(object):
|
|
def __init__(self, style_struct, name, spec=None, animation_value_type=None, keyword=None,
|
|
predefined_type=None, servo_pref=None, gecko_pref=None,
|
|
enabled_in="content", need_index=False,
|
|
gecko_ffi_name=None,
|
|
allowed_in_keyframe_block=True, cast_type='u8',
|
|
logical=False, alias=None, extra_prefixes=None, boxed=False,
|
|
flags=None, allowed_in_page_rule=False, allow_quirks=False, ignored_when_colors_disabled=False,
|
|
vector=False, need_animatable=False, servo_restyle_damage="repaint"):
|
|
self.name = name
|
|
if not spec:
|
|
raise TypeError("Spec should be specified for %s" % name)
|
|
self.spec = spec
|
|
self.keyword = keyword
|
|
self.predefined_type = predefined_type
|
|
self.ident = to_rust_ident(name)
|
|
self.camel_case = to_camel_case(self.ident)
|
|
self.style_struct = style_struct
|
|
self.servo_pref = servo_pref
|
|
self.gecko_pref = gecko_pref
|
|
# 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.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.alias = alias.split() if alias else []
|
|
self.extra_prefixes = extra_prefixes.split() if extra_prefixes else []
|
|
self.boxed = arg_to_bool(boxed)
|
|
self.flags = flags.split() if flags else []
|
|
self.allowed_in_page_rule = arg_to_bool(allowed_in_page_rule)
|
|
self.allow_quirks = allow_quirks
|
|
self.ignored_when_colors_disabled = ignored_when_colors_disabled
|
|
self.is_vector = vector
|
|
|
|
# https://drafts.csswg.org/css-animations/#keyframes
|
|
# > The <declaration-list> inside of <keyframe-block> accepts any CSS property
|
|
# > except those defined in this specification,
|
|
# > but does accept the `animation-play-state` property and interprets it specially.
|
|
self.allowed_in_keyframe_block = allowed_in_keyframe_block \
|
|
and allowed_in_keyframe_block != "False"
|
|
|
|
# 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.transitionable = animation_value_type != "none" \
|
|
and animation_value_type != "discrete"
|
|
self.is_animatable_with_computed_value = animation_value_type == "ComputedValue" \
|
|
or animation_value_type == "discrete"
|
|
if self.logical:
|
|
# Logical properties will be animatable (i.e. the animation type is
|
|
# discrete). For now, it is still non-animatable.
|
|
self.animatable = False
|
|
self.transitionable = False
|
|
self.animation_value_type = None
|
|
|
|
# See compute_damage for the various values this can take
|
|
self.servo_restyle_damage = servo_restyle_damage
|
|
|
|
def experimental(self, product):
|
|
if product == "gecko":
|
|
return bool(self.gecko_pref)
|
|
return bool(self.servo_pref)
|
|
|
|
# FIXME(emilio): Shorthand and Longhand should really share a base class.
|
|
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 may_be_disabled_in(self, shorthand, product):
|
|
if product == "gecko":
|
|
return self.gecko_pref and self.gecko_pref != shorthand.gecko_pref
|
|
return self.servo_pref and self.servo_pref != shorthand.servo_pref
|
|
|
|
def base_type(self):
|
|
if self.predefined_type and not self.is_vector:
|
|
return "::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 = "::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",
|
|
"BackgroundRepeat",
|
|
"BorderImageRepeat",
|
|
"BorderStyle",
|
|
"ColumnCount",
|
|
"Contain",
|
|
"FontStyleAdjust",
|
|
"FontSynthesis",
|
|
"FontWeight",
|
|
"GridAutoFlow",
|
|
"ImageOrientation",
|
|
"InitialLetter",
|
|
"Integer",
|
|
"JustifyContent",
|
|
"JustifyItems",
|
|
"JustifySelf",
|
|
"MozForceBrokenImageIcon",
|
|
"MozScriptLevel",
|
|
"MozScriptMinSize",
|
|
"MozScriptSizeMultiplier",
|
|
"NonNegativeNumber",
|
|
"Opacity",
|
|
"OutlineStyle",
|
|
"OverscrollBehavior",
|
|
"Percentage",
|
|
"SVGPaintOrder",
|
|
"ScrollSnapType",
|
|
"TextDecorationLine",
|
|
"TouchAction",
|
|
"TransformStyle",
|
|
"XSpan",
|
|
"XTextZoom",
|
|
"ZIndex",
|
|
}
|
|
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(object):
|
|
def __init__(self, name, sub_properties, spec=None, servo_pref=None, gecko_pref=None,
|
|
enabled_in="content",
|
|
allowed_in_keyframe_block=True, alias=None, extra_prefixes=None,
|
|
allowed_in_page_rule=False, flags=None):
|
|
self.name = name
|
|
if not spec:
|
|
raise TypeError("Spec should be specified for %s" % name)
|
|
self.spec = spec
|
|
self.ident = to_rust_ident(name)
|
|
self.camel_case = to_camel_case(self.ident)
|
|
self.servo_pref = servo_pref
|
|
self.gecko_pref = gecko_pref
|
|
self.sub_properties = sub_properties
|
|
assert enabled_in in ["", "ua", "chrome", "content"]
|
|
self.enabled_in = enabled_in
|
|
self.alias = alias.split() if alias else []
|
|
self.extra_prefixes = extra_prefixes.split() if extra_prefixes else []
|
|
self.allowed_in_page_rule = arg_to_bool(allowed_in_page_rule)
|
|
self.flags = flags.split() if flags else []
|
|
|
|
# https://drafts.csswg.org/css-animations/#keyframes
|
|
# > The <declaration-list> inside of <keyframe-block> accepts any CSS property
|
|
# > except those defined in this specification,
|
|
# > but does accept the `animation-play-state` property and interprets it specially.
|
|
self.allowed_in_keyframe_block = allowed_in_keyframe_block \
|
|
and allowed_in_keyframe_block != "False"
|
|
|
|
def get_animatable(self):
|
|
animatable = False
|
|
for sub in self.sub_properties:
|
|
if sub.animatable:
|
|
animatable = True
|
|
break
|
|
return animatable
|
|
|
|
def get_transitionable(self):
|
|
transitionable = False
|
|
for sub in self.sub_properties:
|
|
if sub.transitionable:
|
|
transitionable = True
|
|
break
|
|
return transitionable
|
|
|
|
animatable = property(get_animatable)
|
|
transitionable = property(get_transitionable)
|
|
|
|
def experimental(self, product):
|
|
if product == "gecko":
|
|
return bool(self.gecko_pref)
|
|
return bool(self.servo_pref)
|
|
|
|
# FIXME(emilio): Shorthand and Longhand should really share a base class.
|
|
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"
|
|
|
|
|
|
class Alias(object):
|
|
def __init__(self, name, original):
|
|
self.name = name
|
|
self.ident = to_rust_ident(name)
|
|
self.camel_case = to_camel_case(self.ident)
|
|
self.enabled_in = original.enabled_in
|
|
self.servo_pref = original.servo_pref
|
|
self.gecko_pref = original.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":
|
|
return bool(self.gecko_pref)
|
|
return bool(self.servo_pref)
|
|
|
|
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"
|
|
|
|
|
|
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 = name.lower()
|
|
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 []
|
|
|
|
|
|
class PropertiesData(object):
|
|
def __init__(self, product):
|
|
self.product = product
|
|
self.style_structs = []
|
|
self.current_style_struct = None
|
|
self.longhands = []
|
|
self.longhands_by_name = {}
|
|
self.longhand_aliases = []
|
|
self.shorthands = []
|
|
self.shorthand_aliases = []
|
|
|
|
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.product == "gecko":
|
|
for prefix in property.extra_prefixes:
|
|
property.alias.append('-%s-%s' % (prefix, property.name))
|
|
|
|
def declare_longhand(self, name, products="gecko servo", **kwargs):
|
|
products = products.split()
|
|
if self.product not in products:
|
|
return
|
|
|
|
longhand = Longhand(self.current_style_struct, name, **kwargs)
|
|
self.add_prefixed_aliases(longhand)
|
|
self.longhand_aliases += list(map(lambda x: Alias(x, longhand), longhand.alias))
|
|
self.current_style_struct.longhands.append(longhand)
|
|
self.longhands.append(longhand)
|
|
self.longhands_by_name[name] = longhand
|
|
|
|
return longhand
|
|
|
|
def declare_shorthand(self, name, sub_properties, products="gecko servo", *args, **kwargs):
|
|
products = products.split()
|
|
if self.product not in products:
|
|
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)
|
|
self.shorthand_aliases += list(map(lambda x: Alias(x, shorthand), shorthand.alias))
|
|
self.shorthands.append(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
|