Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(113)

Side by Side Diff: third_party/WebKit/Source/build/scripts/make_computed_style_base.py

Issue 2766933002: Unify logic for properties and nonproperties in computed_style_base.py (Closed)
Patch Set: Rebase Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 25 matching lines...) Expand all
71 self.name = class_member_name(name_for_methods) 80 self.name = class_member_name(name_for_methods)
72 self.property_name = property_name 81 self.property_name = property_name
73 self.type_name = type_name 82 self.type_name = type_name
74 self.field_template = field_template 83 self.field_template = field_template
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'
alancutter (OOO until 2018) 2017/03/22 07:01:44 We should aim to not store the field_role anywhere
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 self.is_property or self.is_nonproperty:
alancutter (OOO until 2018) 2017/03/22 07:01:44 Use "if not self.is_inherited_flag" as this is abo
shend 2017/03/22 22:29:18 Done.
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
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 # Must be a boolean flag
alancutter (OOO until 2018) 2017/03/22 07:01:44 This comment should be an assert instead.
shend 2017/03/22 22:29:18 Done.
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.
alancutter (OOO until 2018) 2017/03/22 07:01:44 Instead of describing the input (which is a detail
shend 2017/03/22 22:29:18 Yes I don't like the name nonproperties. Do you ha
shend 2017/03/23 00:05:29 Actually, the term nonproperty was already in the
alancutter (OOO until 2018) 2017/03/23 02:13:41 The name nonproperties itself is okay it's just st
shend 2017/03/23 02:54:50 Hmm it might be possible to unify the code paths f
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
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_['field_type_path'] = property_.get('field_type_path')
alancutter (OOO until 2018) 2017/03/22 07:01:44 I'd rather see it crash if it's missing than be de
shend 2017/03/22 22:29:18 Hmm, so I'm trying to figure out what you mean her
alancutter (OOO until 2018) 2017/03/23 02:13:41 Ah, sorry it does make sense to have this then. I'
265 property_['name_for_methods'] = property_['name']
266 property_['type_name'] = property_.get('type_name', 'E' + enum_type_ name(property_['name_for_methods']))
alancutter (OOO until 2018) 2017/03/22 07:01:44 Let's use "if attr not in object: object[attr] = d
shend 2017/03/22 22:29:18 Same here, would we write "if object[attr] is None
alancutter (OOO until 2018) 2017/03/23 02:13:41 Doesn't this default only apply if the attr isn't
shend 2017/03/23 02:54:50 Yeah don't worry, your previous comment clarified
267
270 property_values = self._properties.values() 268 property_values = self._properties.values()
271 269
272 # Override the type name when field_type_path is specified 270 # Override the type name when field_type_path is specified
273 for property_ in property_values: 271 for property_ in property_values:
274 if property_['field_type_path']: 272 if property_['field_type_path']:
275 property_['type_name'] = property_['field_type_path'].split('/') [-1] 273 property_['type_name'] = property_['field_type_path'].split('/') [-1]
276 274
277 # Create all the enums used by properties 275 # Create all the enums used by properties and nonproperties
278 self._generated_enums = _create_enums(self._properties.values()) 276 self._generated_enums = _create_enums(property_values + NONPROPERTIES)
279 277
280 # Create all the fields 278 # Create all the fields
281 all_fields = _create_fields(self._properties.values()) 279 all_fields = (_create_fields('property', property_values) +
280 _create_fields('nonproperty', NONPROPERTIES))
alancutter (OOO until 2018) 2017/03/22 07:01:44 We don't really need these "create X" comments, th
shend 2017/03/22 22:29:18 Done.
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._property_values = property_values
alancutter (OOO until 2018) 2017/03/22 07:01:44 No need to upgrade the local variable as a member,
shend 2017/03/22 22:29:18 self._property_values is actually used in a separa
alancutter (OOO until 2018) 2017/03/23 02:13:41 That method can continue to use self._properties.v
shend 2017/03/23 02:54:49 Yep okay, done.
312 self._include_paths = _get_include_paths(property_values + NONPROPERTIES )
313 313
314 314
315 @template_expander.use_jinja('ComputedStyleBase.h.tmpl') 315 @template_expander.use_jinja('ComputedStyleBase.h.tmpl')
316 def generate_base_computed_style_h(self): 316 def generate_base_computed_style_h(self):
317 return { 317 return {
318 'properties': self._properties, 318 'properties': self._properties,
319 'enums': self._generated_enums, 319 'enums': self._generated_enums,
320 'include_paths': self._include_paths, 320 'include_paths': self._include_paths,
321 'fields': self._fields, 321 'fields': self._fields,
322 'expected_total_field_bytes': self._expected_total_field_bytes, 322 'expected_total_field_bytes': self._expected_total_field_bytes,
(...skipping 14 matching lines...) Expand all
337 'properties': self._properties, 337 'properties': self._properties,
338 'enums': self._generated_enums, 338 'enums': self._generated_enums,
339 'fields': self._fields, 339 'fields': self._fields,
340 'expected_total_field_bytes': self._expected_total_field_bytes, 340 'expected_total_field_bytes': self._expected_total_field_bytes,
341 } 341 }
342 342
343 @template_expander.use_jinja('CSSValueIDMappingsGenerated.h.tmpl') 343 @template_expander.use_jinja('CSSValueIDMappingsGenerated.h.tmpl')
344 def generate_css_value_mappings(self): 344 def generate_css_value_mappings(self):
345 mappings = {} 345 mappings = {}
346 346
347 for property_ in self._properties.values(): 347 for property_ in self._property_values:
348 if property_['field_template'] == 'keyword': 348 if property_['field_template'] == 'keyword':
349 mappings[property_['type_name']] = { 349 mappings[property_['type_name']] = {
350 'default_value': enum_value_name(property_['initial_keyword' ]), 350 'default_value': enum_value_name(property_['initial_keyword' ]),
351 'mapping': [(enum_value_name(k), enum_for_css_keyword(k)) fo r k in property_['keywords']], 351 'mapping': [(enum_value_name(k), enum_for_css_keyword(k)) fo r k in property_['keywords']],
352 } 352 }
353 353
354 return { 354 return {
355 'include_paths': self._include_paths, 355 'include_paths': self._include_paths,
356 'mappings': mappings, 356 'mappings': mappings,
357 } 357 }
358 358
359 if __name__ == '__main__': 359 if __name__ == '__main__':
360 json5_generator.Maker(ComputedStyleBaseWriter).main() 360 json5_generator.Maker(ComputedStyleBaseWriter).main()
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698