#!/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 import os from generator import * _js_custom_members = set([ 'AudioBufferSourceNode.start', 'AudioBufferSourceNode.stop', 'CSSStyleDeclaration.setProperty', 'Element.insertAdjacentElement', 'Element.insertAdjacentHTML', 'Element.insertAdjacentText', 'Element.remove', 'ElementEvents.mouseWheel', 'IDBDatabase.transaction', 'MouseEvent.offsetX', 'MouseEvent.offsetY', 'SelectElement.options', 'SelectElement.selectedOptions', 'TableElement.createTBody', 'LocalWindow.cancelAnimationFrame', 'LocalWindow.document', 'LocalWindow.indexedDB', 'LocalWindow.location', 'LocalWindow.open', 'LocalWindow.requestAnimationFrame', 'LocalWindow.webkitCancelAnimationFrame', 'LocalWindow.webkitRequestAnimationFrame', 'WheelEvent.wheelDeltaX', 'WheelEvent.wheelDeltaY', ]) # Types that are accessible cross-frame in a limited fashion. # In these cases, the base type (e.g., Window) provides restricted access # while the subtype (e.g., LocalWindow) provides full access to the # corresponding objects if there are from the same frame. _secure_base_types = { 'LocalWindow': 'Window', 'LocalLocation': 'Location', 'LocalHistory': 'History', } def SecureOutputType(generator, type_name, is_dart_type=False): if is_dart_type: dart_name = type_name else: dart_name = generator._DartType(type_name) # We only need to secure Window. Only local History and Location are returned # in generated code. if dart_name == 'LocalWindow': return _secure_base_types[dart_name] return dart_name # Information for generating element constructors. # # TODO(sra): maybe remove all the argument complexity and use cascades. # # var c = new CanvasElement(width: 100, height: 70); # var c = new CanvasElement()..width = 100..height = 70; # class ElementConstructorInfo(object): def __init__(self, name=None, tag=None, params=[], opt_params=[], factory_provider_name='_Elements'): self.name = name # The constructor name 'h1' in 'HeadingElement.h1' self.tag = tag or name # The HTML tag self.params = params self.opt_params = opt_params self.factory_provider_name = factory_provider_name def ConstructorInfo(self, interface_name): info = OperationInfo() info.overloads = None info.declared_name = interface_name info.name = interface_name info.constructor_name = self.name info.js_name = None info.type_name = interface_name info.param_infos = map(lambda tXn: ParamInfo(tXn[1], tXn[0], True), self.opt_params) info.requires_named_arguments = True return info _html_element_constructors = { 'AnchorElement' : ElementConstructorInfo(tag='a', opt_params=[('DOMString', 'href')]), 'AreaElement': 'area', 'ButtonElement': 'button', 'BRElement': 'br', 'BaseElement': 'base', 'BodyElement': 'body', 'ButtonElement': 'button', 'CanvasElement': ElementConstructorInfo(tag='canvas', opt_params=[('int', 'width'), ('int', 'height')]), 'ContentElement': 'content', 'DataListElement': 'datalist', 'DListElement': 'dl', 'DetailsElement': 'details', 'DivElement': 'div', 'EmbedElement': 'embed', 'FieldSetElement': 'fieldset', 'FormElement': 'form', 'HRElement': 'hr', 'HeadElement': 'head', 'HeadingElement': [ElementConstructorInfo('h1'), ElementConstructorInfo('h2'), ElementConstructorInfo('h3'), ElementConstructorInfo('h4'), ElementConstructorInfo('h5'), ElementConstructorInfo('h6')], 'HtmlElement': 'html', 'IFrameElement': 'iframe', 'ImageElement': ElementConstructorInfo(tag='img', opt_params=[('DOMString', 'src'), ('int', 'width'), ('int', 'height')]), 'InputElement': ElementConstructorInfo(tag='input', opt_params=[('DOMString', 'type')]), 'KeygenElement': 'keygen', 'LIElement': 'li', 'LabelElement': 'label', 'LegendElement': 'legend', 'LinkElement': 'link', 'MapElement': 'map', 'MenuElement': 'menu', 'MeterElement': 'meter', 'OListElement': 'ol', 'ObjectElement': 'object', 'OptGroupElement': 'optgroup', 'OutputElement': 'output', 'ParagraphElement': 'p', 'ParamElement': 'param', 'PreElement': 'pre', 'ProgressElement': 'progress', 'ScriptElement': 'script', 'SelectElement': 'select', 'SourceElement': 'source', 'SpanElement': 'span', 'StyleElement': 'style', 'TableCaptionElement': 'caption', 'TableCellElement': 'td', 'TableColElement': 'col', 'TableElement': 'table', 'TableRowElement': 'tr', #'TableSectionElement' 'TextAreaElement': 'textarea', 'TitleElement': 'title', 'TrackElement': 'track', 'UListElement': 'ul', 'VideoElement': 'video' } def HtmlElementConstructorInfos(typename): """Returns list of ElementConstructorInfos about the convenience constructors for an Element.""" # TODO(sra): Handle multiple and named constructors. if typename not in _html_element_constructors: return [] infos = _html_element_constructors[typename] if isinstance(infos, str): infos = ElementConstructorInfo(tag=infos) if not isinstance(infos, list): infos = [infos] return infos def EmitHtmlElementFactoryConstructors(emitter, infos, typename, class_name, rename_type): for info in infos: constructor_info = info.ConstructorInfo(typename) inits = emitter.Emit( '\n' ' static $RETURN_TYPE $CONSTRUCTOR($PARAMS) {\n' ' $CLASS _e = _document.$dom_createElement("$TAG");\n' '$!INITS' ' return _e;\n' ' }\n', RETURN_TYPE=rename_type(constructor_info.type_name), CONSTRUCTOR=constructor_info.ConstructorFactoryName(rename_type), CLASS=class_name, TAG=info.tag, PARAMS=constructor_info.ParametersDeclaration( rename_type, force_optional=True)) for param in constructor_info.param_infos: inits.Emit(' if ($E != null) _e.$E = $E;\n', E=param.name) # ------------------------------------------------------------------------------ class HtmlDartInterfaceGenerator(object): """Generates dart interface and implementation for the DOM IDL interface.""" def __init__(self, options, library_emitter, event_generator, interface, backend): self._renamer = options.renamer self._database = options.database self._template_loader = options.templates self._type_registry = options.type_registry self._library_emitter = library_emitter self._event_generator = event_generator self._interface = interface self._backend = backend self._interface_type_info = self._type_registry.TypeInfo(self._interface.id) def Generate(self): if 'Callback' in self._interface.ext_attrs: self.GenerateCallback() else: self.GenerateInterface() def GenerateCallback(self): """Generates a typedef for the callback interface.""" handlers = [operation for operation in self._interface.operations if operation.id == 'handleEvent'] info = AnalyzeOperation(self._interface, handlers) code = self._library_emitter.FileEmitter(self._interface.id) code.Emit(self._template_loader.Load('callback.darttemplate')) code.Emit('typedef void $NAME($PARAMS);\n', NAME=self._interface.id, PARAMS=info.ParametersDeclaration(self._DartType)) self._backend.GenerateCallback(info) def GenerateInterface(self): interface_name = self._interface_type_info.interface_name() if (self._interface_type_info.has_generated_interface() and not self._interface_type_info.merged_into()): interface_emitter = self._library_emitter.FileEmitter(interface_name) else: interface_emitter = emitter.Emitter() template_file = 'interface_%s.darttemplate' % interface_name interface_template = (self._template_loader.TryLoad(template_file) or self._template_loader.Load('interface.darttemplate')) implements = [] for parent in self._interface.parents: parent_type_info = self._type_registry.TypeInfo(parent.type.id) implements.append(parent_type_info.interface_name()) 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()) if interface_name in _secure_base_types: implements.append(_secure_base_types[interface_name]) comment = ' extends' implements_str = '' if implements: implements_str += ' implements ' + ', '.join(implements) comment = ',' factory_provider = None if interface_name in interface_factories: factory_provider = interface_factories[interface_name] constructors = [] constructor_info = AnalyzeConstructor(self._interface) if constructor_info: constructors.append(constructor_info) factory_provider = '_' + interface_name + 'FactoryProvider' factory_provider_emitter = self._library_emitter.FileEmitter( '_%sFactoryProvider' % interface_name) self._backend.EmitFactoryProvider( constructor_info, factory_provider, factory_provider_emitter) infos = HtmlElementConstructorInfos(interface_name) if infos: template = self._template_loader.Load( 'factoryprovider_Elements.darttemplate') EmitHtmlElementFactoryConstructors( self._library_emitter.FileEmitter('_Elements', template), infos, self._interface.id, self._interface_type_info.implementation_name(), self._DartType) for info in infos: constructors.append(info.ConstructorInfo(self._interface.id)) if factory_provider: assert factory_provider == info.factory_provider_name else: factory_provider = info.factory_provider_name # TODO(vsm): Add appropriate package / namespace syntax. (self._type_comment_emitter, self._members_emitter, self._top_level_emitter) = interface_emitter.Emit( interface_template + '$!TOP_LEVEL', ID=interface_name, EXTENDS=implements_str) self._type_comment_emitter.Emit("/// @domName $DOMNAME", DOMNAME=self._interface.doc_js_name) implementation_emitter = self._ImplementationEmitter() base_class = self._backend.RootClassName() if self._interface.parents: supertype = self._interface.parents[0].type.id if not IsDartCollectionType(supertype) and not IsPureInterface(supertype): type_info = self._type_registry.TypeInfo(supertype) if type_info.merged_into() and self._backend.ImplementsMergedMembers(): type_info = self._type_registry.TypeInfo(type_info.merged_into()) base_class = type_info.implementation_name() implemented_interfaces = [interface_name] +\ self._backend.AdditionalImplementedInterfaces() self._implementation_members_emitter = implementation_emitter.Emit( self._backend.ImplementationTemplate(), CLASSNAME=self._interface_type_info.implementation_name(), EXTENDS=' extends %s' % base_class if base_class else '', IMPLEMENTS=' implements ' + ', '.join(implemented_interfaces), NATIVESPEC=self._backend.NativeSpec()) self._backend.StartInterface(self._implementation_members_emitter) for constructor_info in constructors: constructor_info.GenerateFactoryInvocation( self._DartType, self._members_emitter, factory_provider) element_type = None for interface in self._database.Hierarchy(self._interface): type_info = self._type_registry.TypeInfo(interface.id) if type_info.is_typed_array(): element_type = type_info.list_item_type() break if element_type: self._members_emitter.Emit( '\n' ' factory $CTOR(int length) =>\n' ' $FACTORY.create$(CTOR)(length);\n' '\n' ' factory $CTOR.fromList(List<$TYPE> list) =>\n' ' $FACTORY.create$(CTOR)_fromList(list);\n' '\n' ' factory $CTOR.fromBuffer(ArrayBuffer buffer, [int byteOffset, int length]) => \n' ' $FACTORY.create$(CTOR)_fromBuffer(buffer, byteOffset, length);\n', CTOR=self._interface.id, TYPE=self._DartType(element_type), FACTORY=factory_provider) events_interface = self._event_generator.ProcessInterface( self._interface, interface_name, self._backend.CustomJSMembers(), interface_emitter, implementation_emitter) if events_interface: self._EmitEventGetter(events_interface, '_%sImpl' % events_interface) old_backend = self._backend if not self._backend.ImplementsMergedMembers(): self._backend = HtmlGeneratorDummyBackend() merged_interface = self._interface_type_info.merged_interface() if merged_interface: self.AddMembers(self._database.GetInterface(merged_interface)) self._backend = old_backend self.AddMembers(self._interface) self.AddSecondaryMembers(self._interface) self._backend.FinishInterface() def AddMembers(self, interface): 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) # 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) 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 secondary_parents: if isinstance(parent_interface, str): # IsDartCollectionType(parent_interface) continue for attr in sorted(parent_interface.attributes, ConstantOutputOrder): if not FindMatchingAttribute(interface, attr): self.AddSecondaryAttribute(parent_interface, 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.AddSecondaryOperation(parent_interface, info) def AddIndexer(self, element_type): self._backend.AddIndexer(element_type) def AmendIndexer(self, element_type): self._backend.AmendIndexer(element_type) def AddAttribute(self, attribute, is_secondary=False): dom_name = DartDomNameOfAttribute(attribute) html_name = self._renamer.RenameMember( self._interface.id, attribute, dom_name, 'get:') if not html_name or self._IsPrivate(html_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 html_name == html_setter_name) if not is_secondary: self._members_emitter.Emit('\n /** @domName $DOMINTERFACE.$DOMNAME */', DOMINTERFACE=attribute.doc_js_interface_name, DOMNAME=dom_name) if read_only: template = '\n $TYPE get $NAME;\n' else: template = '\n $TYPE $NAME;\n' self._members_emitter.Emit(template, NAME=html_name, TYPE=SecureOutputType(self, attribute.type.id)) self._backend.AddAttribute(attribute, html_name, read_only) def AddSecondaryAttribute(self, interface, attribute): self._backend.SecondaryContext(interface) self.AddAttribute(attribute, True) def AddOperation(self, info, skip_declaration=False): """ Arguments: operations - contains the overloads, one or more operations with the same name. """ # 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. html_name = self._renamer.RenameMember(self._interface.id, info.operations[0], info.name) if not html_name: if info.name == 'item': # FIXME: item should be renamed to operator[], not removed. self._backend.AddOperation(info, '_item') return if not self._IsPrivate(html_name) and not skip_declaration: self._members_emitter.Emit('\n /** @domName $DOMINTERFACE.$DOMNAME */', DOMINTERFACE=info.overloads[0].doc_js_interface_name, DOMNAME=info.name) if info.IsStatic(): # FIXME: provide a type. self._members_emitter.Emit( '\n' ' static final $NAME = $IMPL_CLASS_NAME.$NAME;\n', IMPL_CLASS_NAME=self._interface_type_info.implementation_name(), NAME=html_name) else: self._members_emitter.Emit( '\n' ' $TYPE $NAME($PARAMS);\n', TYPE=SecureOutputType(self, info.type_name), NAME=html_name, PARAMS=info.ParametersDeclaration(self._DartType)) self._backend.AddOperation(info, html_name) def AddSecondaryOperation(self, interface, info): self._backend.SecondaryContext(interface) self.AddOperation(info, True) def AddConstant(self, constant): type = TypeOrNothing(self._DartType(constant.type.id), constant.type.id) self._members_emitter.Emit('\n static const $TYPE$NAME = $VALUE;\n', NAME=constant.id, TYPE=type, VALUE=constant.value) def _ImplementationEmitter(self): if IsPureInterface(self._interface.id): return emitter.Emitter() basename = self._interface_type_info.implementation_name() if (self._interface_type_info.merged_into() and self._backend.ImplementsMergedMembers()): # Merged members are implemented in target interface implementation. return emitter.Emitter() return self._library_emitter.FileEmitter(basename.lstrip('_')) def _EmitEventGetter(self, events_interface, events_class): self._members_emitter.Emit( '\n /**' '\n * @domName EventTarget.addEventListener, ' 'EventTarget.removeEventListener, EventTarget.dispatchEvent' '\n */' '\n $TYPE get on;\n', TYPE=events_interface) self._implementation_members_emitter.Emit( '\n $TYPE get on =>\n new $TYPE(this);\n', TYPE=events_class) 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: if IsDartCollectionType(parent.type.id): result.append(parent.type.id) continue if self._database.HasInterface(parent.type.id): parent_interface = self._database.GetInterface(parent.type.id) 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('_') class HtmlGeneratorDummyBackend(object): def AddAttribute(self, attribute, html_name, read_only): pass def AddOperation(self, info, html_name): pass # ------------------------------------------------------------------------------ class Dart2JSBackend(object): """Generates a dart2js class for the dart:html library from a DOM IDL interface. """ def __init__(self, interface, options): self._interface = interface self._database = options.database self._template_loader = options.templates self._type_registry = options.type_registry self._interface_type_info = self._type_registry.TypeInfo(self._interface.id) self._current_secondary_parent = None def ImplementsMergedMembers(self): return True def GenerateCallback(self, info): pass def RootClassName(self): return None 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(): implements.append('JavaScriptIndexingBehavior') return implements def NativeSpec(self): native_spec = MakeNativeSpec(self._interface.javascript_binding_name) return ' native "%s"' % native_spec def ImplementationTemplate(self): template_file = ('impl_%s.darttemplate' % self._interface_type_info.interface_name()) return (self._template_loader.TryLoad(template_file) or self._template_loader.Load('dart2js_impl.darttemplate')) def StartInterface(self, emitter): self._members_emitter = emitter def FinishInterface(self): pass def EmitFactoryProvider(self, constructor_info, factory_provider, emitter): template_file = ('factoryprovider_%s.darttemplate' % self._interface_type_info.interface_name()) template = self._template_loader.TryLoad(template_file) if not template: template = self._template_loader.Load('factoryprovider.darttemplate') interface_name = self._interface_type_info.interface_name() arguments = constructor_info.ParametersAsArgumentList() comma = ',' if arguments else '' emitter.Emit( template, FACTORYPROVIDER=factory_provider, CONSTRUCTOR=interface_name, PARAMETERS=constructor_info.ParametersDeclaration(self._DartType), NAMED_CONSTRUCTOR=constructor_info.name or interface_name, ARGUMENTS=arguments, PRE_ARGUMENTS_COMMA=comma, ARGUMENTS_PATTERN=','.join(['#'] * len(constructor_info.param_infos))) def SecondaryContext(self, interface): if interface is not self._current_secondary_parent: self._current_secondary_parent = interface self._members_emitter.Emit('\n // From $WHERE\n', WHERE=interface.id) def AddIndexer(self, element_type): """Adds all the methods required to complete implementation of List.""" # We would like to simply inherit the implementation of everything except # length, [], and maybe []=. It is possible to extend from a base # array implementation class only when there is no other implementation # inheritance. There might be no implementation inheritance other than # DOMBaseWrapper for many classes, but there might be some where the # array-ness is introduced by a non-root interface: # # interface Y extends X, List ... # # In the non-root case we have to choose between: # # class YImpl extends XImpl { add List methods; } # # and # # class YImpl extends ListBase { copies of transitive XImpl methods; } # self._members_emitter.Emit( '\n' ' $TYPE operator[](int index) => JS("$TYPE", "#[#]", this, index);\n', TYPE=self._NarrowOutputType(element_type)) if 'CustomIndexedSetter' in self._interface.ext_attrs: self._members_emitter.Emit( '\n' ' void operator[]=(int index, $TYPE value) =>' ' JS("void", "#[#] = #", this, index, value);\n', TYPE=self._NarrowInputType(element_type)) else: # The HTML library implementation of NodeList has a custom indexed setter # implementation that uses the parent node the NodeList is associated # with if one is available. if self._interface.id != 'NodeList': self._members_emitter.Emit( '\n' ' void operator[]=(int index, $TYPE value) {\n' ' throw new UnsupportedError("Cannot assign element of immutable List.");\n' ' }\n', TYPE=self._NarrowInputType(element_type)) # TODO(sra): Use separate mixins for mutable implementations of List. # TODO(sra): Use separate mixins for typed array implementations of List. if self._interface.id != 'NodeList': template_file = 'immutable_list_mixin.darttemplate' has_contains = any(op.id == 'contains' for op in self._interface.operations) template = self._template_loader.Load( template_file, {'DEFINE_CONTAINS': not has_contains}) self._members_emitter.Emit(template, E=self._DartType(element_type)) def AddAttribute(self, attribute, html_name, read_only): if self._HasCustomImplementation(attribute.id): return if attribute.id != html_name: self._AddAttributeUsingProperties(attribute, html_name, read_only) return # If the attribute is shadowing, we can't generate a shadowing # field (Issue 1633). # TODO(sra): _FindShadowedAttribute does not take into account the html # renaming. we should be looking for another attribute that has the same # html_name. Two attributes with the same IDL name might not match if one # is renamed. (super_attribute, super_attribute_interface) = self._FindShadowedAttribute( attribute) if super_attribute: if read_only: if attribute.type.id == super_attribute.type.id: # Compatible attribute, use the superclass property. This works # because JavaScript will do its own dynamic dispatch. self._members_emitter.Emit( '\n' ' // Use implementation from $SUPER.\n' ' // final $TYPE $NAME;\n', SUPER=super_attribute_interface, NAME=DartDomNameOfAttribute(attribute), TYPE=self._NarrowOutputType(attribute.type.id)) return self._members_emitter.Emit('\n // Shadowing definition.') self._AddAttributeUsingProperties(attribute, html_name, read_only) return # If the type has a conversion we need a getter or setter to contain the # conversion code. if (self._OutputConversion(attribute.type.id, attribute.id) or self._InputConversion(attribute.type.id, attribute.id)): self._AddAttributeUsingProperties(attribute, html_name, read_only) return output_type = self._NarrowOutputType(attribute.type.id) input_type = self._NarrowInputType(attribute.type.id) if not read_only: self._members_emitter.Emit( '\n $TYPE $NAME;' '\n', NAME=DartDomNameOfAttribute(attribute), TYPE=output_type) else: self._members_emitter.Emit( '\n final $TYPE $NAME;' '\n', NAME=DartDomNameOfAttribute(attribute), TYPE=output_type) def _AddAttributeUsingProperties(self, attribute, html_name, read_only): self._AddRenamingGetter(attribute, html_name) if not read_only: self._AddRenamingSetter(attribute, html_name) def _AddRenamingGetter(self, attr, html_name): conversion = self._OutputConversion(attr.type.id, attr.id) if conversion: return self._AddConvertingGetter(attr, html_name, conversion) return_type = self._NarrowOutputType(attr.type.id) self._members_emitter.Emit( # TODO(sra): Use metadata to provide native name. '\n $TYPE get $HTML_NAME => JS("$TYPE", "#.$NAME", this);' '\n', HTML_NAME=html_name, NAME=attr.id, TYPE=return_type) def _AddRenamingSetter(self, attr, html_name): conversion = self._InputConversion(attr.type.id, attr.id) if conversion: return self._AddConvertingSetter(attr, html_name, conversion) self._members_emitter.Emit( # TODO(sra): Use metadata to provide native name. '\n void set $HTML_NAME($TYPE value) {' '\n JS("void", "#.$NAME = #", this, value);' '\n }' '\n', HTML_NAME=html_name, NAME=attr.id, TYPE=self._NarrowInputType(attr.type.id)) def _AddConvertingGetter(self, attr, html_name, conversion): self._members_emitter.Emit( # TODO(sra): Use metadata to provide native name. '\n $RETURN_TYPE get $HTML_NAME => $CONVERT(this._$(HTML_NAME));' '\n $NATIVE_TYPE get _$HTML_NAME =>' ' JS("$NATIVE_TYPE", "#.$NAME", this);' '\n', CONVERT=conversion.function_name, HTML_NAME=html_name, NAME=attr.id, RETURN_TYPE=conversion.output_type, NATIVE_TYPE=conversion.input_type) def _AddConvertingSetter(self, attr, html_name, conversion): self._members_emitter.Emit( # TODO(sra): Use metadata to provide native name. '\n void set $HTML_NAME($INPUT_TYPE value) {' '\n this._$HTML_NAME = $CONVERT(value);' '\n }' '\n void set _$HTML_NAME(/*$NATIVE_TYPE*/ value) {' '\n JS("void", "#.$NAME = #", this, value);' '\n }' '\n', CONVERT=conversion.function_name, HTML_NAME=html_name, NAME=attr.id, INPUT_TYPE=conversion.input_type, NATIVE_TYPE=conversion.output_type) def AmendIndexer(self, element_type): pass def AddOperation(self, info, html_name): """ Arguments: info: An OperationInfo object. """ if self._HasCustomImplementation(info.name): return # Any conversions needed? if any(self._OperationRequiresConversions(op) for op in info.overloads): self._AddOperationWithConversions(info, html_name) else: self._AddDirectNativeOperation(info, html_name) def _AddDirectNativeOperation(self, info, html_name): # Do we need a native body? if html_name != info.declared_name: return_type = self._NarrowOutputType(info.type_name) operation_emitter = self._members_emitter.Emit('$!SCOPE', MODIFIERS='static ' if info.IsStatic() else '', TYPE=return_type, HTML_NAME=html_name, NAME=info.declared_name, PARAMS=info.ParametersDeclaration(self._NarrowInputType)) operation_emitter.Emit( '\n' #' // @native("$NAME")\n;' ' $MODIFIERS$TYPE $(HTML_NAME)($PARAMS) native "$NAME";\n') else: self._members_emitter.Emit( '\n' ' $MODIFIERS$TYPE $NAME($PARAMS) native;\n', MODIFIERS='static ' if info.IsStatic() else '', TYPE=self._NarrowOutputType(info.type_name), NAME=info.name, PARAMS=info.ParametersDeclaration(self._NarrowInputType)) def _AddOperationWithConversions(self, info, html_name): # Assert all operations have same return type. assert len(set([op.type.id for op in info.operations])) == 1 info = info.CopyAndWidenDefaultParameters() output_conversion = self._OutputConversion(info.type_name, info.declared_name) if output_conversion: return_type = output_conversion.output_type native_return_type = output_conversion.input_type else: return_type = self._NarrowInputType(info.type_name) native_return_type = return_type def InputType(type_name): conversion = self._InputConversion(type_name, info.declared_name) if conversion: return conversion.input_type else: return self._NarrowInputType(type_name) if type_name else 'dynamic' body = self._members_emitter.Emit( '\n' ' $MODIFIERS$TYPE $(HTML_NAME)($PARAMS) {\n' '$!BODY' ' }\n', MODIFIERS='static ' if info.IsStatic() else '', TYPE=return_type, HTML_NAME=html_name, PARAMS=info.ParametersDeclaration(InputType)) parameter_names = [param_info.name for param_info in info.param_infos] parameter_types = [InputType(param_info.type_id) for param_info in info.param_infos] operations = info.operations method_version = [0] temp_version = [0] def GenerateCall(operation, argument_count, checks): checks = filter(lambda e: e != 'true', checks) if checks: (stmts_emitter, call_emitter) = body.Emit( ' if ($CHECKS) {\n$!STMTS$!CALL }\n', INDENT=' ', CHECKS=' &&\n '.join(checks)) else: (stmts_emitter, call_emitter) = body.Emit('$!A$!B', INDENT=' '); method_version[0] += 1 target = '_%s_%d' % (html_name, method_version[0]); arguments = [] target_parameters = [] for position, arg in enumerate(operation.arguments[:argument_count]): conversion = self._InputConversion(arg.type.id, operation.id) param_name = operation.arguments[position].id if conversion: temp_version[0] += 1 temp_name = '%s_%s' % (param_name, temp_version[0]) temp_type = conversion.output_type stmts_emitter.Emit( '$(INDENT)$TYPE $NAME = $CONVERT($ARG);\n', TYPE=TypeOrVar(temp_type), NAME=temp_name, CONVERT=conversion.function_name, ARG=parameter_names[position]) arguments.append(temp_name) param_type = temp_type verified_type = temp_type # verified by assignment in checked mode. else: arguments.append(parameter_names[position]) param_type = self._NarrowInputType(arg.type.id) # Verified by argument checking on entry to the dispatcher. verified_type = InputType(info.param_infos[position].type_id) # 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' target_parameters.append( '%s%s' % (TypeOrNothing(param_type), param_name)) argument_list = ', '.join(arguments) # TODO(sra): If the native method has zero type checks, we can 'inline' is # and call it directly with a JS-expression. call = '%s(%s)' % (target, argument_list) if output_conversion: call = '%s(%s)' % (output_conversion.function_name, call) if operation.type.id == 'void': call_emitter.Emit('$(INDENT)$CALL;\n$(INDENT)return;\n', CALL=call) else: call_emitter.Emit('$(INDENT)return $CALL;\n', CALL=call) self._members_emitter.Emit( ' $TYPE$TARGET($PARAMS) native "$NATIVE";\n', TYPE=TypeOrNothing(native_return_type), TARGET=target, PARAMS=', '.join(target_parameters), NATIVE=info.declared_name) def GenerateChecksAndCall(operation, argument_count): checks = ['!?%s' % name for name in parameter_names] for i in range(0, argument_count): argument = operation.arguments[i] parameter_name = parameter_names[i] test_type = self._DartType(argument.type.id) if test_type in ['dynamic', 'Object']: checks[i] = '?%s' % parameter_name elif test_type == parameter_types[i]: checks[i] = 'true' else: checks[i] = '(%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'. GenerateCall(operation, argument_count, checks) # TODO: Optimize the dispatch to avoid repeated checks. if len(operations) > 1: for operation in operations: for position, argument in enumerate(operation.arguments): if self._IsOptional(operation, argument): GenerateChecksAndCall(operation, position) GenerateChecksAndCall(operation, len(operation.arguments)) body.Emit( ' throw const Exception("Incorrect number or type of arguments");' '\n'); else: operation = operations[0] argument_count = len(operation.arguments) for position, argument in list(enumerate(operation.arguments))[::-1]: if self._IsOptional(operation, argument): check = '?%s' % parameter_names[position] GenerateCall(operation, position + 1, [check]) argument_count = position GenerateCall(operation, argument_count, []) def _IsOptional(self, operation, argument): return IsOptional(argument) def _OperationRequiresConversions(self, operation): return (self._OperationRequiresOutputConversion(operation) or self._OperationRequiresInputConversions(operation)) def _OperationRequiresOutputConversion(self, operation): return self._OutputConversion(operation.type.id, operation.id) def _OperationRequiresInputConversions(self, operation): return any(self._InputConversion(arg.type.id, operation.id) for arg in operation.arguments) def _OutputConversion(self, idl_type, member): return FindConversion(idl_type, 'get', self._interface.id, member) def _InputConversion(self, idl_type, member): return FindConversion(idl_type, 'set', self._interface.id, member) def _HasCustomImplementation(self, member_name): member_name = '%s.%s' % (self._interface_type_info.interface_name(), member_name) return member_name in _js_custom_members def CustomJSMembers(self): return _js_custom_members 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 _NarrowOutputType(self, type_name): secure_name = SecureOutputType(self, type_name, True) return self._NarrowToImplementationType(secure_name) def _FindShadowedAttribute(self, attr): """Returns (attribute, superinterface) or (None, None).""" def FindInParent(interface): """Returns matching attribute in parent, or None.""" if interface.parents: parent = interface.parents[0] if IsDartCollectionType(parent.type.id): return (None, None) if IsPureInterface(parent.type.id): return (None, None) if self._database.HasInterface(parent.type.id): interfaces_to_search_in = [] parent_interface_name = parent.type.id interfaces_to_search_in.append(parent_interface_name) parent_type_info = self._type_registry.TypeInfo(parent_interface_name) if parent_type_info.merged_into(): # IDL parent was merged into another interface, which became a # parent interface in Dart. parent_interface_name = parent_type_info.merged_into() interfaces_to_search_in.append(parent_interface_name) elif parent_type_info.merged_interface(): # IDL parent has another interface that was merged into it. interfaces_to_search_in.append(parent_type_info.merged_interface()) for interface_name in interfaces_to_search_in: interface = self._database.GetInterface(interface_name) attr2 = FindMatchingAttribute(interface, attr) if attr2: return (attr2, parent_interface_name) return FindInParent( self._database.GetInterface(parent_interface_name)) return (None, None) return FindInParent(self._interface) if attr else (None, None) def _DartType(self, type_name): return self._type_registry.DartType(type_name) # ------------------------------------------------------------------------------ class DartLibraryEmitter(): def __init__(self, multiemitter, template, dart_sources_dir): self._multiemitter = multiemitter self._template = template self._dart_sources_dir = dart_sources_dir self._path_to_emitter = {} def FileEmitter(self, basename, template=None): path = os.path.join(self._dart_sources_dir, '%s.dart' % basename) if not path in self._path_to_emitter: emitter = self._multiemitter.FileEmitter(path) if not template is None: emitter = emitter.Emit(template) self._path_to_emitter[path] = emitter return self._path_to_emitter[path] def EmitLibrary(self, library_file_path, auxiliary_dir): def massage_path(path): # The most robust way to emit path separators is to use / always. return path.replace('\\', '/') library_emitter = self._multiemitter.FileEmitter(library_file_path) library_file_dir = os.path.dirname(library_file_path) auxiliary_dir = os.path.relpath(auxiliary_dir, library_file_dir) imports_emitter = library_emitter.Emit( self._template, AUXILIARY_DIR=massage_path(auxiliary_dir)) for path in sorted(self._path_to_emitter.keys()): relpath = os.path.relpath(path, library_file_dir) imports_emitter.Emit( "part '$PATH';\n", PATH=massage_path(relpath))