Backed out 5 changesets (bug 1731541) for causing multiple wpt failures. CLOSED TREE
[gecko.git] / servo / components / style / properties / data.py
blobbbda4dc9b15cd1a455dd0733c6dc800f4656607f
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/.
5 import re
6 from counted_unknown_properties import COUNTED_UNKNOWN_PROPERTIES
8 PHYSICAL_SIDES = ["top", "right", "bottom", "left"]
9 LOGICAL_SIDES = ["block-start", "block-end", "inline-start", "inline-end"]
10 PHYSICAL_SIZES = ["width", "height"]
11 LOGICAL_SIZES = ["block-size", "inline-size"]
12 PHYSICAL_CORNERS = ["top-left", "top-right", "bottom-right", "bottom-left"]
13 LOGICAL_CORNERS = ["start-start", "start-end", "end-start", "end-end"]
14 PHYSICAL_AXES = ["x", "y"]
15 LOGICAL_AXES = ["inline", "block"]
17 # bool is True when logical
18 ALL_SIDES = [(side, False) for side in PHYSICAL_SIDES] + [
19 (side, True) for side in LOGICAL_SIDES
21 ALL_SIZES = [(size, False) for size in PHYSICAL_SIZES] + [
22 (size, True) for size in LOGICAL_SIZES
24 ALL_CORNERS = [(corner, False) for corner in PHYSICAL_CORNERS] + [
25 (corner, True) for corner in LOGICAL_CORNERS
27 ALL_AXES = [(axis, False) for axis in PHYSICAL_AXES] + [
28 (axis, True) for axis in LOGICAL_AXES
31 SYSTEM_FONT_LONGHANDS = """font_family font_size font_style
32 font_stretch font_weight""".split()
34 # Bitfield values for all rule types which can have property declarations.
35 STYLE_RULE = 1 << 0
36 PAGE_RULE = 1 << 1
37 KEYFRAME_RULE = 1 << 2
39 ALL_RULES = STYLE_RULE | PAGE_RULE | KEYFRAME_RULE
40 DEFAULT_RULES = STYLE_RULE | KEYFRAME_RULE
41 DEFAULT_RULES_AND_PAGE = DEFAULT_RULES | PAGE_RULE
42 DEFAULT_RULES_EXCEPT_KEYFRAME = STYLE_RULE
44 # Rule name to value dict
45 RULE_VALUES = {
46 "Style": STYLE_RULE,
47 "Page": PAGE_RULE,
48 "Keyframe": KEYFRAME_RULE,
52 def rule_values_from_arg(that):
53 if isinstance(that, int):
54 return that
55 mask = 0
56 for rule in that.split():
57 mask |= RULE_VALUES[rule]
58 return mask
61 def maybe_moz_logical_alias(engine, side, prop):
62 if engine == "gecko" and side[1]:
63 axis, dir = side[0].split("-")
64 if axis == "inline":
65 return prop % dir
66 return None
69 def to_rust_ident(name):
70 name = name.replace("-", "_")
71 if name in ["static", "super", "box", "move"]: # Rust keywords
72 name += "_"
73 return name
76 def to_snake_case(ident):
77 return re.sub("([A-Z]+)", lambda m: "_" + m.group(1).lower(), ident).strip("_")
80 def to_camel_case(ident):
81 return re.sub(
82 "(^|_|-)([a-z0-9])", lambda m: m.group(2).upper(), ident.strip("_").strip("-")
86 def to_camel_case_lower(ident):
87 camel = to_camel_case(ident)
88 return camel[0].lower() + camel[1:]
91 # https://drafts.csswg.org/cssom/#css-property-to-idl-attribute
92 def to_idl_name(ident):
93 return re.sub("-([a-z])", lambda m: m.group(1).upper(), ident)
96 def parse_aliases(value):
97 aliases = {}
98 for pair in value.split():
99 [a, v] = pair.split("=")
100 aliases[a] = v
101 return aliases
104 class Keyword(object):
105 def __init__(
106 self,
107 name,
108 values,
109 gecko_constant_prefix=None,
110 gecko_enum_prefix=None,
111 custom_consts=None,
112 extra_gecko_values=None,
113 extra_servo_2013_values=None,
114 extra_servo_2020_values=None,
115 gecko_aliases=None,
116 servo_2013_aliases=None,
117 servo_2020_aliases=None,
118 gecko_strip_moz_prefix=None,
119 gecko_inexhaustive=None,
121 self.name = name
122 self.values = values.split()
123 if gecko_constant_prefix and gecko_enum_prefix:
124 raise TypeError(
125 "Only one of gecko_constant_prefix and gecko_enum_prefix "
126 "can be specified"
128 self.gecko_constant_prefix = (
129 gecko_constant_prefix or "NS_STYLE_" + self.name.upper().replace("-", "_")
131 self.gecko_enum_prefix = gecko_enum_prefix
132 self.extra_gecko_values = (extra_gecko_values or "").split()
133 self.extra_servo_2013_values = (extra_servo_2013_values or "").split()
134 self.extra_servo_2020_values = (extra_servo_2020_values or "").split()
135 self.gecko_aliases = parse_aliases(gecko_aliases or "")
136 self.servo_2013_aliases = parse_aliases(servo_2013_aliases or "")
137 self.servo_2020_aliases = parse_aliases(servo_2020_aliases or "")
138 self.consts_map = {} if custom_consts is None else custom_consts
139 self.gecko_strip_moz_prefix = (
140 True if gecko_strip_moz_prefix is None else gecko_strip_moz_prefix
142 self.gecko_inexhaustive = gecko_inexhaustive or (gecko_enum_prefix is None)
144 def values_for(self, engine):
145 if engine == "gecko":
146 return self.values + self.extra_gecko_values
147 elif engine == "servo-2013":
148 return self.values + self.extra_servo_2013_values
149 elif engine == "servo-2020":
150 return self.values + self.extra_servo_2020_values
151 else:
152 raise Exception("Bad engine: " + engine)
154 def aliases_for(self, engine):
155 if engine == "gecko":
156 return self.gecko_aliases
157 elif engine == "servo-2013":
158 return self.servo_2013_aliases
159 elif engine == "servo-2020":
160 return self.servo_2020_aliases
161 else:
162 raise Exception("Bad engine: " + engine)
164 def gecko_constant(self, value):
165 moz_stripped = (
166 value.replace("-moz-", "")
167 if self.gecko_strip_moz_prefix
168 else value.replace("-moz-", "moz-")
170 mapped = self.consts_map.get(value)
171 if self.gecko_enum_prefix:
172 parts = moz_stripped.replace("-", "_").split("_")
173 parts = mapped if mapped else [p.title() for p in parts]
174 return self.gecko_enum_prefix + "::" + "".join(parts)
175 else:
176 suffix = mapped if mapped else moz_stripped.replace("-", "_")
177 return self.gecko_constant_prefix + "_" + suffix.upper()
179 def needs_cast(self):
180 return self.gecko_enum_prefix is None
182 def maybe_cast(self, type_str):
183 return "as " + type_str if self.needs_cast() else ""
185 def casted_constant_name(self, value, cast_type):
186 if cast_type is None:
187 raise TypeError("We should specify the cast_type.")
189 if self.gecko_enum_prefix is None:
190 return cast_type.upper() + "_" + self.gecko_constant(value)
191 else:
192 return (
193 cast_type.upper()
194 + "_"
195 + self.gecko_constant(value).upper().replace("::", "_")
199 def arg_to_bool(arg):
200 if isinstance(arg, bool):
201 return arg
202 assert arg in ["True", "False"], "Unexpected value for boolean arguement: " + repr(
205 return arg == "True"
208 def parse_property_aliases(alias_list):
209 result = []
210 if alias_list:
211 for alias in alias_list.split():
212 (name, _, pref) = alias.partition(":")
213 result.append((name, pref))
214 return result
217 def to_phys(name, logical, physical):
218 return name.replace(logical, physical).replace("inset-", "")
221 class Property(object):
222 def __init__(
223 self,
224 name,
225 spec,
226 servo_2013_pref,
227 servo_2020_pref,
228 gecko_pref,
229 enabled_in,
230 rule_types_allowed,
231 aliases,
232 extra_prefixes,
233 flags,
235 self.name = name
236 if not spec:
237 raise TypeError("Spec should be specified for " + name)
238 self.spec = spec
239 self.ident = to_rust_ident(name)
240 self.camel_case = to_camel_case(self.ident)
241 self.servo_2013_pref = servo_2013_pref
242 self.servo_2020_pref = servo_2020_pref
243 self.gecko_pref = gecko_pref
244 self.rule_types_allowed = rule_values_from_arg(rule_types_allowed)
245 # For enabled_in, the setup is as follows:
246 # It needs to be one of the four values: ["", "ua", "chrome", "content"]
247 # * "chrome" implies "ua", and implies that they're explicitly
248 # enabled.
249 # * "" implies the property will never be parsed.
250 # * "content" implies the property is accessible unconditionally,
251 # modulo a pref, set via servo_pref / gecko_pref.
252 assert enabled_in in ("", "ua", "chrome", "content")
253 self.enabled_in = enabled_in
254 self.aliases = parse_property_aliases(aliases)
255 self.extra_prefixes = parse_property_aliases(extra_prefixes)
256 self.flags = flags.split() if flags else []
258 def rule_types_allowed_names(self):
259 for name in RULE_VALUES:
260 if self.rule_types_allowed & RULE_VALUES[name] != 0:
261 yield name
263 def experimental(self, engine):
264 if engine == "gecko":
265 return bool(self.gecko_pref)
266 elif engine == "servo-2013":
267 return bool(self.servo_2013_pref)
268 elif engine == "servo-2020":
269 return bool(self.servo_2020_pref)
270 else:
271 raise Exception("Bad engine: " + engine)
273 def explicitly_enabled_in_ua_sheets(self):
274 return self.enabled_in in ("ua", "chrome")
276 def explicitly_enabled_in_chrome(self):
277 return self.enabled_in == "chrome"
279 def enabled_in_content(self):
280 return self.enabled_in == "content"
282 def nscsspropertyid(self):
283 return "nsCSSPropertyID::eCSSProperty_" + self.ident
286 class Longhand(Property):
287 def __init__(
288 self,
289 style_struct,
290 name,
291 spec=None,
292 animation_value_type=None,
293 keyword=None,
294 predefined_type=None,
295 servo_2013_pref=None,
296 servo_2020_pref=None,
297 gecko_pref=None,
298 enabled_in="content",
299 need_index=False,
300 gecko_ffi_name=None,
301 has_effect_on_gecko_scrollbars=None,
302 rule_types_allowed=DEFAULT_RULES,
303 cast_type="u8",
304 logical=False,
305 logical_group=None,
306 aliases=None,
307 extra_prefixes=None,
308 boxed=False,
309 flags=None,
310 allow_quirks="No",
311 ignored_when_colors_disabled=False,
312 simple_vector_bindings=False,
313 vector=False,
314 servo_restyle_damage="repaint",
315 affects=None,
317 Property.__init__(
318 self,
319 name=name,
320 spec=spec,
321 servo_2013_pref=servo_2013_pref,
322 servo_2020_pref=servo_2020_pref,
323 gecko_pref=gecko_pref,
324 enabled_in=enabled_in,
325 rule_types_allowed=rule_types_allowed,
326 aliases=aliases,
327 extra_prefixes=extra_prefixes,
328 flags=flags,
331 self.affects = affects
332 self.flags += self.affects_flags()
334 self.keyword = keyword
335 self.predefined_type = predefined_type
336 self.style_struct = style_struct
337 self.has_effect_on_gecko_scrollbars = has_effect_on_gecko_scrollbars
338 assert (
339 has_effect_on_gecko_scrollbars in [None, False, True]
340 and not style_struct.inherited
341 or (gecko_pref is None and enabled_in != "")
342 == (has_effect_on_gecko_scrollbars is None)
343 ), (
344 "Property "
345 + name
346 + ": has_effect_on_gecko_scrollbars must be "
347 + "specified, and must have a value of True or False, iff a "
348 + "property is inherited and is behind a Gecko pref or internal"
350 self.need_index = need_index
351 self.gecko_ffi_name = gecko_ffi_name or "m" + self.camel_case
352 self.cast_type = cast_type
353 self.logical = arg_to_bool(logical)
354 self.logical_group = logical_group
355 if self.logical:
356 assert logical_group, "Property " + name + " must have a logical group"
358 self.boxed = arg_to_bool(boxed)
359 self.allow_quirks = allow_quirks
360 self.ignored_when_colors_disabled = ignored_when_colors_disabled
361 self.is_vector = vector
362 self.simple_vector_bindings = simple_vector_bindings
364 # This is done like this since just a plain bool argument seemed like
365 # really random.
366 if animation_value_type is None:
367 raise TypeError(
368 "animation_value_type should be specified for (" + name + ")"
370 self.animation_value_type = animation_value_type
372 self.animatable = animation_value_type != "none"
373 self.transitionable = (
374 animation_value_type != "none" and animation_value_type != "discrete"
376 self.is_animatable_with_computed_value = (
377 animation_value_type == "ComputedValue"
378 or animation_value_type == "discrete"
381 # See compute_damage for the various values this can take
382 self.servo_restyle_damage = servo_restyle_damage
384 def affects_flags(self):
385 # Layout is the stronger hint. This property animation affects layout
386 # or frame construction. `display` or `width` are examples that should
387 # use this.
388 if self.affects == "layout":
389 return ["AFFECTS_LAYOUT"]
390 # This property doesn't affect layout, but affects overflow.
391 # `transform` and co. are examples of this.
392 if self.affects == "overflow":
393 return ["AFFECTS_OVERFLOW"]
394 # This property affects the rendered output but doesn't affect layout.
395 # `opacity`, `color`, or `z-index` are examples of this.
396 if self.affects == "paint":
397 return ["AFFECTS_PAINT"]
398 # This property doesn't affect rendering in any way.
399 # `user-select` is an example of this.
400 assert self.affects == "", (
401 "Property "
402 + self.name
403 + ': affects must be specified and be one of ["layout", "overflow", "paint", ""], see Longhand.affects_flags for documentation'
405 return []
407 @staticmethod
408 def type():
409 return "longhand"
411 # For a given logical property return all the physical property names
412 # corresponding to it.
413 def all_physical_mapped_properties(self, data):
414 if not self.logical:
415 return []
417 candidates = [
418 s for s in LOGICAL_SIDES + LOGICAL_SIZES + LOGICAL_CORNERS if s in self.name
419 ] + [s for s in LOGICAL_AXES if self.name.endswith(s)]
420 assert len(candidates) == 1
421 logical_side = candidates[0]
423 physical = (
424 PHYSICAL_SIDES
425 if logical_side in LOGICAL_SIDES
426 else PHYSICAL_SIZES
427 if logical_side in LOGICAL_SIZES
428 else PHYSICAL_AXES
429 if logical_side in LOGICAL_AXES
430 else LOGICAL_CORNERS
432 return [
433 data.longhands_by_name[to_phys(self.name, logical_side, physical_side)]
434 for physical_side in physical
437 def may_be_disabled_in(self, shorthand, engine):
438 if engine == "gecko":
439 return self.gecko_pref and self.gecko_pref != shorthand.gecko_pref
440 elif engine == "servo-2013":
441 return (
442 self.servo_2013_pref
443 and self.servo_2013_pref != shorthand.servo_2013_pref
445 elif engine == "servo-2020":
446 return (
447 self.servo_2020_pref
448 and self.servo_2020_pref != shorthand.servo_2020_pref
450 else:
451 raise Exception("Bad engine: " + engine)
453 def base_type(self):
454 if self.predefined_type and not self.is_vector:
455 return "crate::values::specified::{}".format(self.predefined_type)
456 return "longhands::{}::SpecifiedValue".format(self.ident)
458 def specified_type(self):
459 if self.predefined_type and not self.is_vector:
460 ty = "crate::values::specified::{}".format(self.predefined_type)
461 else:
462 ty = "longhands::{}::SpecifiedValue".format(self.ident)
463 if self.boxed:
464 ty = "Box<{}>".format(ty)
465 return ty
467 def specified_is_copy(self):
468 if self.is_vector or self.boxed:
469 return False
470 if self.predefined_type:
471 return self.predefined_type in {
472 "AlignContent",
473 "AlignItems",
474 "AlignSelf",
475 "Appearance",
476 "AspectRatio",
477 "BaselineSource",
478 "BreakBetween",
479 "BreakWithin",
480 "BackgroundRepeat",
481 "BorderImageRepeat",
482 "BorderStyle",
483 "table::CaptionSide",
484 "Clear",
485 "ColumnCount",
486 "Contain",
487 "ContentVisibility",
488 "ContainerType",
489 "Display",
490 "FillRule",
491 "Float",
492 "FontLanguageOverride",
493 "FontSizeAdjust",
494 "FontStretch",
495 "FontStyle",
496 "FontSynthesis",
497 "FontVariantEastAsian",
498 "FontVariantLigatures",
499 "FontVariantNumeric",
500 "FontWeight",
501 "GreaterThanOrEqualToOneNumber",
502 "GridAutoFlow",
503 "ImageRendering",
504 "InitialLetter",
505 "Integer",
506 "JustifyContent",
507 "JustifyItems",
508 "JustifySelf",
509 "LineBreak",
510 "LineClamp",
511 "MasonryAutoFlow",
512 "ui::MozTheme",
513 "BoolInteger",
514 "text::MozControlCharacterVisibility",
515 "MathDepth",
516 "MozScriptMinSize",
517 "MozScriptSizeMultiplier",
518 "TransformBox",
519 "TextDecorationSkipInk",
520 "NonNegativeNumber",
521 "OffsetRotate",
522 "Opacity",
523 "OutlineStyle",
524 "Overflow",
525 "OverflowAnchor",
526 "OverflowClipBox",
527 "OverflowWrap",
528 "OverscrollBehavior",
529 "PageOrientation",
530 "Percentage",
531 "PrintColorAdjust",
532 "ForcedColorAdjust",
533 "Resize",
534 "RubyPosition",
535 "SVGOpacity",
536 "SVGPaintOrder",
537 "ScrollbarGutter",
538 "ScrollSnapAlign",
539 "ScrollSnapAxis",
540 "ScrollSnapStop",
541 "ScrollSnapStrictness",
542 "ScrollSnapType",
543 "TextAlign",
544 "TextAlignLast",
545 "TextDecorationLine",
546 "TextEmphasisPosition",
547 "TextJustify",
548 "TextTransform",
549 "TextUnderlinePosition",
550 "TouchAction",
551 "TransformStyle",
552 "UserSelect",
553 "WordBreak",
554 "XSpan",
555 "XTextScale",
556 "ZIndex",
557 "Zoom",
559 if self.name == "overflow-y":
560 return True
561 return bool(self.keyword)
563 def animated_type(self):
564 assert self.animatable
565 computed = "<{} as ToComputedValue>::ComputedValue".format(self.base_type())
566 if self.is_animatable_with_computed_value:
567 return computed
568 return "<{} as ToAnimatedValue>::AnimatedValue".format(computed)
571 class Shorthand(Property):
572 def __init__(
573 self,
574 name,
575 sub_properties,
576 spec=None,
577 servo_2013_pref=None,
578 servo_2020_pref=None,
579 gecko_pref=None,
580 enabled_in="content",
581 rule_types_allowed=DEFAULT_RULES,
582 aliases=None,
583 extra_prefixes=None,
584 flags=None,
586 Property.__init__(
587 self,
588 name=name,
589 spec=spec,
590 servo_2013_pref=servo_2013_pref,
591 servo_2020_pref=servo_2020_pref,
592 gecko_pref=gecko_pref,
593 enabled_in=enabled_in,
594 rule_types_allowed=rule_types_allowed,
595 aliases=aliases,
596 extra_prefixes=extra_prefixes,
597 flags=flags,
599 self.sub_properties = sub_properties
601 def get_animatable(self):
602 for sub in self.sub_properties:
603 if sub.animatable:
604 return True
605 return False
607 def get_transitionable(self):
608 transitionable = False
609 for sub in self.sub_properties:
610 if sub.transitionable:
611 transitionable = True
612 break
613 return transitionable
615 animatable = property(get_animatable)
616 transitionable = property(get_transitionable)
618 @staticmethod
619 def type():
620 return "shorthand"
623 class Alias(object):
624 def __init__(self, name, original, gecko_pref):
625 self.name = name
626 self.ident = to_rust_ident(name)
627 self.camel_case = to_camel_case(self.ident)
628 self.original = original
629 self.enabled_in = original.enabled_in
630 self.animatable = original.animatable
631 self.servo_2013_pref = original.servo_2013_pref
632 self.servo_2020_pref = original.servo_2020_pref
633 self.gecko_pref = gecko_pref
634 self.transitionable = original.transitionable
635 self.rule_types_allowed = original.rule_types_allowed
636 self.flags = original.flags
638 @staticmethod
639 def type():
640 return "alias"
642 def rule_types_allowed_names(self):
643 for name in RULE_VALUES:
644 if self.rule_types_allowed & RULE_VALUES[name] != 0:
645 yield name
647 def experimental(self, engine):
648 if engine == "gecko":
649 return bool(self.gecko_pref)
650 elif engine == "servo-2013":
651 return bool(self.servo_2013_pref)
652 elif engine == "servo-2020":
653 return bool(self.servo_2020_pref)
654 else:
655 raise Exception("Bad engine: " + engine)
657 def explicitly_enabled_in_ua_sheets(self):
658 return self.enabled_in in ["ua", "chrome"]
660 def explicitly_enabled_in_chrome(self):
661 return self.enabled_in == "chrome"
663 def enabled_in_content(self):
664 return self.enabled_in == "content"
666 def nscsspropertyid(self):
667 return "nsCSSPropertyID::eCSSPropertyAlias_%s" % self.ident
670 class Method(object):
671 def __init__(self, name, return_type=None, arg_types=None, is_mut=False):
672 self.name = name
673 self.return_type = return_type
674 self.arg_types = arg_types or []
675 self.is_mut = is_mut
677 def arg_list(self):
678 args = ["_: " + x for x in self.arg_types]
679 args = ["&mut self" if self.is_mut else "&self"] + args
680 return ", ".join(args)
682 def signature(self):
683 sig = "fn %s(%s)" % (self.name, self.arg_list())
684 if self.return_type:
685 sig = sig + " -> " + self.return_type
686 return sig
688 def declare(self):
689 return self.signature() + ";"
691 def stub(self):
692 return self.signature() + "{ unimplemented!() }"
695 class StyleStruct(object):
696 def __init__(self, name, inherited, gecko_name=None, additional_methods=None):
697 self.gecko_struct_name = "Gecko" + name
698 self.name = name
699 self.name_lower = to_snake_case(name)
700 self.ident = to_rust_ident(self.name_lower)
701 self.longhands = []
702 self.inherited = inherited
703 self.gecko_name = gecko_name or name
704 self.gecko_ffi_name = "nsStyle" + self.gecko_name
705 self.additional_methods = additional_methods or []
706 self.document_dependent = self.gecko_name in ["Font", "Visibility", "Text"]
709 class PropertiesData(object):
710 def __init__(self, engine):
711 self.engine = engine
712 self.style_structs = []
713 self.current_style_struct = None
714 self.longhands = []
715 self.longhands_by_name = {}
716 self.longhands_by_logical_group = {}
717 self.longhand_aliases = []
718 self.shorthands = []
719 self.shorthands_by_name = {}
720 self.shorthand_aliases = []
721 self.counted_unknown_properties = [
722 CountedUnknownProperty(p) for p in COUNTED_UNKNOWN_PROPERTIES
725 def new_style_struct(self, *args, **kwargs):
726 style_struct = StyleStruct(*args, **kwargs)
727 self.style_structs.append(style_struct)
728 self.current_style_struct = style_struct
730 def active_style_structs(self):
731 return [s for s in self.style_structs if s.additional_methods or s.longhands]
733 def add_prefixed_aliases(self, property):
734 # FIXME Servo's DOM architecture doesn't support vendor-prefixed properties.
735 # See servo/servo#14941.
736 if self.engine == "gecko":
737 for prefix, pref in property.extra_prefixes:
738 property.aliases.append(("-%s-%s" % (prefix, property.name), pref))
740 def declare_longhand(self, name, engines=None, **kwargs):
741 engines = engines.split()
742 if self.engine not in engines:
743 return
745 longhand = Longhand(self.current_style_struct, name, **kwargs)
746 self.add_prefixed_aliases(longhand)
747 longhand.aliases = [Alias(xp[0], longhand, xp[1]) for xp in longhand.aliases]
748 self.longhand_aliases += longhand.aliases
749 self.current_style_struct.longhands.append(longhand)
750 self.longhands.append(longhand)
751 self.longhands_by_name[name] = longhand
752 if longhand.logical_group:
753 self.longhands_by_logical_group.setdefault(
754 longhand.logical_group, []
755 ).append(longhand)
757 return longhand
759 def declare_shorthand(self, name, sub_properties, engines, *args, **kwargs):
760 engines = engines.split()
761 if self.engine not in engines:
762 return
764 sub_properties = [self.longhands_by_name[s] for s in sub_properties]
765 shorthand = Shorthand(name, sub_properties, *args, **kwargs)
766 self.add_prefixed_aliases(shorthand)
767 shorthand.aliases = [Alias(xp[0], shorthand, xp[1]) for xp in shorthand.aliases]
768 self.shorthand_aliases += shorthand.aliases
769 self.shorthands.append(shorthand)
770 self.shorthands_by_name[name] = shorthand
771 return shorthand
773 def shorthands_except_all(self):
774 return [s for s in self.shorthands if s.name != "all"]
776 def all_aliases(self):
777 return self.longhand_aliases + self.shorthand_aliases
780 def _add_logical_props(data, props):
781 groups = set()
782 for prop in props:
783 if prop not in data.longhands_by_name:
784 assert data.engine in ["servo-2013", "servo-2020"]
785 continue
786 prop = data.longhands_by_name[prop]
787 if prop.logical_group:
788 groups.add(prop.logical_group)
789 for group in groups:
790 for prop in data.longhands_by_logical_group[group]:
791 props.add(prop.name)
794 # These are probably Gecko bugs and should be supported per spec.
795 def _remove_common_first_line_and_first_letter_properties(props, engine):
796 if engine == "gecko":
797 props.remove("tab-size")
798 props.remove("hyphens")
799 props.remove("line-break")
800 props.remove("text-align-last")
801 props.remove("text-emphasis-position")
802 props.remove("text-emphasis-style")
803 props.remove("text-emphasis-color")
805 props.remove("overflow-wrap")
806 props.remove("text-align")
807 props.remove("text-justify")
808 props.remove("white-space")
809 props.remove("word-break")
810 props.remove("text-indent")
813 class PropertyRestrictions:
814 @staticmethod
815 def logical_group(data, group):
816 return [p.name for p in data.longhands_by_logical_group[group]]
818 @staticmethod
819 def shorthand(data, shorthand):
820 if shorthand not in data.shorthands_by_name:
821 return []
822 return [p.name for p in data.shorthands_by_name[shorthand].sub_properties]
824 @staticmethod
825 def spec(data, spec_path):
826 return [p.name for p in data.longhands if spec_path in p.spec]
828 # https://drafts.csswg.org/css-pseudo/#first-letter-styling
829 @staticmethod
830 def first_letter(data):
831 props = set(
833 "color",
834 "opacity",
835 "float",
836 "initial-letter",
837 # Kinda like css-fonts?
838 "-moz-osx-font-smoothing",
839 # Kinda like css-text?
840 "-webkit-text-stroke-width",
841 "-webkit-text-fill-color",
842 "-webkit-text-stroke-color",
843 "vertical-align",
844 # Will become shorthand of vertical-align (Bug 1830771)
845 "baseline-source",
846 "line-height",
847 # Kinda like css-backgrounds?
848 "background-blend-mode",
850 + PropertyRestrictions.shorthand(data, "padding")
851 + PropertyRestrictions.shorthand(data, "margin")
852 + PropertyRestrictions.spec(data, "css-fonts")
853 + PropertyRestrictions.spec(data, "css-backgrounds")
854 + PropertyRestrictions.spec(data, "css-text")
855 + PropertyRestrictions.spec(data, "css-shapes")
856 + PropertyRestrictions.spec(data, "css-text-decor")
859 _add_logical_props(data, props)
861 _remove_common_first_line_and_first_letter_properties(props, data.engine)
862 return props
864 # https://drafts.csswg.org/css-pseudo/#first-line-styling
865 @staticmethod
866 def first_line(data):
867 props = set(
869 # Per spec.
870 "color",
871 "opacity",
872 # Kinda like css-fonts?
873 "-moz-osx-font-smoothing",
874 # Kinda like css-text?
875 "-webkit-text-stroke-width",
876 "-webkit-text-fill-color",
877 "-webkit-text-stroke-color",
878 "vertical-align",
879 # Will become shorthand of vertical-align (Bug 1830771)
880 "baseline-source",
881 "line-height",
882 # Kinda like css-backgrounds?
883 "background-blend-mode",
885 + PropertyRestrictions.spec(data, "css-fonts")
886 + PropertyRestrictions.spec(data, "css-backgrounds")
887 + PropertyRestrictions.spec(data, "css-text")
888 + PropertyRestrictions.spec(data, "css-text-decor")
891 # These are probably Gecko bugs and should be supported per spec.
892 for prop in PropertyRestrictions.shorthand(data, "border"):
893 props.remove(prop)
894 for prop in PropertyRestrictions.shorthand(data, "border-radius"):
895 props.remove(prop)
896 props.remove("box-shadow")
898 _remove_common_first_line_and_first_letter_properties(props, data.engine)
899 return props
901 # https://drafts.csswg.org/css-pseudo/#placeholder
903 # The spec says that placeholder and first-line have the same restrictions,
904 # but that's not true in Gecko and we also allow a handful other properties
905 # for ::placeholder.
906 @staticmethod
907 def placeholder(data):
908 props = PropertyRestrictions.first_line(data)
909 props.add("opacity")
910 props.add("white-space")
911 props.add("text-overflow")
912 props.add("text-align")
913 props.add("text-justify")
914 return props
916 # https://drafts.csswg.org/css-pseudo/#marker-pseudo
917 @staticmethod
918 def marker(data):
919 return set(
921 "white-space",
922 "color",
923 "text-combine-upright",
924 "text-transform",
925 "unicode-bidi",
926 "direction",
927 "content",
928 "line-height",
929 "-moz-osx-font-smoothing",
931 + PropertyRestrictions.spec(data, "css-fonts")
932 + PropertyRestrictions.spec(data, "css-animations")
933 + PropertyRestrictions.spec(data, "css-transitions")
936 # https://www.w3.org/TR/webvtt1/#the-cue-pseudo-element
937 @staticmethod
938 def cue(data):
939 return set(
941 "color",
942 "opacity",
943 "visibility",
944 "text-shadow",
945 "white-space",
946 "text-combine-upright",
947 "ruby-position",
948 # XXX Should these really apply to cue?
949 "-moz-osx-font-smoothing",
950 # FIXME(emilio): background-blend-mode should be part of the
951 # background shorthand, and get reset, per
952 # https://drafts.fxtf.org/compositing/#background-blend-mode
953 "background-blend-mode",
955 + PropertyRestrictions.shorthand(data, "text-decoration")
956 + PropertyRestrictions.shorthand(data, "background")
957 + PropertyRestrictions.shorthand(data, "outline")
958 + PropertyRestrictions.shorthand(data, "font")
959 + PropertyRestrictions.shorthand(data, "font-synthesis")
963 class CountedUnknownProperty:
964 def __init__(self, name):
965 self.name = name
966 self.ident = to_rust_ident(name)
967 self.camel_case = to_camel_case(self.ident)