| # Copyright 2016 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. |
| |
| # pylint: disable=import-error,print-statement,relative-import |
| |
| """Generates Web Agent API bindings. |
| |
| The Web Agent API bindings provide a stable, IDL-generated interface for the |
| Web Agents. |
| |
| The Web Agents are the high-level services like Autofill, |
| Autocomplete, Translate, Distiller, Phishing Detector, and others. Web Agents |
| typically want to introspec the document and rendering infromation to implement |
| browser features. |
| |
| The bindings are meant to be as simple and as ephemeral as possible, mostly just |
| wrapping existing DOM classes. Their primary goal is to avoid leaking the actual |
| DOM classes to the Web Agents layer. |
| """ |
| |
| import os |
| import posixpath |
| import sys |
| |
| from code_generator import CodeGeneratorBase, render_template |
| # TODO(dglazkov): Move TypedefResolver to code_generator.py |
| from code_generator_v8 import TypedefResolver |
| |
| sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', '..', '..', '..', |
| 'third_party', 'blink', 'tools')) |
| from blinkpy.common.name_style_converter import NameStyleConverter |
| |
| MODULE_PYNAME = os.path.splitext(os.path.basename(__file__))[0] + '.py' |
| |
| STRING_INCLUDE_PATH = 'platform/wtf/text/WTFString.h' |
| WEB_AGENT_API_IDL_ATTRIBUTE = 'WebAgentAPI' |
| |
| |
| def interface_context(idl_interface, type_resolver): |
| builder = InterfaceContextBuilder(MODULE_PYNAME, type_resolver) |
| builder.set_class_name(idl_interface.name) |
| builder.set_inheritance(idl_interface.parent) |
| |
| for idl_attribute in idl_interface.attributes: |
| builder.add_attribute(idl_attribute) |
| |
| for idl_operation in idl_interface.operations: |
| builder.add_operation(idl_operation) |
| |
| return builder.build() |
| |
| |
| class TypeResolver(object): |
| """Resolves Web IDL types into corresponding C++ types and include paths |
| to the generated and existing files.""" |
| |
| def __init__(self, interfaces_info): |
| self.interfaces_info = interfaces_info |
| |
| def includes_from_interface(self, interface_name): |
| interface_info = self.interfaces_info.get(interface_name) |
| if interface_info is None: |
| raise KeyError('Unknown interface "%s".' % interface_name) |
| return set([interface_info['include_path']]) |
| |
| def _includes_from_type(self, idl_type): |
| if idl_type.is_void: |
| return set() |
| if idl_type.is_primitive_type: |
| return set() |
| if idl_type.is_string_type: |
| return set([STRING_INCLUDE_PATH]) |
| |
| # TODO(dglazkov): Handle complex/weird types. |
| return self.includes_from_interface(idl_type.base_type) |
| |
| def includes_from_definition(self, idl_definition): |
| return self._includes_from_type(idl_definition.idl_type) |
| |
| def type_from_definition(self, idl_definition): |
| # TODO(dglazkov): The output of this method must be a reasonable C++ |
| # type that can be used directly in the jinja2 template. |
| return idl_definition.idl_type.base_type |
| |
| def base_class_includes(self): |
| return set(['platform/heap/Handle.h']) |
| |
| |
| class MethodOverloadSplitter(object): |
| """Because of union and optional types being used as arguments, some |
| operations may result in more than one generated method. This class |
| contains the logic for spliting an operation into multiple C++ overloads. |
| """ |
| |
| def __init__(self, idl_operation): |
| self.idl_operation = idl_operation |
| |
| def _update_argument_lists(self, argument_lists, idl_types): |
| """Given a list of IdlTypes and an existing list of argument lists (yes, |
| this is a list of lists), produces a next generation of the list of |
| lists. This is where the actual splitting into overloads happens. |
| """ |
| result = [] |
| for argument_list in argument_lists: |
| for idl_type in idl_types: |
| new_argument_list = list(argument_list) |
| if idl_type is not None: |
| new_argument_list.append(idl_type) |
| result.append(new_argument_list) |
| return result |
| |
| def _enumerate_argument_types(self, idl_argument): |
| """Given an IdlArgument, returns a list of types that are included |
| in this argument. If optional, the list will include a 'None'.""" |
| argument_type = idl_argument.idl_type |
| # TODO(dglazkov): What should we do with primitive nullable args? |
| if (argument_type.is_nullable and |
| argument_type.inner_type.is_primitive_type): |
| raise ValueError('Primitive nullable types are not supported.') |
| |
| idl_types = [] |
| if idl_argument.is_optional: |
| idl_types.append(None) # None is used to convey optionality. |
| if argument_type.is_union_type: |
| idl_types = idl_types + argument_type.member_types |
| else: |
| idl_types.append(argument_type) |
| return idl_types |
| |
| def split_into_overloads(self): |
| """Splits an operation into one or more overloads that correctly reflect |
| the WebIDL semantics of the operation arguments. For example, |
| running this method on an IdlOperation that represents this WebIDL |
| definition: |
| |
| void addEventListener( |
| DOMString type, |
| EventListener? listener, |
| optional (AddEventListenerOptions or boolean) options) |
| |
| will produce a list of 3 argument lists: |
| |
| 1) [DOMString, EventListener], since the third argument is optional, |
| 2) [DOMString, EventListener, AddEventListenerOptions], since the |
| third argument is a union type with AddEventListenerOptions as |
| one of its member types, and |
| 3) [DOMString, EventListener, boolean], since the other union member |
| type of the third argument is boolean. |
| |
| This example is also captured as test in |
| MethodOverloadSplitterTest.test_split_add_event_listener. |
| """ |
| |
| argument_lists = [[]] |
| for idl_argument in self.idl_operation.arguments: |
| idl_types = self._enumerate_argument_types(idl_argument) |
| argument_lists = self._update_argument_lists(argument_lists, |
| idl_types) |
| return argument_lists |
| |
| |
| class InterfaceContextBuilder(object): |
| def __init__(self, code_generator, type_resolver): |
| self.result = {'code_generator': code_generator} |
| self.type_resolver = type_resolver |
| |
| def set_class_name(self, class_name): |
| converter = NameStyleConverter(class_name) |
| self.result['class_name'] = converter.to_all_cases() |
| self._ensure_set('cpp_includes').update( |
| self.type_resolver.includes_from_interface(class_name)) |
| |
| def set_inheritance(self, base_interface): |
| if base_interface is None: |
| self._ensure_set('header_includes').update( |
| self.type_resolver.base_class_includes()) |
| return |
| self.result['base_class'] = base_interface |
| self._ensure_set('header_includes').update( |
| self.type_resolver.includes_from_interface(base_interface)) |
| |
| def _ensure_set(self, name): |
| return self.result.setdefault(name, set()) |
| |
| def _ensure_list(self, name): |
| return self.result.setdefault(name, []) |
| |
| def add_attribute(self, idl_attribute): |
| self._ensure_list('attributes').append( |
| self.create_attribute(idl_attribute)) |
| self._ensure_set('cpp_includes').update( |
| self.type_resolver.includes_from_definition(idl_attribute)) |
| |
| def add_operation(self, idl_operation): |
| if not idl_operation.name: |
| return |
| overload_splitter = MethodOverloadSplitter(idl_operation) |
| overloads = overload_splitter.split_into_overloads() |
| argument_names = [argument.name for argument |
| in idl_operation.arguments] |
| for argument_types in overloads: |
| arguments = [] |
| for position, argument_type in enumerate(argument_types): |
| arguments.append( |
| self.create_argument(argument_names[position], |
| argument_type)) |
| self._ensure_list('methods').append( |
| self.create_method(idl_operation, arguments)) |
| self._ensure_set('cpp_includes').update( |
| self.type_resolver.includes_from_definition(idl_operation)) |
| |
| def create_argument(self, argument_name, argument_type): |
| name_converter = NameStyleConverter(argument_name) |
| return { |
| 'name': name_converter.to_snake_case(), |
| 'type': argument_type.base_type, |
| } |
| |
| def create_method(self, idl_operation, arguments): |
| name_converter = NameStyleConverter(idl_operation.name) |
| return_type = self.type_resolver.type_from_definition(idl_operation) |
| return { |
| 'name': name_converter.to_upper_camel_case(), |
| 'type': return_type, |
| 'arguments': arguments |
| } |
| |
| def create_attribute(self, idl_attribute): |
| name = idl_attribute.name |
| return_type = self.type_resolver.type_from_definition(idl_attribute) |
| return { |
| 'name': name, |
| 'type': return_type |
| } |
| |
| def build(self): |
| return self.result |
| |
| |
| class CodeGeneratorWebAgentAPI(CodeGeneratorBase): |
| def __init__(self, info_provider, cache_dir, output_dir, snake_case): |
| CodeGeneratorBase.__init__(self, MODULE_PYNAME, info_provider, |
| cache_dir, output_dir, snake_case) |
| self.type_resolver = TypeResolver(info_provider.interfaces_info) |
| self.typedef_resolver = TypedefResolver(info_provider) |
| |
| def get_template(self, file_extension): |
| template_filename = 'web_agent_api_interface.%s.tmpl' % file_extension |
| return self.jinja_env.get_template(template_filename) |
| |
| def generate_file(self, template_context, file_extension): |
| template = self.get_template(file_extension) |
| path = posixpath.join( |
| self.output_dir, |
| '%s.%s' % (template_context['class_name']['snake_case'], |
| file_extension)) |
| text = render_template(template, template_context) |
| return (path, text) |
| |
| def generate_interface_code(self, interface): |
| # TODO(dglazkov): Implement callback interfaces. |
| # TODO(dglazkov): Make sure partial interfaces are handled. |
| if interface.is_callback or interface.is_partial: |
| raise ValueError('Partial or callback interfaces are not supported') |
| |
| template_context = interface_context(interface, self.type_resolver) |
| |
| return ( |
| self.generate_file(template_context, 'h'), |
| self.generate_file(template_context, 'cc') |
| ) |
| |
| def generate_code(self, definitions, definition_name): |
| self.typedef_resolver.resolve(definitions, definition_name) |
| |
| # TODO(dglazkov): Implement dictionaries |
| if definition_name not in definitions.interfaces: |
| return None |
| |
| interface = definitions.interfaces[definition_name] |
| if WEB_AGENT_API_IDL_ATTRIBUTE not in interface.extended_attributes: |
| return None |
| |
| return self.generate_interface_code(interface) |