OLD | NEW |
| (Empty) |
1 #!/usr/bin/python | |
2 # Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
3 # for details. All rights reserved. Use of this source code is governed by a | |
4 # BSD-style license that can be found in the LICENSE file. | |
5 | |
6 """This module provides functionality to generate dart:html event classes.""" | |
7 | |
8 import logging | |
9 | |
10 _logger = logging.getLogger('dartgenerator') | |
11 | |
12 # Events without onEventName attributes in the IDL we want to support. | |
13 # We can automatically extract most event names by checking for | |
14 # onEventName methods in the IDL but some events aren't listed so we need | |
15 # to manually add them here so that they are easy for users to find. | |
16 _html_manual_events = { | |
17 'Element': ['touchleave', 'touchenter', 'webkitTransitionEnd'], | |
18 'Window': ['DOMContentLoaded'] | |
19 } | |
20 | |
21 # These event names must be camel case when attaching event listeners | |
22 # using addEventListener even though the onEventName properties in the DOM for | |
23 # them are not camel case. | |
24 _on_attribute_to_event_name_mapping = { | |
25 'webkitanimationend': 'webkitAnimationEnd', | |
26 'webkitanimationiteration': 'webkitAnimationIteration', | |
27 'webkitanimationstart': 'webkitAnimationStart', | |
28 'webkitspeechchange': 'webkitSpeechChange', | |
29 'webkittransitionend': 'webkitTransitionEnd', | |
30 } | |
31 | |
32 # Mapping from raw event names to the pretty camelCase event names exposed as | |
33 # properties in dart:html. If the DOM exposes a new event name, you will need | |
34 # to add the lower case to camel case conversion for that event name here. | |
35 _html_event_names = { | |
36 'DOMContentLoaded': 'contentLoaded', | |
37 'abort': 'abort', | |
38 'addstream': 'addStream', | |
39 'addtrack': 'addTrack', | |
40 'audioend': 'audioEnd', | |
41 'audioprocess': 'audioProcess', | |
42 'audiostart': 'audioStart', | |
43 'beforecopy': 'beforeCopy', | |
44 'beforecut': 'beforeCut', | |
45 'beforepaste': 'beforePaste', | |
46 'beforeunload': 'beforeUnload', | |
47 'blocked': 'blocked', | |
48 'blur': 'blur', | |
49 'cached': 'cached', | |
50 'canplay': 'canPlay', | |
51 'canplaythrough': 'canPlayThrough', | |
52 'change': 'change', | |
53 'chargingchange': 'chargingChange', | |
54 'chargingtimechange': 'chargingTimeChange', | |
55 'checking': 'checking', | |
56 'click': 'click', | |
57 'close': 'close', | |
58 'complete': 'complete', | |
59 'connect': 'connect', | |
60 'connecting': 'connecting', | |
61 'contextmenu': 'contextMenu', | |
62 'copy': 'copy', | |
63 'cuechange': 'cueChange', | |
64 'cut': 'cut', | |
65 'dblclick': 'doubleClick', | |
66 'devicemotion': 'deviceMotion', | |
67 'deviceorientation': 'deviceOrientation', | |
68 'dischargingtimechange': 'dischargingTimeChange', | |
69 'display': 'display', | |
70 'downloading': 'downloading', | |
71 'drag': 'drag', | |
72 'dragend': 'dragEnd', | |
73 'dragenter': 'dragEnter', | |
74 'dragleave': 'dragLeave', | |
75 'dragover': 'dragOver', | |
76 'dragstart': 'dragStart', | |
77 'drop': 'drop', | |
78 'durationchange': 'durationChange', | |
79 'emptied': 'emptied', | |
80 'end': 'end', | |
81 'ended': 'ended', | |
82 'enter': 'enter', | |
83 'error': 'error', | |
84 'exit': 'exit', | |
85 'focus': 'focus', | |
86 'hashchange': 'hashChange', | |
87 'icecandidate': 'iceCandidate', | |
88 'icechange': 'iceChange', | |
89 'input': 'input', | |
90 'invalid': 'invalid', | |
91 'keydown': 'keyDown', | |
92 'keypress': 'keyPress', | |
93 'keyup': 'keyUp', | |
94 'levelchange': 'levelChange', | |
95 'load': 'load', | |
96 'loadeddata': 'loadedData', | |
97 'loadedmetadata': 'loadedMetadata', | |
98 'loadend': 'loadEnd', | |
99 'loadstart': 'loadStart', | |
100 'message': 'message', | |
101 'mousedown': 'mouseDown', | |
102 'mousemove': 'mouseMove', | |
103 'mouseout': 'mouseOut', | |
104 'mouseover': 'mouseOver', | |
105 'mouseup': 'mouseUp', | |
106 'mousewheel': 'mouseWheel', | |
107 'mute': 'mute', | |
108 'negotiationneeded': 'negotiationNeeded', | |
109 'nomatch': 'noMatch', | |
110 'noupdate': 'noUpdate', | |
111 'obsolete': 'obsolete', | |
112 'offline': 'offline', | |
113 'online': 'online', | |
114 'open': 'open', | |
115 'pagehide': 'pageHide', | |
116 'pageshow': 'pageShow', | |
117 'paste': 'paste', | |
118 'pause': 'pause', | |
119 'play': 'play', | |
120 'playing': 'playing', | |
121 'popstate': 'popState', | |
122 'progress': 'progress', | |
123 'ratechange': 'rateChange', | |
124 'readystatechange': 'readyStateChange', | |
125 'removestream': 'removeStream', | |
126 'removetrack': 'removeTrack', | |
127 'reset': 'reset', | |
128 'resize': 'resize', | |
129 'result': 'result', | |
130 'resultdeleted': 'resultDeleted', | |
131 'scroll': 'scroll', | |
132 'search': 'search', | |
133 'seeked': 'seeked', | |
134 'seeking': 'seeking', | |
135 'select': 'select', | |
136 'selectionchange': 'selectionChange', | |
137 'selectstart': 'selectStart', | |
138 'show': 'show', | |
139 'soundend': 'soundEnd', | |
140 'soundstart': 'soundStart', | |
141 'speechend': 'speechEnd', | |
142 'speechstart': 'speechStart', | |
143 'stalled': 'stalled', | |
144 'start': 'start', | |
145 'statechange': 'stateChange', | |
146 'storage': 'storage', | |
147 'submit': 'submit', | |
148 'success': 'success', | |
149 'suspend': 'suspend', | |
150 'timeupdate': 'timeUpdate', | |
151 'touchcancel': 'touchCancel', | |
152 'touchend': 'touchEnd', | |
153 'touchenter': 'touchEnter', | |
154 'touchleave': 'touchLeave', | |
155 'touchmove': 'touchMove', | |
156 'touchstart': 'touchStart', | |
157 'unload': 'unload', | |
158 'upgradeneeded': 'upgradeNeeded', | |
159 'unmute': 'unmute', | |
160 'updateready': 'updateReady', | |
161 'versionchange': 'versionChange', | |
162 'volumechange': 'volumeChange', | |
163 'waiting': 'waiting', | |
164 'webkitAnimationEnd': 'animationEnd', | |
165 'webkitAnimationIteration': 'animationIteration', | |
166 'webkitAnimationStart': 'animationStart', | |
167 'webkitfullscreenchange': 'fullscreenChange', | |
168 'webkitfullscreenerror': 'fullscreenError', | |
169 'webkitkeyadded': 'keyAdded', | |
170 'webkitkeyerror': 'keyError', | |
171 'webkitkeymessage': 'keyMessage', | |
172 'webkitneedkey': 'needKey', | |
173 'webkitpointerlockchange': 'pointerLockChange', | |
174 'webkitpointerlockerror': 'pointerLockError', | |
175 'webkitSpeechChange': 'speechChange', | |
176 'webkitsourceclose': 'sourceClose', | |
177 'webkitsourceended': 'sourceEnded', | |
178 'webkitsourceopen': 'sourceOpen', | |
179 'webkitTransitionEnd': 'transitionEnd', | |
180 'write': 'write', | |
181 'writeend': 'writeEnd', | |
182 'writestart': 'writeStart' | |
183 } | |
184 | |
185 # These classes require an explicit declaration for the "on" method even though | |
186 # they don't declare any unique events, because the concrete class hierarchy | |
187 # doesn't match the interface hierarchy. | |
188 _html_explicit_event_classes = set(['DocumentFragment']) | |
189 | |
190 class HtmlEventGenerator(object): | |
191 | |
192 def __init__(self, database, renamer, template_loader): | |
193 self._event_classes = set() | |
194 self._database = database | |
195 self._renamer = renamer | |
196 self._template_loader = template_loader | |
197 | |
198 def ProcessInterface(self, interface, html_interface_name, custom_events, | |
199 events_implementation_emitter): | |
200 event_names = set([attr.id[2:] for attr in interface.attributes | |
201 if attr.type.id == 'EventListener']) | |
202 | |
203 # Document and DocumentFragment actually derive from Element, so omit | |
204 # any events which are duplicated with that. | |
205 if interface.id == 'Document' or interface.id == 'DocumentFragment': | |
206 element_interface = self._database.GetInterface('Element') | |
207 for attr in element_interface.attributes: | |
208 if attr.type.id == 'EventListener' and attr.id[2:] in event_names: | |
209 event_names.remove(attr.id[2:]) | |
210 | |
211 if not event_names and interface.id not in _html_explicit_event_classes: | |
212 return None | |
213 | |
214 self._event_classes.add(interface.id) | |
215 events_class_name = html_interface_name + 'Events' | |
216 parent_events_class_name = self._GetParentEventsClassName(interface) | |
217 | |
218 if not event_names: | |
219 return parent_events_class_name | |
220 | |
221 template_file = 'impl_%s.darttemplate' % events_class_name | |
222 template = (self._template_loader.TryLoad(template_file) or | |
223 '\n' | |
224 '/// @docsEditable true\n' | |
225 'class $CLASSNAME extends $SUPER {\n' | |
226 ' /// @docsEditable true\n' | |
227 ' $CLASSNAME(EventTarget _ptr) : super(_ptr);\n' | |
228 '$!MEMBERS}\n') | |
229 | |
230 # TODO(jacobr): specify the type of _ptr as EventTarget | |
231 implementation_events_members = events_implementation_emitter.Emit( | |
232 template, | |
233 CLASSNAME=events_class_name, | |
234 SUPER='%s' % parent_events_class_name) | |
235 | |
236 dom_event_names = set() | |
237 for event in event_names: | |
238 dom_name = event | |
239 dom_name = _on_attribute_to_event_name_mapping.get(dom_name, dom_name) | |
240 dom_event_names.add(dom_name) | |
241 if html_interface_name in _html_manual_events: | |
242 dom_event_names.update(_html_manual_events[html_interface_name]) | |
243 for dom_name in sorted(dom_event_names): | |
244 if dom_name not in _html_event_names: | |
245 _logger.warn('omitting %s event as there is no HTML name for it' % dom_n
ame) | |
246 continue | |
247 | |
248 html_name = _html_event_names[dom_name] | |
249 full_event_name = '%sEvents.%s' % (html_interface_name, html_name) | |
250 if not full_event_name in custom_events: | |
251 implementation_events_members.Emit( | |
252 "\n" | |
253 " /// @docsEditable true\n" | |
254 " EventListenerList get $NAME => this['$DOM_NAME'];\n", | |
255 NAME=html_name, | |
256 DOM_NAME=dom_name) | |
257 | |
258 return events_class_name | |
259 | |
260 # TODO(jacobr): this isn't quite right.... | |
261 def _GetParentEventsClassName(self, interface): | |
262 # Ugly hack as we don't specify that Document and DocumentFragment inherit | |
263 # from Element in our IDL. | |
264 if interface.id == 'Document' or interface.id == 'DocumentFragment': | |
265 return 'ElementEvents' | |
266 | |
267 parent_events_class_name = 'Events' | |
268 interfaces_with_events = set() | |
269 for parent in self._database.Hierarchy(interface): | |
270 if parent != interface and parent.id in self._event_classes: | |
271 parent_name = self._renamer.RenameInterface(parent) | |
272 parent_events_class_name = parent_name + 'Events' | |
273 interfaces_with_events.add(parent) | |
274 if len(interfaces_with_events) > 1: | |
275 raise Exception('Only one parent event class allowed ' + interface.id) | |
276 return parent_events_class_name | |
OLD | NEW |