OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright 2016 The Chromium Authors. All rights reserved. | 2 # Copyright 2016 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 import math | 6 import math |
7 import sys | 7 import sys |
8 | 8 |
9 import json5_generator | 9 import json5_generator |
10 import template_expander | 10 import template_expander |
11 import make_style_builder | 11 import make_style_builder |
12 | 12 |
13 from name_utilities import ( | 13 from name_utilities import ( |
14 enum_for_css_keyword, enum_value_name, class_member_name, method_name | 14 enum_for_css_keyword, enum_type_name, enum_value_name, class_member_name, me
thod_name |
15 ) | 15 ) |
16 | 16 |
17 | 17 |
18 # Temporary hard-coded list of fields that are not CSS properties. | 18 # Temporary hard-coded list of fields that are not CSS properties. |
19 # Ideally these would be specified in a .json5 file. | 19 # TODO(shend): Put this into its own JSON5 file. |
20 NONPROPERTY_FIELDS = [ | 20 NONPROPERTIES = [ |
21 {'name': 'IsLink', 'field_template': 'monotonic_flag'}, | 21 {'name': 'IsLink', 'field_template': 'monotonic_flag', |
| 22 'inherited': False, 'independent': False}, |
22 # Style can not be shared. | 23 # Style can not be shared. |
23 {'name': 'Unique', 'field_template': 'monotonic_flag'}, | 24 {'name': 'Unique', 'field_template': 'monotonic_flag', |
| 25 'inherited': False, 'independent': False}, |
24 # Whether this style is affected by these pseudo-classes. | 26 # Whether this style is affected by these pseudo-classes. |
25 {'name': 'AffectedByFocus', 'field_template': 'monotonic_flag'}, | 27 {'name': 'AffectedByFocus', 'field_template': 'monotonic_flag', |
26 {'name': 'AffectedByHover', 'field_template': 'monotonic_flag'}, | 28 'inherited': False, 'independent': False}, |
27 {'name': 'AffectedByActive', 'field_template': 'monotonic_flag'}, | 29 {'name': 'AffectedByHover', 'field_template': 'monotonic_flag', |
28 {'name': 'AffectedByDrag', 'field_template': 'monotonic_flag'}, | 30 'inherited': False, 'independent': False}, |
| 31 {'name': 'AffectedByActive', 'field_template': 'monotonic_flag', |
| 32 'inherited': False, 'independent': False}, |
| 33 {'name': 'AffectedByDrag', 'field_template': 'monotonic_flag', |
| 34 'inherited': False, 'independent': False}, |
29 # A non-inherited property references a variable or @apply is used | 35 # A non-inherited property references a variable or @apply is used |
30 {'name': 'HasVariableReferenceFromNonInheritedProperty', 'field_template': '
monotonic_flag'}, | 36 {'name': 'HasVariableReferenceFromNonInheritedProperty', 'field_template': '
monotonic_flag', |
| 37 'inherited': False, 'independent': False}, |
31 # Explicitly inherits a non-inherited property | 38 # Explicitly inherits a non-inherited property |
32 {'name': 'HasExplicitlyInheritedProperties', 'field_template': 'monotonic_fl
ag'}, | 39 {'name': 'HasExplicitlyInheritedProperties', 'field_template': 'monotonic_fl
ag', |
| 40 'inherited': False, 'independent': False}, |
33 # These properties only have generated storage, and their methods are handwr
itten in ComputedStyle. | 41 # These properties only have generated storage, and their methods are handwr
itten in ComputedStyle. |
34 # TODO(shend): Remove these fields and delete the 'storage_only' template. | 42 # TODO(shend): Remove these fields and delete the 'storage_only' template. |
35 {'name': 'EmptyState', 'field_template': 'storage_only', 'size': 1} | 43 {'name': 'EmptyState', 'field_template': 'storage_only', 'size': 1, 'default
_value': 'false', |
| 44 'type_name': 'bool', 'inherited': False, 'independent': False}, |
36 ] | 45 ] |
37 | 46 |
38 | 47 |
39 class Field(object): | 48 class Field(object): |
40 """ | 49 """ |
41 The generated ComputedStyle object is made up of a series of Fields. | 50 The generated ComputedStyle object is made up of a series of Fields. |
42 Each Field has a name, size, type, etc, and a bunch of attributes to | 51 Each Field has a name, size, type, etc, and a bunch of attributes to |
43 determine which methods it will be used in. | 52 determine which methods it will be used in. |
44 | 53 |
45 A Field also has enough information to use any storage type in C++, such as | 54 A Field also has enough information to use any storage type in C++, such as |
(...skipping 29 matching lines...) Expand all Loading... |
75 self.size = size | 84 self.size = size |
76 self.default_value = default_value | 85 self.default_value = default_value |
77 | 86 |
78 # Field role: one of these must be true | 87 # Field role: one of these must be true |
79 self.is_property = field_role == 'property' | 88 self.is_property = field_role == 'property' |
80 self.is_inherited_flag = field_role == 'inherited_flag' | 89 self.is_inherited_flag = field_role == 'inherited_flag' |
81 self.is_nonproperty = field_role == 'nonproperty' | 90 self.is_nonproperty = field_role == 'nonproperty' |
82 assert (self.is_property, self.is_inherited_flag, self.is_nonproperty).c
ount(True) == 1, \ | 91 assert (self.is_property, self.is_inherited_flag, self.is_nonproperty).c
ount(True) == 1, \ |
83 'Field role has to be exactly one of: property, inherited_flag, nonp
roperty' | 92 'Field role has to be exactly one of: property, inherited_flag, nonp
roperty' |
84 | 93 |
85 if self.is_property: | 94 if not self.is_inherited_flag: |
86 self.is_inherited = kwargs.pop('inherited') | 95 self.is_inherited = kwargs.pop('inherited') |
87 self.is_independent = kwargs.pop('independent') | 96 self.is_independent = kwargs.pop('independent') |
88 assert self.is_inherited or not self.is_independent, 'Only inherited
fields can be independent' | 97 assert self.is_inherited or not self.is_independent, 'Only inherited
fields can be independent' |
89 | 98 |
90 self.is_inherited_method_name = method_name(name_for_methods + 'IsIn
herited') | 99 self.is_inherited_method_name = method_name(name_for_methods + 'IsIn
herited') |
91 | 100 |
92 # Method names | 101 # Method names |
93 getter_prefix = 'Get' if name_for_methods == self.type_name else '' | 102 getter_prefix = 'Get' if name_for_methods == self.type_name else '' |
94 self.getter_method_name = method_name(getter_prefix + name_for_methods) | 103 self.getter_method_name = method_name(getter_prefix + name_for_methods) |
95 self.setter_method_name = method_name('Set' + name_for_methods) | 104 self.setter_method_name = method_name('Set' + name_for_methods) |
(...skipping 30 matching lines...) Expand all Loading... |
126 assert set(enums[enum_name]) == set(enum_values), \ | 135 assert set(enums[enum_name]) == set(enum_values), \ |
127 ("'" + property_['name'] + "' can't have type_name '" + enum
_name + "' " | 136 ("'" + property_['name'] + "' can't have type_name '" + enum
_name + "' " |
128 "because it was used by a previous property, but with a dif
ferent set of keywords. " | 137 "because it was used by a previous property, but with a dif
ferent set of keywords. " |
129 "Either give it a different name or ensure the keywords are
the same.") | 138 "Either give it a different name or ensure the keywords are
the same.") |
130 | 139 |
131 enums[enum_name] = enum_values | 140 enums[enum_name] = enum_values |
132 | 141 |
133 return enums | 142 return enums |
134 | 143 |
135 | 144 |
136 def _create_property_field(property_): | 145 def _create_field(field_role, property_): |
137 """ | 146 """ |
138 Create a property field from a CSS property and return the Field object. | 147 Create a property or nonproperty field. |
139 """ | 148 """ |
140 bits_needed = math.log(len(property_['keywords']), 2) # TODO: implement for
non-enums | 149 assert field_role in ('property', 'nonproperty') |
141 type_name = property_['type_name'] | |
142 | 150 |
143 assert property_['initial_keyword'] is not None, \ | 151 name_for_methods = property_['name_for_methods'] |
144 ('MakeComputedStyleBase requires an initial keyword for keyword fields,
none specified ' | 152 |
145 'for property ' + property_['name']) | 153 if property_['field_template'] == 'keyword': |
146 default_value = type_name + '::' + enum_value_name(property_['initial_keywor
d']) | 154 assert property_['initial_keyword'] is not None, \ |
| 155 ('MakeComputedStyleBase requires an initial keyword for keyword fiel
ds, none specified ' |
| 156 'for property ' + property_['name']) |
| 157 type_name = property_['type_name'] |
| 158 default_value = type_name + '::' + enum_value_name(property_['initial_ke
yword']) |
| 159 size = int(math.ceil(math.log(len(property_['keywords']), 2))) |
| 160 elif property_['field_template'] == 'storage_only': |
| 161 # 'storage_only' fields need to specify a size, type_name and default_va
lue |
| 162 type_name = property_['type_name'] |
| 163 default_value = property_['default_value'] |
| 164 size = property_['size'] |
| 165 else: |
| 166 assert property_['field_template'] in ('flag', 'monotonic_flag') |
| 167 type_name = 'bool' |
| 168 default_value = 'false' |
| 169 size = 1 |
147 | 170 |
148 return Field( | 171 return Field( |
149 'property', | 172 field_role, |
150 property_['name_for_methods'], | 173 name_for_methods, |
151 property_name=property_['name'], | 174 property_name=property_['name'], |
152 inherited=property_['inherited'], | 175 inherited=property_['inherited'], |
153 independent=property_['independent'], | 176 independent=property_['independent'], |
154 type_name=type_name, | 177 type_name=type_name, |
155 field_template=property_['field_template'], | 178 field_template=property_['field_template'], |
156 size=int(math.ceil(bits_needed)), | 179 size=size, |
157 default_value=default_value, | 180 default_value=default_value, |
158 ) | 181 ) |
159 | 182 |
160 | 183 |
161 def _create_inherited_flag_field(property_): | 184 def _create_inherited_flag_field(property_): |
162 """ | 185 """ |
163 Create the field used for an inheritance fast path from an independent CSS p
roperty, | 186 Create the field used for an inheritance fast path from an independent CSS p
roperty, |
164 and return the Field object. | 187 and return the Field object. |
165 """ | 188 """ |
166 return Field( | 189 return Field( |
167 'inherited_flag', | 190 'inherited_flag', |
168 property_['name_for_methods'] + 'IsInherited', | 191 property_['name_for_methods'] + 'IsInherited', |
169 property_name=property_['name'], | 192 property_name=property_['name'], |
170 type_name='bool', | 193 type_name='bool', |
171 field_template='flag', | 194 field_template='flag', |
172 size=1, | 195 size=1, |
173 default_value='true', | 196 default_value='true', |
174 ) | 197 ) |
175 | 198 |
176 | 199 |
177 def _create_nonproperty_field(property_): | 200 def _create_fields(field_role, properties): |
178 """ | 201 """ |
179 Create a nonproperty field from an entry in NONPROPERTY_FIELDS and return th
e Field object. | 202 Create ComputedStyle fields from properties or nonproperties and return a li
st of Field objects. |
180 """ | |
181 # TODO(shend): Make this work for nonflags | |
182 assert property_['field_template'] in ('flag', 'monotonic_flag', 'storage_on
ly'), \ | |
183 "Nonproperties with arbitrary templates are not yet supported" | |
184 | |
185 if property_['field_template'] == 'storage_only': | |
186 assert 'size' in property_, 'storage_only fields need to specify a size' | |
187 size = property_['size'] | |
188 else: | |
189 # Otherwise the field must be some type of flag. | |
190 size = 1 | |
191 | |
192 return Field( | |
193 'nonproperty', | |
194 property_['name_for_methods'], | |
195 property_name=property_['name'], | |
196 type_name='bool', | |
197 field_template=property_['field_template'], | |
198 size=size, | |
199 default_value='false', | |
200 ) | |
201 | |
202 | |
203 def _create_fields(properties): | |
204 """ | |
205 Create ComputedStyle fields from CSS properties and return a list of Field o
bjects. | |
206 """ | 203 """ |
207 fields = [] | 204 fields = [] |
208 for property_ in properties: | 205 for property_ in properties: |
209 # Only generate properties that have a field template | 206 # Only generate properties that have a field template |
210 if property_['field_template'] is not None: | 207 if property_['field_template'] is not None: |
211 # If the property is independent, add the single-bit sized isInherit
ed flag | 208 # If the property is independent, add the single-bit sized isInherit
ed flag |
212 # to the list of Fields as well. | 209 # to the list of Fields as well. |
213 if property_['independent']: | 210 if property_['independent']: |
214 fields.append(_create_inherited_flag_field(property_)) | 211 fields.append(_create_inherited_flag_field(property_)) |
215 | 212 |
216 fields.append(_create_property_field(property_)) | 213 fields.append(_create_field(field_role, property_)) |
217 | |
218 # TODO(shend): Merge NONPROPERTY_FIELDS with property_values so that propert
ies and | |
219 # nonproperties can be treated uniformly. | |
220 for property_ in NONPROPERTY_FIELDS: | |
221 property_['name_for_methods'] = property_['name'] | |
222 fields.append(_create_nonproperty_field(property_)) | |
223 | 214 |
224 return fields | 215 return fields |
225 | 216 |
226 | 217 |
227 def _pack_fields(fields): | 218 def _pack_fields(fields): |
228 """ | 219 """ |
229 Group a list of fields into buckets to minimise padding. | 220 Group a list of fields into buckets to minimise padding. |
230 Returns a list of buckets, where each bucket is a list of Field objects. | 221 Returns a list of buckets, where each bucket is a list of Field objects. |
231 """ | 222 """ |
232 # Since fields cannot cross word boundaries, in order to minimize | 223 # Since fields cannot cross word boundaries, in order to minimize |
(...skipping 27 matching lines...) Expand all Loading... |
260 class ComputedStyleBaseWriter(make_style_builder.StyleBuilderWriter): | 251 class ComputedStyleBaseWriter(make_style_builder.StyleBuilderWriter): |
261 def __init__(self, json5_file_path): | 252 def __init__(self, json5_file_path): |
262 super(ComputedStyleBaseWriter, self).__init__(json5_file_path) | 253 super(ComputedStyleBaseWriter, self).__init__(json5_file_path) |
263 self._outputs = { | 254 self._outputs = { |
264 'ComputedStyleBase.h': self.generate_base_computed_style_h, | 255 'ComputedStyleBase.h': self.generate_base_computed_style_h, |
265 'ComputedStyleBase.cpp': self.generate_base_computed_style_cpp, | 256 'ComputedStyleBase.cpp': self.generate_base_computed_style_cpp, |
266 'ComputedStyleBaseConstants.h': self.generate_base_computed_style_co
nstants, | 257 'ComputedStyleBaseConstants.h': self.generate_base_computed_style_co
nstants, |
267 'CSSValueIDMappingsGenerated.h': self.generate_css_value_mappings, | 258 'CSSValueIDMappingsGenerated.h': self.generate_css_value_mappings, |
268 } | 259 } |
269 | 260 |
| 261 # TODO(shend): Remove this once we move NONPROPERTIES to its own JSON fi
le, |
| 262 # since the JSON5 reader will handle missing fields and defaults. |
| 263 for property_ in NONPROPERTIES: |
| 264 property_['name_for_methods'] = property_['name'] |
| 265 if 'field_type_path' not in property_: |
| 266 property_['field_type_path'] = None |
| 267 if 'type_name' not in property_: |
| 268 property_['type_name'] = 'E' + enum_type_name(property_['name_fo
r_methods']) |
| 269 |
270 property_values = self._properties.values() | 270 property_values = self._properties.values() |
271 | 271 |
272 # Override the type name when field_type_path is specified | 272 # Override the type name when field_type_path is specified |
273 for property_ in property_values: | 273 for property_ in property_values: |
274 if property_['field_type_path']: | 274 if property_['field_type_path']: |
275 property_['type_name'] = property_['field_type_path'].split('/')
[-1] | 275 property_['type_name'] = property_['field_type_path'].split('/')
[-1] |
276 | 276 |
277 # Create all the enums used by properties | 277 self._generated_enums = _create_enums(property_values + NONPROPERTIES) |
278 self._generated_enums = _create_enums(self._properties.values()) | |
279 | 278 |
280 # Create all the fields | 279 all_fields = (_create_fields('property', property_values) + |
281 all_fields = _create_fields(self._properties.values()) | 280 _create_fields('nonproperty', NONPROPERTIES)) |
282 | 281 |
283 # Group fields into buckets | 282 # Group fields into buckets |
284 field_buckets = _pack_fields(all_fields) | 283 field_buckets = _pack_fields(all_fields) |
285 | 284 |
286 # The expected size of ComputedStyleBase is equivalent to as many words | 285 # The expected size of ComputedStyleBase is equivalent to as many words |
287 # as the total number of buckets. | 286 # as the total number of buckets. |
288 self._expected_total_field_bytes = len(field_buckets) | 287 self._expected_total_field_bytes = len(field_buckets) |
289 | 288 |
290 # The most optimal size of ComputedStyleBase is the total sum of all the | 289 # The most optimal size of ComputedStyleBase is the total sum of all the |
291 # field sizes, rounded up to the nearest word. If this produces the | 290 # field sizes, rounded up to the nearest word. If this produces the |
292 # incorrect value, either the packing algorithm is not optimal or there | 291 # incorrect value, either the packing algorithm is not optimal or there |
293 # is no way to pack the fields such that excess padding space is not | 292 # is no way to pack the fields such that excess padding space is not |
294 # added. | 293 # added. |
295 # If this fails, increase extra_padding_bytes by 1, but be aware that | 294 # If this fails, increase padding_bytes by 1, but be aware that |
296 # this also increases ComputedStyleBase by 1 word. | 295 # this also increases ComputedStyleBase by 1 word. |
297 # We should be able to bring extra_padding_bytes back to 0 from time to | 296 # We should be able to bring padding_bytes back to 0 from time to |
298 # time. | 297 # time. |
299 extra_padding_bytes = 0 | 298 padding_bytes = 0 |
300 optimal_total_field_bytes = int(math.ceil(sum(f.size for f in all_fields
) / 32.0)) | 299 optimal_total_field_bytes = int(math.ceil(sum(f.size for f in all_fields
) / 32.0)) |
301 real_total_field_bytes = optimal_total_field_bytes + extra_padding_bytes | 300 real_total_field_bytes = optimal_total_field_bytes + padding_bytes |
302 assert self._expected_total_field_bytes == real_total_field_bytes, \ | 301 assert self._expected_total_field_bytes == real_total_field_bytes, \ |
303 ('The field packing algorithm produced %s bytes, optimal is %s bytes
' % | 302 ('The field packing algorithm produced %s bytes, optimal is %s bytes
' % |
304 (self._expected_total_field_bytes, real_total_field_bytes)) | 303 (self._expected_total_field_bytes, real_total_field_bytes)) |
305 | 304 |
306 # Order the fields so fields in each bucket are adjacent. | 305 # Order the fields so fields in each bucket are adjacent. |
307 self._fields = [] | 306 self._fields = [] |
308 for bucket in field_buckets: | 307 for bucket in field_buckets: |
309 for field in bucket: | 308 for field in bucket: |
310 self._fields.append(field) | 309 self._fields.append(field) |
311 | 310 |
312 self._include_paths = _get_include_paths(self._properties.values()) | 311 self._include_paths = _get_include_paths(property_values + NONPROPERTIES
) |
313 | 312 |
314 | 313 |
315 @template_expander.use_jinja('ComputedStyleBase.h.tmpl') | 314 @template_expander.use_jinja('ComputedStyleBase.h.tmpl') |
316 def generate_base_computed_style_h(self): | 315 def generate_base_computed_style_h(self): |
317 return { | 316 return { |
318 'properties': self._properties, | 317 'properties': self._properties, |
319 'enums': self._generated_enums, | 318 'enums': self._generated_enums, |
320 'include_paths': self._include_paths, | 319 'include_paths': self._include_paths, |
321 'fields': self._fields, | 320 'fields': self._fields, |
322 'expected_total_field_bytes': self._expected_total_field_bytes, | 321 'expected_total_field_bytes': self._expected_total_field_bytes, |
(...skipping 28 matching lines...) Expand all Loading... |
351 'mapping': [(enum_value_name(k), enum_for_css_keyword(k)) fo
r k in property_['keywords']], | 350 'mapping': [(enum_value_name(k), enum_for_css_keyword(k)) fo
r k in property_['keywords']], |
352 } | 351 } |
353 | 352 |
354 return { | 353 return { |
355 'include_paths': self._include_paths, | 354 'include_paths': self._include_paths, |
356 'mappings': mappings, | 355 'mappings': mappings, |
357 } | 356 } |
358 | 357 |
359 if __name__ == '__main__': | 358 if __name__ == '__main__': |
360 json5_generator.Maker(ComputedStyleBaseWriter).main() | 359 json5_generator.Maker(ComputedStyleBaseWriter).main() |
OLD | NEW |