1 # Copyright (C) 2013 Google Inc. All rights reserved. 2 # 3 # Redistribution and use in source and binary forms, with or without 4 # modification, are permitted provided that the following conditions are 5 # met: 6 # 7 # * Redistributions of source code must retain the above copyright 8 # notice, this list of conditions and the following disclaimer. 9 # * Redistributions in binary form must reproduce the above 10 # copyright notice, this list of conditions and the following disclaimer 11 # in the documentation and/or other materials provided with the 12 # distribution. 13 # * Neither the name of Google Inc. nor the names of its 14 # contributors may be used to endorse or promote products derived from 15 # this software without specific prior written permission. 16 # 17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 29 """Generate Blink V8 bindings (.h and .cpp files). 30 31 Input: An object of class IdlDefinitions, containing an IDL interface X 32 Output: V8X.h and V8X.cpp 33 """ 34 35 import os 36 import posixpath 37 import re 38 import sys 39 40 # jinja2 is in chromium's third_party directory. 41 module_path, module_name = os.path.split(__file__) 42 third_party = os.path.join(module_path, os.pardir, os.pardir, os.pardir, os.pardir) 43 sys.path.append(third_party) 44 import jinja2 45 46 47 CALLBACK_INTERFACE_CPP_INCLUDES = set([ 48 'core/dom/ScriptExecutionContext.h', 49 'bindings/v8/V8Binding.h', 50 'bindings/v8/V8Callback.h', 51 'wtf/Assertions.h', 52 ]) 53 54 55 CALLBACK_INTERFACE_H_INCLUDES = set([ 56 'bindings/v8/ActiveDOMCallback.h', 57 'bindings/v8/DOMWrapperWorld.h', 58 'bindings/v8/ScopedPersistent.h', 59 ]) 60 61 62 INTERFACE_CPP_INCLUDES = set([ 63 'RuntimeEnabledFeatures.h', 64 'bindings/v8/ScriptController.h', 65 'bindings/v8/V8Binding.h', 66 'core/dom/ContextFeatures.h', 67 'core/dom/Document.h', 68 'core/page/Frame.h', 69 'core/platform/chromium/TraceEvent.h', 70 'wtf/UnusedParam.h', 71 ]) 72 73 74 INTERFACE_H_INCLUDES = set([ 75 'bindings/v8/V8Binding.h', 76 ]) 77 78 79 CPP_TYPE_SPECIAL_CONVERSION_RULES = { 80 'float': 'float', 81 'double': 'double', 82 'long long': 'long long', 83 'unsigned long long': 'unsigned long long', 84 'long': 'int', 85 'short': 'int', 86 'byte': 'int', 87 'boolean': 'bool', 88 'DOMString': 'const String&', 89 } 90 91 92 PRIMITIVE_TYPES = set([ 93 'boolean', 94 'void', 95 'Date', 96 'byte', 97 'octet', 98 'short', 99 'long', 100 'long long', 101 'unsigned short', 102 'unsigned long', 103 'unsigned long long', 104 'float', 105 'double', 106 ]) 107 108 109 def apply_template(path_to_template, contents): 110 dirname, basename = os.path.split(path_to_template) 111 jinja_env = jinja2.Environment(trim_blocks=True, loader=jinja2.FileSystemLoader([dirname])) 112 template = jinja_env.get_template(basename) 113 return template.render(contents) 114 115 116 def cpp_value_to_js_value(data_type, cpp_value, isolate, creation_context=''): 117 """Returns a expression that represent JS value corresponding to a C++ value.""" 118 if data_type == 'boolean': 119 return 'v8Boolean(%s, %s)' % (cpp_value, isolate) 120 if data_type in ['long long', 'unsigned long long', 'DOMTimeStamp']: 121 # long long and unsigned long long are not representable in ECMAScript. 122 return 'v8::Number::New(static_cast<double>(%s))' % cpp_value 123 if primitive_type(data_type): 124 if data_type not in ['float', 'double']: 125 raise Exception('unexpected data_type %s' % data_type) 126 return 'v8::Number::New(%s)' % cpp_value 127 if data_type == 'DOMString': 128 return 'v8String(%s, %s)' % (cpp_value, isolate) 129 if array_or_sequence_type(data_type): 130 return 'v8Array(%s, %s)' % (cpp_value, isolate) 131 return 'toV8(%s, %s, %s)' % (cpp_value, creation_context, isolate) 132 133 134 def generate_conditional_string(interface_or_attribute_or_operation): 135 if 'Conditional' not in interface_or_attribute_or_operation.extended_attributes: 136 return '' 137 conditional = interface_or_attribute_or_operation.extended_attributes['Conditional'] 138 for operator in ['&', '|']: 139 if operator in conditional: 140 conditions = set(conditional.split(operator)) 141 operator_separator = ' %s%s ' % (operator, operator) 142 return operator_separator.join(['ENABLE(%s)' % expression for expression in sorted(conditions)]) 143 return 'ENABLE(%s)' % conditional 144 145 146 def includes_for_type(data_type): 147 if primitive_type(data_type) or data_type == 'DOMString': 148 return set() 149 if array_or_sequence_type(data_type): 150 return includes_for_type(array_or_sequence_type(data_type)) 151 return set(['V8%s.h' % data_type]) 152 153 154 def includes_for_cpp_class(class_name, relative_dir_posix): 155 return set([posixpath.join('bindings', relative_dir_posix, class_name + '.h')]) 156 157 158 def includes_for_operation(operation): 159 includes = includes_for_type(operation.data_type) 160 for parameter in operation.arguments: 161 includes |= includes_for_type(parameter.data_type) 162 return includes 163 164 165 def primitive_type(data_type): 166 return data_type in PRIMITIVE_TYPES 167 168 169 def sequence_type(data_type): 170 matched = re.match(r'sequence<([\w\d_\s]+)>', data_type) 171 if not matched: 172 return None 173 return matched.group(1) 174 175 176 def array_type(data_type): 177 matched = re.match(r'([\w\d_\s]+)\[\]', data_type) 178 if not matched: 179 return None 180 return matched.group(1) 181 182 183 def array_or_sequence_type(data_type): 184 return array_type(data_type) or sequence_type(data_type) 185 186 def cpp_type(data_type, pointer_type): 187 """Returns the C++ type corresponding to the IDL type. 188 189 Args: 190 pointer_type: 191 'raw': return raw pointer form (e.g. Foo*) 192 'RefPtr': return RefPtr form (e.g. RefPtr<Foo>) 193 'PassRefPtr': return PassRefPtr form (e.g. RefPtr<Foo>) 194 """ 195 if data_type in CPP_TYPE_SPECIAL_CONVERSION_RULES: 196 return CPP_TYPE_SPECIAL_CONVERSION_RULES[data_type] 197 if array_or_sequence_type(data_type): 198 return 'const Vector<%s >&' % cpp_type(array_or_sequence_type(data_type), 'RefPtr') 199 if pointer_type == 'raw': 200 return data_type + '*' 201 if pointer_type in ['RefPtr', 'PassRefPtr']: 202 return '%s<%s>' % (pointer_type, data_type) 203 raise Exception('Unrecognized pointer type: "%s"' % pointer_type) 204 205 206 def v8_type(data_type): 207 return 'V8' + data_type 208 209 210 def cpp_method_name(attribute_or_operation): 211 return attribute_or_operation.extended_attributes.get('ImplementedAs', attribute_or_operation.name) 212 213 214 def cpp_class_name(interface): 215 return interface.extended_attributes.get('ImplementedAs', interface.name) 216 217 218 def v8_class_name(interface): 219 return v8_type(interface.name) 220 221 222 class CodeGeneratorV8: 223 def __init__(self, definitions, interface_name, output_directory, relative_dir_posix, idl_directories, verbose=False): 224 self.idl_definitions = definitions 225 self.interface_name = interface_name 226 self.idl_directories = idl_directories 227 self.output_directory = output_directory 228 self.relative_dir_posix = relative_dir_posix 229 self.verbose = verbose 230 self.interface = None 231 self.header_includes = set() 232 self.cpp_includes = set() 233 if definitions: # FIXME: remove check when remove write_dummy_header_and_cpp 234 try: 235 self.interface = definitions.interfaces[interface_name] 236 except KeyError: 237 raise Exception('%s not in IDL definitions' % interface_name) 238 239 def generate_cpp_to_js_conversion(self, data_type, cpp_value, format_string, isolate, creation_context=''): 240 """Returns a statement that converts a C++ value to a JS value. 241 242 Also add necessary includes to self.cpp_includes. 243 """ 244 self.cpp_includes |= includes_for_type(data_type) 245 js_value = cpp_value_to_js_value(data_type, cpp_value, isolate, creation_context) 246 return format_string % js_value 247 248 def write_dummy_header_and_cpp(self): 249 # FIXME: fix GYP so these files aren't needed and remove this method 250 target_interface_name = self.interface_name 251 header_basename = 'V8%s.h' % target_interface_name 252 cpp_basename = 'V8%s.cpp' % target_interface_name 253 contents = """/* 254 This file is generated just to tell build scripts that {header_basename} and 255 {cpp_basename} are created for {target_interface_name}.idl, and thus 256 prevent the build scripts from trying to generate {header_basename} and 257 {cpp_basename} at every build. This file must not be tried to compile. 258 */ 259 """.format(**locals()) 260 self.write_header_code(header_basename, contents) 261 self.write_cpp_code(cpp_basename, contents) 262 263 def write_header_and_cpp(self): 264 header_basename = v8_class_name(self.interface) + '.h' 265 cpp_basename = v8_class_name(self.interface) + '.cpp' 266 if self.interface.is_callback: 267 header_template = 'templates/callback_interface.h' 268 cpp_template = 'templates/callback_interface.cpp' 269 template_contents = self.generate_callback_interface() 270 else: 271 header_template = 'templates/interface.h' 272 cpp_template = 'templates/interface.cpp' 273 template_contents = self.generate_interface() 274 template_contents['conditional_string'] = generate_conditional_string(self.interface) 275 header_file_text = apply_template(header_template, template_contents) 276 cpp_file_text = apply_template(cpp_template, template_contents) 277 self.write_header_code(header_basename, header_file_text) 278 self.write_cpp_code(cpp_basename, cpp_file_text) 279 280 def write_header_code(self, header_basename, header_file_text): 281 header_filename = os.path.join(self.output_directory, header_basename) 282 with open(header_filename, 'w') as header_file: 283 header_file.write(header_file_text) 284 285 def write_cpp_code(self, cpp_basename, cpp_file_text): 286 cpp_filename = os.path.join(self.output_directory, cpp_basename) 287 with open(cpp_filename, 'w') as cpp_file: 288 cpp_file.write(cpp_file_text) 289 290 def generate_attribute(self, attribute): 291 self.cpp_includes |= includes_for_type(attribute.data_type) 292 return { 293 'name': attribute.name, 294 'conditional_string': generate_conditional_string(attribute), 295 'cpp_method_name': cpp_method_name(attribute), 296 'cpp_type': cpp_type(attribute.data_type, pointer_type='RefPtr'), 297 'v8_type': v8_type(attribute.data_type), 298 } 299 300 def generate_interface(self): 301 self.header_includes = INTERFACE_H_INCLUDES 302 self.header_includes |= includes_for_cpp_class(cpp_class_name(self.interface), self.relative_dir_posix) 303 self.cpp_includes = INTERFACE_CPP_INCLUDES 304 305 template_contents = { 306 'interface_name': self.interface.name, 307 'cpp_class_name': cpp_class_name(self.interface), 308 'v8_class_name': v8_class_name(self.interface), 309 'attributes': [self.generate_attribute(attribute) for attribute in self.interface.attributes], 310 # Size 0 constant array is not allowed in VC++ 311 'number_of_attributes': 'WTF_ARRAY_LENGTH(%sAttributes)' % v8_class_name(self.interface) if self.interface.attributes else '0', 312 'attribute_templates': v8_class_name(self.interface) + 'Attributes' if self.interface.attributes else '0', 313 } 314 # Add includes afterwards, as they are modified by generate_attribute etc. 315 template_contents['header_includes'] = sorted(list(self.header_includes)) 316 template_contents['cpp_includes'] = sorted(list(self.cpp_includes)) 317 return template_contents 318 319 def generate_callback_interface(self): 320 self.header_includes = CALLBACK_INTERFACE_H_INCLUDES 321 self.header_includes |= includes_for_cpp_class(cpp_class_name(self.interface), self.relative_dir_posix) 322 self.cpp_includes = CALLBACK_INTERFACE_CPP_INCLUDES 323 324 def generate_argument(argument): 325 receiver = 'v8::Handle<v8::Value> %sHandle = %%s;' % argument.name 326 cpp_to_js_conversion = self.generate_cpp_to_js_conversion(argument.data_type, argument.name, receiver, 'isolate', creation_context='v8::Handle<v8::Object>()') 327 return { 328 'name': argument.name, 329 'cpp_to_js_conversion': cpp_to_js_conversion, 330 } 331 332 def generate_method(operation): 333 def argument_declaration(argument): 334 return '%s %s' % (cpp_type(argument.data_type, 'raw'), argument.name) 335 336 arguments = [] 337 custom = 'Custom' in operation.extended_attributes 338 if not custom: 339 self.cpp_includes |= includes_for_operation(operation) 340 if operation.data_type != 'boolean': 341 raise Exception("We don't yet support callbacks that return non-boolean values.") 342 arguments = [generate_argument(argument) for argument in operation.arguments] 343 method = { 344 'return_cpp_type': cpp_type(operation.data_type, 'RefPtr'), 345 'name': operation.name, 346 'arguments': arguments, 347 'argument_declaration': ', '.join([argument_declaration(argument) for argument in operation.arguments]), 348 'handles': ', '.join(['%sHandle' % argument.name for argument in operation.arguments]), 349 'custom': custom, 350 } 351 return method 352 353 methods = [generate_method(operation) for operation in self.interface.operations] 354 template_contents = { 355 'cpp_class_name': self.interface.name, 356 'v8_class_name': v8_class_name(self.interface), 357 'cpp_includes': sorted(list(self.cpp_includes)), 358 'header_includes': sorted(list(self.header_includes)), 359 'methods': methods, 360 } 361 return template_contents 362