tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

data.py (39300B)


      1 # This Source Code Form is subject to the terms of the Mozilla Public
      2 # License, v. 2.0. If a copy of the MPL was not distributed with this
      3 # file, You can obtain one at https://mozilla.org/MPL/2.0/.
      4 
      5 import re
      6 from counted_unknown_properties import COUNTED_UNKNOWN_PROPERTIES
      7 
      8 # It is important that the order of these physical / logical variants matches
      9 # the order of the enum variants in logical_geometry.rs
     10 PHYSICAL_SIDES = ["top", "right", "bottom", "left"]
     11 PHYSICAL_CORNERS = ["top-left", "top-right", "bottom-right", "bottom-left"]
     12 PHYSICAL_AXES = ["y", "x"]
     13 PHYSICAL_SIZES = ["height", "width"]
     14 LOGICAL_SIDES = ["block-start", "block-end", "inline-start", "inline-end"]
     15 LOGICAL_CORNERS = ["start-start", "start-end", "end-start", "end-end"]
     16 LOGICAL_SIZES = ["block-size", "inline-size"]
     17 LOGICAL_AXES = ["block", "inline"]
     18 
     19 # bool is True when logical
     20 ALL_SIDES = [(side, False) for side in PHYSICAL_SIDES] + [
     21    (side, True) for side in LOGICAL_SIDES
     22 ]
     23 ALL_SIZES = [(size, False) for size in PHYSICAL_SIZES] + [
     24    (size, True) for size in LOGICAL_SIZES
     25 ]
     26 ALL_CORNERS = [(corner, False) for corner in PHYSICAL_CORNERS] + [
     27    (corner, True) for corner in LOGICAL_CORNERS
     28 ]
     29 ALL_AXES = [(axis, False) for axis in PHYSICAL_AXES] + [
     30    (axis, True) for axis in LOGICAL_AXES
     31 ]
     32 
     33 SYSTEM_FONT_LONGHANDS = """font_family font_size font_style
     34                           font_stretch font_weight""".split()
     35 
     36 PRIORITARY_PROPERTIES = set(
     37    [
     38        # The writing-mode group has the most priority of all property groups, as
     39        # sizes like font-size can depend on it.
     40        "writing-mode",
     41        "direction",
     42        "text-orientation",
     43        # The fonts and colors group has the second priority, as all other lengths
     44        # and colors depend on them.
     45        #
     46        # There are some interdependencies between these, but we fix them up in
     47        # Cascade::fixup_font_stuff.
     48        # Needed to properly compute the zoomed font-size.
     49        "-x-text-scale",
     50        # Needed to do font-size computation in a language-dependent way.
     51        "-x-lang",
     52        # Needed for ruby to respect language-dependent min-font-size
     53        # preferences properly, see bug 1165538.
     54        "-moz-min-font-size-ratio",
     55        # font-size depends on math-depth's computed value.
     56        "math-depth",
     57        # Needed to compute the first available font and its used size,
     58        # in order to compute font-relative units correctly.
     59        "font-size",
     60        "font-size-adjust",
     61        "font-weight",
     62        "font-stretch",
     63        "font-style",
     64        "font-family",
     65        # color-scheme affects how system colors and light-dark() resolve.
     66        "color-scheme",
     67        # forced-color-adjust affects whether colors are adjusted.
     68        "forced-color-adjust",
     69        # Zoom affects all absolute lengths.
     70        "zoom",
     71        # Line height lengths depend on this.
     72        "line-height",
     73    ]
     74 )
     75 
     76 VISITED_DEPENDENT_PROPERTIES = set(
     77    [
     78        "column-rule-color",
     79        "text-emphasis-color",
     80        "-webkit-text-fill-color",
     81        "-webkit-text-stroke-color",
     82        "text-decoration-color",
     83        "fill",
     84        "stroke",
     85        "caret-color",
     86        "background-color",
     87        "border-top-color",
     88        "border-right-color",
     89        "border-bottom-color",
     90        "border-left-color",
     91        "border-block-start-color",
     92        "border-inline-end-color",
     93        "border-block-end-color",
     94        "border-inline-start-color",
     95        "outline-color",
     96        "color",
     97    ]
     98 )
     99 
    100 # Bitfield values for all rule types which can have property declarations.
    101 STYLE_RULE = 1 << 0
    102 PAGE_RULE = 1 << 1
    103 KEYFRAME_RULE = 1 << 2
    104 POSITION_TRY_RULE = 1 << 3
    105 SCOPE_RULE = 1 << 4
    106 
    107 ALL_RULES = STYLE_RULE | PAGE_RULE | KEYFRAME_RULE | SCOPE_RULE
    108 DEFAULT_RULES = STYLE_RULE | KEYFRAME_RULE | SCOPE_RULE
    109 DEFAULT_RULES_AND_PAGE = DEFAULT_RULES | PAGE_RULE
    110 DEFAULT_RULES_EXCEPT_KEYFRAME = STYLE_RULE | SCOPE_RULE
    111 DEFAULT_RULES_AND_POSITION_TRY = DEFAULT_RULES | POSITION_TRY_RULE
    112 
    113 # Rule name to value dict
    114 RULE_VALUES = {
    115    "Style": STYLE_RULE,
    116    "Page": PAGE_RULE,
    117    "Keyframe": KEYFRAME_RULE,
    118    "PositionTry": POSITION_TRY_RULE,
    119    "Scope": SCOPE_RULE,
    120 }
    121 
    122 
    123 def rule_values_from_arg(that):
    124    if isinstance(that, int):
    125        return that
    126    mask = 0
    127    for rule in that.split():
    128        mask |= RULE_VALUES[rule]
    129    return mask
    130 
    131 
    132 def maybe_moz_logical_alias(engine, side, prop):
    133    if engine == "gecko" and side[1]:
    134        axis, dir = side[0].split("-")
    135        if axis == "inline":
    136            return prop % dir
    137    return None
    138 
    139 
    140 def to_rust_ident(name):
    141    name = name.replace("-", "_")
    142    if name in ["static", "super", "box", "move"]:  # Rust keywords
    143        name += "_"
    144    return name
    145 
    146 
    147 def to_snake_case(ident):
    148    return re.sub("([A-Z]+)", lambda m: "_" + m.group(1).lower(), ident).strip("_")
    149 
    150 
    151 def to_camel_case(ident):
    152    return re.sub(
    153        "(^|_|-)([a-z0-9])", lambda m: m.group(2).upper(), ident.strip("_").strip("-")
    154    )
    155 
    156 
    157 def to_camel_case_lower(ident):
    158    camel = to_camel_case(ident)
    159    return camel[0].lower() + camel[1:]
    160 
    161 
    162 # https://drafts.csswg.org/cssom/#css-property-to-idl-attribute
    163 def to_idl_name(ident):
    164    return re.sub("-([a-z])", lambda m: m.group(1).upper(), ident)
    165 
    166 
    167 def parse_aliases(value):
    168    aliases = {}
    169    for pair in value.split():
    170        [a, v] = pair.split("=")
    171        aliases[a] = v
    172    return aliases
    173 
    174 
    175 class Keyword(object):
    176    def __init__(
    177        self,
    178        name,
    179        values,
    180        gecko_constant_prefix=None,
    181        gecko_enum_prefix=None,
    182        custom_consts=None,
    183        extra_gecko_values=None,
    184        extra_servo_values=None,
    185        gecko_aliases=None,
    186        servo_aliases=None,
    187        gecko_strip_moz_prefix=None,
    188        gecko_inexhaustive=None,
    189    ):
    190        self.name = name
    191        self.values = values.split()
    192        if gecko_constant_prefix and gecko_enum_prefix:
    193            raise TypeError(
    194                "Only one of gecko_constant_prefix and gecko_enum_prefix "
    195                "can be specified"
    196            )
    197        self.gecko_constant_prefix = (
    198            gecko_constant_prefix or "NS_STYLE_" + self.name.upper().replace("-", "_")
    199        )
    200        self.gecko_enum_prefix = gecko_enum_prefix
    201        self.extra_gecko_values = (extra_gecko_values or "").split()
    202        self.extra_servo_values = (extra_servo_values or "").split()
    203        self.gecko_aliases = parse_aliases(gecko_aliases or "")
    204        self.servo_aliases = parse_aliases(servo_aliases or "")
    205        self.consts_map = {} if custom_consts is None else custom_consts
    206        self.gecko_strip_moz_prefix = (
    207            True if gecko_strip_moz_prefix is None else gecko_strip_moz_prefix
    208        )
    209        self.gecko_inexhaustive = gecko_inexhaustive or (gecko_enum_prefix is None)
    210 
    211    def values_for(self, engine):
    212        if engine == "gecko":
    213            return self.values + self.extra_gecko_values
    214        elif engine == "servo":
    215            return self.values + self.extra_servo_values
    216        else:
    217            raise Exception("Bad engine: " + engine)
    218 
    219    def aliases_for(self, engine):
    220        if engine == "gecko":
    221            return self.gecko_aliases
    222        elif engine == "servo":
    223            return self.servo_aliases
    224        else:
    225            raise Exception("Bad engine: " + engine)
    226 
    227    def gecko_constant(self, value):
    228        moz_stripped = (
    229            value.replace("-moz-", "")
    230            if self.gecko_strip_moz_prefix
    231            else value.replace("-moz-", "moz-")
    232        )
    233        mapped = self.consts_map.get(value)
    234        if self.gecko_enum_prefix:
    235            parts = moz_stripped.replace("-", "_").split("_")
    236            parts = mapped if mapped else [p.title() for p in parts]
    237            return self.gecko_enum_prefix + "::" + "".join(parts)
    238        else:
    239            suffix = mapped if mapped else moz_stripped.replace("-", "_")
    240            return self.gecko_constant_prefix + "_" + suffix.upper()
    241 
    242    def needs_cast(self):
    243        return self.gecko_enum_prefix is None
    244 
    245    def maybe_cast(self, type_str):
    246        return "as " + type_str if self.needs_cast() else ""
    247 
    248    def casted_constant_name(self, value, cast_type):
    249        if cast_type is None:
    250            raise TypeError("We should specify the cast_type.")
    251 
    252        if self.gecko_enum_prefix is None:
    253            return cast_type.upper() + "_" + self.gecko_constant(value)
    254        else:
    255            return (
    256                cast_type.upper()
    257                + "_"
    258                + self.gecko_constant(value).upper().replace("::", "_")
    259            )
    260 
    261 
    262 def arg_to_bool(arg):
    263    if isinstance(arg, bool):
    264        return arg
    265    assert arg in ["True", "False"], "Unexpected value for boolean arguement: " + repr(
    266        arg
    267    )
    268    return arg == "True"
    269 
    270 
    271 def parse_property_aliases(alias_list):
    272    result = []
    273    if alias_list:
    274        for alias in alias_list.split():
    275            (name, _, pref) = alias.partition(":")
    276            result.append((name, pref))
    277    return result
    278 
    279 
    280 def to_phys(name, logical, physical):
    281    return name.replace(logical, physical).replace("inset-", "")
    282 
    283 
    284 class Property(object):
    285    def __init__(
    286        self,
    287        name,
    288        spec,
    289        servo_pref,
    290        gecko_pref,
    291        enabled_in,
    292        rule_types_allowed,
    293        aliases,
    294        extra_prefixes,
    295        flags,
    296    ):
    297        self.name = name
    298        if not spec:
    299            raise TypeError("Spec should be specified for " + name)
    300        self.spec = spec
    301        self.ident = to_rust_ident(name)
    302        self.camel_case = to_camel_case(self.ident)
    303        self.servo_pref = servo_pref
    304        self.gecko_pref = gecko_pref
    305        self.rule_types_allowed = rule_values_from_arg(rule_types_allowed)
    306        # For enabled_in, the setup is as follows:
    307        # It needs to be one of the four values: ["", "ua", "chrome", "content"]
    308        #  * "chrome" implies "ua", and implies that they're explicitly
    309        #    enabled.
    310        #  * "" implies the property will never be parsed.
    311        #  * "content" implies the property is accessible unconditionally,
    312        #    modulo a pref, set via servo_pref / gecko_pref.
    313        assert enabled_in in ("", "ua", "chrome", "content")
    314        self.enabled_in = enabled_in
    315        self.aliases = parse_property_aliases(aliases)
    316        self.extra_prefixes = parse_property_aliases(extra_prefixes)
    317        self.flags = flags.split() if flags else []
    318 
    319    def rule_types_allowed_names(self):
    320        for name in RULE_VALUES:
    321            if self.rule_types_allowed & RULE_VALUES[name] != 0:
    322                yield name
    323 
    324    def experimental(self, engine):
    325        if engine == "gecko":
    326            return bool(self.gecko_pref)
    327        elif engine == "servo":
    328            return bool(self.servo_pref)
    329        else:
    330            raise Exception("Bad engine: " + engine)
    331 
    332    def explicitly_enabled_in_ua_sheets(self):
    333        return self.enabled_in in ("ua", "chrome")
    334 
    335    def explicitly_enabled_in_chrome(self):
    336        return self.enabled_in == "chrome"
    337 
    338    def enabled_in_content(self):
    339        return self.enabled_in == "content"
    340 
    341    def is_visited_dependent(self):
    342        return self.name in VISITED_DEPENDENT_PROPERTIES
    343 
    344    def is_prioritary(self):
    345        return self.name in PRIORITARY_PROPERTIES
    346 
    347    def noncustomcsspropertyid(self):
    348        return "NonCustomCSSPropertyId::eCSSProperty_" + self.ident
    349 
    350 
    351 class Longhand(Property):
    352    def __init__(
    353        self,
    354        style_struct,
    355        name,
    356        spec=None,
    357        animation_type=None,
    358        keyword=None,
    359        predefined_type=None,
    360        servo_pref=None,
    361        gecko_pref=None,
    362        enabled_in="content",
    363        need_index=False,
    364        gecko_ffi_name=None,
    365        has_effect_on_gecko_scrollbars=None,
    366        rule_types_allowed=DEFAULT_RULES,
    367        cast_type="u8",
    368        logical=False,
    369        logical_group=None,
    370        aliases=None,
    371        extra_prefixes=None,
    372        boxed=False,
    373        flags=None,
    374        allow_quirks="No",
    375        ignored_when_colors_disabled=False,
    376        simple_vector_bindings=False,
    377        vector=False,
    378        servo_restyle_damage="rebuild_box",
    379        affects=None,
    380    ):
    381        Property.__init__(
    382            self,
    383            name=name,
    384            spec=spec,
    385            servo_pref=servo_pref,
    386            gecko_pref=gecko_pref,
    387            enabled_in=enabled_in,
    388            rule_types_allowed=rule_types_allowed,
    389            aliases=aliases,
    390            extra_prefixes=extra_prefixes,
    391            flags=flags,
    392        )
    393 
    394        self.affects = affects
    395        self.flags += self.affects_flags()
    396 
    397        self.keyword = keyword
    398        self.predefined_type = predefined_type
    399        self.style_struct = style_struct
    400        self.has_effect_on_gecko_scrollbars = has_effect_on_gecko_scrollbars
    401        assert (
    402            has_effect_on_gecko_scrollbars in [None, False, True]
    403            and not style_struct.inherited
    404            or (gecko_pref is None and enabled_in != "")
    405            == (has_effect_on_gecko_scrollbars is None)
    406        ), (
    407            "Property "
    408            + name
    409            + ": has_effect_on_gecko_scrollbars must be "
    410            + "specified, and must have a value of True or False, iff a "
    411            + "property is inherited and is behind a Gecko pref or internal"
    412        )
    413        self.need_index = need_index
    414        self.gecko_ffi_name = gecko_ffi_name or "m" + self.camel_case
    415        self.cast_type = cast_type
    416        self.logical = arg_to_bool(logical)
    417        self.logical_group = logical_group
    418        if self.logical:
    419            assert logical_group, "Property " + name + " must have a logical group"
    420 
    421        self.boxed = arg_to_bool(boxed)
    422        self.allow_quirks = allow_quirks
    423        self.ignored_when_colors_disabled = ignored_when_colors_disabled
    424        self.is_vector = vector
    425        self.simple_vector_bindings = simple_vector_bindings
    426 
    427        # This is done like this since just a plain bool argument seemed like
    428        # really random.
    429        if animation_type is None:
    430            animation_type = "normal"
    431        assert animation_type in ["none", "normal", "discrete"]
    432        self.animation_type = animation_type
    433        self.animatable = animation_type != "none"
    434 
    435        # See compute_damage for the various values this can take
    436        self.servo_restyle_damage = servo_restyle_damage
    437 
    438    def affects_flags(self):
    439        # Layout is the stronger hint. This property animation affects layout
    440        # or frame construction. `display` or `width` are examples that should
    441        # use this.
    442        if self.affects == "layout":
    443            return ["AFFECTS_LAYOUT"]
    444        # This property doesn't affect layout, but affects overflow.
    445        # `transform` and co. are examples of this.
    446        if self.affects == "overflow":
    447            return ["AFFECTS_OVERFLOW"]
    448        # This property affects the rendered output but doesn't affect layout.
    449        # `opacity`, `color`, or `z-index` are examples of this.
    450        if self.affects == "paint":
    451            return ["AFFECTS_PAINT"]
    452        # This property doesn't affect rendering in any way.
    453        # `user-select` is an example of this.
    454        assert self.affects == "", (
    455            "Property "
    456            + self.name
    457            + ': affects must be specified and be one of ["layout", "overflow", "paint", ""], see Longhand.affects_flags for documentation'
    458        )
    459        return []
    460 
    461    @staticmethod
    462    def type():
    463        return "longhand"
    464 
    465    # For a given logical property, return the kind of mapping we need to
    466    # perform, and which logical value we represent, in a tuple.
    467    def logical_mapping_data(self, data):
    468        if not self.logical:
    469            return []
    470        # Sizes and axes are basically the same for mapping, we just need
    471        # slightly different replacements (block-size -> height, etc rather
    472        # than -x/-y) below.
    473        for [ty, logical_items, physical_items] in [
    474            ["Side", LOGICAL_SIDES, PHYSICAL_SIDES],
    475            ["Corner", LOGICAL_CORNERS, PHYSICAL_CORNERS],
    476            ["Axis", LOGICAL_SIZES, PHYSICAL_SIZES],
    477            ["Axis", LOGICAL_AXES, PHYSICAL_AXES],
    478        ]:
    479            candidate = [s for s in logical_items if s in self.name]
    480            if candidate:
    481                assert len(candidate) == 1
    482                return [ty, candidate[0], logical_items, physical_items]
    483        assert False, "Don't know how to deal with " + self.name
    484 
    485    def logical_mapping_kind(self, data):
    486        assert self.logical
    487        [kind, item, _, _] = self.logical_mapping_data(data)
    488        return "LogicalMappingKind::{}(Logical{}::{})".format(
    489            kind, kind, to_camel_case(item.replace("-size", ""))
    490        )
    491 
    492    # For a given logical property return all the physical property names
    493    # corresponding to it.
    494    def all_physical_mapped_properties(self, data):
    495        if not self.logical:
    496            return []
    497        [_, logical_side, _, physical_items] = self.logical_mapping_data(data)
    498        return [
    499            data.longhands_by_name[to_phys(self.name, logical_side, physical_side)]
    500            for physical_side in physical_items
    501        ]
    502 
    503    def may_be_disabled_in(self, shorthand, engine):
    504        if engine == "gecko":
    505            return self.gecko_pref and self.gecko_pref != shorthand.gecko_pref
    506        elif engine == "servo":
    507            return self.servo_pref and self.servo_pref != shorthand.servo_pref
    508        else:
    509            raise Exception("Bad engine: " + engine)
    510 
    511    def base_type(self):
    512        if self.predefined_type and not self.is_vector:
    513            return "crate::values::specified::{}".format(self.predefined_type)
    514        return "longhands::{}::SpecifiedValue".format(self.ident)
    515 
    516    def specified_type(self):
    517        if self.predefined_type and not self.is_vector:
    518            ty = "crate::values::specified::{}".format(self.predefined_type)
    519        else:
    520            ty = "longhands::{}::SpecifiedValue".format(self.ident)
    521        if self.boxed:
    522            ty = "Box<{}>".format(ty)
    523        return ty
    524 
    525    def is_zoom_dependent(self):
    526        if not self.predefined_type:
    527            return False
    528        # TODO: Get this from SpecifiedValueInfo or so instead; see bug 1887627.
    529        return self.predefined_type in {
    530            "BorderSpacing",
    531            "FontSize",
    532            "Inset",
    533            "Length",
    534            "LengthPercentage",
    535            "LengthPercentageOrAuto",
    536            "LetterSpacing",
    537            "LineHeight",
    538            "LineWidth",
    539            "MaxSize",
    540            "NonNegativeLength",
    541            "NonNegativeLengthOrAuto",
    542            "NonNegativeLengthOrNumber",
    543            "NonNegativeLengthOrNumberRect",
    544            "NonNegativeLengthPercentage",
    545            "NonNegativeLengthPercentageOrNormal",
    546            "Position",
    547            "PositionOrAuto",
    548            "SimpleShadow",
    549            "Size",
    550            "SVGLength",
    551            "SVGStrokeDashArray",
    552            "SVGWidth",
    553            "TextDecorationLength",
    554            "TextDecorationInset",
    555            "TextIndent",
    556            "WordSpacing",
    557        }
    558 
    559    def is_inherited_zoom_dependent_property(self):
    560        if self.logical:
    561            return False
    562        if not self.style_struct.inherited:
    563            return False
    564        return self.is_zoom_dependent()
    565 
    566    def specified_is_copy(self):
    567        if self.is_vector or self.boxed:
    568            return False
    569        if self.predefined_type:
    570            return self.predefined_type in {
    571                "Appearance",
    572                "AnimationComposition",
    573                "AnimationDirection",
    574                "AnimationFillMode",
    575                "AnimationPlayState",
    576                "AspectRatio",
    577                "BaselineSource",
    578                "BreakBetween",
    579                "BreakWithin",
    580                "BackgroundRepeat",
    581                "BorderImageRepeat",
    582                "BorderStyle",
    583                "table::CaptionSide",
    584                "Clear",
    585                "ColumnCount",
    586                "Contain",
    587                "ContentVisibility",
    588                "ContainerType",
    589                "Display",
    590                "FillRule",
    591                "Float",
    592                "FontLanguageOverride",
    593                "FontSizeAdjust",
    594                "FontStretch",
    595                "FontStyle",
    596                "FontSynthesis",
    597                "FontSynthesisStyle",
    598                "FontVariantEastAsian",
    599                "FontVariantLigatures",
    600                "FontVariantNumeric",
    601                "FontWeight",
    602                "GreaterThanOrEqualToOneNumber",
    603                "GridAutoFlow",
    604                "ImageRendering",
    605                "Inert",
    606                "InitialLetter",
    607                "Integer",
    608                "PositionArea",
    609                "PositionAreaKeyword",
    610                "PositionProperty",
    611                "ContentDistribution",
    612                "ItemPlacement",
    613                "SelfAlignment",
    614                "JustifyItems",
    615                "LineBreak",
    616                "LineClamp",
    617                "MasonryAutoFlow",
    618                "MozTheme",
    619                "BoolInteger",
    620                "text::MozControlCharacterVisibility",
    621                "MathDepth",
    622                "MozScriptMinSize",
    623                "MozScriptSizeMultiplier",
    624                "TransformBox",
    625                "TextDecorationSkipInk",
    626                "NonNegativeNumber",
    627                "OffsetRotate",
    628                "Opacity",
    629                "OutlineStyle",
    630                "Overflow",
    631                "OverflowAnchor",
    632                "OverflowWrap",
    633                "OverscrollBehavior",
    634                "PageOrientation",
    635                "Percentage",
    636                "PointerEvents",
    637                "PositionTryOrder",
    638                "PositionVisibility",
    639                "PrintColorAdjust",
    640                "ForcedColorAdjust",
    641                "Resize",
    642                "RubyPosition",
    643                "SVGOpacity",
    644                "SVGPaintOrder",
    645                "ScrollbarGutter",
    646                "ScrollSnapAlign",
    647                "ScrollSnapAxis",
    648                "ScrollSnapStop",
    649                "ScrollSnapStrictness",
    650                "ScrollSnapType",
    651                "TextAlign",
    652                "TextAlignLast",
    653                "TextAutospace",
    654                "TextDecorationLine",
    655                "TextEmphasisPosition",
    656                "TextJustify",
    657                "TextTransform",
    658                "TextUnderlinePosition",
    659                "TouchAction",
    660                "TransformStyle",
    661                "UserFocus",
    662                "UserSelect",
    663                "VectorEffect",
    664                "WordBreak",
    665                "WritingModeProperty",
    666                "XSpan",
    667                "XTextScale",
    668                "ZIndex",
    669                "Zoom",
    670            }
    671        if self.name == "overflow-y":
    672            return True
    673        return bool(self.keyword)
    674 
    675    def animated_type(self):
    676        assert self.animatable
    677        computed = "<{} as ToComputedValue>::ComputedValue".format(self.base_type())
    678        if self.animation_type == "discrete":
    679            return computed
    680        return "<{} as ToAnimatedValue>::AnimatedValue".format(computed)
    681 
    682 
    683 class Shorthand(Property):
    684    def __init__(
    685        self,
    686        name,
    687        sub_properties,
    688        spec=None,
    689        servo_pref=None,
    690        gecko_pref=None,
    691        enabled_in="content",
    692        rule_types_allowed=DEFAULT_RULES,
    693        aliases=None,
    694        extra_prefixes=None,
    695        flags=None,
    696    ):
    697        Property.__init__(
    698            self,
    699            name=name,
    700            spec=spec,
    701            servo_pref=servo_pref,
    702            gecko_pref=gecko_pref,
    703            enabled_in=enabled_in,
    704            rule_types_allowed=rule_types_allowed,
    705            aliases=aliases,
    706            extra_prefixes=extra_prefixes,
    707            flags=flags,
    708        )
    709        self.sub_properties = sub_properties
    710 
    711    def get_animatable(self):
    712        for sub in self.sub_properties:
    713            if sub.animatable:
    714                return True
    715        return False
    716 
    717    animatable = property(get_animatable)
    718 
    719    @staticmethod
    720    def type():
    721        return "shorthand"
    722 
    723 
    724 class Alias(object):
    725    def __init__(self, name, original, gecko_pref):
    726        self.name = name
    727        self.ident = to_rust_ident(name)
    728        self.camel_case = to_camel_case(self.ident)
    729        self.original = original
    730        self.enabled_in = original.enabled_in
    731        self.animatable = original.animatable
    732        self.servo_pref = original.servo_pref
    733        self.gecko_pref = gecko_pref
    734        self.rule_types_allowed = original.rule_types_allowed
    735        self.flags = original.flags
    736 
    737    @staticmethod
    738    def type():
    739        return "alias"
    740 
    741    def rule_types_allowed_names(self):
    742        for name in RULE_VALUES:
    743            if self.rule_types_allowed & RULE_VALUES[name] != 0:
    744                yield name
    745 
    746    def experimental(self, engine):
    747        if engine == "gecko":
    748            return bool(self.gecko_pref)
    749        elif engine == "servo":
    750            return bool(self.servo_pref)
    751        else:
    752            raise Exception("Bad engine: " + engine)
    753 
    754    def explicitly_enabled_in_ua_sheets(self):
    755        return self.enabled_in in ["ua", "chrome"]
    756 
    757    def explicitly_enabled_in_chrome(self):
    758        return self.enabled_in == "chrome"
    759 
    760    def enabled_in_content(self):
    761        return self.enabled_in == "content"
    762 
    763    def noncustomcsspropertyid(self):
    764        return "NonCustomCSSPropertyId::eCSSPropertyAlias_%s" % self.ident
    765 
    766 
    767 class Method(object):
    768    def __init__(self, name, return_type=None, arg_types=None, is_mut=False):
    769        self.name = name
    770        self.return_type = return_type
    771        self.arg_types = arg_types or []
    772        self.is_mut = is_mut
    773 
    774    def arg_list(self):
    775        args = ["_: " + x for x in self.arg_types]
    776        args = ["&mut self" if self.is_mut else "&self"] + args
    777        return ", ".join(args)
    778 
    779    def signature(self):
    780        sig = "fn %s(%s)" % (self.name, self.arg_list())
    781        if self.return_type:
    782            sig = sig + " -> " + self.return_type
    783        return sig
    784 
    785    def declare(self):
    786        return self.signature() + ";"
    787 
    788    def stub(self):
    789        return self.signature() + "{ unimplemented!() }"
    790 
    791 
    792 class StyleStruct(object):
    793    def __init__(self, name, inherited, gecko_name=None):
    794        self.gecko_struct_name = "Gecko" + name
    795        self.name = name
    796        self.name_lower = to_snake_case(name)
    797        self.ident = to_rust_ident(self.name_lower)
    798        self.longhands = []
    799        self.inherited = inherited
    800        self.gecko_name = gecko_name or name
    801        self.gecko_ffi_name = "nsStyle" + self.gecko_name
    802        self.document_dependent = self.gecko_name in ["Font", "Visibility", "Text"]
    803 
    804 
    805 class PropertiesData(object):
    806    def __init__(self, engine):
    807        self.engine = engine
    808        self.longhands = []
    809        self.longhands_by_name = {}
    810        self.longhands_by_logical_group = {}
    811        self.longhand_aliases = []
    812        self.shorthands = []
    813        self.shorthands_by_name = {}
    814        self.shorthand_aliases = []
    815        self.counted_unknown_properties = [
    816            CountedUnknownProperty(p) for p in COUNTED_UNKNOWN_PROPERTIES
    817        ]
    818 
    819        self.style_structs = [
    820            StyleStruct("Background", inherited=False),
    821            StyleStruct("Border", inherited=False),
    822            StyleStruct("Box", inherited=False, gecko_name="Display"),
    823            StyleStruct("Column", inherited=False),
    824            StyleStruct("Counters", inherited=False, gecko_name="Content"),
    825            StyleStruct("Effects", inherited=False),
    826            StyleStruct("Font", inherited=True),
    827            StyleStruct("InheritedBox", inherited=True, gecko_name="Visibility"),
    828            StyleStruct("InheritedSVG", inherited=True, gecko_name="SVG"),
    829            StyleStruct("InheritedTable", inherited=True, gecko_name="TableBorder"),
    830            StyleStruct("InheritedText", inherited=True, gecko_name="Text"),
    831            StyleStruct("InheritedUI", inherited=True, gecko_name="UI"),
    832            StyleStruct("List", inherited=True),
    833            StyleStruct("Margin", inherited=False),
    834            StyleStruct("Outline", inherited=False),
    835            StyleStruct("Padding", inherited=False),
    836            StyleStruct("Page", inherited=False),
    837            StyleStruct("Position", inherited=False),
    838            StyleStruct("SVG", inherited=False, gecko_name="SVGReset"),
    839            StyleStruct("Table", inherited=False),
    840            StyleStruct("Text", inherited=False, gecko_name="TextReset"),
    841            StyleStruct("UI", inherited=False, gecko_name="UIReset"),
    842            StyleStruct("XUL", inherited=False),
    843        ]
    844        self.current_style_struct = None
    845 
    846    def active_style_structs(self):
    847        return [s for s in self.style_structs if s.longhands]
    848 
    849    def add_prefixed_aliases(self, property):
    850        # FIXME Servo's DOM architecture doesn't support vendor-prefixed properties.
    851        #       See servo/servo#14941.
    852        if self.engine == "gecko":
    853            for prefix, pref in property.extra_prefixes:
    854                property.aliases.append(("-%s-%s" % (prefix, property.name), pref))
    855 
    856    def declare_longhand(self, name, engines=None, **kwargs):
    857        engines = engines.split()
    858        if self.engine not in engines:
    859            return
    860 
    861        longhand = Longhand(self.current_style_struct, name, **kwargs)
    862        self.add_prefixed_aliases(longhand)
    863        longhand.aliases = [Alias(xp[0], longhand, xp[1]) for xp in longhand.aliases]
    864        self.longhand_aliases += longhand.aliases
    865        self.current_style_struct.longhands.append(longhand)
    866        self.longhands.append(longhand)
    867        self.longhands_by_name[name] = longhand
    868        if longhand.logical_group:
    869            self.longhands_by_logical_group.setdefault(
    870                longhand.logical_group, []
    871            ).append(longhand)
    872 
    873        return longhand
    874 
    875    def declare_shorthand(self, name, sub_properties, engines, *args, **kwargs):
    876        engines = engines.split()
    877        if self.engine not in engines:
    878            return
    879 
    880        sub_properties = [self.longhands_by_name[s] for s in sub_properties]
    881        shorthand = Shorthand(name, sub_properties, *args, **kwargs)
    882        self.add_prefixed_aliases(shorthand)
    883        shorthand.aliases = [Alias(xp[0], shorthand, xp[1]) for xp in shorthand.aliases]
    884        self.shorthand_aliases += shorthand.aliases
    885        self.shorthands.append(shorthand)
    886        self.shorthands_by_name[name] = shorthand
    887        return shorthand
    888 
    889    def shorthands_except_all(self):
    890        return [s for s in self.shorthands if s.name != "all"]
    891 
    892    def all_aliases(self):
    893        return self.longhand_aliases + self.shorthand_aliases
    894 
    895 
    896 def _add_logical_props(data, props):
    897    groups = set()
    898    for prop in props:
    899        if prop not in data.longhands_by_name:
    900            assert data.engine == "servo"
    901            continue
    902        prop = data.longhands_by_name[prop]
    903        if prop.logical_group:
    904            groups.add(prop.logical_group)
    905    for group in groups:
    906        for prop in data.longhands_by_logical_group[group]:
    907            props.add(prop.name)
    908 
    909 
    910 # These are probably Gecko bugs and should be supported per spec.
    911 def _remove_common_first_line_and_first_letter_properties(props, engine):
    912    if engine == "gecko":
    913        props.remove("tab-size")
    914        props.remove("hyphens")
    915        props.remove("line-break")
    916        props.remove("text-align-last")
    917        props.remove("text-emphasis-position")
    918        props.remove("text-emphasis-style")
    919        props.remove("text-emphasis-color")
    920        props.remove("text-wrap-style")
    921 
    922    props.remove("overflow-wrap")
    923    props.remove("text-align")
    924    props.remove("text-justify")
    925    props.remove("white-space-collapse")
    926    props.remove("text-wrap-mode")
    927    props.remove("word-break")
    928    props.remove("text-indent")
    929 
    930 
    931 class PropertyRestrictions:
    932    @staticmethod
    933    def logical_group(data, group):
    934        return [p.name for p in data.longhands_by_logical_group[group]]
    935 
    936    @staticmethod
    937    def shorthand(data, shorthand):
    938        if shorthand not in data.shorthands_by_name:
    939            return []
    940        return [p.name for p in data.shorthands_by_name[shorthand].sub_properties]
    941 
    942    @staticmethod
    943    def spec(data, spec_path):
    944        return [p.name for p in data.longhands if spec_path in p.spec]
    945 
    946    # https://svgwg.org/svg2-draft/propidx.html
    947    @staticmethod
    948    def svg_text_properties():
    949        props = set(
    950            [
    951                "fill",
    952                "fill-opacity",
    953                "fill-rule",
    954                "paint-order",
    955                "stroke",
    956                "stroke-dasharray",
    957                "stroke-dashoffset",
    958                "stroke-linecap",
    959                "stroke-linejoin",
    960                "stroke-miterlimit",
    961                "stroke-opacity",
    962                "stroke-width",
    963                "text-rendering",
    964                "vector-effect",
    965            ]
    966        )
    967        return props
    968 
    969    @staticmethod
    970    def webkit_text_properties():
    971        props = set(
    972            [
    973                # Kinda like css-text?
    974                "-webkit-text-stroke-width",
    975                "-webkit-text-fill-color",
    976                "-webkit-text-stroke-color",
    977            ]
    978        )
    979        return props
    980 
    981    # https://drafts.csswg.org/css-pseudo/#first-letter-styling
    982    @staticmethod
    983    def first_letter(data):
    984        props = set(
    985            [
    986                "color",
    987                "opacity",
    988                "float",
    989                "initial-letter",
    990                # Kinda like css-fonts?
    991                "-moz-osx-font-smoothing",
    992                "vertical-align",
    993                # Will become shorthand of vertical-align (Bug 1830771)
    994                "baseline-source",
    995                "line-height",
    996                # Kinda like css-backgrounds?
    997                "background-blend-mode",
    998            ]
    999            + PropertyRestrictions.shorthand(data, "padding")
   1000            + PropertyRestrictions.shorthand(data, "margin")
   1001            + PropertyRestrictions.spec(data, "css-fonts")
   1002            + PropertyRestrictions.spec(data, "css-backgrounds")
   1003            + PropertyRestrictions.spec(data, "css-text")
   1004            + PropertyRestrictions.spec(data, "css-shapes")
   1005            + PropertyRestrictions.spec(data, "css-text-decor")
   1006        )
   1007        props = props.union(PropertyRestrictions.svg_text_properties())
   1008        props = props.union(PropertyRestrictions.webkit_text_properties())
   1009 
   1010        _add_logical_props(data, props)
   1011 
   1012        _remove_common_first_line_and_first_letter_properties(props, data.engine)
   1013        return props
   1014 
   1015    # https://drafts.csswg.org/css-pseudo/#first-line-styling
   1016    @staticmethod
   1017    def first_line(data):
   1018        props = set(
   1019            [
   1020                # Per spec.
   1021                "color",
   1022                "opacity",
   1023                # Kinda like css-fonts?
   1024                "-moz-osx-font-smoothing",
   1025                "vertical-align",
   1026                # Will become shorthand of vertical-align (Bug 1830771)
   1027                "baseline-source",
   1028                "line-height",
   1029                # Kinda like css-backgrounds?
   1030                "background-blend-mode",
   1031            ]
   1032            + PropertyRestrictions.spec(data, "css-fonts")
   1033            + PropertyRestrictions.spec(data, "css-backgrounds")
   1034            + PropertyRestrictions.spec(data, "css-text")
   1035            + PropertyRestrictions.spec(data, "css-text-decor")
   1036        )
   1037        props = props.union(PropertyRestrictions.svg_text_properties())
   1038        props = props.union(PropertyRestrictions.webkit_text_properties())
   1039 
   1040        # These are probably Gecko bugs and should be supported per spec.
   1041        for prop in PropertyRestrictions.shorthand(data, "border"):
   1042            props.remove(prop)
   1043        for prop in PropertyRestrictions.shorthand(data, "border-radius"):
   1044            props.remove(prop)
   1045        props.remove("box-shadow")
   1046 
   1047        _remove_common_first_line_and_first_letter_properties(props, data.engine)
   1048        return props
   1049 
   1050    # https://drafts.csswg.org/css-pseudo/#placeholder
   1051    #
   1052    # The spec says that placeholder and first-line have the same restrictions,
   1053    # but that's not true in Gecko and we also allow a handful other properties
   1054    # for ::placeholder.
   1055    @staticmethod
   1056    def placeholder(data):
   1057        props = PropertyRestrictions.first_line(data)
   1058        props.add("opacity")
   1059        props.add("text-overflow")
   1060        props.add("text-align")
   1061        props.add("text-justify")
   1062        for p in PropertyRestrictions.shorthand(data, "text-wrap"):
   1063            props.add(p)
   1064        for p in PropertyRestrictions.shorthand(data, "white-space"):
   1065            props.add(p)
   1066        # ::placeholder can't be SVG text
   1067        props -= PropertyRestrictions.svg_text_properties()
   1068 
   1069        return props
   1070 
   1071    # https://drafts.csswg.org/css-lists-3/#marker-properties
   1072    @staticmethod
   1073    def marker(data):
   1074        return set(
   1075            [
   1076                "color",
   1077                "content",
   1078                "counter-increment",
   1079                "counter-reset",
   1080                "counter-set",
   1081                "cursor",
   1082                "direction",
   1083                "hyphens",
   1084                "line-height",
   1085                "quotes",
   1086                "text-combine-upright",
   1087                "text-emphasis-color",
   1088                "text-emphasis-position",
   1089                "text-emphasis-style",
   1090                "text-orientation",
   1091                "text-shadow",
   1092                "text-transform",
   1093                "unicode-bidi",
   1094                "-moz-osx-font-smoothing",
   1095            ]
   1096            + PropertyRestrictions.shorthand(data, "text-wrap")
   1097            + PropertyRestrictions.shorthand(data, "white-space")
   1098            + PropertyRestrictions.spec(data, "css-fonts")
   1099            + PropertyRestrictions.spec(data, "css-animations")
   1100            + PropertyRestrictions.spec(data, "css-transitions")
   1101        )
   1102 
   1103    # https://www.w3.org/TR/webvtt1/#the-cue-pseudo-element
   1104    @staticmethod
   1105    def cue(data):
   1106        return set(
   1107            [
   1108                "color",
   1109                "opacity",
   1110                "visibility",
   1111                "text-shadow",
   1112                "text-combine-upright",
   1113                "ruby-position",
   1114                # XXX Should these really apply to cue?
   1115                "-moz-osx-font-smoothing",
   1116                # FIXME(emilio): background-blend-mode should be part of the
   1117                # background shorthand, and get reset, per
   1118                # https://drafts.fxtf.org/compositing/#background-blend-mode
   1119                "background-blend-mode",
   1120            ]
   1121            + PropertyRestrictions.shorthand(data, "text-decoration")
   1122            + PropertyRestrictions.shorthand(data, "text-wrap")
   1123            + PropertyRestrictions.shorthand(data, "white-space")
   1124            + PropertyRestrictions.shorthand(data, "background")
   1125            + PropertyRestrictions.shorthand(data, "outline")
   1126            + PropertyRestrictions.shorthand(data, "font")
   1127            + PropertyRestrictions.shorthand(data, "font-synthesis")
   1128        )
   1129 
   1130 
   1131 class CountedUnknownProperty:
   1132    def __init__(self, name):
   1133        self.name = name
   1134        self.ident = to_rust_ident(name)
   1135        self.camel_case = to_camel_case(self.ident)