#!/usr/bin/python # Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file # for details. All rights reserved. Use of this source code is governed by a # BSD-style license that can be found in the LICENSE file. """This module provides shared functionality for the system to generate dart:html APIs from the IDL database.""" import emitter from generator import AnalyzeOperation, ConstantOutputOrder, \ DartDomNameOfAttribute, FindMatchingAttribute, IsDartCollectionType, \ IsPureInterface, TypeOrNothing, GetAnnotationsAndComments, \ FormatAnnotationsAndComments # Types that are accessible cross-frame in a limited fashion. # In these cases, the base type (e.g., WindowBase) provides restricted access # while the subtype (e.g., Window) provides full access to the # corresponding objects if there are from the same frame. _secure_base_types = { 'Window': 'WindowBase', 'Location': 'LocationBase', 'History': 'HistoryBase', } class HtmlDartGenerator(object): def __init__(self, interface, options): self._database = options.database self._interface = interface self._type_registry = options.type_registry self._interface_type_info = self._type_registry.TypeInfo(self._interface.id) self._renamer = options.renamer self._library_name = self._renamer.GetLibraryName(self._interface) def EmitSupportCheck(self): if self.HasSupportCheck(): check = self.GetSupportCheck() if type(check) != tuple: signature = 'get supported' else: signature = check[0] check = check[1] self._members_emitter.Emit('\n' ' /// Checks if this type is supported on the current platform.\n' ' static bool $SIGNATURE => $SUPPORT_CHECK;\n', SIGNATURE=signature, SUPPORT_CHECK=check) def EmitEventGetter(self, events_class_name): self._members_emitter.Emit( "\n @DocsEditable" "\n @DomName('EventTarget.addEventListener, " "EventTarget.removeEventListener, EventTarget.dispatchEvent')" "\n @deprecated" "\n $TYPE get on =>\n new $TYPE(this);\n", TYPE=events_class_name) def AddMembers(self, interface, declare_only=False): for const in sorted(interface.constants, ConstantOutputOrder): self.AddConstant(const) for attr in sorted(interface.attributes, ConstantOutputOrder): if attr.type.id != 'EventListener': self.AddAttribute(attr, declare_only) # The implementation should define an indexer if the interface directly # extends List. element_type = None requires_indexer = False if self._interface_type_info.list_item_type(): self.AddIndexer(self._interface_type_info.list_item_type()) else: for parent in self._database.Hierarchy(self._interface): if parent == self._interface: continue parent_type_info = self._type_registry.TypeInfo(parent.id) if parent_type_info.list_item_type(): self.AmendIndexer(parent_type_info.list_item_type()) break # Group overloaded operations by id. operationsById = {} for operation in interface.operations: if operation.id not in operationsById: operationsById[operation.id] = [] operationsById[operation.id].append(operation) # Generate operations. for id in sorted(operationsById.keys()): operations = operationsById[id] info = AnalyzeOperation(interface, operations) self.AddOperation(info, declare_only) def AddSecondaryMembers(self, interface): # With multiple inheritance, attributes and operations of non-first # interfaces need to be added. Sometimes the attribute or operation is # defined in the current interface as well as a parent. In that case we # avoid making a duplicate definition and pray that the signatures match. secondary_parents = self._TransitiveSecondaryParents(interface) for parent_interface in sorted(secondary_parents): if isinstance(parent_interface, str): continue for attr in sorted(parent_interface.attributes, ConstantOutputOrder): if not FindMatchingAttribute(interface, attr): self.SecondaryContext(parent_interface) self.AddAttribute(attr) # Group overloaded operations by id. operationsById = {} for operation in parent_interface.operations: if operation.id not in operationsById: operationsById[operation.id] = [] operationsById[operation.id].append(operation) # Generate operations. for id in sorted(operationsById.keys()): if not any(op.id == id for op in interface.operations): operations = operationsById[id] info = AnalyzeOperation(interface, operations) self.SecondaryContext(parent_interface) self.AddOperation(info) def AddConstant(self, constant): const_name = self._renamer.RenameMember( self._interface.id, constant, constant.id, 'get:', dartify_name=False) if not const_name: return type = TypeOrNothing(self._DartType(constant.type.id), constant.type.id) self._members_emitter.Emit('\n static const $TYPE$NAME = $VALUE;\n', NAME=const_name, TYPE=type, VALUE=constant.value) def AddAttribute(self, attribute, declare_only=False): """ Adds an attribute to the generated class. Arguments: attribute - The attribute which is to be added. declare_only- True if the attribute should be declared as an abstract member and not include invocation code. """ dom_name = DartDomNameOfAttribute(attribute) attr_name = self._renamer.RenameMember( self._interface.id, attribute, dom_name, 'get:') if not attr_name or self._IsPrivate(attr_name): return html_setter_name = self._renamer.RenameMember( self._interface.id, attribute, dom_name, 'set:') read_only = (attribute.is_read_only or 'Replaceable' in attribute.ext_attrs or not html_setter_name) # We don't yet handle inconsistent renames of the getter and setter yet. assert(not html_setter_name or attr_name == html_setter_name) if declare_only: self.DeclareAttribute(attribute, self.SecureOutputType(attribute.type.id), attr_name, read_only) else: self.EmitAttribute(attribute, attr_name, read_only) def AddOperation(self, info, declare_only=False): """ Adds an operation to the generated class. Arguments: info - The operation info of the operation to be added. declare_only- True if the operation should be declared as an abstract member and not include invocation code. """ # FIXME: When we pass in operations[0] below, we're assuming all # overloaded operations have the same security attributes. This # is currently true, but we should consider filtering earlier or # merging the relevant data into info itself. method_name = self._renamer.RenameMember(self._interface.id, info.operations[0], info.name, 'call:') if not method_name: if info.name == 'item': # FIXME: item should be renamed to operator[], not removed. self.EmitOperation(info, '_item') return if declare_only: self.DeclareOperation(info, self.SecureOutputType(info.type_name), method_name) else: self.EmitOperation(info, method_name) def _GenerateOverloadDispatcher(self, signatures, is_void, parameter_names, declaration, generate_call, is_optional, can_omit_type_check=lambda type, pos: False): body_emitter = self._members_emitter.Emit( '\n' ' $DECLARATION {\n' '$!BODY' ' }\n', DECLARATION=declaration) version = [0] def GenerateCall(signature_index, argument_count, checks): if checks: (stmts_emitter, call_emitter) = body_emitter.Emit( ' if ($CHECKS) {\n$!STMTS$!CALL }\n', INDENT=' ', CHECKS=' && '.join(checks)) else: (stmts_emitter, call_emitter) = body_emitter.Emit( '$!STMTS$!CALL', INDENT=' '); if is_void: call_emitter = call_emitter.Emit('$(INDENT)$!CALL;\n$(INDENT)return;\n') else: call_emitter = call_emitter.Emit('$(INDENT)return $!CALL;\n') version[0] += 1 generate_call(stmts_emitter, call_emitter, version[0], signature_index, argument_count) def GenerateChecksAndCall(signature_index, argument_count): checks = [] for i in range(0, argument_count): argument = signatures[signature_index][i] parameter_name = parameter_names[i] test_type = self._DartType(argument.type.id) if test_type in ['dynamic', 'Object']: checks.append('?%s' % parameter_name) elif not can_omit_type_check(test_type, i): checks.append('(%s is %s || %s == null)' % ( parameter_name, test_type, parameter_name)) # There can be multiple presence checks. We need them all since a later # optional argument could have been passed by name, leaving 'holes'. checks.extend(['!?%s' % name for name in parameter_names[argument_count:]]) GenerateCall(signature_index, argument_count, checks) # TODO: Optimize the dispatch to avoid repeated checks. if len(signatures) > 1: for signature_index, signature in enumerate(signatures): for argument_position, argument in enumerate(signature): if is_optional(signature_index, argument): GenerateChecksAndCall(signature_index, argument_position) GenerateChecksAndCall(signature_index, len(signature)) body_emitter.Emit( ' throw new ArgumentError("Incorrect number or type of arguments");' '\n'); else: signature = signatures[0] argument_count = len(signature) for argument_position, argument in list(enumerate(signature))[::-1]: if is_optional(0, argument): check = '?%s' % parameter_names[argument_position] # argument_count instead of argument_position + 1 is used here to cover one # complicated case with the effectively optional argument in the middle. # Consider foo(x, [Optional] y, [Optional=DefaultIsNullString] z) # (as of now it's modelled after HTMLMediaElement.webkitAddKey). # y is optional in WebCore, while z is not. # In this case, if y was actually passed, we'd like to emit foo(x, y, z) invocation, # not foo(x, y). GenerateCall(0, argument_count, [check]) argument_count = argument_position GenerateCall(0, argument_count, []) def _GenerateDispatcherBody(self, operations, parameter_names, declaration, generate_call, is_optional, can_omit_type_check=lambda type, pos: False): def GenerateCall( stmts_emitter, call_emitter, version, signature_index, argument_count): generate_call( stmts_emitter, call_emitter, version, operations[signature_index], argument_count) def IsOptional(signature_index, argument): return is_optional(operations[signature_index], argument) self._GenerateOverloadDispatcher( [operation.arguments for operation in operations], operations[0].type.id == 'void', parameter_names, declaration, GenerateCall, IsOptional, can_omit_type_check) def AdditionalImplementedInterfaces(self): # TODO: Include all implemented interfaces, including other Lists. implements = [] if self._interface_type_info.is_typed_array(): element_type = self._interface_type_info.list_item_type() implements.append('List<%s>' % self._DartType(element_type)) if self._interface_type_info.list_item_type(): item_type_info = self._type_registry.TypeInfo( self._interface_type_info.list_item_type()) implements.append('List<%s>' % item_type_info.dart_type()) return implements def AddConstructors(self, constructors, factory_name, factory_constructor_name): """ Adds all of the constructors. Arguments: constructors - List of the constructors to be added. factory_name - Name of the factory for this class. factory_constructor_name - The name of the constructor on the factory_name to call (calls an autogenerated FactoryProvider if unspecified) """ for constructor_info in constructors: self._AddConstructor( constructor_info, factory_name, factory_constructor_name) typed_array_type = None for interface in self._database.Hierarchy(self._interface): type_info = self._type_registry.TypeInfo(interface.id) if type_info.is_typed_array(): typed_array_type = type_info.list_item_type() break annotations = FormatAnnotationsAndComments( GetAnnotationsAndComments(self._library_name, self._interface.id, self._interface.id), ' ') fromListAnnotations = FormatAnnotationsAndComments( GetAnnotationsAndComments(self._library_name, self._interface.id, 'fromList'), ' ') fromBufferAnnotations = FormatAnnotationsAndComments( GetAnnotationsAndComments(self._library_name, self._interface.id, 'fromBuffer'), ' ') if typed_array_type: self._members_emitter.Emit( '\n $(ANNOTATIONS)factory $CTOR(int length) =>\n' ' $FACTORY.create$(CTOR)(length);\n' '\n $(LIST_ANNOTATIONS)factory $CTOR.fromList(List<$TYPE> list) =>\n' ' $FACTORY.create$(CTOR)_fromList(list);\n' '\n $(BUFFER_ANNOTATIONS)factory $CTOR.fromBuffer(ArrayBuffer buffer, ' '[int byteOffset, int length]) => \n' ' $FACTORY.create$(CTOR)_fromBuffer(buffer, byteOffset, length);\n', CTOR=self._interface.id, ANNOTATIONS=annotations, LIST_ANNOTATIONS=fromListAnnotations, BUFFER_ANNOTATIONS=fromBufferAnnotations, TYPE=self._DartType(typed_array_type), FACTORY=factory_name) def _AddConstructor(self, constructor_info, factory_name, factory_constructor_name): if self.GenerateCustomFactory(constructor_info): return annotations = FormatAnnotationsAndComments( GetAnnotationsAndComments(self._library_name, self._interface.id, self._interface.id), ' ') if not factory_constructor_name: factory_constructor_name = '_create' factory_parameters = constructor_info.ParametersAsArgumentList() has_factory_provider = True else: factory_parameters = ', '.join(constructor_info.factory_parameters) has_factory_provider = False if constructor_info.pure_dart_constructor: # TODO(antonm): use common dispatcher generation for this case as well. has_optional = any(param_info.is_optional for param_info in constructor_info.param_infos) if not has_optional: self._members_emitter.Emit( '\n $(ANNOTATIONS)' 'factory $CTOR($PARAMS) => ' '$FACTORY.$CTOR_FACTORY_NAME($FACTORY_PARAMS);\n', CTOR=constructor_info._ConstructorFullName(self._DartType), PARAMS=constructor_info.ParametersDeclaration(self._DartType), FACTORY=factory_name, ANNOTATIONS=annotations, CTOR_FACTORY_NAME=factory_constructor_name, FACTORY_PARAMS=factory_parameters) else: if has_factory_provider: dispatcher_emitter = self._members_emitter.Emit( '\n $(ANNOTATIONS)' 'factory $CTOR($PARAMS) {\n' '$!DISPATCHER' ' return $FACTORY._create($FACTORY_PARAMS);\n' ' }\n', CTOR=constructor_info._ConstructorFullName(self._DartType), PARAMS=constructor_info.ParametersDeclaration(self._DartType), FACTORY=factory_name, ANNOTATIONS=annotations, FACTORY_PARAMS=constructor_info.ParametersAsArgumentList()) for index, param_info in enumerate(constructor_info.param_infos): if param_info.is_optional: dispatcher_emitter.Emit( ' if (!?$OPT_PARAM_NAME) {\n' ' return $FACTORY._create($FACTORY_PARAMS);\n' ' }\n', OPT_PARAM_NAME=param_info.name, FACTORY=factory_name, FACTORY_PARAMS=constructor_info.ParametersAsArgumentList(index)) else: inits = self._members_emitter.Emit( '\n $(ANNOTATIONS)' 'factory $CONSTRUCTOR($PARAMS) {\n' ' var e = $FACTORY.$CTOR_FACTORY_NAME($FACTORY_PARAMS);\n' '$!INITS' ' return e;\n' ' }\n', CONSTRUCTOR=constructor_info._ConstructorFullName(self._DartType), ANNOTATIONS=annotations, FACTORY=factory_name, CTOR_FACTORY_NAME=factory_constructor_name, PARAMS=constructor_info.ParametersDeclaration(self._DartType), FACTORY_PARAMS=factory_parameters) for index, param_info in enumerate(constructor_info.param_infos): if param_info.is_optional: inits.Emit(' if ($E != null) e.$E = $E;\n', E=param_info.name) else: def GenerateCall( stmts_emitter, call_emitter, version, signature_index, argument_count): name = emitter.Format('_create_$VERSION', VERSION=version) call_emitter.Emit('$FACTORY.$NAME($FACTORY_PARAMS)', FACTORY=factory_name, NAME=name, FACTORY_PARAMS= \ constructor_info.ParametersAsArgumentList(argument_count)) self.EmitStaticFactoryOverload( constructor_info, name, constructor_info.idl_args[signature_index][:argument_count]) def IsOptional(signature_index, argument): return self.IsConstructorArgumentOptional(argument) self._GenerateOverloadDispatcher( constructor_info.idl_args, False, [info.name for info in constructor_info.param_infos], emitter.Format('$(ANNOTATIONS)factory $CTOR($PARAMS)', CTOR=constructor_info._ConstructorFullName(self._DartType), ANNOTATIONS=annotations, PARAMS=constructor_info.ParametersDeclaration(self._DartType)), GenerateCall, IsOptional) def EmitHelpers(self, base_class): pass def DeclareAttribute(self, attribute, type_name, attr_name, read_only): """ Declares an attribute but does not include the code to invoke it. """ if read_only: template = '\n $TYPE get $NAME;\n' else: template = '\n $TYPE $NAME;\n' self._members_emitter.Emit(template, NAME=attr_name, TYPE=type_name) def DeclareOperation(self, operation, return_type_name, method_name): """ Declares an operation but does not include the code to invoke it. Arguments: operation - The operation to be declared. return_type_name - The name of the return type. method_name - The name of the method. """ self._members_emitter.Emit( '\n' ' $TYPE $NAME($PARAMS);\n', TYPE=return_type_name, NAME=method_name, PARAMS=operation.ParametersDeclaration(self._DartType)) def EmitListMixin(self, element_name): # TODO(sra): Use separate mixins for mutable implementations of List. # TODO(sra): Use separate mixins for typed array implementations of List. template_file = 'immutable_list_mixin.darttemplate' has_contains = any(op.id == 'contains' for op in self._interface.operations) has_clear = any(op.id == 'clear' for op in self._interface.operations) has_length = False has_length_setter = False for attr in self._interface.attributes: if attr.id == 'length': has_length = True has_length_setter = not attr.is_read_only has_num_items = any(attr.id == 'numberOfItems' for attr in self._interface.attributes) template = self._template_loader.Load( template_file, { 'DEFINE_CONTAINS': not has_contains, 'DEFINE_CLEAR': not has_clear, 'DEFINE_LENGTH_AS_NUM_ITEMS': not has_length and has_num_items, 'DEFINE_LENGTH_SETTER': not has_length_setter, }) self._members_emitter.Emit(template, E=element_name) def SecureOutputType(self, type_name, is_dart_type=False): """ Converts the type name to the secure type name for return types. """ if is_dart_type: dart_name = type_name else: dart_name = self._DartType(type_name) # We only need to secure Window. Only local History and Location are # returned in generated code. assert(dart_name != 'HistoryBase' and dart_name != 'LocationBase') if dart_name == 'Window': return _secure_base_types[dart_name] return dart_name def SecureBaseName(self, type_name): if type_name in _secure_base_types: return _secure_base_types[type_name] def _TransitiveSecondaryParents(self, interface): """Returns a list of all non-primary parents. The list contains the interface objects for interfaces defined in the database, and the name for undefined interfaces. """ def walk(parents): for parent in parents: parent_name = parent.type.id if parent_name == 'EventTarget': # Currently EventTarget is implemented as a mixin, not a proper # super interface---ignore its members. continue if IsDartCollectionType(parent_name): result.append(parent_name) continue if self._database.HasInterface(parent_name): parent_interface = self._database.GetInterface(parent_name) result.append(parent_interface) walk(parent_interface.parents) result = [] if interface.parents: parent = interface.parents[0] if IsPureInterface(parent.type.id): walk(interface.parents) else: walk(interface.parents[1:]) return result def _DartType(self, type_name): return self._type_registry.DartType(type_name) def _IsPrivate(self, name): return name.startswith('_')