blob: b779e079244456978d37ed4377e8e3b409095139 [file] [log] [blame]
#!/usr/bin/env python
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from blinkbuild.name_style_converter import NameStyleConverter
from core.css.field_alias_expander import FieldAliasExpander
import json5_generator
from make_origin_trials import OriginTrialsWriter
from name_utilities import enum_key_for_css_property, id_for_css_property
from name_utilities import enum_key_for_css_property_alias, id_for_css_property_alias
# These values are converted using CSSPrimitiveValue in the setter function,
# if applicable.
PRIMITIVE_TYPES = [
'short', 'unsigned short', 'int', 'unsigned int', 'unsigned', 'float',
'LineClampValue'
]
def validate_property(prop):
name = prop['name']
has_method = lambda x: x in prop['property_methods']
assert prop['is_property'] or prop['is_descriptor'], \
'Entry must be a property, descriptor, or both [%s]' % name
assert not prop['interpolable'] or prop['is_longhand'], \
'Only longhands can be interpolable [%s]' % name
assert not has_method('ParseSingleValue') or prop['is_longhand'], \
'Only longhands can implement ParseSingleValue [%s]' % name
assert not has_method('ParseShorthand') or prop['is_shorthand'], \
'Only shorthands can implement ParseShorthand [%s]' % name
assert not prop['field_template'] or prop['is_longhand'], \
'Only longhands can have a field_template [%s]' % name
assert not prop['valid_for_first_letter'] or prop['is_longhand'], \
'Only longhands can be valid_for_first_letter [%s]' % name
assert not prop['valid_for_cue'] or prop['is_longhand'], \
'Only longhands can be valid_for_cue [%s]' % name
assert not prop['valid_for_marker'] or prop['is_longhand'], \
'Only longhands can be valid_for_marker [%s]' % name
assert not prop['valid_for_highlight'] or prop['is_longhand'], \
'Only longhands can be valid_for_highlight [%s]' % name
def validate_alias(alias):
name = alias['name']
is_internal = lambda x: x['name'].original.startswith('-internal-')
assert not alias['runtime_flag'], \
'Runtime flags are not supported for aliases [%s]' % name
assert not is_internal(alias), \
'Internal aliases not supported [%s]' % name
def validate_field(field):
name = field['name']
assert not field['mutable'] or field['field_template'] == 'monotonic_flag',\
'mutable requires field_template:monotonic_flag [%s]' % name
class CSSProperties(object):
def __init__(self, file_paths):
assert len(file_paths) >= 3, \
"CSSProperties at least needs both css_properties.json5, \
computed_style_field_aliases.json5 and \
runtime_enabled_features.json5 to function"
# computed_style_field_aliases.json5. Used to expand out parameters used
# in the various generators for ComputedStyle.
self._field_alias_expander = FieldAliasExpander(file_paths[1])
# _alias_offset must be a power of 2.
self._alias_offset = 1024
# 0: CSSPropertyID::kInvalid
# 1: CSSPropertyID::kVariable
self._first_enum_value = 2
self._last_used_enum_value = self._first_enum_value
self._last_high_priority_property = None
self._properties_by_id = {}
self._properties_by_name = {}
self._aliases = []
self._longhands = []
self._shorthands = []
self._properties_including_aliases = []
# Add default data in css_properties.json5. This must be consistent
# across instantiations of this class.
css_properties_file = json5_generator.Json5File.load_from_files(
[file_paths[0]])
self._default_parameters = css_properties_file.parameters
# Map of feature name -> origin trial feature name
origin_trial_features = {}
# TODO(crbug/1031309): Refactor OriginTrialsWriter to reuse logic here.
origin_trials_writer = OriginTrialsWriter([file_paths[2]], "")
for feature in origin_trials_writer.origin_trial_features:
origin_trial_features[str(feature['name'])] = True
self.add_properties(css_properties_file.name_dictionaries,
origin_trial_features)
assert self._first_enum_value + len(self._properties_by_id) < \
self._alias_offset, \
'Property aliasing expects fewer than %d properties.' % \
self._alias_offset
self._last_unresolved_property_id = max(
property_["enum_value"] for property_ in self._aliases)
# Process extra files passed in.
self._extra_fields = []
for i in range(3, len(file_paths)):
fields = json5_generator.Json5File.load_from_files(
[file_paths[i]], default_parameters=self._default_parameters)
self._extra_fields.extend(fields.name_dictionaries)
for field in self._extra_fields:
self.expand_parameters(field)
validate_field(field)
def add_properties(self, properties, origin_trial_features):
for property_ in properties:
self._properties_by_name[property_['name'].original] = property_
for property_ in properties:
property_['is_shorthand'] = \
property_['is_property'] and bool(property_['longhands'])
property_['is_longhand'] = \
property_['is_property'] and not property_['is_shorthand']
self.expand_visited(property_)
property_['in_origin_trial'] = False
self.expand_origin_trials(property_, origin_trial_features)
self.expand_surrogate(property_)
self._aliases = [
property_ for property_ in properties if property_['alias_for']
]
self._shorthands = [
property_ for property_ in properties if property_['longhands']
]
self._longhands = [
property_ for property_ in properties
if (not property_['alias_for'] and not property_['longhands'])
]
# Sort the properties by priority, then alphabetically. Ensure that
# the resulting order is deterministic.
# Sort properties by priority, then alphabetically.
for property_ in self._longhands + self._shorthands:
self.expand_parameters(property_)
validate_property(property_)
priority_numbers = {'High': 0, 'Low': 1}
priority = priority_numbers[property_['priority']]
name_without_leading_dash = property_['name'].original
if name_without_leading_dash.startswith('-'):
name_without_leading_dash = name_without_leading_dash[1:]
property_['sorting_key'] = (priority, name_without_leading_dash)
sorting_keys = {}
for property_ in self._longhands + self._shorthands:
key = property_['sorting_key']
assert key not in sorting_keys, \
('Collision detected - two properties have the same name and '
'priority, a potentially non-deterministic ordering can '
'occur: {}, {} and {}'.format(
key, property_['name'].original, sorting_keys[key]))
sorting_keys[key] = property_['name'].original
self._longhands.sort(key=lambda p: p['sorting_key'])
self._shorthands.sort(key=lambda p: p['sorting_key'])
# The sorted index becomes the CSSPropertyID enum value.
for property_ in self._longhands + self._shorthands:
property_['enum_value'] = self._last_used_enum_value
self._last_used_enum_value += 1
# Add the new property into the map of properties.
assert property_['property_id'] not in self._properties_by_id, \
('property with ID {} appears more than once in the '
'properties list'.format(property_['property_id']))
self._properties_by_id[property_['property_id']] = property_
if property_['priority'] == 'High':
self._last_high_priority_property = property_
self.expand_aliases()
self._properties_including_aliases = self._longhands + \
self._shorthands + self._aliases
def expand_origin_trials(self, property_, origin_trial_features):
if not property_['runtime_flag']:
return
if property_['runtime_flag'] in origin_trial_features:
property_['in_origin_trial'] = True
def expand_visited(self, property_):
if not property_['visited_property_for']:
return
visited_property_for = property_['visited_property_for']
unvisited_property = self._properties_by_name[visited_property_for]
property_['visited'] = True
# The visited property needs a link to the unvisited counterpart.
property_['unvisited_property'] = unvisited_property
# The unvisited property needs a link to the visited counterpart.
assert 'visited_property' not in unvisited_property, \
'A property may not have multiple visited properties'
unvisited_property['visited_property'] = property_
def expand_surrogate(self, property_):
if not property_['surrogate_for']:
return
assert property_['surrogate_for'] in self._properties_by_name, \
'surrogate_for must name a property'
# Upgrade 'surrogate_for' to property reference.
property_['surrogate_for'] = self._properties_by_name[
property_['surrogate_for']]
def expand_aliases(self):
for i, alias in enumerate(self._aliases):
validate_alias(alias)
aliased_property = self._properties_by_id[id_for_css_property(
alias['alias_for'])]
aliased_property.setdefault('aliases', [])
aliased_property['aliases'].append(alias['name'].original)
updated_alias = aliased_property.copy()
updated_alias['name'] = alias['name']
updated_alias['alias_for'] = alias['alias_for']
updated_alias['aliased_property'] = aliased_property[
'name'].to_upper_camel_case()
updated_alias['property_id'] = id_for_css_property_alias(
alias['name'])
updated_alias['enum_key'] = enum_key_for_css_property_alias(
alias['name'])
updated_alias['enum_value'] = aliased_property['enum_value'] + \
self._alias_offset * len(aliased_property['aliases'])
updated_alias['superclass'] = 'CSSUnresolvedProperty'
updated_alias['namespace_group'] = \
'Shorthand' if aliased_property['longhands'] else 'Longhand'
self._aliases[i] = updated_alias
def expand_parameters(self, property_):
def set_if_none(property_, key, value):
if key not in property_ or property_[key] is None:
property_[key] = value
# Basic info.
name = property_['name']
property_['property_id'] = id_for_css_property(name)
property_['enum_key'] = enum_key_for_css_property(name)
property_['is_internal'] = name.original.startswith('-internal-')
method_name = property_['name_for_methods']
if not method_name:
method_name = name.to_upper_camel_case().replace('Webkit', '')
set_if_none(property_, 'inherited', False)
# Initial function, Getters and Setters for ComputedStyle.
set_if_none(property_, 'initial', 'Initial' + method_name)
simple_type_name = str(property_['type_name']).split('::')[-1]
set_if_none(property_, 'name_for_methods', method_name)
set_if_none(property_, 'type_name', 'E' + method_name)
set_if_none(
property_, 'getter', method_name
if simple_type_name != method_name else 'Get' + method_name)
set_if_none(property_, 'setter', 'Set' + method_name)
if property_['inherited']:
property_['is_inherited_setter'] = (
'Set' + method_name + 'IsInherited')
property_['is_animation_property'] = (
property_['priority'] == 'Animation')
# Figure out whether this property should have style builders at all.
# E.g. shorthands do not get style builders.
property_['style_builder_declare'] = (property_['is_property']
and not property_['longhands'])
# Figure out whether we should generate style builder implementations.
for x in ['initial', 'inherit', 'value']:
suppressed = x in property_['style_builder_custom_functions']
declared = property_['style_builder_declare']
property_['style_builder_generate_%s' % x] = (declared
and not suppressed)
# Expand StyleBuilderConverter params where necessary.
if property_['type_name'] in PRIMITIVE_TYPES:
set_if_none(property_, 'converter', 'CSSPrimitiveValue')
else:
set_if_none(property_, 'converter', 'CSSIdentifierValue')
assert not property_['alias_for'], \
'Use expand_aliases to expand aliases'
if not property_['longhands']:
property_['superclass'] = 'Longhand'
property_['namespace_group'] = 'Longhand'
elif property_['longhands']:
property_['superclass'] = 'Shorthand'
property_['namespace_group'] = 'Shorthand'
# Expand out field templates.
if property_['field_template']:
self._field_alias_expander.expand_field_alias(property_)
type_name = property_['type_name']
if (property_['field_template'] == 'keyword'
or property_['field_template'] == 'multi_keyword'):
default_value = (type_name + '::' + NameStyleConverter(
property_['default_value']).to_enum_value())
elif (property_['field_template'] == 'external'
or property_['field_template'] == 'primitive'
or property_['field_template'] == 'pointer'):
default_value = property_['default_value']
else:
assert property_['field_template'] == 'monotonic_flag', \
"Please put a valid value for field_template; got " + \
str(property_['field_template'])
property_['type_name'] = 'bool'
default_value = 'false'
property_['default_value'] = default_value
property_['unwrapped_type_name'] = property_['type_name']
if property_['wrapper_pointer_name']:
assert property_['field_template'] in ['pointer', 'external']
if property_['field_template'] == 'external':
property_['type_name'] = '{}<{}>'.format(
property_['wrapper_pointer_name'], type_name)
# Default values for extra parameters in computed_style_extra_fields.json5.
set_if_none(property_, 'custom_copy', False)
set_if_none(property_, 'custom_compare', False)
set_if_none(property_, 'mutable', False)
if property_['logical_property_group']:
group = property_['logical_property_group']
assert 'name' in group, 'name option is required'
assert 'resolver' in group, 'resolver option is required'
logicals = {
'block', 'inline', 'block-start', 'block-end', 'inline-start',
'inline-end', 'start-start', 'start-end', 'end-start',
'end-end'
}
physicals = {
'vertical', 'horizontal', 'top', 'bottom', 'left', 'right',
'top-left', 'top-right', 'bottom-right', 'bottom-left'
}
if group['resolver'] in logicals:
group['is_logical'] = True
elif group['resolver'] in physicals:
group['is_logical'] = False
else:
assert 0, 'invalid resolver option'
group['name'] = NameStyleConverter(group['name'])
group['resolver_name'] = NameStyleConverter(group['resolver'])
if not property_['style_builder_template'] and group['is_logical']:
property_['style_builder_template'] = 'direction_aware'
@property
def default_parameters(self):
return self._default_parameters
@property
def aliases(self):
return self._aliases
@property
def shorthands(self):
return self._shorthands
@property
def shorthands_including_aliases(self):
return self._shorthands + [x for x in self._aliases if x['longhands']]
@property
def longhands(self):
return self._longhands
@property
def longhands_including_aliases(self):
return self._longhands + [
x for x in self._aliases if not x['longhands']
]
@property
def properties_by_name(self):
return self._properties_by_name
@property
def properties_by_id(self):
return self._properties_by_id
@property
def properties_including_aliases(self):
return self._properties_including_aliases
@property
def first_property_id(self):
return self._first_enum_value
@property
def last_property_id(self):
return self._first_enum_value + len(self._properties_by_id) - 1
@property
def last_unresolved_property_id(self):
return self._last_unresolved_property_id
@property
def last_high_priority_property_id(self):
return self._last_high_priority_property['enum_key']
@property
def property_id_bit_length(self):
return int.bit_length(self._last_unresolved_property_id)
@property
def alias_offset(self):
return self._alias_offset
@property
def extra_fields(self):
return self._extra_fields