# 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 import toml import os from itertools import groupby 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"]
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 and light-dark() 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", # appearance and -moz-default-appearance control whether # @appearance-base rules apply. "-moz-default-appearance", "appearance",
]
)
def parse_property_aliases(alias_list):
result = [] if alias_list: for alias in alias_list:
(name, _, pref) = alias.partition(":")
result.append((name, pref)) return result
class Property(object): def __init__(
self,
name,
spec,
servo_pref,
gecko_pref,
enabled_in,
rule_types_allowed,
aliases,
extra_prefixes,
flags,
):
self.name = name ifnot 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_pref = servo_pref
self.gecko_pref = gecko_pref
self.idl_method = idl_method(name, self.camel_case)
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
self.parse_method = parse_method
self.initial_value = initial_value
self.initial_specified_value = initial_specified_value
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] andnot style_struct.inherited or (gecko_pref isNoneand enabled_in != "")
== (has_effect_on_gecko_scrollbars isNone)
), ( "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.gecko_ffi_name = gecko_ffi_name or"m" + self.camel_case
self.cast_type = cast_type
self.logical = logical
self.logical_group = logical_group if self.logical: assert logical_group, f"Property {name} must have a logical group"
# 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): ifnot 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] assertFalse, "Don't know how to deal with " + self.name
# For a given logical property return all the physical property names # corresponding to it. def all_physical_mapped_properties(self, data): ifnot 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 base_type(self): if self.predefined_type andnot self.vector: return"crate::values::specified::{}".format(self.predefined_type) return"longhands::{}::SpecifiedValue".format(self.ident)
def specified_type(self): if self.predefined_type andnot self.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 is_zoom_dependent(self): ifnot self.predefined_type: returnFalse # TODO: Get this from SpecifiedValueInfo or so instead; see bug 1887627. return self.predefined_type in { "BorderSideWidth", "BorderSpacing", "BoxShadow", "Filter", "FontSize", "Inset", "Length", "LengthPercentage", "LengthPercentageOrAuto", "LetterSpacing", "LineHeight", "LineWidth", "MaxSize", "Margin", "NonNegativeLength", "NonNegativeLengthOrAuto", "NonNegativeLengthOrNumber", "NonNegativeLengthOrNumberRect", "NonNegativeLengthPercentage", "NonNegativeLengthPercentageOrAuto", "NonNegativeLengthPercentageOrNormal", "Position", "PositionOrAuto", "SimpleShadow", "Size", "SVGLength", "SVGStrokeDashArray", "SVGWidth", "TextDecorationLength", "TextDecorationInset", "TextIndent", "WordSpacing",
}
def is_inherited_zoom_dependent_property(self): if self.logical: returnFalse ifnot self.style_struct.inherited: returnFalse return self.is_zoom_dependent()
longhands_toml = toml.loads(open(os.path.join(os.path.dirname(__file__), "longhands.toml")).read()) for name, args in longhands_toml.items():
style_struct = self.style_struct_by_name_lower(args["struct"]) del args['struct']
# Handle keyword properties if'keyword'in args:
keyword_dict = args.pop('keyword') if'values'notin keyword_dict: raise TypeError(f"{name}: keyword should have 'values'")
values = keyword_dict.pop('values')
keyword = Keyword(name, values, **keyword_dict)
self.declare_longhand(style_struct, name, keyword=keyword, **args) else: # Handle predefined_type properties if'type'notin args: raise TypeError(f"{name} should have a type")
args['predefined_type'] = args.pop('type') if'initial'notin args andnot args.get('vector'): raise TypeError(f"{name} should have an initial value (only vector properties should lack one)")
args['initial_value'] = args.pop('initial', None)
self.declare_longhand(style_struct, name, **args)
for group, props in self.logical_groups.items():
logical_count = sum(1for p in props if p.logical) if logical_count * 2 != len(props): raise RuntimeError(f"Logical group {group} has unbalanced logical / physical properties")
# After this code, `data.longhands` is sorted in the following order: # - first all keyword variants and all variants known to be Copy, # - second all the other variants, such as all variants with the same field # have consecutive discriminants. # The variable `variants` contain the same entries as `data.longhands` in # the same order, but must exist separately to the data source, because # we then need to add three additional variants `WideKeywordDeclaration`, # `VariableDeclaration` and `CustomDeclaration`.
self.declaration_variants = [] for property in self.longhands:
self.declaration_variants.append({ "name": property.camel_case, "type": property.specified_type(), "doc": "`" + property.name + "`", "copy": property.specified_is_copy(),
})
groups = {}
keyfunc = lambda x: x["type"]
sortkeys = {} # WARNING: It is *really* important for the variants of `LonghandId` # and `PropertyDeclaration` to be defined in the exact same order, # with the exception of `CSSWideKeyword`, `WithVariables` and `Custom`, # which don't exist in `LonghandId`. for ty, group in groupby(sorted(self.declaration_variants, key=keyfunc), keyfunc):
group = list(group)
groups[ty] = group for v in group: if len(group) == 1:
sortkeys[v["name"]] = (not v["copy"], 1, v["name"], "") else:
sortkeys[v["name"]] = (not v["copy"], len(group), ty, v["name"]) # It is extremely important to sort the `data.longhands` array here so # that it is in the same order as `variants`, for `LonghandId` and # `PropertyDeclarationId` to coincide.
self.longhands.sort(key=lambda x: sortkeys[x.camel_case])
self.declaration_variants.sort(key=lambda x: sortkeys[x["name"]])
self.declaration_extra_variants = [
{ "name": "CSSWideKeyword", "type": "WideKeywordDeclaration", "doc": "A CSS-wide keyword.", "copy": False,
},
{ "name": "WithVariables", "type": "VariableDeclaration", "doc": "An unparsed declaration.", "copy": False,
},
{ "name": "Custom", "type": "CustomDeclaration", "doc": "A custom property declaration.", "copy": False,
},
] for v in self.declaration_extra_variants:
self.declaration_variants.append(v)
groups[v["type"]] = [v]
shorthands_toml = toml.loads(open(os.path.join(os.path.dirname(__file__), "shorthands.toml")).read()) for name, args in shorthands_toml.items():
self.declare_shorthand(name, **args)
self.declare_all_shorthand()
def declare_all_shorthand(self): # We don't define the 'all' shorthand using the regular helpers:shorthand # mechanism, since it causes some very large types to be generated. # # Make sure logical properties appear before its physical # counter-parts, in order to prevent bugs like: # # https://bugzilla.mozilla.org/show_bug.cgi?id=1410028 # # FIXME(emilio): Adopt the resolution from: # # https://github.com/w3c/csswg-drafts/issues/1898 # # when there is one, whatever that is.
logical_longhands = []
other_longhands = [] for p in self.longhands: if p.name in ['direction', 'unicode-bidi']: continue; ifnot p.enabled_in_content() andnot p.experimental(self.engine): continue; if"style"notin p.rule_types_allowed_names(): continue; if p.logical:
logical_longhands.append(p) else:
other_longhands.append(p)
# Cache locality when iterating over the `all` shorthand is important # for transition handling, so we sort by style struct. # We technically don't care about the logical prop order (because those # are not animated themselves), but we sort them the same way for # consistency.
logical_longhands.sort(key=lambda p: p.style_struct.name)
other_longhands.sort(key=lambda p: p.style_struct.name)
def _load_descriptors(self, filename):
path = os.path.join(os.path.dirname(__file__), filename)
data = toml.loads(open(path).read()) return [Descriptor(name, **args) for name, args in data.items()]
def style_struct_by_name_lower(self, name): for s in self.style_structs: if s.name_lower == name: return s raise TypeError(f"Unexpected struct name {name}")
def active_style_structs(self): return [s for s in self.style_structs if s.longhands]
def add_prefixed_aliases(self, property): for prefix, pref in property.extra_prefixes:
property.aliases.append(("-%s-%s" % (prefix, property.name), pref))
def declare_longhand(self, style_struct, name, extra_gecko_aliases=None, engine=None, **kwargs): if engine and self.engine != engine: return if extra_gecko_aliases and self.engine == "gecko":
kwargs.setdefault('aliases', []).extend(extra_gecko_aliases)
longhand = Longhand(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
style_struct.longhands.append(longhand)
self.longhands.append(longhand)
self.longhands_by_name[name] = longhand if longhand.logical_group:
self.logical_groups.setdefault(
longhand.logical_group, []
).append(longhand)
return longhand
def declare_shorthand(self, name, sub_properties, extra_gecko_sub_properties=None, extra_gecko_aliases=None, engine=None, *args, **kwargs): if engine and self.engine != engine: return if self.engine == "gecko": if extra_gecko_sub_properties:
sub_properties.extend(extra_gecko_sub_properties) if extra_gecko_aliases:
kwargs.setdefault('aliases', []).extend(extra_gecko_aliases)
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 _add_logical_props(data, props):
groups = set() for prop in props: if prop notin data.longhands_by_name: assert data.engine == "servo" continue
prop = data.longhands_by_name[prop] if prop.logical_group:
groups.add(prop.logical_group) for group in groups: for prop in data.logical_groups[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("text-wrap-style")
class PropertyRestrictions:
@staticmethod def logical_group(data, group): return [p.name for p in data.logical_groups[group]]
@staticmethod def shorthand(data, shorthand): if shorthand notin 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]
# 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")
# 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() # Historically ::placeholder's line-height was !important in the UA sheet.
props.remove("line-height")
# 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)
Messung V0.5 in Prozent
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.19Bemerkung:
(vorverarbeitet am 2026-08-25)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.