| #!/usr/bin/env python3 | |
| # 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, IsPureInterface, \ | |
| TypeOrNothing, ConvertToFuture, GetCallbackInfo | |
| from copy import deepcopy | |
| from htmlrenamer import convert_to_future_members, custom_html_constructors, \ | |
| GetDDC_Extension, keep_overloaded_members, overloaded_and_renamed,\ | |
| private_html_members, renamed_html_members, renamed_overloads, \ | |
| removed_html_members | |
| from generator import TypeOrVar | |
| import logging | |
| from mdnreader import MDNReader | |
| import monitored | |
| import sys | |
| _logger = logging.getLogger('htmldartgenerator') | |
| # 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', | |
| } | |
| _custom_factories = [ | |
| 'Notification', | |
| 'EventSource', | |
| ] | |
| class HtmlDartGenerator(object): | |
| def __init__(self, interface, options, dart_use_blink, logger): | |
| self._dart_use_blink = dart_use_blink | |
| 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._metadata = options.metadata | |
| self._library_name = self._renamer.GetLibraryName(self._interface) | |
| self._mdn_reader = MDNReader() | |
| _logger.setLevel(logger.level) | |
| 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( | |
| "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, dart_js_interop=False): | |
| if self._interface.id == 'WebGLRenderingContextBase' or self._interface.id == 'WebGL2RenderingContextBase' or \ | |
| self._interface.id == 'WebGLDrawBuffers': | |
| # Constants in classes WebGLRenderingContextBase, WebGL2RenderingContext, WebGLDrawBuffers are consolidated into | |
| # one synthesized class (WebGL). | |
| self._gl_constants.extend(interface.constants) | |
| else: | |
| for const in sorted(interface.constants, key=ConstantOutputOrder): | |
| self.AddConstant(const) | |
| for attr in sorted(interface.attributes, key=ConstantOutputOrder): | |
| if attr.type.id != 'EventHandler' and 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(), | |
| self._interface_type_info.list_item_type_nullable()) | |
| 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 name. | |
| self._AddRenamedOverloads(interface) | |
| operationsByName = self._OperationsByName(interface) | |
| if self.OmitOperationOverrides(): | |
| self._RemoveShadowingOperationsWithSameSignature( | |
| operationsByName, interface) | |
| # Generate operations. | |
| for id in sorted(operationsByName.keys()): | |
| operations = operationsByName[id] | |
| info = AnalyzeOperation(interface, operations) | |
| self.AddOperation(info, declare_only, dart_js_interop) | |
| if ('%s.%s' % (interface.id, | |
| info.declared_name) in convert_to_future_members): | |
| self.AddOperation(ConvertToFuture(info), declare_only) | |
| def AddSecondaryMembers(self, interface): | |
| secondary_parents = self._database.TransitiveSecondaryParents( | |
| interface, not self._dart_use_blink) | |
| remove_duplicate_parents = list(set(secondary_parents)) | |
| if len(secondary_parents) != len(remove_duplicate_parents): | |
| secondary_parents = remove_duplicate_parents | |
| parent_list = ", ".join( | |
| [" %s" % (parent.id) for parent in secondary_parents]) | |
| _logger.warn('Interface %s has duplicate parent interfaces %s - ' \ | |
| 'ignoring duplicates. Please file a bug with the dart:html team.' % (interface.id, parent_list)) | |
| for parent_interface in sorted(secondary_parents, | |
| key=ConstantOutputOrder): | |
| if isinstance(parent_interface, str): | |
| continue | |
| for attr in sorted(parent_interface.attributes, | |
| key=ConstantOutputOrder): | |
| if not FindMatchingAttribute(interface, attr): | |
| if attr.type.id != 'EventHandler': | |
| self.SecondaryContext(parent_interface) | |
| self.AddAttribute(attr) | |
| # Group overloaded operations by name. | |
| operationsByName = self._OperationsByName(parent_interface) | |
| if self.OmitOperationOverrides(): | |
| self._RemoveShadowingOperationsWithSameSignature( | |
| operationsByName, interface) | |
| # Generate operations. | |
| for id in sorted(operationsByName.keys()): | |
| if not any(op.id == id for op in interface.operations): | |
| operations = operationsByName[id] | |
| info = AnalyzeOperation(interface, operations) | |
| self.SecondaryContext(parent_interface) | |
| self.AddOperation(info) | |
| def _RemoveShadowingOperationsWithSameSignature(self, operationsByName, | |
| interface): | |
| if not interface.parents: | |
| return | |
| parent_name = interface.parents[0].type.id | |
| parent = self._database.GetInterface(parent_name) | |
| if parent == self._interface or parent == interface: | |
| return | |
| # Never remove operations that are added as a result of an implements they | |
| # are pure interfaces (mixins to this interface). | |
| if (IsPureInterface(parent_name, self._database)): | |
| return | |
| for operation in parent.operations: | |
| if operation.id in operationsByName: | |
| operations = operationsByName[operation.id] | |
| for existing_operation in operations: | |
| if existing_operation.SameSignatureAs(operation): | |
| del operationsByName[operation.id] | |
| def _AddRenamedOverloads(self, interface): | |
| """The IDL has a number of functions with the same name but that accept | |
| different types. This is fine for JavaScript, but results in vague type | |
| signatures for Dart. We rename some of these (by adding a new identical | |
| operation with a different DartName), but leave the original version as | |
| well in some cases.""" | |
| potential_added_operations = set() | |
| operations_by_name = self._OperationsByName(interface) | |
| already_renamed = [ | |
| operation.ext_attrs['DartName'] | |
| if 'DartName' in operation.ext_attrs else '' | |
| for operation in interface.operations | |
| ] | |
| added_operations = [] | |
| for operation in interface.operations: | |
| full_operation_str = self._GetStringRepresentation( | |
| interface, operation) | |
| if (full_operation_str in renamed_overloads and | |
| renamed_overloads[full_operation_str] not in already_renamed | |
| ): | |
| if '%s.%s' % (interface.id, | |
| operation.id) in overloaded_and_renamed: | |
| cloned_operation = deepcopy(operation) | |
| cloned_operation.ext_attrs['DartName'] = renamed_overloads[ | |
| full_operation_str] | |
| added_operations.append(cloned_operation) | |
| else: | |
| dart_name = renamed_overloads[full_operation_str] | |
| if not dart_name: | |
| continue | |
| operation.ext_attrs['DartName'] = dart_name | |
| potential_added_operations.add(operation.id) | |
| self._EnsureNoMultipleTypeSignatures(interface, operation, | |
| operations_by_name) | |
| interface.operations += added_operations | |
| self._AddDesiredOverloadedOperations(potential_added_operations, | |
| interface, operations_by_name) | |
| def _AddDesiredOverloadedOperations(self, potential_added_operations, | |
| interface, original_operations_by_name): | |
| """For some cases we desire to keep the overloaded version in dart, for | |
| simplicity of API, and explain the parameters accepted in documentation.""" | |
| updated_operations_by_name = self._OperationsByName(interface) | |
| for operation_id in potential_added_operations: | |
| if (operation_id not in updated_operations_by_name and '%s.%s' % | |
| (interface.id, operation_id) in keep_overloaded_members): | |
| for operation in original_operations_by_name[operation_id]: | |
| cloned_operation = deepcopy(operation) | |
| cloned_operation.ext_attrs['DartName'] = operation_id | |
| interface.operations.append(cloned_operation) | |
| def _EnsureNoMultipleTypeSignatures(self, interface, operation, | |
| operations_by_name): | |
| """Make sure that there is now at most one operation with a particular | |
| operation.id. If not, stop library generation, and throw an error, requiring | |
| programmer input about the best name change before proceeding.""" | |
| operation_str = '%s.%s' % (interface.id, operation.id) | |
| if (operation.id in operations_by_name and | |
| len(operations_by_name[operation.id]) > 1 and len( | |
| list( | |
| filter( | |
| lambda overload: overload.startswith(operation_str), | |
| renamed_overloads.keys()))) == 0 and | |
| operation_str not in keep_overloaded_members and | |
| operation_str not in overloaded_and_renamed and | |
| operation_str not in renamed_html_members and | |
| operation_str not in private_html_members and | |
| operation_str not in removed_html_members and | |
| operation.id != '__getter__' and | |
| operation.id != '__setter__' and operation.id != '__delete__'): | |
| _logger.warn( | |
| 'Multiple type signatures for %s.%s. Please file a bug with' | |
| ' the dart:html team to determine if one of these functions should be' | |
| ' renamed.' % (interface.id, operation.id)) | |
| def _GetStringRepresentation(self, interface, operation): | |
| """Given an IDLOperation, return a object-independent representation of the | |
| operations's signature.""" | |
| return '%s.%s(%s)' % (interface.id, operation.id, ', '.join( | |
| ['%s %s' % (arg.type.id, arg.id) for arg in operation.arguments])) | |
| def _OperationsByName(self, interface): | |
| operationsByName = {} | |
| for operation in interface.operations: | |
| name = operation.ext_attrs.get('DartName', operation.id) | |
| operationsByName.setdefault(name, []).append(operation) | |
| return operationsByName | |
| def OmitOperationOverrides(self): | |
| return False | |
| def AddConstant(self, constant): | |
| const_name = self._renamer.RenameMember( | |
| self._interface.id, | |
| constant, | |
| constant.id, | |
| 'get:', | |
| dartify_name=False) | |
| if not const_name: | |
| return | |
| annotations = self._metadata.GetFormattedMetadata( | |
| self._library_name, self._interface, constant.id, ' ') | |
| type = TypeOrNothing(self._DartType(constant.type.id), constant.type.id) | |
| self._members_emitter.Emit( | |
| '\n $(ANNOTATIONS)static const $TYPE$NAME = $VALUE;\n', | |
| ANNOTATIONS=annotations, | |
| 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: | |
| 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) | |
| # any is assumed to be nullable | |
| if attribute.type.id == 'any': | |
| attribute.type.nullable = True | |
| if declare_only: | |
| self.DeclareAttribute(attribute, attr_name, read_only) | |
| else: | |
| self.EmitAttribute(attribute, attr_name, read_only) | |
| def AddOperation(self, info, declare_only=False, dart_js_interop=False): | |
| # TODO(terry): Hack window has 2 overloaded getter one returns Window and | |
| # and other object (we'll always return Window)? | |
| if self._interface.id == "Window" and info.name == '__getter__': | |
| info.operations[1].type = info.operations[0].type | |
| """ 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, | |
| nullable=info.type_nullable), | |
| method_name) | |
| else: | |
| self.EmitOperation(info, method_name, dart_js_interop) | |
| def _GenerateOverloadDispatcher( | |
| self, | |
| info, | |
| signatures, | |
| is_void, | |
| declaration, | |
| generate_call, | |
| is_optional, | |
| emitter, | |
| can_omit_type_check=lambda type, pos: False): | |
| parameter_names = [p.name for p in info.param_infos] | |
| number_of_required_in_dart = info.NumberOfRequiredInDart() | |
| body_emitter = 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 IsTypeChecking(interface_argument): | |
| return 'LegacyInterfaceTypeChecking' in interface_argument.ext_attrs or \ | |
| self._database.HasInterface(interface_argument.id) | |
| def GenerateChecksAndCall(signature_index, argument_count): | |
| checks = [] | |
| typechecked_interface = IsTypeChecking(self._interface) | |
| for i in reversed(range(0, argument_count)): | |
| argument = signatures[signature_index][i] | |
| parameter_name = parameter_names[i] | |
| test_type = self._NarrowToImplementationType(argument.type.id) | |
| if test_type in ['dynamic', 'Object']: | |
| checks.append('%s != null' % parameter_name) | |
| elif not can_omit_type_check(test_type, i): | |
| typechecked = typechecked_interface or IsTypeChecking( | |
| argument) | |
| converts_null = \ | |
| ('TreatNullAs' in argument.ext_attrs) or \ | |
| (argument.default_value is not None) or \ | |
| (argument.default_value_is_null) | |
| if argument.type.nullable or converts_null or not typechecked: | |
| checks.append( | |
| '(%s is %s || %s == null)' % | |
| (parameter_name, test_type, parameter_name)) | |
| else: | |
| checks.append( | |
| '(%s is %s)' % (parameter_name, test_type)) | |
| elif i >= number_of_required_in_dart and not argument.type.nullable: | |
| checks.append('%s != null' % 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 == null' % 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: | |
| index_swaps = {} | |
| for signature_index, signature in enumerate(signatures): | |
| for argument_position, argument in enumerate(signature): | |
| if argument.type.id != 'ArrayBuffer': | |
| continue | |
| candidates = enumerate(signatures[signature_index + 1:], | |
| signature_index + 1) | |
| for candidate_index, candidate in candidates: | |
| if len(candidate) <= argument_position: | |
| continue | |
| if candidate[ | |
| argument_position].type.id != 'ArrayBufferView': | |
| continue | |
| if len(index_swaps): | |
| raise Exception( | |
| 'Cannot deal with more than a single swap') | |
| index_swaps[candidate_index] = signature_index | |
| index_swaps[signature_index] = candidate_index | |
| for signature_index in range(len(signatures)): | |
| signature_index = index_swaps.get(signature_index, | |
| signature_index) | |
| signature = signatures[signature_index] | |
| 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 != null' % 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, [Default=NullString] optional 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, | |
| info, | |
| operations, | |
| 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(argument) | |
| emitter = self._members_emitter | |
| self._GenerateOverloadDispatcher( | |
| info, [operation.arguments for operation in operations], | |
| operations[0].type.id == 'void', declaration, GenerateCall, | |
| IsOptional, emitter, can_omit_type_check) | |
| def AdditionalImplementedInterfaces(self): | |
| # TODO: Include all implemented interfaces, including other Lists. | |
| implements = [] | |
| if self._interface_type_info.list_item_type(): | |
| item_type = self._type_registry.TypeInfo( | |
| self._interface_type_info.list_item_type()).dart_type() | |
| if self._interface_type_info.list_item_type_nullable(): | |
| item_type += '?' | |
| implements.append('List<%s>' % item_type) | |
| return implements | |
| def Mixins(self): | |
| mixins = [] | |
| if self._interface_type_info.list_item_type(): | |
| item_type = self._type_registry.TypeInfo( | |
| self._interface_type_info.list_item_type()).dart_type() | |
| if self._interface_type_info.list_item_type_nullable(): | |
| item_type += '?' | |
| mixins.append('ListMixin<%s>' % item_type) | |
| mixins.append('ImmutableListMixin<%s>' % item_type) | |
| return mixins | |
| def AddConstructors(self, constructors, factory_name, | |
| factory_constructor_name, constructor_emitter): | |
| """ 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) | |
| constructor_emitter - Emitter used to emit constructors when generating | |
| classes using the static extension pattern. | |
| """ | |
| for constructor_info in constructors: | |
| self._AddConstructor(constructor_info, factory_name, | |
| factory_constructor_name, constructor_emitter) | |
| def _AddConstructor(self, constructor_info, factory_name, | |
| factory_constructor_name, constructor_emitter): | |
| # Hack to ignore the constructor used by JavaScript. | |
| if ((self._interface.id == 'HTMLImageElement' or | |
| self._interface.id == 'Blob' or | |
| self._interface.id == 'DOMException') and | |
| not constructor_info.pure_dart_constructor): | |
| return | |
| if self.GenerateCustomFactory(constructor_info): | |
| return | |
| metadata = self._metadata.GetFormattedMetadata( | |
| self._library_name, self._interface, self._interface.id, ' ') | |
| target_emitter = constructor_emitter if constructor_emitter else \ | |
| self._members_emitter | |
| if not factory_constructor_name: | |
| factory_constructor_name = '_create' | |
| factory_parameters = constructor_info.ParametersAsArgumentList() | |
| else: | |
| factory_parameters = ', '.join(constructor_info.factory_parameters) | |
| def InputType(type_name): | |
| conversion = self._InputConversion(type_name, | |
| constructor_info.declared_name) | |
| if conversion: | |
| return conversion.input_type | |
| else: | |
| return self._NarrowInputType( | |
| type_name) if type_name else 'dynamic' | |
| 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) | |
| factory_call = self.MakeFactoryCall( | |
| factory_name, factory_constructor_name, factory_parameters, | |
| constructor_info) | |
| if not has_optional: | |
| target_emitter.Emit( | |
| '\n $(METADATA)' | |
| 'factory $CTOR($PARAMS) => ' | |
| '$FACTORY_CALL;\n', | |
| CTOR=constructor_info._ConstructorFullName(self._DartType), | |
| PARAMS=constructor_info.ParametersAsDeclaration(InputType), | |
| FACTORY_CALL=factory_call, | |
| METADATA=metadata) | |
| else: | |
| inits = target_emitter.Emit( | |
| '\n $(METADATA)' | |
| 'factory $CONSTRUCTOR($PARAMS) {\n' | |
| ' $CONSTRUCTOR e = $FACTORY_CALL;\n' | |
| '$!INITS' | |
| ' return e;\n' | |
| ' }\n', | |
| CONSTRUCTOR=constructor_info._ConstructorFullName( | |
| self._DartType), | |
| METADATA=metadata, | |
| FACTORY_CALL=factory_call, | |
| PARAMS=constructor_info.ParametersAsDeclaration(InputType)) | |
| 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: | |
| custom_factory_ctr = self._interface.id in _custom_factories | |
| if self._interface_type_info.has_generated_interface(): | |
| constructor_full_name = constructor_info._ConstructorFullName( | |
| self._DartType) | |
| else: | |
| # The interface is suppress_interface so use the implementation_name not | |
| # the dart_type. | |
| constructor_full_name = self._interface_type_info.implementation_name( | |
| ) | |
| factory_name = constructor_full_name | |
| def GenerateCall(stmts_emitter, call_emitter, version, | |
| signature_index, argument_count): | |
| name = emitter.Format('_create_$VERSION', VERSION=version) | |
| arguments = constructor_info.idl_args[ | |
| signature_index][:argument_count] | |
| args = None | |
| call_template = '' | |
| if self._dart_use_blink: | |
| type_ids = [p.type.id for p in arguments] | |
| base_name, rs = \ | |
| self.DeriveNativeEntry("constructorCallback", 'Constructor', argument_count) | |
| qualified_name = \ | |
| self.DeriveQualifiedBlinkName(self._interface.id, | |
| base_name) | |
| args = constructor_info.ParametersAsArgumentList( | |
| argument_count) | |
| # Handle converting Maps to Dictionaries, etc. | |
| (factory_params, converted_arguments, | |
| calling_params) = self._ConvertArgumentTypes( | |
| stmts_emitter, arguments, argument_count, | |
| constructor_info) | |
| args = ', '.join(converted_arguments) | |
| call_template = '$FACTORY_NAME($FACTORY_PARAMS)' | |
| else: | |
| qualified_name = emitter.Format( | |
| '$FACTORY.$NAME', FACTORY=factory_name, NAME=name) | |
| (factory_params, converted_arguments, | |
| calling_params) = self._ConvertArgumentTypes( | |
| stmts_emitter, arguments, argument_count, | |
| constructor_info) | |
| args = ', '.join(converted_arguments) | |
| call_template = '$FACTORY_NAME($FACTORY_PARAMS)' | |
| call_emitter.Emit( | |
| call_template, | |
| FACTORY_NAME=qualified_name, | |
| FACTORY_PARAMS=args) | |
| self.EmitStaticFactoryOverload(constructor_info, name, | |
| arguments, constructor_emitter) | |
| def IsOptional(signature_index, argument): | |
| return self.IsConstructorArgumentOptional(argument) | |
| entry_declaration = emitter.Format( | |
| '$(METADATA)$FACTORY_KEYWORD $CTOR($PARAMS)', | |
| FACTORY_KEYWORD=('factory' if not custom_factory_ctr else | |
| 'static %s' % constructor_full_name), | |
| CTOR=(('' if not custom_factory_ctr else '_factory') + | |
| constructor_full_name), | |
| METADATA=metadata, | |
| PARAMS=constructor_info.ParametersAsDeclaration(InputType)) | |
| overload_declaration = entry_declaration | |
| self._GenerateOverloadDispatcher(constructor_info, | |
| constructor_info.idl_args, False, | |
| overload_declaration, GenerateCall, | |
| IsOptional, target_emitter) | |
| def _AddFutureifiedOperation(self, info, html_name): | |
| """Given a API function that uses callbacks, convert it to using Futures. | |
| This conversion assumes the success callback is always provided before the | |
| error callback (and so far in the DOM API, this is the case).""" | |
| callback_info = GetCallbackInfo( | |
| self._database.GetInterface(info.callback_args[0].type_id)) | |
| # Generated private members never have named arguments. | |
| ignore_named_parameters = True if html_name.startswith('_') else False | |
| # If more than one callback then the second argument is the error callback. | |
| # Some error callbacks have 2 args (e.g., executeSql) where the second arg | |
| # is the error - this is the argument we want. | |
| error_callback = "" | |
| if len(info.callback_args) > 1: | |
| error_callback_info = GetCallbackInfo( | |
| self._database.GetInterface(info.callback_args[1].type_id)) | |
| error_callbackNames = [] | |
| for paramInfo in error_callback_info.param_infos: | |
| error_callbackNames.append(paramInfo.name) | |
| errorCallbackVariables = ", ".join(error_callbackNames) | |
| errorName = error_callback_info.param_infos[-1].name | |
| error_callback = ( | |
| ',\n %s(%s) { completer.completeError(%s); }' % ( | |
| ('%s : ' % info.callback_args[1].name | |
| if info.requires_named_arguments and | |
| info.callback_args[1].is_optional and | |
| not (ignore_named_parameters) else ''), | |
| errorCallbackVariables, errorName)) | |
| extensions = GetDDC_Extension(self._interface, info.declared_name) | |
| if extensions: | |
| ddc_extensions = "\n".join(extensions) | |
| else: | |
| ddc_extensions = '' | |
| # Some callbacks have more than one parameters. If so use all of | |
| # those parameters. However, if more than one argument use the | |
| # type of the last argument to be returned e.g., executeSql the callback | |
| # is (transaction, resultSet) and only the resultSet is returned SqlResultSet. | |
| callbackArgsLen = len(callback_info.param_infos) | |
| future_generic = '' | |
| callbackVariables = '' | |
| completerVariable = '' | |
| if callbackArgsLen == 1: | |
| callbackVariables = 'value' | |
| completerVariable = callbackVariables | |
| if callback_info.param_infos[0].type_id: | |
| future_generic = '<%s>' % self._DartType( | |
| callback_info.param_infos[0].type_id) | |
| elif callbackArgsLen > 1: | |
| callbackNames = [] | |
| for paramInfo in callback_info.param_infos: | |
| callbackNames.append(paramInfo.name) | |
| callbackVariables = ",".join(callbackNames) | |
| completerVariable = callbackNames[-1] | |
| future_generic = '<%s>' % self._DartType( | |
| callback_info.param_infos[-1].type_id) | |
| param_list = info.ParametersAsArgumentList(None, | |
| ignore_named_parameters) | |
| dictionary_argument = info.dictionaryArgumentName() | |
| convert_map = '' | |
| if dictionary_argument is not None: | |
| mapArg = dictionary_argument[0] | |
| tempVariable = '%s_dict' % mapArg | |
| mapArgOptional = dictionary_argument[1] | |
| if not (extensions): | |
| if not (param_list.endswith(', mapArg') or | |
| param_list.endswith(', options') or | |
| param_list == mapArg): | |
| print( | |
| "ERROR: %s.%s - Last parameter or only parameter %s is not of type Map" | |
| % (self._interface.id, html_name, mapArg)) | |
| param_list = '%s_dict' % param_list | |
| if mapArgOptional: | |
| convert_map = ' var %s = null;\n'\ | |
| ' if (%s != null) {\n'\ | |
| ' %s = convertDartToNative_Dictionary(%s);\n'\ | |
| ' }\n' % (tempVariable, mapArg, tempVariable, mapArg) | |
| else: | |
| convert_map = ' var %s = convertDartToNative_Dictionary(%s);\n' % ( | |
| tempVariable, mapArg) | |
| metadata = '' | |
| if '_RenamingAnnotation' in dir(self): | |
| metadata = ( | |
| self._RenamingAnnotation(info.declared_name, html_name) + | |
| self._Metadata(info.type_name, info.declared_name, None, | |
| info.type_nullable)) | |
| self._members_emitter.Emit( | |
| '\n' | |
| ' $METADATA$MODIFIERS$TYPE$FUTURE_GENERIC $NAME($PARAMS) {\n' | |
| ' $CONVERT_DICTIONARY' | |
| ' var completer = new Completer$(FUTURE_GENERIC)();\n' | |
| ' $ORIGINAL_FUNCTION($PARAMS_LIST\n' | |
| ' $NAMED_PARAM($VARIABLE_NAME) { ' | |
| '$DDC_EXTENSION\n' | |
| 'completer.complete($COMPLETER_NAME); }' | |
| '$ERROR_CALLBACK);\n' | |
| ' return completer.future;\n' | |
| ' }\n', | |
| METADATA=metadata, | |
| MODIFIERS='static ' if info.IsStatic() else '', | |
| TYPE=self.SecureOutputType(info.type_name, | |
| nullable=info.type_nullable), | |
| NAME=html_name[1:], | |
| PARAMS=info. | |
| ParametersAsDeclaration(self._NarrowInputType if '_NarrowInputType' | |
| in dir(self) else self._DartType), | |
| CONVERT_DICTIONARY=convert_map, | |
| PARAMS_LIST='' if param_list == '' else param_list + ',', | |
| NAMED_PARAM=('%s : ' % info.callback_args[0].name | |
| if info.requires_named_arguments and | |
| info.callback_args[0].is_optional and | |
| not (ignore_named_parameters) else ''), | |
| VARIABLE_NAME=callbackVariables, | |
| COMPLETER_NAME=completerVariable, | |
| DDC_EXTENSION=ddc_extensions, | |
| ERROR_CALLBACK=error_callback, | |
| FUTURE_GENERIC=future_generic, | |
| ORIGINAL_FUNCTION=html_name) | |
| def EmitHelpers(self, base_class, members_emitter): | |
| if (not self._members_emitter) and (not members_emitter): | |
| return | |
| if self._interface.id not in custom_html_constructors: | |
| target_emitter = members_emitter if members_emitter else \ | |
| self._members_emitter | |
| target_emitter.Emit( | |
| ' // To suppress missing implicit constructor warnings.\n' | |
| ' factory $CLASSNAME._() { ' | |
| 'throw new UnsupportedError("Not supported"); }\n', | |
| CLASSNAME=self._interface_type_info.implementation_name()) | |
| def DeclareAttribute(self, attribute, attr_name, read_only): | |
| """ Declares an attribute but does not include the code to invoke it. | |
| """ | |
| if read_only: | |
| # HACK(terry): Element is not abstract for Dartium so isContentEditable | |
| # must have a body see impl_Element.darttemplate | |
| if (self._interface.id == 'Element' and | |
| attr_name == 'isContentEditable' and self._dart_js_interop): | |
| return | |
| else: | |
| template = '\n $TYPE get $NAME;\n' | |
| else: | |
| template = '\n $TYPE get $NAME native;\n' \ | |
| '\n set $NAME($TYPE value) native;\n' | |
| # Nullability is determined by attribute compatibility. | |
| is_compat = self._mdn_reader.is_compatible(attribute) | |
| nullable = attribute.type.nullable or not is_compat | |
| self._members_emitter.Emit(template, | |
| NAME=attr_name, | |
| TYPE=self.SecureOutputType( | |
| attribute.type.id, nullable=nullable)) | |
| 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. | |
| """ | |
| # HACK(terry): Element is not abstract for Dartium so click | |
| # must have a body see impl_Element.darttemplate | |
| if (self._interface.id == 'Element' and method_name == 'click' and | |
| self._dart_js_interop): | |
| return | |
| else: | |
| template = '\n $TYPE $NAME($PARAMS);\n' | |
| self._members_emitter.Emit( | |
| template, | |
| TYPE=return_type_name, | |
| NAME=method_name, | |
| PARAMS=operation.ParametersAsDeclaration(self._DartType)) | |
| def EmitListMixin(self, element_name, nullable): | |
| # TODO(sra): Use separate mixins for mutable implementations of List<T>. | |
| # TODO(sra): Use separate mixins for typed array implementations of List<T>. | |
| template_file = 'immutable_list_mixin.darttemplate' | |
| has_length = False | |
| has_length_setter = False | |
| def _HasExplicitIndexedGetter(self): | |
| return any(op.id == 'getItem' for op in self._interface.operations) | |
| def _HasCustomIndexedGetter(self): | |
| return 'CustomIndexedGetter' in self._interface.ext_attrs | |
| def _HasNativeIndexedGetter(self): | |
| return not (_HasCustomIndexedGetter(self) or | |
| _HasExplicitIndexedGetter(self)) | |
| if _HasExplicitIndexedGetter(self): | |
| getter_name = 'getItem' | |
| else: | |
| getter_name = '_nativeIndexedGetter' | |
| 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_LENGTH_AS_NUM_ITEMS': | |
| not has_length and has_num_items, | |
| 'DEFINE_LENGTH_SETTER': | |
| not has_length_setter, | |
| 'USE_NATIVE_INDEXED_GETTER': | |
| _HasNativeIndexedGetter(self) or | |
| _HasExplicitIndexedGetter(self), | |
| }) | |
| if nullable: | |
| element_js = element_name + "|Null" | |
| element_name += '?' | |
| else: | |
| element_js = element_name | |
| self._members_emitter.Emit( | |
| template, E=element_name, EJS=element_js, GETTER=getter_name) | |
| def SecureOutputType(self, | |
| type_name, | |
| is_dart_type=False, | |
| can_narrow_type=False, | |
| nullable=False): | |
| """ Converts the type name to the secure type name for return types. | |
| Arguments: | |
| can_narrow_type - True if the output type can be narrowed further than | |
| what would be accepted for input, used to narrow num APIs down to double | |
| or int. | |
| """ | |
| if is_dart_type: | |
| dart_name = type_name | |
| else: | |
| type_info = self._TypeInfo(type_name) | |
| dart_name = type_info.dart_type() | |
| if can_narrow_type and dart_name == 'num': | |
| dart_name = type_info.native_type() | |
| # 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': | |
| dart_name = _secure_base_types[dart_name] | |
| if type_name == 'any': | |
| dart_name = 'Object' | |
| if nullable and dart_name != 'dynamic': | |
| dart_name = dart_name + '?' | |
| return dart_name | |
| def SecureBaseName(self, type_name): | |
| if type_name in _secure_base_types: | |
| return _secure_base_types[type_name] | |
| def is_DOM_type(self, type_name): | |
| try: | |
| self._type_registry.TypeInfo(type_name) | |
| return True | |
| except RuntimeError: | |
| return False | |
| def _NarrowToImplementationType(self, type_name): | |
| return self._type_registry.TypeInfo(type_name).narrow_dart_type() | |
| def _NarrowInputType(self, type_name): | |
| return self._NarrowToImplementationType(type_name) | |
| def _DartType(self, type_name): | |
| return self._type_registry.DartType(type_name) | |
| def _TypeInfo(self, type_name): | |
| return self._type_registry.TypeInfo(type_name) | |
| def _CallbackConvert(self, argType, info): | |
| if self._database.HasInterface(argType): | |
| interface = self._database.GetInterface(argType) | |
| if "Callback" in interface.ext_attrs: | |
| return interface.ext_attrs['Callback'] | |
| return None | |
| def _ConvertArgumentTypes(self, stmts_emitter, arguments, argument_count, | |
| info): | |
| temp_version = [0] | |
| converted_arguments = [] | |
| target_parameters = [] | |
| calling_parameters = [] | |
| for position, arg in enumerate(arguments[:argument_count]): | |
| callBackInfo = self._CallbackConvert( | |
| arg.type.id, info) # Returns callback arity (# of parameters) | |
| if callBackInfo is None: | |
| conversion = self._InputConversion(arg.type.id, | |
| info.declared_name) | |
| else: | |
| conversion = self._InputConversion('Callback', | |
| info.declared_name) | |
| param_name = arguments[position].id | |
| if conversion: | |
| temp_version[0] += 1 | |
| temp_name = '%s_%s' % (param_name, temp_version[0]) | |
| temp_type = conversion.output_type | |
| null_assert_needed = info.param_infos[position].is_nullable \ | |
| and not conversion.nullable_input | |
| stmts_emitter.Emit( | |
| '$(INDENT)$TYPE $NAME = $CONVERT($ARG$NULLASSERT);\n' | |
| if callBackInfo is None else | |
| '$(INDENT)$TYPE $NAME = $CONVERT($ARG$NULLASSERT, $ARITY);\n', | |
| TYPE=TypeOrVar(temp_type), | |
| NAME=temp_name, | |
| CONVERT=conversion.function_name, | |
| ARG=info.param_infos[position].name, | |
| NULLASSERT='!' if null_assert_needed else '', | |
| ARITY=callBackInfo) | |
| converted_arguments.append(temp_name) | |
| param_type = temp_type | |
| verified_type = temp_type # verified by assignment in checked mode. | |
| else: | |
| converted_arguments.append(info.param_infos[position].name) | |
| if self._database.HasTypeDef(arg.type.id): | |
| param_type = 'dynamic' | |
| else: | |
| param_type = self._NarrowInputType(arg.type.id) | |
| # Verified by argument checking on entry to the dispatcher. | |
| verified_type = self._InputType( | |
| info.param_infos[position].type_id, info) | |
| # The native method does not need an argument type if we know the type. | |
| # But we do need the native methods to have correct function types, so | |
| # be conservative. | |
| if param_type == verified_type: | |
| if param_type in [ | |
| 'String', 'num', 'int', 'double', 'bool', | |
| 'Object' | |
| ]: | |
| param_type = 'dynamic' | |
| arg_is_nullable = arg.type.nullable | |
| # If the parameter is either nullable or optional with no non-null | |
| # default value, it is nullable. | |
| if (info.param_infos[position].is_optional and | |
| (info.param_infos[position].default_value_is_null == True or | |
| info.param_infos[position].default_value == None) | |
| ) or info.param_infos[position].is_nullable: | |
| arg_is_nullable = True | |
| target_parameters.append( | |
| '%s%s' % (TypeOrNothing(param_type, nullable=arg_is_nullable), | |
| param_name)) | |
| calling_parameters.append(',%s ' % param_name) | |
| return target_parameters, converted_arguments, calling_parameters | |
| def _InputType(self, type_name, info): | |
| conversion = self._InputConversion(type_name, info.declared_name) | |
| if conversion: | |
| return conversion.input_type | |
| else: | |
| # If typedef it's a union return dynamic. | |
| if self._database.HasTypeDef(type_name): | |
| return 'dynamic' | |
| else: | |
| return self._NarrowInputType( | |
| type_name) if type_name else 'dynamic' |