Index: client/dom/scripts/systemhtml.py |
diff --git a/client/dom/scripts/systemhtml.py b/client/dom/scripts/systemhtml.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..e18099e093f5c202ea2c2358b2449c925e915544 |
--- /dev/null |
+++ b/client/dom/scripts/systemhtml.py |
@@ -0,0 +1,1335 @@ |
+#!/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 os |
+from generator import * |
+from systembase import * |
+from systemfrog import * |
+from systeminterface import * |
+ |
+# Members from the standard dom that should not be exposed publicly in dart:html |
+# but need to be exposed internally to implement dart:html on top of a standard |
+# browser. |
+_private_html_members = { |
+ 'Element': set(['clientLeft', 'clientTop', 'clientWidth', 'clientHeight', |
+ 'offsetLeft', 'offsetTop', 'offsetWidth', 'offsetHeight', |
+ 'scrollLeft', 'scrollTop', 'scrollWidth', 'scrollHeight', |
+ 'childElementCount', 'firstElementChild', 'hasAttribute', |
+ 'getAttribute', 'removeAttribute', 'setAttribute', 'className', |
+ 'children']), |
+ 'Node' : set(['appendChild', 'removeChild', 'replaceChild', 'attributes', |
+ 'childNodes']), |
+ # TODO(jacobr): other direct translate methods on node such as |
+ # textContext->text |
+ 'Document': set(['createElement', 'createEvent']), |
+ 'Window': set(['getComputedStyle']), |
+ 'EventTarget': set(['removeEventListener', 'addEventListener', |
+ 'dispatchEvent']), |
+ 'Event': set(['initEvent', 'target', 'srcElement', 'currentTarget']) |
+} |
+ |
+# Members from the standard dom that exist in the dart:html library with |
+# identical functionality but with cleaner names. |
+html_library_renames = { |
+ 'Document.createTextNode': 'Text.Text', |
+ 'Document.get:defaultView': 'Document.get:window', |
+ 'DocumentFragment.querySelector': 'Element.query', |
+ 'Element.querySelector': 'Element.query', |
+ 'Document.querySelector': 'Element.query', |
+ 'DocumentFragment.querySelectorAll': 'Element.queryAll', |
+ 'DocumentFragment.querySelectorAll': 'Element.queryAll', |
+ 'Element.querySelectorAll': 'Element.queryAll', |
+ 'Element.scrollIntoViewIfNeeded': 'Element.scrollIntoView', |
+ 'Node.cloneNode': 'Node.clone', |
+ 'Node.get:nextSibling': 'Node.get:nextNode', |
+ 'Node.get:ownerDocument': 'Node.get:document', |
+ 'Node.get:parentNode': 'Node.get:parent', |
+ 'Node.get:previousSibling': 'Node.get:previousNode', |
+} |
+ |
+# Members and classes from the dom that should be removed completelly from |
+# dart:html. These could be expressed in the IDL instead but expressing this |
+# as a simple table instead is more concise. |
+# TODO(jacobr): cleanup and augment this list. |
+_html_library_remove = set([ |
+ 'Window.get:document', # Removed as we have a custom implementation. |
+ 'NodeList.item', |
+ "Attr.*", |
+# "BarProp.*", |
+# "BarInfo.*", |
+# "Blob.webkitSlice", |
+# "CDATASection.*", |
+# "Comment.*", |
+# "DOMImplementation.*", |
+ # TODO(jacobr): listing title here is a temporary hack due to a frog bug |
+ # involving when an interface inherits from another interface and defines |
+ # the same field. BUG(1633) |
+ "Document.get:title", |
+ "Document.set:title", |
+ "Element.get:title", |
+ "Element.set:title", |
+ "Document.get:documentElement", |
+ "Document.get:forms", |
+# "Document.get:selectedStylesheetSet", |
+# "Document.set:selectedStylesheetSet", |
+# "Document.get:preferredStylesheetSet", |
+ "Document.get:links", |
+ "Document.getElementsByTagName", |
+ "Document.set:domain", |
+ "Document.get:implementation", |
+ "Document.createAttributeNS", |
+ "Document.get:inputEncoding", |
+ "Document.getElementsByClassName", |
+ "Document.get:compatMode", |
+ "Document.importNode", |
+ "Document.evaluate", |
+ "Document.get:images", |
+ "Document.querySelector", |
+ "Document.createExpression", |
+ "Document.getOverrideStyle", |
+ "Document.get:xmlStandalone", |
+ "Document.set:xmlStandalone", |
+ "Document.createComment", |
+ "Document.adoptNode", |
+ "Document.get:characterSet", |
+ "Document.createAttribute", |
+ "Document.querySelectorAll", |
+ "Document.get:URL", |
+ "Document.createElementNS", |
+ "Document.createEntityReference", |
+ "Document.get:documentURI", |
+ "Document.set:documentURI", |
+ "Document.createNodeIterator", |
+ "Document.createProcessingInstruction", |
+ "Document.get:doctype", |
+ "Document.getElementsByName", |
+ "Document.createTreeWalker", |
+ "Document.get:location", |
+ "Document.set:location", |
+ "Document.createNSResolver", |
+ "Document.get:xmlEncoding", |
+ "Document.get:defaultCharset", |
+ "Document.get:applets", |
+ "Document.getSelection", |
+ "Document.get:xmlVersion", |
+ "Document.set:xmlVersion", |
+ "Document.get:anchors", |
+ "Document.getElementsByTagNameNS", |
+ "DocumentType.*", |
+ "Element.hasAttributeNS", |
+ "Element.getAttributeNS", |
+ "Element.setAttributeNode", |
+ "Element.getAttributeNode", |
+ "Element.removeAttributeNode", |
+ "Element.removeAttributeNS", |
+ "Element.setAttributeNodeNS", |
+ "Element.getAttributeNodeNS", |
+ "Element.setAttributeNS", |
+# "EventSource.get:url", |
+# TODO(jacobr): should these be removed? |
+ "Document.close", |
+ "Document.hasFocus", |
+ |
+ "Document.get:vlinkColor", |
+ "Document.set:vlinkColor", |
+ "Document.captureEvents", |
+ "Document.releaseEvents", |
+ "Document.get:compatMode", |
+ "Document.get:designMode", |
+ "Document.set:designMode", |
+ "Document.get:dir", |
+ "Document.set:dir", |
+ "Document.get:all", |
+ "Document.set:all", |
+ "Document.write", |
+ "Document.get:fgColor", |
+ "Document.set:fgColor", |
+ "Document.get:bgColor", |
+ "Document.set:bgColor", |
+ "Document.get:plugins", |
+ "Document.get:alinkColor", |
+ "Document.set:alinkColor", |
+ "Document.get:embeds", |
+ "Document.open", |
+ "Document.clear", |
+ "Document.get:scripts", |
+ "Document.writeln", |
+ "Document.get:linkColor", |
+ "Document.set:linkColor", |
+ "Element.get:itemRef", |
+ "Element.set:className", |
+ "Element.get:outerText", |
+ "Element.set:outerText", |
+ "Element.get:accessKey", |
+ "Element.set:accessKey", |
+ "Element.get:itemType", |
+ "Element.get:innerText", |
+ "Element.set:innerText", |
+ "Element.set:outerHTML", |
+ "Element.get:itemScope", |
+ "Element.set:itemScope", |
+ "Element.get:itemValue", |
+ "Element.set:itemValue", |
+ "Element.get:itemId", |
+ "Element.set:itemId", |
+ "Element.get:itemProp", |
+ "EmbedElement.getSVGDocument", |
+ "FormElement.get:elements", |
+ "HTMLFrameElement.*", |
+ "HTMLFrameSetElement.*", |
+ "HTMLHtmlElement.get:version", |
+ "HTMLHtmlElement.set:version", |
+# "IFrameElement.getSVGDocument", #TODO(jacobr): should this be removed |
+ "InputElement.get:dirName", |
+ "InputElement.set:dirName", |
+ "HTMLIsIndexElement.*", |
+ "ObjectElement.getSVGDocument", |
+ "HTMLOptionsCollection.*", |
+ "HTMLPropertiesCollection.*", |
+ "SelectElement.remove", |
+ "TextAreaElement.get:dirName", |
+ "TextAreaElement.set:dirName", |
+ "NamedNodeMap.*", |
+ "Node.isEqualNode", |
+ "Node.get:TEXT_NODE", |
+ "Node.hasAttributes", |
+ "Node.get:DOCUMENT_TYPE_NODE", |
+ "Node.get:DOCUMENT_POSITION_FOLLOWING", |
+ "Node.get:childNodes", |
+ "Node.lookupNamespaceURI", |
+ "Node.get:ELEMENT_NODE", |
+ "Node.get:namespaceURI", |
+ "Node.get:DOCUMENT_FRAGMENT_NODE", |
+ "Node.get:localName", |
+ "Node.dispatchEvent", |
+ "Node.isDefaultNamespace", |
+ "Node.compareDocumentPosition", |
+ "Node.get:baseURI", |
+ "Node.isSameNode", |
+ "Node.get:DOCUMENT_POSITION_DISCONNECTED", |
+ "Node.get:DOCUMENT_NODE", |
+ "Node.get:DOCUMENT_POSITION_CONTAINS", |
+ "Node.get:COMMENT_NODE", |
+ "Node.get:ENTITY_REFERENCE_NODE", |
+ "Node.isSupported", |
+ "Node.get:firstChild", |
+ "Node.get:DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC", |
+ "Node.get:lastChild", |
+ "Node.get:attributes", |
+ "Node.get:NOTATION_NODE", |
+ "Node.normalize", |
+ "Node.get:parentElement", |
+ "Node.get:ATTRIBUTE_NODE", |
+ "Node.get:ENTITY_NODE", |
+ "Node.get:DOCUMENT_POSITION_CONTAINED_BY", |
+ "Node.get:prefix", |
+ "Node.set:prefix", |
+ "Node.get:DOCUMENT_POSITION_PRECEDING", |
+ "Node.get:nodeType", |
+ "Node.removeEventListener", |
+ "Node.get:nodeValue", |
+ "Node.set:nodeValue", |
+ "Node.get:CDATA_SECTION_NODE", |
+ "Node.get:nodeName", |
+ "Node.addEventListener", |
+ "Node.lookupPrefix", |
+ "Node.get:PROCESSING_INSTRUCTION_NODE", |
+ "Notification.dispatchEvent", |
+ "Notification.addEventListener", |
+ "Notification.removeEventListener"]) |
+ |
+# Events without onEventName attributes in the IDL we want to support. |
+# We can automatically extract most event event names by checking for |
+# onEventName methods in the IDL but some events aren't listed so we need |
+# to manually add them here so that they are easy for users to find. |
+_html_manual_events = { |
+ 'Element': ['touchleave', 'webkitTransitionEnd'], |
+ 'Window': ['DOMContentLoaded'] |
+} |
+ |
+# These event names must be camel case when attaching event listeners |
+# using addEventListener even though the onEventName properties in the DOM for |
+# them are not camel case. |
+_on_attribute_to_event_name_mapping = { |
+ 'webkitanimationend': 'webkitAnimationEnd', |
+ 'webkitanimationiteration': 'webkitAnimationIteration', |
+ 'webkitanimationstart': 'webkitAnimationStart', |
+ 'webkitfullscreenchange': 'webkitFullScreenChange', |
+ 'webkitfullscreenerror': 'webkitFullScreenError', |
+ 'webkitspeechchange': 'webkitSpeechChange', |
+ 'webkittransitionend': 'webkitTransitionEnd', |
+} |
+ |
+# Mapping from raw event names to the pretty camelCase event names exposed as |
+# properties in dart:html. If the DOM exposes a new event name, you will need |
+# to add the lower case to camel case conversion for that event name here. |
+_html_event_names = { |
+ 'DOMContentLoaded': 'contentLoaded', |
+ 'touchleave': 'touchLeave', |
+ 'abort': 'abort', |
+ 'beforecopy': 'beforeCopy', |
+ 'beforecut': 'beforeCut', |
+ 'beforepaste': 'beforePaste', |
+ 'beforeunload': 'beforeUnload', |
+ 'blur': 'blur', |
+ 'cached': 'cached', |
+ 'canplay': 'canPlay', |
+ 'canplaythrough': 'canPlayThrough', |
+ 'change': 'change', |
+ 'checking': 'checking', |
+ 'click': 'click', |
+ 'close': 'close', |
+ 'contextmenu': 'contextMenu', |
+ 'copy': 'copy', |
+ 'cut': 'cut', |
+ 'dblclick': 'doubleClick', |
+ 'devicemotion': 'deviceMotion', |
+ 'deviceorientation': 'deviceOrientation', |
+ 'display': 'display', |
+ 'downloading': 'downloading', |
+ 'drag': 'drag', |
+ 'dragend': 'dragEnd', |
+ 'dragenter': 'dragEnter', |
+ 'dragleave': 'dragLeave', |
+ 'dragover': 'dragOver', |
+ 'dragstart': 'dragStart', |
+ 'drop': 'drop', |
+ 'durationchange': 'durationChange', |
+ 'emptied': 'emptied', |
+ 'ended': 'ended', |
+ 'error': 'error', |
+ 'focus': 'focus', |
+ 'hashchange': 'hashChange', |
+ 'input': 'input', |
+ 'invalid': 'invalid', |
+ 'keydown': 'keyDown', |
+ 'keypress': 'keyPress', |
+ 'keyup': 'keyUp', |
+ 'load': 'load', |
+ 'loadeddata': 'loadedData', |
+ 'loadedmetadata': 'loadedMetadata', |
+ 'loadend': 'loadEnd', |
+ 'loadstart': 'loadStart', |
+ 'message': 'message', |
+ 'mousedown': 'mouseDown', |
+ 'mousemove': 'mouseMove', |
+ 'mouseout': 'mouseOut', |
+ 'mouseover': 'mouseOver', |
+ 'mouseup': 'mouseUp', |
+ 'mousewheel': 'mouseWheel', |
+ 'noupdate': 'noUpdate', |
+ 'obsolete': 'obsolete', |
+ 'offline': 'offline', |
+ 'online': 'online', |
+ 'open': 'open', |
+ 'pagehide': 'pageHide', |
+ 'pageshow': 'pageShow', |
+ 'paste': 'paste', |
+ 'pause': 'pause', |
+ 'play': 'play', |
+ 'playing': 'playing', |
+ 'popstate': 'popState', |
+ 'progress': 'progress', |
+ 'ratechange': 'rateChange', |
+ 'readystatechange': 'readyStateChange', |
+ 'reset': 'reset', |
+ 'resize': 'resize', |
+ 'scroll': 'scroll', |
+ 'search': 'search', |
+ 'seeked': 'seeked', |
+ 'seeking': 'seeking', |
+ 'select': 'select', |
+ 'selectionchange': 'selectionChange', |
+ 'selectstart': 'selectStart', |
+ 'show': 'show', |
+ 'stalled': 'stalled', |
+ 'storage': 'storage', |
+ 'submit': 'submit', |
+ 'suspend': 'suspend', |
+ 'timeupdate': 'timeUpdate', |
+ 'touchcancel': 'touchCancel', |
+ 'touchend': 'touchEnd', |
+ 'touchmove': 'touchMove', |
+ 'touchstart': 'touchStart', |
+ 'unload': 'unload', |
+ 'updateready': 'updateReady', |
+ 'volumechange': 'volumeChange', |
+ 'waiting': 'waiting', |
+ 'webkitAnimationEnd': 'animationEnd', |
+ 'webkitAnimationIteration': 'animationIteration', |
+ 'webkitAnimationStart': 'animationStart', |
+ 'webkitFullScreenChange': 'fullScreenChange', |
+ 'webkitFullScreenError': 'fullScreenError', |
+ 'webkitSpeechChange': 'speechChange', |
+ 'webkitTransitionEnd': 'transitionEnd' |
+} |
+ |
+def _OnAttributeToEventName(on_method): |
+ event_name = on_method.id[2:] |
+ if event_name in _on_attribute_to_event_name_mapping: |
+ return _on_attribute_to_event_name_mapping[event_name] |
+ else: |
+ return event_name |
+ |
+def _DomToHtmlEvents(interface_id, events): |
+ event_names = set(map(_OnAttributeToEventName, events)) |
+ if interface_id in _html_manual_events: |
+ for manual_event_name in _html_manual_events[interface_id]: |
+ event_names.add(manual_event_name) |
+ |
+ return sorted(event_names, key=lambda name: _html_event_names[name]) |
+ |
+# ------------------------------------------------------------------------------ |
+ |
+class HtmlSystem(System): |
+ |
+ def __init__(self, templates, database, emitters, output_dir, generator): |
+ super(HtmlSystem, self).__init__( |
+ templates, database, emitters, output_dir) |
+ self._event_classes = set() |
+ self._seen_event_names = {} |
+ self._generator = generator |
+ |
+ def _AllowInHtmlLibrary(self, interface, member): |
+ if self._PrivateInHtmlLibrary(interface, member): |
+ return False |
+ for interface_name in ([interface.id] + |
+ self._generator._AllImplementedInterfaces(interface)): |
+ if interface.id + '.' + member in _html_library_remove: |
+ return False |
+ return True |
+ |
+ def _PrivateInHtmlLibrary(self, interface, member): |
+ for interface_name in ([interface.id] + |
+ self._generator._AllImplementedInterfaces(interface)): |
+ if (interface_name in _private_html_members and |
+ member in _private_html_members[interface_name]): |
+ return True |
+ return False |
+ |
+ # TODO(jacobr): this already exists |
+ def _TraverseParents(self, interface, callback): |
+ for parent in interface.parents: |
+ parent_id = parent.type.id |
+ if self._database.HasInterface(parent_id): |
+ parent_interface = self._database.GetInterface(parent_id) |
+ callback(parent_interface) |
+ self._TraverseParents(parent_interface, callback) |
+ |
+ # TODO(jacobr): this isn't quite right.... |
+ def _GetParentsEventsClasses(self, interface): |
+ # Ugly hack as we don't specify that Document inherits from Element |
+ # in our IDL. |
+ if interface.id == 'Document': |
+ return ['ElementEvents'] |
+ |
+ interfaces_with_events = set() |
+ def visit(parent): |
+ if parent.id in self._event_classes: |
+ interfaces_with_events.add(parent) |
+ |
+ self._TraverseParents(interface, visit) |
+ if len(interfaces_with_events) == 0: |
+ return ['Events'] |
+ else: |
+ names = [] |
+ for interface in interfaces_with_events: |
+ names.append(interface.id + 'Events') |
+ return names |
+ |
+class HtmlInterfacesSystem(HtmlSystem): |
+ |
+ def __init__(self, templates, database, emitters, output_dir, generator): |
+ super(HtmlInterfacesSystem, self).__init__( |
+ templates, database, emitters, output_dir, generator) |
+ self._dart_interface_file_paths = [] |
+ |
+ def InterfaceGenerator(self, |
+ interface, |
+ common_prefix, |
+ super_interface_name, |
+ source_filter): |
+ """.""" |
+ interface_name = interface.id |
+ dart_interface_file_path = self._FilePathForDartInterface(interface_name) |
+ |
+ self._dart_interface_file_paths.append(dart_interface_file_path) |
+ |
+ dart_interface_code = self._emitters.FileEmitter(dart_interface_file_path) |
+ |
+ template_file = 'interface_%s.darttemplate' % interface_name |
+ template = self._templates.TryLoad(template_file) |
+ if not template: |
+ template = self._templates.Load('interface.darttemplate') |
+ |
+ return HtmlDartInterfaceGenerator( |
+ interface, dart_interface_code, |
+ template, |
+ common_prefix, super_interface_name, |
+ source_filter, self) |
+ |
+ def ProcessCallback(self, interface, info): |
+ """Generates a typedef for the callback interface.""" |
+ interface_name = interface.id |
+ file_path = self._FilePathForDartInterface(interface_name) |
+ self._ProcessCallback(interface, info, file_path) |
+ |
+ def GenerateLibraries(self, lib_dir): |
+ pass |
+ |
+ |
+ def _FilePathForDartInterface(self, interface_name): |
+ """Returns the file path of the Dart interface definition.""" |
+ # TODO(jmesserly): is this the right path |
+ return os.path.join(self._output_dir, 'html', 'interface', |
+ '%s.dart' % interface_name) |
+ |
+# ------------------------------------------------------------------------------ |
+ |
+# TODO(jmesserly): inheritance is probably not the right way to factor this long |
+# term, but it makes merging better for now. |
+class HtmlDartInterfaceGenerator(DartInterfaceGenerator): |
+ """Generates Dart Interface definition for one DOM IDL interface.""" |
+ |
+ def __init__(self, interface, emitter, template, |
+ common_prefix, super_interface, source_filter, system): |
+ super(HtmlDartInterfaceGenerator, self).__init__(interface, |
+ emitter, template, common_prefix, super_interface, source_filter) |
+ self._system = system |
+ |
+ def StartInterface(self): |
+ typename = self._interface.id |
+ |
+ extends = [] |
+ suppressed_extends = [] |
+ |
+ for parent in self._interface.parents: |
+ # TODO(vsm): Remove source_filter. |
+ if MatchSourceFilter(self._source_filter, parent): |
+ # Parent is a DOM type. |
+ extends.append(parent.type.id) |
+ elif '<' in parent.type.id: |
+ # Parent is a Dart collection type. |
+ # TODO(vsm): Make this check more robust. |
+ extends.append(parent.type.id) |
+ else: |
+ suppressed_extends.append('%s.%s' % |
+ (self._common_prefix, parent.type.id)) |
+ |
+ comment = ' extends' |
+ extends_str = '' |
+ if extends: |
+ extends_str += ' extends ' + ', '.join(extends) |
+ comment = ',' |
+ if suppressed_extends: |
+ extends_str += ' /*%s %s */' % (comment, ', '.join(suppressed_extends)) |
+ |
+ if typename in interface_factories: |
+ extends_str += ' default ' + interface_factories[typename] |
+ |
+ # TODO(vsm): Add appropriate package / namespace syntax. |
+ (self._members_emitter, |
+ self._top_level_emitter) = self._emitter.Emit( |
+ self._template + '$!TOP_LEVEL', |
+ ID=typename, |
+ EXTENDS=extends_str) |
+ |
+ element_type = MaybeTypedArrayElementType(self._interface) |
+ if element_type: |
+ self._members_emitter.Emit( |
+ '\n' |
+ ' $CTOR(int length);\n' |
+ '\n' |
+ ' $CTOR.fromList(List<$TYPE> list);\n' |
+ '\n' |
+ ' $CTOR.fromBuffer(ArrayBuffer buffer);\n', |
+ CTOR=self._interface.id, |
+ TYPE=element_type) |
+ |
+ def AddAttribute(self, getter, setter): |
+ if getter and not self._system._AllowInHtmlLibrary(self._interface, |
+ 'get:' + getter.id): |
+ getter = None |
+ if setter and not self._system._AllowInHtmlLibrary(self._interface, |
+ 'set:' + setter.id): |
+ setter = None |
+ if not getter and not setter: |
+ return |
+ if getter and setter and getter.type.id == setter.type.id: |
+ self._members_emitter.Emit('\n $TYPE $NAME;\n', |
+ NAME=getter.id, TYPE=getter.type.id); |
+ return |
+ if getter and not setter: |
+ self._members_emitter.Emit('\n final $TYPE $NAME;\n', |
+ NAME=getter.id, TYPE=getter.type.id); |
+ return |
+ raise Exception('Unexpected getter/setter combination %s %s' % |
+ (getter, setter)) |
+ |
+ def AddOperation(self, info): |
+ """ |
+ Arguments: |
+ operations - contains the overloads, one or more operations with the same |
+ name. |
+ """ |
+ if self._system._AllowInHtmlLibrary(self._interface, info.name): |
+ self._members_emitter.Emit('\n' |
+ ' $TYPE $NAME($PARAMS);\n', |
+ TYPE=info.type_name, |
+ NAME=info.name, |
+ PARAMS=info.ParametersInterfaceDeclaration()) |
+ |
+ def FinishInterface(self): |
+ pass |
+ |
+ def AddConstant(self, constant): |
+ self._EmitConstant(self._members_emitter, constant) |
+ |
+ def AddEventAttributes(self, event_attrs): |
+ event_attrs = _DomToHtmlEvents(self._interface.id, event_attrs) |
+ self._system._event_classes.add(self._interface.id) |
+ events_interface = self._interface.id + 'Events' |
+ self._members_emitter.Emit('\n $TYPE get on();\n', |
+ TYPE=events_interface) |
+ events_members = self._emitter.Emit( |
+ '\ninterface $INTERFACE extends $PARENTS {\n$!MEMBERS}\n', |
+ INTERFACE=events_interface, |
+ PARENTS=', '.join( |
+ self._system._GetParentsEventsClasses(self._interface))) |
+ |
+ for event_name in event_attrs: |
+ if event_name in _html_event_names: |
+ events_members.Emit('\n EventListenerList get $NAME();\n', |
+ NAME=_html_event_names[event_name]) |
+ else: |
+ raise Exception('No known html even name for event: ' + event_name) |
+ |
+# ------------------------------------------------------------------------------ |
+ |
+# TODO(jmesserly): inheritance is probably not the right way to factor this long |
+# term, but it makes merging better for now. |
+class HtmlFrogClassGenerator(FrogInterfaceGenerator): |
+ """Generates a Frog class for the dart:html library from a DOM IDL |
+ interface. |
+ """ |
+ |
+ def __init__(self, system, interface, template, super_interface, dart_code): |
+ super(HtmlFrogClassGenerator, self).__init__( |
+ system, interface, template, super_interface, dart_code) |
+ |
+ |
+ def StartInterface(self): |
+ interface = self._interface |
+ interface_name = interface.id |
+ |
+ self._class_name = self._ImplClassName(interface_name) |
+ |
+ base = None |
+ if interface.parents: |
+ supertype = interface.parents[0].type.id |
+ # FIXME: We're currently injecting List<..> and EventTarget as |
+ # supertypes in dart.idl. We should annotate/preserve as |
+ # attributes instead. For now, this hack lets the interfaces |
+ # inherit, but not the classes. |
+ if (not IsDartListType(supertype) and |
+ not supertype == 'EventTarget'): |
+ base = self._ImplClassName(supertype) |
+ if IsDartCollectionType(supertype): |
+ # List methods are injected in AddIndexer. |
+ pass |
+ elif supertype == 'EventTarget': |
+ # Most implementors of EventTarget specify the EventListener operations |
+ # again. If the operations are not specified, try to inherit from the |
+ # EventTarget implementation. |
+ # |
+ # Applies to MessagePort. |
+ if not [op for op in interface.operations if op.id == 'addEventListener']: |
+ base = self._ImplClassName(supertype) |
+ else: |
+ base = self._ImplClassName(supertype) |
+ |
+ native_spec = MakeNativeSpec(interface.javascript_binding_name) |
+ |
+ extends = ' extends ' + base if base else '' |
+ |
+ # TODO: Include all implemented interfaces, including other Lists. |
+ implements = [interface_name] |
+ element_type = MaybeTypedArrayElementType(self._interface) |
+ if element_type: |
+ implements.append('List<' + element_type + '>') |
+ |
+ self._members_emitter = self._dart_code.Emit( |
+ self._template, |
+ #class $CLASSNAME$EXTENDS$IMPLEMENTS$NATIVESPEC { |
+ #$!MEMBERS |
+ #} |
+ CLASSNAME=self._class_name, |
+ EXTENDS=extends, |
+ IMPLEMENTS=' implements ' + ', '.join(implements), |
+ NATIVESPEC=' native "' + native_spec + '"') |
+ |
+ element_type = MaybeTypedArrayElementType(interface) |
+ if element_type: |
+ self.AddTypedArrayConstructors(element_type) |
+ |
+ def AddAttribute(self, getter, setter): |
+ |
+ if self._system._PrivateInHtmlLibrary(self._interface, getter.id): |
+ if getter: |
+ self._AddGetter(getter, True) |
+ if setter: |
+ self._AddSetter(setter, True) |
+ return |
+ if getter and not self._system._AllowInHtmlLibrary(self._interface, |
+ 'get:' + getter.id): |
+ getter = None |
+ if setter and not self._system._AllowInHtmlLibrary(self._interface, |
+ 'set:' + setter.id): |
+ setter = None |
+ if not getter and not setter: |
+ return |
+ # If the (getter, setter) pair is shadowing, we can't generate a shadowing |
+ # field (Issue 1633). |
+ (super_getter, super_getter_interface) = self._FindShadowedAttribute(getter) |
+ (super_setter, super_setter_interface) = self._FindShadowedAttribute(setter) |
+ if super_getter or super_setter: |
+ if getter and not setter and super_getter and not super_setter: |
+ if getter.type.id == super_getter.type.id: |
+ # Compatible getter, use the superclass property. This works because |
+ # JavaScript will do its own dynamic dispatch. |
+ output_type = getter and self._NarrowOutputType(getter.type.id) |
+ self._members_emitter.Emit( |
+ '\n' |
+ ' // Use implementation from $SUPER.\n' |
+ ' // final $TYPE $NAME;\n', |
+ SUPER=super_getter_interface.id, |
+ NAME=getter.id, TYPE=output_type) |
+ return |
+ |
+ self._members_emitter.Emit('\n // Shadowing definition.') |
+ if getter: |
+ self._AddGetter(getter, False) |
+ if setter: |
+ self._AddSetter(setter, False) |
+ return |
+ |
+ if self._interface.id != 'Document': |
+ output_type = getter and self._NarrowOutputType(getter.type.id) |
+ input_type = setter and self._NarrowInputType(setter.type.id) |
+ if getter and setter and input_type == output_type: |
+ self._members_emitter.Emit( |
+ '\n $TYPE $NAME;\n', |
+ NAME=getter.id, TYPE=output_type) |
+ return |
+ if getter and not setter: |
+ self._members_emitter.Emit( |
+ '\n final $TYPE $NAME;\n', |
+ NAME=getter.id, TYPE=output_type) |
+ return |
+ self._AddAttributeUsingProperties(getter, setter, False) |
+ |
+ def _AddAttributeUsingProperties(self, getter, setter, private): |
+ if getter: |
+ self._AddGetter(getter, private) |
+ if setter: |
+ self._AddSetter(setter, private) |
+ |
+ def _AddGetter(self, attr, private): |
+ # TODO(sra): Remove native body when Issue 829 fixed. |
+ self._members_emitter.Emit( |
+ '\n $TYPE get $PRIVATE$NAME() native "return $THIS.$NAME;";\n', |
+ NAME=attr.id, TYPE=self._NarrowOutputType(attr.type.id), |
+ PRIVATE='_' if private else '', |
+ THIS='this.parentNode' if self._interface.id == 'Document' else 'this' |
+ ) |
+ |
+ def _AddSetter(self, attr, private): |
+ # TODO(sra): Remove native body when Issue 829 fixed. |
+ self._members_emitter.Emit( |
+ '\n void set $PRIVATE$NAME($TYPE value)' |
+ ' native "$THIS.$NAME = value;";\n', |
+ NAME=attr.id, TYPE=self._NarrowInputType(attr.type.id), |
+ PRIVATE='_' if private else '', |
+ THIS='this.parentNode' if self._interface.id == 'Document' else 'this') |
+ |
+ def AddOperation(self, info): |
+ """ |
+ Arguments: |
+ info: An OperationInfo object. |
+ """ |
+ private_in_html = self._system._PrivateInHtmlLibrary(self._interface, |
+ info.name) |
+ if private_in_html or self._interface.id == 'Document': |
+ # TODO(vsm): Handle overloads. |
+ # TODO(jacobr): handle document more efficiently for cases where any |
+ # document is fine. For example: use window.document instead of |
+ # this.parentNode. |
+ return_type = self._NarrowOutputType(info.type_name) |
+ self._members_emitter.Emit( |
+ '\n' |
+ ' $TYPE $PRIVATE$NAME($PARAMS)' |
+ ' native "$(RETURN)$(THIS).$NAME($PARAMNAMES);";\n', |
+ TYPE=return_type, |
+ RETURN='' if return_type == 'void' else 'return ', |
+ NAME=info.name, |
+ PRIVATE='_' if private_in_html else '', |
+ THIS='this.parentNode' if self._interface.id == 'Document' |
+ else 'this', |
+ PARAMNAMES=info.ParametersAsArgumentList(), |
+ PARAMS=info.ParametersImplementationDeclaration( |
+ lambda type_name: self._NarrowInputType(type_name))) |
+ elif self._system._AllowInHtmlLibrary(self._interface, info.name): |
+ # TODO(jacobr): this is duplicated from the parent class. |
+ self._members_emitter.Emit( |
+ '\n' |
+ ' $TYPE $NAME($PARAMS) native;\n', |
+ TYPE=self._NarrowOutputType(info.type_name), |
+ NAME=info.name, |
+ PARAMS=info.ParametersImplementationDeclaration( |
+ lambda type_name: self._NarrowInputType(type_name))) |
+ |
+ def AddEventAttributes(self, event_attrs): |
+ event_attrs = _DomToHtmlEvents(self._interface.id, event_attrs) |
+ events_class = '_' + self._interface.id + 'EventsImpl' |
+ events_interface = self._interface.id + 'Events' |
+ self._members_emitter.Emit( |
+ '\n $TYPE get on() =>\n new $TYPE($EVENTTARGET);\n', |
+ TYPE=events_class, |
+ EVENTTARGET='_jsDocument' if self._interface.id == 'Document' |
+ else 'this') |
+ |
+ self._system._event_classes.add(self._interface.id) |
+ |
+ parent_event_classes = self._system._GetParentsEventsClasses( |
+ self._interface) |
+ if len(parent_event_classes) != 1: |
+ raise Exception('Only one parent event class allowed ' |
+ + self._interface.id) |
+ |
+ # TODO(jacobr): specify the type of _ptr as EventTarget |
+ events_members = self._dart_code.Emit( |
+ '\n' |
+ 'class $CLASSNAME extends $SUPER implements $INTERFACE {\n' |
+ ' $CLASSNAME(_ptr) : super(_ptr);\n' |
+ '$!MEMBERS}\n', |
+ TARGETCLASS=self._NarrowOutputType(self._interface.id), |
+ CLASSNAME=events_class, |
+ INTERFACE=events_interface, |
+ SUPER='_' + parent_event_classes[0] + 'Impl') |
+ |
+ for event_name in event_attrs: |
+ if event_name in _html_event_names: |
+ events_members.Emit( |
+ "\n" |
+ " EventListenerList get $NAME() => _get('$RAWNAME');\n", |
+ RAWNAME=event_name, |
+ NAME=_html_event_names[event_name]) |
+ else: |
+ raise Exception('No known html even name for event: ' + event_name) |
+ |
+# ------------------------------------------------------------------------------ |
+ |
+class HtmlFrogSystem(HtmlSystem): |
+ |
+ def __init__(self, templates, database, emitters, output_dir, generator): |
+ super(HtmlFrogSystem, self).__init__( |
+ templates, database, emitters, output_dir, generator) |
+ self._dart_frog_file_paths = [] |
+ |
+ |
+ def InterfaceGenerator(self, |
+ interface, |
+ common_prefix, |
+ super_interface_name, |
+ source_filter): |
+ """.""" |
+ dart_frog_file_path = self._FilePathForFrogImpl(interface.id) |
+ self._dart_frog_file_paths.append(dart_frog_file_path) |
+ |
+ template_file = 'impl_%s.darttemplate' % interface.id |
+ template = self._templates.TryLoad(template_file) |
+ if not template: |
+ template = self._templates.Load('frog_impl.darttemplate') |
+ |
+ dart_code = self._emitters.FileEmitter(dart_frog_file_path) |
+ return HtmlFrogClassGenerator(self, interface, template, |
+ super_interface_name, dart_code) |
+ |
+ def GenerateLibraries(self, lib_dir): |
+ self._GenerateLibFile( |
+ 'html_frog.darttemplate', |
+ os.path.join(lib_dir, 'html_frog.dart'), |
+ (self._interface_system._dart_interface_file_paths + |
+ self._interface_system._dart_callback_file_paths + |
+ self._dart_frog_file_paths)) |
+ |
+ def Finish(self): |
+ pass |
+ |
+ def _FilePathForFrogImpl(self, interface_name): |
+ """Returns the file path of the Frog implementation.""" |
+ # TODO(jmesserly): is this the right path |
+ return os.path.join(self._output_dir, 'html', 'frog', |
+ '%s.dart' % interface_name) |
+ |
+# ------------------------------------------------------------------------------ |
+ |
+class WrappingInterfaceGenerator(object): |
+ """Generates Dart and JS implementation for one DOM IDL interface.""" |
+ |
+ def __init__(self, interface, super_interface, dart_code, base_members): |
+ """Generates Dart and JS code for the given interface. |
+ |
+ Args: |
+ |
+ interface: an IDLInterface instance. It is assumed that all types have |
+ been converted to Dart types (e.g. int, String), unless they are in |
+ the same package as the interface. |
+ super_interface: A string or None, the name of the common interface that |
+ this interface implements, if any. |
+ dart_code: an Emitter for the file containing the Dart implementation |
+ class. |
+ base_members: a set of names of members defined in a base class. This is |
+ used to avoid static member 'overriding' in the generated Dart code. |
+ """ |
+ self._interface = interface |
+ self._super_interface = super_interface |
+ self._dart_code = dart_code |
+ self._base_members = base_members |
+ self._current_secondary_parent = None |
+ |
+ |
+ def StartInterface(self): |
+ interface = self._interface |
+ interface_name = interface.id |
+ |
+ self._class_name = self._ImplClassName(interface_name) |
+ |
+ base = self._BaseClassName(interface) |
+ |
+ (self._members_emitter, |
+ self._top_level_emitter) = self._dart_code.Emit( |
+ '\n' |
+ 'class $CLASS extends $BASE implements $INTERFACE {\n' |
+ ' $CLASS() : super() {}\n' |
+ '\n' |
+ ' static create_$CLASS() native {\n' |
+ ' return new $CLASS();\n' |
+ ' }\n' |
+ '$!MEMBERS' |
+ '\n' |
+ ' String get typeName() { return "$INTERFACE"; }\n' |
+ '}\n' |
+ '$!TOP_LEVEL', |
+ CLASS=self._class_name, BASE=base, INTERFACE=interface_name) |
+ |
+ def _ImplClassName(self, type_name): |
+ return '_' + type_name + 'WrappingImplementation' |
+ |
+ def _BaseClassName(self, interface): |
+ if not interface.parents: |
+ return 'DOMWrapperBase' |
+ |
+ supertype = interface.parents[0].type.id |
+ |
+ # FIXME: We're currently injecting List<..> and EventTarget as |
+ # supertypes in dart.idl. We should annotate/preserve as |
+ # attributes instead. For now, this hack lets the interfaces |
+ # inherit, but not the classes. |
+ # List methods are injected in AddIndexer. |
+ if IsDartListType(supertype) or IsDartCollectionType(supertype): |
+ return 'DOMWrapperBase' |
+ |
+ if supertype == 'EventTarget': |
+ # Most implementors of EventTarget specify the EventListener operations |
+ # again. If the operations are not specified, try to inherit from the |
+ # EventTarget implementation. |
+ # |
+ # Applies to MessagePort. |
+ if not [op for op in interface.operations if op.id == 'addEventListener']: |
+ return self._ImplClassName(supertype) |
+ return 'DOMWrapperBase' |
+ |
+ return self._ImplClassName(supertype) |
+ |
+ def FinishInterface(self): |
+ """.""" |
+ pass |
+ |
+ def AddConstant(self, constant): |
+ # Constants are already defined on the interface. |
+ pass |
+ |
+ def _MethodName(self, prefix, name): |
+ method_name = prefix + name |
+ if name in self._base_members: # Avoid illegal Dart 'static override'. |
+ method_name = method_name + '_' + self._interface.id |
+ return method_name |
+ |
+ def AddAttribute(self, getter, setter): |
+ if getter: |
+ self._AddGetter(getter) |
+ if setter: |
+ self._AddSetter(setter) |
+ |
+ def _AddGetter(self, attr): |
+ # FIXME: Instead of injecting the interface name into the method when it is |
+ # also implemented in the base class, suppress the method altogether if it |
+ # has the same signature. I.e., let the JS do the virtual dispatch instead. |
+ method_name = self._MethodName('_get_', attr.id) |
+ self._members_emitter.Emit( |
+ '\n' |
+ ' $TYPE get $NAME() { return $METHOD(this); }\n' |
+ ' static $TYPE $METHOD(var _this) native;\n', |
+ NAME=attr.id, TYPE=attr.type.id, METHOD=method_name) |
+ |
+ def _AddSetter(self, attr): |
+ # FIXME: See comment on getter. |
+ method_name = self._MethodName('_set_', attr.id) |
+ self._members_emitter.Emit( |
+ '\n' |
+ ' void set $NAME($TYPE value) { $METHOD(this, value); }\n' |
+ ' static void $METHOD(var _this, $TYPE value) native;\n', |
+ NAME=attr.id, TYPE=attr.type.id, METHOD=method_name) |
+ |
+ def AddSecondaryAttribute(self, interface, getter, setter): |
+ self._SecondaryContext(interface) |
+ self.AddAttribute(getter, setter) |
+ |
+ def AddSecondaryOperation(self, interface, info): |
+ self._SecondaryContext(interface) |
+ self.AddOperation(info) |
+ |
+ def AddEventAttributes(self, event_attrs): |
+ pass |
+ |
+ 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 |
+ # get 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<T> ... |
+ # |
+ # In the non-root case we have to choose between: |
+ # |
+ # class YImpl extends XImpl { add List<T> methods; } |
+ # |
+ # and |
+ # |
+ # class YImpl extends ListBase<T> { copies of transitive XImpl methods; } |
+ # |
+ if self._HasNativeIndexGetter(self._interface): |
+ self._EmitNativeIndexGetter(self._interface, element_type) |
+ else: |
+ self._members_emitter.Emit( |
+ '\n' |
+ ' $TYPE operator[](int index) {\n' |
+ ' return item(index);\n' |
+ ' }\n', |
+ TYPE=element_type) |
+ |
+ if self._HasNativeIndexSetter(self._interface): |
+ self._EmitNativeIndexSetter(self._interface, element_type) |
+ else: |
+ self._members_emitter.Emit( |
+ '\n' |
+ ' void operator[]=(int index, $TYPE value) {\n' |
+ ' throw new UnsupportedOperationException("Cannot assign element of immutable List.");\n' |
+ ' }\n', |
+ TYPE=element_type) |
+ |
+ self._members_emitter.Emit( |
+ '\n' |
+ ' void add($TYPE value) {\n' |
+ ' throw new UnsupportedOperationException("Cannot add to immutable List.");\n' |
+ ' }\n' |
+ '\n' |
+ ' void addLast($TYPE value) {\n' |
+ ' throw new UnsupportedOperationException("Cannot add to immutable List.");\n' |
+ ' }\n' |
+ '\n' |
+ ' void addAll(Collection<$TYPE> collection) {\n' |
+ ' throw new UnsupportedOperationException("Cannot add to immutable List.");\n' |
+ ' }\n' |
+ '\n' |
+ ' void sort(int compare($TYPE a, $TYPE b)) {\n' |
+ ' throw new UnsupportedOperationException("Cannot sort immutable List.");\n' |
+ ' }\n' |
+ '\n' |
+ ' void copyFrom(List<Object> src, int srcStart, ' |
+ 'int dstStart, int count) {\n' |
+ ' throw new UnsupportedOperationException("This object is immutable.");\n' |
+ ' }\n' |
+ '\n' |
+ ' int indexOf($TYPE element, [int start = 0]) {\n' |
+ ' return _Lists.indexOf(this, element, start, this.length);\n' |
+ ' }\n' |
+ '\n' |
+ ' int lastIndexOf($TYPE element, [int start = null]) {\n' |
+ ' if (start === null) start = length - 1;\n' |
+ ' return _Lists.lastIndexOf(this, element, start);\n' |
+ ' }\n' |
+ '\n' |
+ ' int clear() {\n' |
+ ' throw new UnsupportedOperationException("Cannot clear immutable List.");\n' |
+ ' }\n' |
+ '\n' |
+ ' $TYPE removeLast() {\n' |
+ ' throw new UnsupportedOperationException("Cannot removeLast on immutable List.");\n' |
+ ' }\n' |
+ '\n' |
+ ' $TYPE last() {\n' |
+ ' return this[length - 1];\n' |
+ ' }\n' |
+ '\n' |
+ ' void forEach(void f($TYPE element)) {\n' |
+ ' _Collections.forEach(this, f);\n' |
+ ' }\n' |
+ '\n' |
+ ' Collection map(f($TYPE element)) {\n' |
+ ' return _Collections.map(this, [], f);\n' |
+ ' }\n' |
+ '\n' |
+ ' Collection<$TYPE> filter(bool f($TYPE element)) {\n' |
+ ' return _Collections.filter(this, new List<$TYPE>(), f);\n' |
+ ' }\n' |
+ '\n' |
+ ' bool every(bool f($TYPE element)) {\n' |
+ ' return _Collections.every(this, f);\n' |
+ ' }\n' |
+ '\n' |
+ ' bool some(bool f($TYPE element)) {\n' |
+ ' return _Collections.some(this, f);\n' |
+ ' }\n' |
+ '\n' |
+ ' void setRange(int start, int length, List<$TYPE> from, [int startFrom]) {\n' |
+ ' throw new UnsupportedOperationException("Cannot setRange on immutable List.");\n' |
+ ' }\n' |
+ '\n' |
+ ' void removeRange(int start, int length) {\n' |
+ ' throw new UnsupportedOperationException("Cannot removeRange on immutable List.");\n' |
+ ' }\n' |
+ '\n' |
+ ' void insertRange(int start, int length, [$TYPE initialValue]) {\n' |
+ ' throw new UnsupportedOperationException("Cannot insertRange on immutable List.");\n' |
+ ' }\n' |
+ '\n' |
+ ' List<$TYPE> getRange(int start, int length) {\n' |
+ ' throw new NotImplementedException();\n' |
+ ' }\n' |
+ '\n' |
+ ' bool isEmpty() {\n' |
+ ' return length == 0;\n' |
+ ' }\n' |
+ '\n' |
+ ' Iterator<$TYPE> iterator() {\n' |
+ ' return new _FixedSizeListIterator<$TYPE>(this);\n' |
+ ' }\n', |
+ TYPE=element_type) |
+ |
+ def _HasNativeIndexGetter(self, interface): |
+ return ('HasIndexGetter' in interface.ext_attrs or |
+ 'HasNumericIndexGetter' in interface.ext_attrs) |
+ |
+ def _EmitNativeIndexGetter(self, interface, element_type): |
+ method_name = '_index' |
+ self._members_emitter.Emit( |
+ '\n' |
+ ' $TYPE operator[](int index) { return $METHOD(this, index); }\n' |
+ ' static $TYPE $METHOD(var _this, int index) native;\n', |
+ TYPE=element_type, METHOD=method_name) |
+ |
+ def _HasNativeIndexSetter(self, interface): |
+ return 'HasCustomIndexSetter' in interface.ext_attrs |
+ |
+ def _EmitNativeIndexSetter(self, interface, element_type): |
+ method_name = '_set_index' |
+ self._members_emitter.Emit( |
+ '\n' |
+ ' void operator[]=(int index, $TYPE value) {\n' |
+ ' return $METHOD(this, index, value);\n' |
+ ' }\n' |
+ ' static $METHOD(_this, index, value) native;\n', |
+ TYPE=element_type, METHOD=method_name) |
+ |
+ def AddOperation(self, info): |
+ """ |
+ Arguments: |
+ info: An OperationInfo object. |
+ """ |
+ body = self._members_emitter.Emit( |
+ '\n' |
+ ' $TYPE $NAME($PARAMS) {\n' |
+ '$!BODY' |
+ ' }\n', |
+ TYPE=info.type_name, |
+ NAME=info.name, |
+ PARAMS=info.ParametersImplementationDeclaration()) |
+ |
+ # Process in order of ascending number of arguments to ensure missing |
+ # optional arguments are processed early. |
+ overloads = sorted(info.overloads, |
+ key=lambda overload: len(overload.arguments)) |
+ self._native_version = 0 |
+ fallthrough = self.GenerateDispatch(body, info, ' ', 0, overloads) |
+ if fallthrough: |
+ body.Emit(' throw "Incorrect number or type of arguments";\n'); |
+ |
+ def GenerateSingleOperation(self, emitter, info, indent, operation): |
+ """Generates a call to a single operation. |
+ |
+ Arguments: |
+ emitter: an Emitter for the body of a block of code. |
+ info: the compound information about the operation and its overloads. |
+ indent: an indentation string for generated code. |
+ operation: the IDLOperation to call. |
+ """ |
+ # TODO(sra): Do we need to distinguish calling with missing optional |
+ # arguments from passing 'null' which is represented as 'undefined'? |
+ def UnwrapArgExpression(name, type): |
+ # TODO: Type specific unwrapping. |
+ return '__dom_unwrap(%s)' % (name) |
+ |
+ def ArgNameAndUnwrapper(arg_info, overload_arg): |
+ (name, type, value) = arg_info |
+ return (name, UnwrapArgExpression(name, type)) |
+ |
+ names_and_unwrappers = [ArgNameAndUnwrapper(info.arg_infos[i], arg) |
+ for (i, arg) in enumerate(operation.arguments)] |
+ unwrap_args = [unwrap_arg for (_, unwrap_arg) in names_and_unwrappers] |
+ arg_names = [name for (name, _) in names_and_unwrappers] |
+ |
+ self._native_version += 1 |
+ native_name = self._MethodName('_', info.name) |
+ if self._native_version > 1: |
+ native_name = '%s_%s' % (native_name, self._native_version) |
+ |
+ argument_expressions = ', '.join(['this'] + arg_names) |
+ if info.type_name != 'void': |
+ emitter.Emit('$(INDENT)return $NATIVENAME($ARGS);\n', |
+ INDENT=indent, |
+ NATIVENAME=native_name, |
+ ARGS=argument_expressions) |
+ else: |
+ emitter.Emit('$(INDENT)$NATIVENAME($ARGS);\n' |
+ '$(INDENT)return;\n', |
+ INDENT=indent, |
+ NATIVENAME=native_name, |
+ ARGS=argument_expressions) |
+ |
+ self._members_emitter.Emit(' static $TYPE $NAME($PARAMS) native;\n', |
+ NAME=native_name, |
+ TYPE=info.type_name, |
+ PARAMS=', '.join(['receiver'] + arg_names) ) |
+ |
+ |
+ def GenerateDispatch(self, emitter, info, indent, position, overloads): |
+ """Generates a dispatch to one of the overloads. |
+ |
+ Arguments: |
+ emitter: an Emitter for the body of a block of code. |
+ info: the compound information about the operation and its overloads. |
+ indent: an indentation string for generated code. |
+ position: the index of the parameter to dispatch on. |
+ overloads: a list of the remaining IDLOperations to dispatch. |
+ |
+ Returns True if the dispatch can fall through on failure, False if the code |
+ always dispatches. |
+ """ |
+ |
+ def NullCheck(name): |
+ return '%s === null' % name |
+ |
+ def TypeCheck(name, type): |
+ return '%s is %s' % (name, type) |
+ |
+ if position == len(info.arg_infos): |
+ if len(overloads) > 1: |
+ raise Exception('Duplicate operations ' + str(overloads)) |
+ operation = overloads[0] |
+ self.GenerateSingleOperation(emitter, info, indent, operation) |
+ return False |
+ |
+ # FIXME: Consider a simpler dispatch that iterates over the |
+ # overloads and generates an overload specific check. Revisit |
+ # when we move to named optional arguments. |
+ |
+ # Partition the overloads to divide and conquer on the dispatch. |
+ positive = [] |
+ negative = [] |
+ first_overload = overloads[0] |
+ (param_name, param_type, param_default) = info.arg_infos[position] |
+ |
+ if position < len(first_overload.arguments): |
+ # FIXME: This will not work if the second overload has a more |
+ # precise type than the first. E.g., |
+ # void foo(Node x); |
+ # void foo(Element x); |
+ type = first_overload.arguments[position].type.id |
+ test = TypeCheck(param_name, type) |
+ pred = lambda op: len(op.arguments) > position and op.arguments[position].type.id == type |
+ else: |
+ type = None |
+ test = NullCheck(param_name) |
+ pred = lambda op: position >= len(op.arguments) |
+ |
+ for overload in overloads: |
+ if pred(overload): |
+ positive.append(overload) |
+ else: |
+ negative.append(overload) |
+ |
+ if positive and negative: |
+ (true_code, false_code) = emitter.Emit( |
+ '$(INDENT)if ($COND) {\n' |
+ '$!TRUE' |
+ '$(INDENT)} else {\n' |
+ '$!FALSE' |
+ '$(INDENT)}\n', |
+ COND=test, INDENT=indent) |
+ fallthrough1 = self.GenerateDispatch( |
+ true_code, info, indent + ' ', position + 1, positive) |
+ fallthrough2 = self.GenerateDispatch( |
+ false_code, info, indent + ' ', position, negative) |
+ return fallthrough1 or fallthrough2 |
+ |
+ if negative: |
+ raise Exception('Internal error, must be all positive') |
+ |
+ # All overloads require the same test. Do we bother? |
+ |
+ # If the test is the same as the method's formal parameter then checked mode |
+ # will have done the test already. (It could be null too but we ignore that |
+ # case since all the overload behave the same and we don't know which types |
+ # in the IDL are not nullable.) |
+ if type == param_type: |
+ return self.GenerateDispatch( |
+ emitter, info, indent, position + 1, positive) |
+ |
+ # Otherwise the overloads have the same type but the type is a substype of |
+ # the method's synthesized formal parameter. e.g we have overloads f(X) and |
+ # f(Y), implemented by the synthesized method f(Z) where X<Z and Y<Z. The |
+ # dispatch has removed f(X), leaving only f(Y), but there is no guarantee |
+ # that Y = Z-X, so we need to check for Y. |
+ true_code = emitter.Emit( |
+ '$(INDENT)if ($COND) {\n' |
+ '$!TRUE' |
+ '$(INDENT)}\n', |
+ COND=test, INDENT=indent) |
+ self.GenerateDispatch( |
+ true_code, info, indent + ' ', position + 1, positive) |
+ return True |