#!/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.getPropertyValue', 'CSSStyleDeclaration.setProperty', 'Element.insertAdjacentElement', 'Element.insertAdjacentHTML', 'Element.insertAdjacentText', 'Element.remove', 'ElementEvents.mouseWheel', 'IDBDatabase.transaction', 'IFrameElement.contentWindow', 'MouseEvent.offsetX', 'MouseEvent.offsetY', 'TableElement.createTBody', 'Window.document', 'Window.indexedDB', 'Window.location', 'Window.open', 'Window.top', 'Window.webkitCancelAnimationFrame', 'Window.webkitRequestAnimationFrame', ]) # This map controls merging of interfaces in dart:html library. # All constants, attributes, and operations of merged interface (key) are # added to target interface (value). All references to the merged interface # (e.g. parameter types, return types, parent interfaces) are replaced with # target interface. There are two important restrictions: # 1) Merged and target interfaces shouldn't have common members, otherwise there # would be duplicated declarations in generated Dart code. # 2) Merged interface should be direct child of target interface, so the # children of merged interface are not affected by the merge. # As a consequence, target interface implementation and its direct children # interface implementations should implement merged attribute accessors and # operations. For example, SVGElement and Element implementation classes should # implement HTMLElement.insertAdjacentElement(), HTMLElement.innerHTML, etc. _merged_html_interfaces = { 'HTMLDocument': 'Document', 'HTMLElement': 'Element' } # 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], None, tXn[0], 'null'), self.opt_params) 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.ParametersInterfaceDeclaration(rename_type)) 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._html_interface_name = options.renamer.RenameInterface(self._interface) 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 $TYPE $NAME($PARAMS);\n', NAME=self._interface.id, TYPE=self._DartType(info.type_name), PARAMS=info.ParametersImplementationDeclaration(self._DartType)) self._backend.GenerateCallback(info) def GenerateInterface(self): if (not self._interface.id in _merged_html_interfaces and # Don't re-generate types that have been converted to native dart types. self._html_interface_name not in nativified_classes): interface_emitter = self._library_emitter.FileEmitter( self._html_interface_name) else: interface_emitter = emitter.Emitter() template_file = 'interface_%s.darttemplate' % self._html_interface_name interface_template = (self._template_loader.TryLoad(template_file) or self._template_loader.Load('interface.darttemplate')) typename = self._html_interface_name implements = [] suppressed_implements = [] for parent in self._interface.parents: # TODO(vsm): Remove source_filter. if MatchSourceFilter(parent): # Parent is a DOM type. implements.append(self._DartType(parent.type.id)) elif '<' in parent.type.id: # Parent is a Dart collection type. # TODO(vsm): Make this check more robust. implements.append(self._DartType(parent.type.id)) else: suppressed_implements.append('%s.%s' % (self._common_prefix, self._DartType(parent.type.id))) comment = ' extends' implements_str = '' if implements: implements_str += ' implements ' + ', '.join(implements) comment = ',' if suppressed_implements: implements_str += ' /*%s %s */' % (comment, ', '.join(suppressed_implements)) factory_provider = None if typename in interface_factories: factory_provider = interface_factories[typename] constructors = [] constructor_info = AnalyzeConstructor(self._interface) if constructor_info: constructors.append(constructor_info) factory_provider = '_' + typename + 'FactoryProvider' factory_provider_emitter = self._library_emitter.FileEmitter( '_%sFactoryProvider' % self._html_interface_name) self._backend.EmitFactoryProvider( constructor_info, factory_provider, factory_provider_emitter) infos = HtmlElementConstructorInfos(typename) if infos: template = self._template_loader.Load( 'factoryprovider_Elements.darttemplate') EmitHtmlElementFactoryConstructors( self._library_emitter.FileEmitter('_Elements', template), infos, self._interface.id, self._backend.ImplementationClassName(), 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=typename, EXTENDS=implements_str) self._type_comment_emitter.Emit("/// @domName $DOMNAME", DOMNAME=self._interface.doc_js_name) if self._backend.HasImplementation(): if not self._interface.id in _merged_html_interfaces: name = self._html_interface_name if self._html_interface_name in nativified_classes: name = nativified_classes[self._html_interface_name] basename = '%sImpl' % name else: basename = '%sImpl_Merged' % self._html_interface_name implementation_emitter = self._library_emitter.FileEmitter(basename) else: implementation_emitter = emitter.Emitter() base_class = self._backend.BaseClassName() implemented_interfaces = [self._html_interface_name] +\ self._backend.AdditionalImplementedInterfaces() self._implementation_members_emitter = implementation_emitter.Emit( self._backend.ImplementationTemplate(), CLASSNAME=self._backend.ImplementationClassName(), 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 = MaybeTypedArrayElementTypeInHierarchy( self._interface, self._database) 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, self._html_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() for merged_interface in _merged_html_interfaces: if _merged_html_interfaces[merged_interface] == self._interface.id: merged_interface = self._database.GetInterface(merged_interface) self.AddMembers(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, requires_indexer) = ListImplementationInfo( interface, self._database) if element_type: if requires_indexer: self.AddIndexer(element_type) else: self.AmendIndexer(element_type) # 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, dom_name, 'get:') if not html_name or self._IsPrivate(html_name): return html_setter_name = self._renamer.RenameMember( self._interface.id, 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 abstract $TYPE get $NAME;\n' else: template = '\n $TYPE $NAME;\n' self._members_emitter.Emit(template, NAME=html_name, TYPE=self._DartType(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. """ html_name = self._renamer.RenameMember(self._interface.id, 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._backend.ImplementationClassName(), NAME=html_name) else: self._members_emitter.Emit('\n' ' $TYPE $NAME($PARAMS);\n', TYPE=self._DartType(info.type_name), NAME=html_name, PARAMS=info.ParametersInterfaceDeclaration(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 _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._html_interface_name = options.renamer.RenameInterface(self._interface) self._current_secondary_parent = None def HasImplementation(self): return not (IsPureInterface(self._interface.id) or self._interface.id in _merged_html_interfaces) def ImplementationClassName(self): return self._ImplClassName(self._html_interface_name) def ImplementsMergedMembers(self): return True def _ImplClassName(self, type_name): name = type_name if type_name in nativified_classes: name = nativified_classes[type_name] return '_%sImpl' % name def GenerateCallback(self, info): pass def BaseClassName(self): if not self._interface.parents: return None supertype = self._interface.parents[0].type.id if IsDartCollectionType(supertype): # List methods are injected in AddIndexer. return None if IsPureInterface(supertype): return None elif supertype == 'NodeList': # Special case as NodeList gets converted to List