1df52df3ed
Rename dom implementation classes to _<Interface>DOMImpl to avoid clashes with html implementation classes, and import dom implementation classes directly into dart:html. Temporary introduce fake dom interfaces for overload dispatching (dom implementation classes don't implement html interfaces yet). R=antonm@google.com Review URL: https://chromiumcodereview.appspot.com//10383302 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@7954 260f80e4-7a28-3924-810f-c04153c831b5
1706 lines
59 KiB
Python
1706 lines
59 KiB
Python
#!/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."""
|
|
|
|
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 = set([
|
|
'Document.createElement',
|
|
'Document.createElementNS',
|
|
'Document.createEvent',
|
|
'Document.createTextNode',
|
|
'Document.createTouchList',
|
|
'Document.getElementById',
|
|
'Document.getElementsByClassName',
|
|
'Document.getElementsByName',
|
|
'Document.getElementsByTagName',
|
|
'Document.querySelectorAll',
|
|
'DocumentFragment.querySelectorAll',
|
|
'Element.childElementCount',
|
|
'Element.children',
|
|
'Element.className',
|
|
'Element.clientHeight',
|
|
'Element.clientLeft',
|
|
'Element.clientTop',
|
|
'Element.clientWidth',
|
|
'Element.firstElementChild',
|
|
'Element.getAttribute',
|
|
'Element.getBoundingClientRect',
|
|
'Element.getClientRects',
|
|
'Element.getElementsByClassName',
|
|
'Element.getElementsByTagName',
|
|
'Element.hasAttribute',
|
|
'Element.lastElementChild',
|
|
'Element.offsetHeight',
|
|
'Element.offsetLeft',
|
|
'Element.offsetTop',
|
|
'Element.offsetWidth',
|
|
'Element.querySelectorAll',
|
|
'Element.removeAttribute',
|
|
'Element.scrollHeight',
|
|
'Element.scrollLeft',
|
|
'Element.scrollTop',
|
|
'Element.scrollWidth',
|
|
'Element.setAttribute',
|
|
'Event.initEvent',
|
|
'EventTarget.addEventListener',
|
|
'EventTarget.dispatchEvent',
|
|
'EventTarget.removeEventListener',
|
|
'MouseEvent.initMouseEvent',
|
|
'Node.appendChild',
|
|
'Node.attributes',
|
|
'Node.childNodes',
|
|
'Node.firstChild',
|
|
'Node.lastChild',
|
|
"Node.nodeType",
|
|
'Node.removeChild',
|
|
'Node.replaceChild',
|
|
'NodeSelector.querySelectorAll',
|
|
'Storage.length',
|
|
'Storage.clear',
|
|
'Storage.getItem',
|
|
'Storage.key',
|
|
'Storage.removeItem',
|
|
'Storage.setItem',
|
|
'Window.getComputedStyle',
|
|
])
|
|
|
|
_manually_generated_html_members = set([
|
|
'Document.querySelectorAll',
|
|
'Document.querySelector',
|
|
])
|
|
|
|
# Members from the standard dom that exist in the dart:html library with
|
|
# identical functionality but with cleaner names.
|
|
_html_library_renames = {
|
|
'Document.defaultView': 'window',
|
|
'DocumentFragment.querySelector': 'query',
|
|
'NodeSelector.querySelector': 'query',
|
|
'Element.querySelector': 'query',
|
|
'Element.webkitMatchesSelector' : 'matchesSelector',
|
|
'Element.scrollIntoViewIfNeeded': 'scrollIntoView',
|
|
'Node.cloneNode': 'clone',
|
|
'Node.nextSibling': 'nextNode',
|
|
'Node.ownerDocument': 'document',
|
|
'Node.parentNode': 'parent',
|
|
'Node.previousSibling': 'previousNode',
|
|
'Node.textContent': 'text',
|
|
'SVGElement.className': '$dom_svgClassName',
|
|
'SVGAnimatedString.className': '$dom_svgClassName',
|
|
'SVGStylable.className': '$dom_svgClassName',
|
|
}
|
|
|
|
#TODO(jacobr): inject annotations into the interfaces based on this table and
|
|
# on _html_library_renames.
|
|
_injected_doc_fragments = {
|
|
'Element.query': ' /** @domName Element.querySelector, Document.getElementById */',
|
|
}
|
|
# 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.
|
|
# Syntax is: ClassName.(get\.|set\.)?MemberName
|
|
# Using get: and set: is optional and should only be used when a getter needs
|
|
# to be suppressed but not the setter, etc.
|
|
# 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.*",
|
|
"Document.get:forms",
|
|
# "Document.get:selectedStylesheetSet",
|
|
# "Document.set:selectedStylesheetSet",
|
|
# "Document.get:preferredStylesheetSet",
|
|
"Document.get:links",
|
|
"Document.set:domain",
|
|
"Document.get:implementation",
|
|
"Document.createAttributeNS",
|
|
"Document.get:inputEncoding",
|
|
"Document.get:height",
|
|
"Document.get:width",
|
|
"Element.getElementsByTagNameNS",
|
|
"Document.get:compatMode",
|
|
"Document.importNode",
|
|
"Document.evaluate",
|
|
"Document.get:images",
|
|
"Document.createExpression",
|
|
"Document.getOverrideStyle",
|
|
"Document.xmlStandalone",
|
|
"Document.createComment",
|
|
"Document.adoptNode",
|
|
"Document.get:characterSet",
|
|
"Document.createAttribute",
|
|
"Document.get:URL",
|
|
"Document.createEntityReference",
|
|
"Document.get:documentURI",
|
|
"Document.set:documentURI",
|
|
"Document.createNodeIterator",
|
|
"Document.createProcessingInstruction",
|
|
"Document.get:doctype",
|
|
"Document.createTreeWalker",
|
|
"Document.location",
|
|
"Document.createNSResolver",
|
|
"Document.get:xmlEncoding",
|
|
"Document.get:defaultCharset",
|
|
"Document.get:applets",
|
|
"Document.getSelection",
|
|
"Document.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",
|
|
"BodyElement.text",
|
|
"AnchorElement.text",
|
|
"OptionElement.text",
|
|
"ScriptElement.text",
|
|
"TitleElement.text",
|
|
# "EventSource.get:url",
|
|
# TODO(jacobr): should these be removed?
|
|
"Document.close",
|
|
"Document.hasFocus",
|
|
|
|
"Document.vlinkColor",
|
|
"Document.captureEvents",
|
|
"Document.releaseEvents",
|
|
"Document.get:compatMode",
|
|
"Document.designMode",
|
|
"Document.dir",
|
|
"Document.all",
|
|
"Document.write",
|
|
"Document.fgColor",
|
|
"Document.bgColor",
|
|
"Document.get:plugins",
|
|
"Document.alinkColor",
|
|
"Document.get:embeds",
|
|
"Document.open",
|
|
"Document.clear",
|
|
"Document.get:scripts",
|
|
"Document.writeln",
|
|
"Document.linkColor",
|
|
"Element.get:itemRef",
|
|
"Element.outerText",
|
|
"Element.accessKey",
|
|
"Element.get:itemType",
|
|
"Element.innerText",
|
|
"Element.set:outerHTML",
|
|
"Element.itemScope",
|
|
"Element.itemValue",
|
|
"Element.itemId",
|
|
"Element.get:itemProp",
|
|
'Element.scrollIntoView',
|
|
'Element.get:classList',
|
|
"EmbedElement.getSVGDocument",
|
|
"FormElement.get:elements",
|
|
"HTMLFrameElement.*",
|
|
"HTMLFrameSetElement.*",
|
|
"HtmlElement.version",
|
|
"HtmlElement.manifest",
|
|
"Document.version",
|
|
"Document.manifest",
|
|
# "IFrameElement.getSVGDocument", #TODO(jacobr): should this be removed
|
|
"InputElement.dirName",
|
|
"HTMLIsIndexElement.*",
|
|
"ObjectElement.getSVGDocument",
|
|
"HTMLOptionsCollection.*",
|
|
"HTMLPropertiesCollection.*",
|
|
"SelectElement.remove",
|
|
"TextAreaElement.dirName",
|
|
"NamedNodeMap.*",
|
|
"Node.isEqualNode",
|
|
"Node.get:TEXT_NODE",
|
|
"Node.hasAttributes",
|
|
"Node.get:DOCUMENT_TYPE_NODE",
|
|
"Node.get:DOCUMENT_POSITION_FOLLOWING",
|
|
"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:DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC",
|
|
"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.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",
|
|
"IFrameElement.get:contentDocument",
|
|
"IFrameElement.get:contentWindow",
|
|
"Window.get:frameElement",
|
|
"Window.get:top",
|
|
])
|
|
|
|
# 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', 'touchenter', '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',
|
|
'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',
|
|
'abort': 'abort',
|
|
'addstream': 'addStream',
|
|
'addtrack': 'addTrack',
|
|
'audioend': 'audioEnd',
|
|
'audioprocess': 'audioProcess',
|
|
'audiostart': 'audioStart',
|
|
'beforecopy': 'beforeCopy',
|
|
'beforecut': 'beforeCut',
|
|
'beforepaste': 'beforePaste',
|
|
'beforeunload': 'beforeUnload',
|
|
'blocked': 'blocked',
|
|
'blur': 'blur',
|
|
'cached': 'cached',
|
|
'canplay': 'canPlay',
|
|
'canplaythrough': 'canPlayThrough',
|
|
'change': 'change',
|
|
'chargingchange': 'chargingChange',
|
|
'chargingtimechange': 'chargingTimeChange',
|
|
'checking': 'checking',
|
|
'click': 'click',
|
|
'close': 'close',
|
|
'complete': 'complete',
|
|
'connect': 'connect',
|
|
'connecting': 'connecting',
|
|
'contextmenu': 'contextMenu',
|
|
'copy': 'copy',
|
|
'cuechange': 'cueChange',
|
|
'cut': 'cut',
|
|
'dblclick': 'doubleClick',
|
|
'devicemotion': 'deviceMotion',
|
|
'deviceorientation': 'deviceOrientation',
|
|
'dischargingtimechange': 'dischargingTimeChange',
|
|
'display': 'display',
|
|
'downloading': 'downloading',
|
|
'drag': 'drag',
|
|
'dragend': 'dragEnd',
|
|
'dragenter': 'dragEnter',
|
|
'dragleave': 'dragLeave',
|
|
'dragover': 'dragOver',
|
|
'dragstart': 'dragStart',
|
|
'drop': 'drop',
|
|
'durationchange': 'durationChange',
|
|
'emptied': 'emptied',
|
|
'end': 'end',
|
|
'ended': 'ended',
|
|
'enter': 'enter',
|
|
'error': 'error',
|
|
'exit': 'exit',
|
|
'focus': 'focus',
|
|
'hashchange': 'hashChange',
|
|
'input': 'input',
|
|
'invalid': 'invalid',
|
|
'keydown': 'keyDown',
|
|
'keypress': 'keyPress',
|
|
'keyup': 'keyUp',
|
|
'levelchange': 'levelChange',
|
|
'load': 'load',
|
|
'loadeddata': 'loadedData',
|
|
'loadedmetadata': 'loadedMetadata',
|
|
'loadend': 'loadEnd',
|
|
'loadstart': 'loadStart',
|
|
'message': 'message',
|
|
'mousedown': 'mouseDown',
|
|
'mousemove': 'mouseMove',
|
|
'mouseout': 'mouseOut',
|
|
'mouseover': 'mouseOver',
|
|
'mouseup': 'mouseUp',
|
|
'mousewheel': 'mouseWheel',
|
|
'nomatch': 'noMatch',
|
|
'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',
|
|
'removestream': 'removeStream',
|
|
'reset': 'reset',
|
|
'resize': 'resize',
|
|
'result': 'result',
|
|
'resultdeleted': 'resultDeleted',
|
|
'scroll': 'scroll',
|
|
'search': 'search',
|
|
'seeked': 'seeked',
|
|
'seeking': 'seeking',
|
|
'select': 'select',
|
|
'selectionchange': 'selectionChange',
|
|
'selectstart': 'selectStart',
|
|
'show': 'show',
|
|
'soundend': 'soundEnd',
|
|
'soundstart': 'soundStart',
|
|
'speechend': 'speechEnd',
|
|
'speechstart': 'speechStart',
|
|
'stalled': 'stalled',
|
|
'start': 'start',
|
|
'statechange': 'stateChange',
|
|
'storage': 'storage',
|
|
'submit': 'submit',
|
|
'success': 'success',
|
|
'suspend': 'suspend',
|
|
'timeupdate': 'timeUpdate',
|
|
'touchcancel': 'touchCancel',
|
|
'touchend': 'touchEnd',
|
|
'touchenter': 'touchEnter',
|
|
'touchleave': 'touchLeave',
|
|
'touchmove': 'touchMove',
|
|
'touchstart': 'touchStart',
|
|
'unload': 'unload',
|
|
'updateready': 'updateReady',
|
|
'versionchange': 'versionChange',
|
|
'volumechange': 'volumeChange',
|
|
'waiting': 'waiting',
|
|
'webkitAnimationEnd': 'animationEnd',
|
|
'webkitAnimationIteration': 'animationIteration',
|
|
'webkitAnimationStart': 'animationStart',
|
|
'webkitfullscreenchange': 'fullscreenChange',
|
|
'webkitfullscreenerror': 'fullscreenError',
|
|
'webkitkeyadded': 'keyAdded',
|
|
'webkitkeyerror': 'keyError',
|
|
'webkitkeymessage': 'keyMessage',
|
|
'webkitneedkey': 'needKey',
|
|
'webkitSpeechChange': 'speechChange',
|
|
'webkitsourceclose': 'sourceClose',
|
|
'webkitsourceended': 'sourceEnded',
|
|
'webkitsourceopen': 'sourceOpen',
|
|
'webkitTransitionEnd': 'transitionEnd',
|
|
'write': 'write',
|
|
'writeend': 'writeEnd',
|
|
'writestart': 'writeStart'
|
|
}
|
|
|
|
|
|
|
|
# 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 ElementCtorInfo(object):
|
|
def __init__(self, tag=None, params=[], opt_params=[],
|
|
factory_provider_name='_Elements'):
|
|
self.tag = tag
|
|
self.params = params
|
|
self.opt_params = opt_params
|
|
self.factory_provider_name = factory_provider_name
|
|
|
|
def ConstructorInfo(self, interface):
|
|
info = OperationInfo()
|
|
info.overloads = None
|
|
info.declared_name = interface.id
|
|
info.name = interface.id
|
|
info.js_name = None
|
|
info.type_name = interface.id
|
|
info.param_infos = map(lambda tXn: ParamInfo(tXn[1], None, tXn[0], 'null'),
|
|
self.opt_params)
|
|
return info
|
|
|
|
_html_element_constructors = {
|
|
'AnchorElement' : ElementCtorInfo(tag='a', opt_params=[('String', 'href')]),
|
|
'AreaElement': 'area',
|
|
'ButtonElement': 'button',
|
|
'BRElement': 'br',
|
|
'BaseElement': 'base',
|
|
'BodyElement': 'body',
|
|
'ButtonElement': 'button',
|
|
'CanvasElement':
|
|
ElementCtorInfo(tag='canvas',
|
|
opt_params=[('int', 'height'), ('int', 'width')]),
|
|
'DListElement': 'dl',
|
|
'DetailsElement': 'details',
|
|
'DivElement': 'div',
|
|
'EmbedElement': 'embed',
|
|
'FieldSetElement': 'fieldset',
|
|
'Form': 'form',
|
|
'HRElement': 'hr',
|
|
'HeadElement': 'head',
|
|
'HtmlElement': 'html',
|
|
'IFrameElement': 'iframe',
|
|
'ImageElement':
|
|
ElementCtorInfo(tag='img',
|
|
opt_params=[('String', 'src'),
|
|
('int', 'height'), ('int', 'width')]),
|
|
'InputElement':
|
|
ElementCtorInfo(tag='input', opt_params=[('String', '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',
|
|
'SourceElement': 'source',
|
|
'SpanElement': 'span',
|
|
'StyleElement': 'style',
|
|
'TableCaptionElement': 'caption',
|
|
'TableCellElement': 'td',
|
|
'TableColElement': 'col',
|
|
'TableElement': 'table',
|
|
'TableRowElement': 'tr',
|
|
#'TableSectionElement' <thead> <tbody> <tfoot>
|
|
'TextAreaElement': 'textarea',
|
|
'TitleElement': 'title',
|
|
'TrackElement': 'track',
|
|
'UListElement': 'ul',
|
|
'VideoElement': 'video'
|
|
}
|
|
|
|
# These classes require an explicit declaration for the "on" method even though
|
|
# they don't declare any unique events, because the concrete class hierarchy
|
|
# doesn't match the interface hierarchy.
|
|
_html_explicit_event_classes = set(['DocumentFragment'])
|
|
|
|
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])
|
|
|
|
def DomToHtmlEvent(event_name):
|
|
assert event_name in _html_event_names, \
|
|
'No known html event name for event: ' + event_name
|
|
return _html_event_names[event_name]
|
|
|
|
# ------------------------------------------------------------------------------
|
|
class HtmlSystemShared(object):
|
|
|
|
def __init__(self, database):
|
|
self._event_classes = set()
|
|
self._seen_event_names = {}
|
|
self._database = database
|
|
self._inheritance_closure = _ComputeInheritanceClosure(database)
|
|
|
|
def _AllowInHtmlLibrary(self, interface, member, member_prefix):
|
|
return not self._Matches(interface, member, member_prefix,
|
|
_html_library_remove)
|
|
|
|
def _Matches(self, interface, member, member_prefix, candidates):
|
|
for interface_name in self._AllAncestorInterfaces(interface):
|
|
if (DartType(interface_name) + '.' + member in candidates or
|
|
DartType(interface_name) + '.' + member_prefix + member in candidates):
|
|
return True
|
|
return False
|
|
|
|
def _AllAncestorInterfaces(self, interface):
|
|
return [interface.id] + self._inheritance_closure[interface.id]
|
|
|
|
def RenameInHtmlLibrary(self, interface, member, member_prefix='',
|
|
implementation_class=False):
|
|
"""
|
|
Returns the name of the member in the HTML library or None if the member is
|
|
suppressed in the HTML library
|
|
"""
|
|
if not self._AllowInHtmlLibrary(interface, member, member_prefix):
|
|
return None
|
|
|
|
target_name = member
|
|
for interface_name in self._AllAncestorInterfaces(interface):
|
|
name = interface_name + '.' + member
|
|
if name in _html_library_renames:
|
|
target_name = _html_library_renames[name]
|
|
name = interface.id + '.' + member_prefix + member
|
|
if name in _html_library_renames:
|
|
target_name = _html_library_renames[name]
|
|
|
|
if not target_name.startswith('_'):
|
|
if self._PrivateInHtmlLibrary(interface, member, member_prefix):
|
|
if not target_name.startswith('$dom_'): # e.g. $dom_svgClassName
|
|
target_name = '$dom_' + target_name
|
|
elif implementation_class and self._ManuallyGeneratedInHtmlLibrary(
|
|
interface, member, member_prefix):
|
|
target_name = '_' + target_name
|
|
|
|
# No rename required
|
|
return target_name
|
|
|
|
def _PrivateInHtmlLibrary(self, interface, member, member_prefix):
|
|
return self._Matches(interface, member, member_prefix,
|
|
_private_html_members)
|
|
|
|
def _ManuallyGeneratedInHtmlLibrary(self, interface, member, member_prefix):
|
|
return self._Matches(interface, member, member_prefix,
|
|
_manually_generated_html_members)
|
|
|
|
# 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 and DocumentFragment inherit
|
|
# from Element in our IDL.
|
|
if interface.id == 'Document' or interface.id == 'DocumentFragment':
|
|
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
|
|
|
|
def GetParentEventsClass(self, interface):
|
|
parent_event_classes = self.GetParentsEventsClasses(interface)
|
|
if len(parent_event_classes) != 1:
|
|
raise Exception('Only one parent event class allowed ' + interface.id)
|
|
return parent_event_classes[0]
|
|
|
|
def _ImplClassName(self, type_name):
|
|
return '_' + type_name + 'Impl'
|
|
|
|
# This returns two values: the first is whether or not an "on" property should
|
|
# be generated for the interface, and the second is the event attributes to
|
|
# generate if it should.
|
|
def GetEventAttributes(self, interface):
|
|
events = set([attr for attr in interface.attributes
|
|
if attr.type.id == 'EventListener'])
|
|
|
|
if events or interface.id in _html_explicit_event_classes:
|
|
return True, events
|
|
else:
|
|
return False, None
|
|
|
|
def IsPrivate(self, name):
|
|
return name.startswith('_')
|
|
|
|
class HtmlSystem(System):
|
|
|
|
def __init__(self, templates, database, emitters, output_dir):
|
|
super(HtmlSystem, self).__init__(
|
|
templates, database, emitters, output_dir)
|
|
self._shared = HtmlSystemShared(database)
|
|
|
|
class HtmlInterfacesSystem(HtmlSystem):
|
|
|
|
def __init__(self, templates, database, emitters, output_dir):
|
|
super(HtmlInterfacesSystem, self).__init__(
|
|
templates, database, emitters, output_dir)
|
|
self._dart_interface_file_paths = []
|
|
self._factory_provider_emitters = {}
|
|
|
|
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(
|
|
self, interface, dart_interface_code,
|
|
template,
|
|
common_prefix, super_interface_name,
|
|
source_filter, self._shared)
|
|
|
|
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):
|
|
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)
|
|
|
|
def _EmitterForFactoryProviderBody(self, name):
|
|
if name not in self._factory_provider_emitters:
|
|
path = self._FilePathForDartInterface(name)
|
|
self._dart_interface_file_paths.append(path)
|
|
template = self._templates.Load('factoryprovider_%s.darttemplate' % name)
|
|
file_emitter = self._emitters.FileEmitter(path)
|
|
self._factory_provider_emitters[name] = file_emitter.Emit(template)
|
|
return self._factory_provider_emitters[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, system, interface, emitter, template,
|
|
common_prefix, super_interface, source_filter, shared):
|
|
super(HtmlDartInterfaceGenerator, self).__init__(system, interface,
|
|
emitter, template, common_prefix, super_interface, source_filter)
|
|
self._shared = shared
|
|
|
|
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(DartType(parent.type.id))
|
|
elif '<' in parent.type.id:
|
|
# Parent is a Dart collection type.
|
|
# TODO(vsm): Make this check more robust.
|
|
extends.append(DartType(parent.type.id))
|
|
else:
|
|
suppressed_extends.append('%s.%s' %
|
|
(self._common_prefix, DartType(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))
|
|
|
|
factory_provider = None
|
|
constructor_info = AnalyzeConstructor(self._interface)
|
|
if constructor_info:
|
|
factory_provider = '_' + typename + 'FactoryProvider';
|
|
|
|
if not constructor_info:
|
|
(constructor_info, factory_provider) = self._EmitElementFactory(typename)
|
|
|
|
if typename in interface_factories:
|
|
factory_provider = interface_factories[typename]
|
|
|
|
if factory_provider:
|
|
extends_str += ' default ' + factory_provider
|
|
|
|
# TODO(vsm): Add appropriate package / namespace syntax.
|
|
(self._type_comment_emitter,
|
|
self._members_emitter,
|
|
self._top_level_emitter) = self._emitter.Emit(
|
|
self._template + '$!TOP_LEVEL',
|
|
ID=typename,
|
|
EXTENDS=extends_str)
|
|
|
|
self._type_comment_emitter.Emit("/// @domName $DOMNAME",
|
|
DOMNAME=self._interface.doc_js_name)
|
|
|
|
if constructor_info:
|
|
self._members_emitter.Emit(
|
|
'\n'
|
|
' $CTOR($PARAMS);\n',
|
|
CTOR=typename,
|
|
PARAMS=constructor_info.ParametersInterfaceDeclaration());
|
|
|
|
element_type = MaybeTypedArrayElementTypeInHierarchy(
|
|
self._interface, self._system._database)
|
|
if element_type:
|
|
self._members_emitter.Emit(
|
|
'\n'
|
|
' $CTOR(int length);\n'
|
|
'\n'
|
|
' $CTOR.fromList(List<$TYPE> list);\n'
|
|
'\n'
|
|
' $CTOR.fromBuffer(ArrayBuffer buffer,'
|
|
' [int byteOffset, int length]);\n',
|
|
CTOR=self._interface.id,
|
|
TYPE=DartType(element_type))
|
|
|
|
emit_events, events = self._shared.GetEventAttributes(self._interface)
|
|
if not emit_events:
|
|
return
|
|
elif events:
|
|
self.AddEventAttributes(events)
|
|
else:
|
|
self._EmitEventGetter(self._shared.GetParentEventsClass(self._interface))
|
|
|
|
def _EmitElementFactory(self, typename):
|
|
"""Returns pair (constructor_info, factory_provider_name)."""
|
|
if typename not in _html_element_constructors:
|
|
return (None, None)
|
|
info = _html_element_constructors[typename]
|
|
if isinstance(info, str): info = ElementCtorInfo(tag=info)
|
|
constructor_info = info.ConstructorInfo(self._interface)
|
|
em = self._system._EmitterForFactoryProviderBody(info.factory_provider_name)
|
|
inits = em.Emit(
|
|
'\n'
|
|
' factory $CONSTRUCTOR($PARAMS) {\n'
|
|
' $CLASSNAME _e = _document.$dom_createElement("$TAG");\n'
|
|
'$!INITS'
|
|
' return _e;\n'
|
|
' }\n',
|
|
CONSTRUCTOR=typename,
|
|
CLASSNAME='_' + typename + 'Impl', # TODO: fix
|
|
TAG=info.tag,
|
|
PARAMS=constructor_info.ParametersInterfaceDeclaration())
|
|
for param in constructor_info.param_infos:
|
|
inits.Emit(' if ($E != null) _e.$E = $E;\n', E=param.name)
|
|
|
|
return (constructor_info, info.factory_provider_name)
|
|
|
|
|
|
def AddAttribute(self, getter, setter):
|
|
dom_name = DartDomNameOfAttribute(getter)
|
|
html_getter_name = self._shared.RenameInHtmlLibrary(
|
|
self._interface, dom_name, 'get:')
|
|
html_setter_name = self._shared.RenameInHtmlLibrary(
|
|
self._interface, dom_name, 'set:')
|
|
|
|
if not html_getter_name or self._shared.IsPrivate(html_getter_name):
|
|
getter = None
|
|
if not html_setter_name or self._shared.IsPrivate(html_setter_name):
|
|
setter = None
|
|
if not getter and not setter:
|
|
return
|
|
|
|
# We don't yet handle inconsistent renames of the getter and setter yet.
|
|
if html_getter_name and html_setter_name:
|
|
assert html_getter_name == html_setter_name
|
|
|
|
self._members_emitter.Emit('\n /** @domName $DOMINTERFACE.$DOMNAME */',
|
|
DOMINTERFACE=getter.doc_js_interface_name,
|
|
DOMNAME=dom_name)
|
|
if (getter and setter and
|
|
DartType(getter.type.id) == DartType(setter.type.id)):
|
|
self._members_emitter.Emit('\n $TYPE $NAME;\n',
|
|
NAME=html_getter_name,
|
|
TYPE=DartType(getter.type.id));
|
|
return
|
|
if getter and not setter:
|
|
self._members_emitter.Emit('\n final $TYPE $NAME;\n',
|
|
NAME=html_getter_name,
|
|
TYPE=DartType(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.
|
|
"""
|
|
html_name = self._shared.RenameInHtmlLibrary(
|
|
self._interface, info.name)
|
|
if html_name and not self._shared.IsPrivate(html_name):
|
|
self._members_emitter.Emit('\n /** @domName $DOMINTERFACE.$DOMNAME */',
|
|
DOMINTERFACE=info.overloads[0].doc_js_interface_name,
|
|
DOMNAME=info.name)
|
|
|
|
self._members_emitter.Emit('\n'
|
|
' $TYPE $NAME($PARAMS);\n',
|
|
TYPE=info.type_name,
|
|
NAME=html_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._shared._event_classes.add(self._interface.id)
|
|
events_interface = self._interface.id + 'Events'
|
|
self._EmitEventGetter(events_interface)
|
|
|
|
events_members = self._emitter.Emit(
|
|
'\ninterface $INTERFACE extends $PARENTS {\n$!MEMBERS}\n',
|
|
INTERFACE=events_interface,
|
|
PARENTS=', '.join(
|
|
self._shared.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)
|
|
|
|
def _EmitEventGetter(self, events_interface):
|
|
self._members_emitter.Emit(
|
|
'\n /**'
|
|
'\n * @domName EventTarget.addEventListener, '
|
|
'EventTarget.removeEventListener, EventTarget.dispatchEvent'
|
|
'\n */'
|
|
'\n $TYPE get on();\n',
|
|
TYPE=events_interface)
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# 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,
|
|
shared):
|
|
super(HtmlFrogClassGenerator, self).__init__(
|
|
system, interface, template, super_interface, dart_code)
|
|
self._shared = shared
|
|
|
|
def _ImplClassName(self, type_name):
|
|
return self._shared._ImplClassName(type_name)
|
|
|
|
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
|
|
if IsDartCollectionType(supertype):
|
|
# List methods are injected in AddIndexer.
|
|
pass
|
|
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<%s>' % DartType(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 + '"')
|
|
if self._members_emitter == None:
|
|
raise Exception("Class %s doesn't use the $!MEMBERS variable" %
|
|
self._class_name)
|
|
|
|
# Emit a factory provider class for the constructor.
|
|
constructor_info = AnalyzeConstructor(interface)
|
|
if constructor_info:
|
|
self._EmitFactoryProvider(interface_name, constructor_info)
|
|
|
|
emit_events, events = self._shared.GetEventAttributes(self._interface)
|
|
if not emit_events:
|
|
return
|
|
elif events:
|
|
self.AddEventAttributes(events)
|
|
else:
|
|
parent_events_class = self._shared.GetParentEventsClass(self._interface)
|
|
self._EmitEventGetter('_' + parent_events_class + 'Impl')
|
|
|
|
def _EmitFactoryProvider(self, interface_name, constructor_info):
|
|
template_file = 'factoryprovider_%s.darttemplate' % interface_name
|
|
template = self._system._templates.TryLoad(template_file)
|
|
if not template:
|
|
template = self._system._templates.Load('factoryprovider.darttemplate')
|
|
|
|
factory_provider = '_' + interface_name + 'FactoryProvider'
|
|
emitter = self._system._ImplFileEmitter(factory_provider)
|
|
emitter.Emit(
|
|
template,
|
|
FACTORYPROVIDER=factory_provider,
|
|
CONSTRUCTOR=interface_name,
|
|
PARAMETERS=constructor_info.ParametersImplementationDeclaration(),
|
|
NAMED_CONSTRUCTOR=constructor_info.name or interface_name,
|
|
ARGUMENTS=constructor_info.ParametersAsArgumentList())
|
|
|
|
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; }
|
|
#
|
|
self._members_emitter.Emit(
|
|
'\n'
|
|
' $TYPE operator[](int index) native "return 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) native "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 UnsupportedOperationException("Cannot assign element of immutable List.");\n'
|
|
' }\n',
|
|
TYPE=self._NarrowInputType(element_type))
|
|
|
|
# TODO(sra): Use separate mixins for mutable implementations of List<T>.
|
|
# TODO(sra): Use separate mixins for typed array implementations of List<T>.
|
|
if self._interface.id != 'NodeList':
|
|
template_file = 'immutable_list_mixin.darttemplate'
|
|
template = self._system._templates.Load(template_file)
|
|
self._members_emitter.Emit(template, E=DartType(element_type))
|
|
|
|
def AddAttribute(self, getter, setter):
|
|
|
|
html_getter_name = self._shared.RenameInHtmlLibrary(
|
|
self._interface, DartDomNameOfAttribute(getter), 'get:',
|
|
implementation_class=True)
|
|
html_setter_name = self._shared.RenameInHtmlLibrary(
|
|
self._interface, DartDomNameOfAttribute(getter), 'set:',
|
|
implementation_class=True)
|
|
|
|
if not html_getter_name:
|
|
getter = None
|
|
if not html_setter_name:
|
|
setter = None
|
|
|
|
if not getter and not setter:
|
|
return
|
|
|
|
if ((getter and html_getter_name != getter.id) or
|
|
(setter and html_setter_name != setter.id)):
|
|
if getter:
|
|
self._AddRenamingGetter(getter, html_getter_name)
|
|
if setter:
|
|
self._AddRenamingSetter(setter, html_setter_name)
|
|
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 DartType(getter.type.id) == DartType(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=DartDomNameOfAttribute(getter),
|
|
TYPE=output_type)
|
|
return
|
|
|
|
self._members_emitter.Emit('\n // Shadowing definition.')
|
|
self._AddAttributeUsingProperties(getter, setter)
|
|
return
|
|
|
|
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=DartDomNameOfAttribute(getter),
|
|
TYPE=output_type)
|
|
return
|
|
if getter and not setter:
|
|
self._members_emitter.Emit(
|
|
'\n final $TYPE $NAME;\n',
|
|
NAME=DartDomNameOfAttribute(getter),
|
|
TYPE=output_type)
|
|
return
|
|
self._AddAttributeUsingProperties(getter, setter)
|
|
|
|
def _AddAttributeUsingProperties(self, getter, setter):
|
|
if getter:
|
|
self._AddGetter(getter)
|
|
if setter:
|
|
self._AddSetter(setter)
|
|
|
|
def _AddGetter(self, attr):
|
|
self._AddRenamingGetter(attr, DartDomNameOfAttribute(attr))
|
|
|
|
def _AddSetter(self, attr):
|
|
self._AddRenamingSetter(attr, DartDomNameOfAttribute(attr))
|
|
|
|
def _AddRenamingGetter(self, attr, html_name):
|
|
return_type = self._NarrowOutputType(attr.type.id)
|
|
self._members_emitter.Emit(
|
|
'\n $TYPE get $(HTML_NAME)() native "return this.$NAME;";\n',
|
|
HTML_NAME=html_name,
|
|
NAME=attr.id,
|
|
TYPE=return_type)
|
|
|
|
def _AddRenamingSetter(self, attr, html_name):
|
|
self._members_emitter.Emit(
|
|
'\n void set $HTML_NAME($TYPE value)'
|
|
' native "this.$NAME = value;";\n',
|
|
HTML_NAME=html_name,
|
|
NAME=attr.id,
|
|
TYPE=self._NarrowInputType(attr.type.id))
|
|
|
|
def AddOperation(self, info):
|
|
"""
|
|
Arguments:
|
|
info: An OperationInfo object.
|
|
"""
|
|
html_name = self._shared.RenameInHtmlLibrary(
|
|
self._interface, info.name, implementation_class=True)
|
|
if not html_name:
|
|
return
|
|
|
|
# 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',
|
|
TYPE=return_type,
|
|
HTML_NAME=html_name,
|
|
NAME=info.declared_name,
|
|
PARAMS=info.ParametersImplementationDeclaration(
|
|
lambda type_name: self._NarrowInputType(type_name)))
|
|
|
|
operation_emitter.Emit(
|
|
'\n'
|
|
' $TYPE $(HTML_NAME)($PARAMS) native "$NAME";\n')
|
|
else:
|
|
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._EmitEventGetter(events_class)
|
|
|
|
self._shared._event_classes.add(self._interface.id)
|
|
|
|
parent_event_class = self._shared.GetParentEventsClass(self._interface)
|
|
|
|
# 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',
|
|
CLASSNAME=events_class,
|
|
INTERFACE=events_interface,
|
|
SUPER='_' + parent_event_class + '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)
|
|
|
|
def _EmitEventGetter(self, events_class):
|
|
self._members_emitter.Emit(
|
|
'\n $TYPE get on() =>\n new $TYPE(this);\n',
|
|
TYPE=events_class)
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
class HtmlFrogSystem(HtmlSystem):
|
|
|
|
def __init__(self, templates, database, emitters, output_dir):
|
|
super(HtmlFrogSystem, self).__init__(
|
|
templates, database, emitters, output_dir)
|
|
self._dart_frog_file_paths = []
|
|
|
|
|
|
def InterfaceGenerator(self,
|
|
interface,
|
|
common_prefix,
|
|
super_interface_name,
|
|
source_filter):
|
|
"""."""
|
|
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._ImplFileEmitter(interface.id)
|
|
return HtmlFrogClassGenerator(self, interface, template,
|
|
super_interface_name, dart_code, self._shared)
|
|
|
|
def GenerateLibraries(self):
|
|
self._GenerateLibFile(
|
|
'html_frog.darttemplate',
|
|
os.path.join(self._output_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 _ImplFileEmitter(self, name):
|
|
"""Returns the file emitter of the Frog implementation file."""
|
|
# TODO(jmesserly): is this the right path
|
|
path = os.path.join(self._output_dir, 'html', 'frog', '%s.dart' % name)
|
|
self._dart_frog_file_paths.append(path)
|
|
return self._emitters.FileEmitter(path)
|
|
|
|
# -----------------------------------------------------------------------------
|
|
|
|
class HtmlDartiumSystem(HtmlSystem):
|
|
|
|
def __init__(self, templates, database, emitters, auxiliary_dir,
|
|
dom_implementation_classes, output_dir):
|
|
"""Prepared for generating wrapping implementation.
|
|
|
|
- Creates emitter for Dart code.
|
|
"""
|
|
super(HtmlDartiumSystem, self).__init__(
|
|
templates, database, emitters, output_dir)
|
|
self._auxiliary_dir = auxiliary_dir
|
|
self._dom_implementation_classes = dom_implementation_classes
|
|
self._shared = HtmlSystemShared(database)
|
|
self._dart_dartium_file_paths = []
|
|
self._wrap_cases = []
|
|
|
|
def InterfaceGenerator(self,
|
|
interface,
|
|
common_prefix,
|
|
super_interface_name,
|
|
source_filter):
|
|
"""."""
|
|
template_file = 'impl_%s.darttemplate' % interface.id
|
|
template = self._templates.TryLoad(template_file)
|
|
# TODO(jacobr): change this name as it is confusing.
|
|
if not template:
|
|
template = self._templates.Load('frog_impl.darttemplate')
|
|
|
|
dart_code = self._ImplFileEmitter(interface.id)
|
|
return HtmlDartiumInterfaceGenerator(self, interface, template,
|
|
super_interface_name, dart_code, self._BaseDefines(interface),
|
|
self._shared)
|
|
|
|
def _ImplFileEmitter(self, name):
|
|
"""Returns the file emitter of the Dartium implementation file."""
|
|
path = os.path.join(self._output_dir, 'html', 'dartium', '%s.dart' % name)
|
|
self._dart_dartium_file_paths.append(path)
|
|
return self._emitters.FileEmitter(path);
|
|
|
|
def ProcessCallback(self, interface, info):
|
|
pass
|
|
|
|
def GenerateLibraries(self):
|
|
# Library generated for implementation.
|
|
auxiliary_dir = os.path.relpath(self._auxiliary_dir, self._output_dir)
|
|
|
|
self._GenerateLibFile(
|
|
'html_dartium.darttemplate',
|
|
os.path.join(self._output_dir, 'html_dartium.dart'),
|
|
(self._interface_system._dart_interface_file_paths +
|
|
self._interface_system._dart_callback_file_paths +
|
|
self._dart_dartium_file_paths +
|
|
self._dom_implementation_classes),
|
|
AUXILIARY_DIR=MassagePath(auxiliary_dir),
|
|
WRAPCASES='\n'.join(self._wrap_cases))
|
|
|
|
def Finish(self):
|
|
pass
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# TODO(jacobr): there is far too much duplicated code between these bindings
|
|
# and the Frog bindings. A larger scale refactoring needs to be performed to
|
|
# reduce the duplicated logic.
|
|
class HtmlDartiumInterfaceGenerator(object):
|
|
"""Generates a wrapper based implementation fo the HTML library that works
|
|
on Dartium. This is not intended to be the final solution for implementing
|
|
dart:html on Dartium. Eventually we should generate direct wrapperless
|
|
dart:html bindings that work on dartium."""
|
|
|
|
def __init__(self, system, interface, template, super_interface, dart_code,
|
|
base_members, shared):
|
|
"""Generates Dart wrapper code for the given interface.
|
|
|
|
Args:
|
|
system: system that is executing this generator.
|
|
template: template that output is generated into.
|
|
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.
|
|
shared: functionaly shared across all Html generators.
|
|
"""
|
|
self._system = system
|
|
self._interface = interface
|
|
self._super_interface = super_interface
|
|
self._dart_code = dart_code
|
|
self._base_members = base_members
|
|
self._current_secondary_parent = None
|
|
self._shared = shared
|
|
self._template = template
|
|
|
|
def DomObjectName(self):
|
|
return '_ptr'
|
|
|
|
# TODO(jacobr): these 3 methods are duplicated.
|
|
def _NarrowToImplementationType(self, type_name):
|
|
# TODO(sra): Move into the 'system' and cache the result.
|
|
if type_name == 'EventListener':
|
|
# Callbacks are typedef functions so don't have a class.
|
|
return type_name
|
|
if self._system._database.HasInterface(type_name):
|
|
interface = self._system._database.GetInterface(type_name)
|
|
if RecognizeCallback(interface):
|
|
# Callbacks are typedef functions so don't have a class.
|
|
return type_name
|
|
else:
|
|
return self._ImplClassName(type_name)
|
|
return type_name
|
|
|
|
def _NarrowInputType(self, type_name):
|
|
return self._NarrowToImplementationType(type_name)
|
|
|
|
def _NarrowOutputType(self, type_name):
|
|
return self._NarrowToImplementationType(type_name)
|
|
|
|
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
|
|
if not IsDartListType(supertype):
|
|
base = self._ImplClassName(supertype)
|
|
if IsDartCollectionType(supertype):
|
|
# List methods are injected in AddIndexer.
|
|
pass
|
|
else:
|
|
base = self._ImplClassName(supertype)
|
|
|
|
# TODO(jacobr): this is fragile. There isn't a guarantee that
|
|
# dart:dom_deprecated will continue to exactly match the IDL
|
|
# names.
|
|
dom_name = interface.javascript_binding_name
|
|
self._system._wrap_cases.append(
|
|
" case '%s': return new %s._wrap(domObject);" %
|
|
(dom_name, self._class_name))
|
|
|
|
extends = ' extends ' + base if base else ' extends _DOMTypeBase'
|
|
|
|
# TODO: Include all implemented interfaces, including other Lists.
|
|
implements = [interface_name]
|
|
element_type = MaybeTypedArrayElementType(self._interface)
|
|
if element_type:
|
|
implements.append('List<' + DartType(element_type) + '>')
|
|
implements_str = ', '.join(implements)
|
|
|
|
(self._members_emitter,
|
|
self._top_level_emitter) = self._dart_code.Emit(
|
|
self._template + '$!TOP_LEVEL',
|
|
#class $CLASSNAME$EXTENDS$IMPLEMENTS$NATIVESPEC {
|
|
#$!MEMBERS
|
|
#}
|
|
NATIVESPEC='', # hack to make reusing the same templates work.
|
|
CLASSNAME=self._class_name,
|
|
EXTENDS=extends,
|
|
IMPLEMENTS=' implements ' + implements_str)
|
|
|
|
self._members_emitter.Emit(
|
|
' $(CLASSNAME)._wrap(ptr) : super._wrap(ptr);\n',
|
|
CLASSNAME=self._class_name)
|
|
|
|
# Emit a factory provider class for the constructor.
|
|
constructor_info = AnalyzeConstructor(interface)
|
|
if constructor_info:
|
|
self._EmitFactoryProvider(interface_name, constructor_info)
|
|
|
|
emit_events, events = self._shared.GetEventAttributes(self._interface)
|
|
if emit_events:
|
|
self._members_emitter.Emit(
|
|
'\n'
|
|
' Events get on() => $THIS.on;\n',
|
|
THIS=self.DomObjectName())
|
|
|
|
def _EmitFactoryProvider(self, interface_name, constructor_info):
|
|
template_file = 'factoryprovider_%s.darttemplate' % interface_name
|
|
template = self._system._templates.TryLoad(template_file)
|
|
if not template:
|
|
template = self._system._templates.Load('factoryprovider.darttemplate')
|
|
|
|
factory_provider = '_' + interface_name + 'FactoryProvider'
|
|
emitter = self._system._ImplFileEmitter(factory_provider)
|
|
emitter.Emit(
|
|
template,
|
|
FACTORYPROVIDER=factory_provider,
|
|
INTERFACE=interface_name,
|
|
DOM_INTERFACE=self._interface.javascript_binding_name,
|
|
PARAMETERS=constructor_info.ParametersImplementationDeclaration(),
|
|
NAMED_CONSTRUCTOR=constructor_info.name or interface_name,
|
|
ARGUMENTS=self._UnwrappedParameters(constructor_info,
|
|
len(constructor_info.param_infos)))
|
|
|
|
def _UnwrappedParameters(self, operation_info, length):
|
|
"""Returns string for an argument list that unwraps first |length|
|
|
parameters."""
|
|
def UnwrapParamInfo(param_info):
|
|
# TODO(sra): Type dependent unwrapping.
|
|
return '_unwrap(%s)' % param_info.name
|
|
|
|
return ', '.join(map(UnwrapParamInfo, operation_info.param_infos[:length]))
|
|
|
|
def _BaseClassName(self, interface):
|
|
if not interface.parents:
|
|
return '_DOMTypeBase'
|
|
|
|
supertype = DartType(interface.parents[0].type.id)
|
|
|
|
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 _ImplClassName(self, type_name):
|
|
return self._shared._ImplClassName(type_name)
|
|
|
|
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):
|
|
dom_name = DartDomNameOfAttribute(getter or setter)
|
|
html_getter_name = self._shared.RenameInHtmlLibrary(
|
|
self._interface, dom_name, 'get:', implementation_class=True)
|
|
html_setter_name = self._shared.RenameInHtmlLibrary(
|
|
self._interface, dom_name, 'set:', implementation_class=True)
|
|
|
|
if getter and html_getter_name:
|
|
self._AddGetter(getter, html_getter_name)
|
|
if setter and html_setter_name:
|
|
self._AddSetter(setter, html_setter_name)
|
|
|
|
def _AddGetter(self, attr, html_name):
|
|
self._members_emitter.Emit(
|
|
'\n'
|
|
' $TYPE get $(HTML_NAME)() => _wrap($(THIS).$DOM_NAME);\n',
|
|
HTML_NAME=html_name,
|
|
DOM_NAME=DartDomNameOfAttribute(attr),
|
|
TYPE=DartType(attr.type.id),
|
|
THIS=self.DomObjectName())
|
|
|
|
def _AddSetter(self, attr, html_name):
|
|
self._members_emitter.Emit(
|
|
'\n'
|
|
' void set $(HTML_NAME)($TYPE value) { '
|
|
'$(THIS).$DOM_NAME = _unwrap(value); }\n',
|
|
HTML_NAME=html_name,
|
|
DOM_NAME=DartDomNameOfAttribute(attr),
|
|
TYPE=DartType(attr.type.id),
|
|
THIS=self.DomObjectName())
|
|
|
|
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 _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)
|
|
|
|
# TODO(jacobr): change this to more directly match the frog version.
|
|
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) => _wrap($(THIS)[index]);\n'
|
|
'\n',
|
|
THIS=self.DomObjectName(),
|
|
TYPE=DartType(element_type))
|
|
|
|
if self._HasNativeIndexSetter(self._interface):
|
|
self._EmitNativeIndexSetter(self._interface, 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 UnsupportedOperationException("Cannot assign element of immutable List.");\n'
|
|
' }\n',
|
|
TYPE=DartType(element_type))
|
|
|
|
# The list interface for this class is manually generated.
|
|
if self._interface.id == 'NodeList':
|
|
return
|
|
|
|
# TODO(sra): Use separate mixins for mutable implementations of List<T>.
|
|
# TODO(sra): Use separate mixins for typed array implementations of List<T>.
|
|
template_file = 'immutable_list_mixin.darttemplate'
|
|
template = self._system._templates.Load(template_file)
|
|
self._members_emitter.Emit(template, E=DartType(element_type))
|
|
|
|
def AmendIndexer(self, element_type):
|
|
pass
|
|
|
|
def _HasNativeIndexGetter(self, interface):
|
|
return ('IndexedGetter' in interface.ext_attrs or
|
|
'NumericIndexedGetter' in interface.ext_attrs)
|
|
|
|
def _EmitNativeIndexGetter(self, interface, element_type):
|
|
method_name = '_index'
|
|
self._members_emitter.Emit(
|
|
'\n $TYPE operator[](int index) => _wrap($(THIS)[index]);\n',
|
|
TYPE=DartType(element_type),
|
|
THIS=self.DomObjectName(),
|
|
METHOD=method_name)
|
|
|
|
def _HasNativeIndexSetter(self, interface):
|
|
return 'CustomIndexedSetter' 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 $(THIS)[index] = _unwrap(value);\n'
|
|
' }\n',
|
|
THIS=self.DomObjectName(),
|
|
TYPE=DartType(element_type),
|
|
METHOD=method_name)
|
|
|
|
def AddOperation(self, info):
|
|
"""
|
|
Arguments:
|
|
info: An OperationInfo object.
|
|
"""
|
|
html_name = self._shared.RenameInHtmlLibrary(
|
|
self._interface, info.name, implementation_class=True)
|
|
|
|
if not html_name:
|
|
return
|
|
|
|
arguments = self._UnwrappedParameters(info, len(info.param_infos))
|
|
function_call = '%s.%s(%s)' % (self.DomObjectName(), info.name, arguments)
|
|
if info.type_name != 'void':
|
|
# We could place the logic for handling Document directly in _wrap
|
|
# but we chose to place it here so that bugs in the wrapper and
|
|
# wrapperless implementations are more consistent.
|
|
function_call = '_wrap(%s)' % function_call
|
|
|
|
self._members_emitter.Emit(
|
|
'\n'
|
|
' $TYPE $HTML_NAME($PARAMS) => $FUNCTION_CALL;\n',
|
|
TYPE=info.type_name,
|
|
HTML_NAME=html_name,
|
|
PARAMS=info.ParametersImplementationDeclaration(),
|
|
FUNCTION_CALL=function_call)
|
|
|
|
def AddStaticOperation(self, info):
|
|
pass
|
|
|
|
def _ComputeInheritanceClosure(database):
|
|
def Collect(interface, seen, collected):
|
|
name = interface.id
|
|
if '<' in name:
|
|
# TODO(sra): Handle parameterized types.
|
|
return
|
|
if not name in seen:
|
|
seen.add(name)
|
|
collected.append(name)
|
|
for parent in interface.parents:
|
|
# TODO(sra): Handle parameterized types.
|
|
if not '<' in parent.type.id:
|
|
if database.HasInterface(parent.type.id):
|
|
Collect(database.GetInterface(parent.type.id),
|
|
seen, collected)
|
|
|
|
inheritance_closure = {}
|
|
for interface in database.GetInterfaces():
|
|
seen = set()
|
|
collected = []
|
|
Collect(interface, seen, collected)
|
|
inheritance_closure[interface.id] = collected
|
|
return inheritance_closure
|