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)