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 shared functionality for the system to generate | |
7 Dart:html APIs from the IDL database.""" | |
8 | |
9 import emitter | |
10 import os | |
11 from generator import * | |
12 from htmldartgenerator import * | |
13 | |
14 _js_custom_members = set([ | |
15 'AudioBufferSourceNode.start', | |
16 'AudioBufferSourceNode.stop', | |
17 'AudioContext.createGain', | |
18 'AudioContext.createScriptProcessor', | |
19 'Console.memory', | |
20 'Console.profiles', | |
21 'Console.assertCondition', | |
22 'Console.count', | |
23 'Console.debug', | |
24 'Console.dir', | |
25 'Console.dirxml', | |
26 'Console.error', | |
27 'Console.group', | |
28 'Console.groupCollapsed', | |
29 'Console.groupEnd', | |
30 'Console.info', | |
31 'Console.log', | |
32 'Console.markTimeline', | |
33 'Console.profile', | |
34 'Console.profileEnd', | |
35 'Console.time', | |
36 'Console.timeEnd', | |
37 'Console.timeStamp', | |
38 'Console.trace', | |
39 'Console.warn', | |
40 'CSSStyleDeclaration.setProperty', | |
41 'Element.insertAdjacentElement', | |
42 'Element.insertAdjacentHTML', | |
43 'Element.insertAdjacentText', | |
44 'Element.remove', | |
45 'ElementEvents.mouseWheel', | |
46 'HTMLCanvasElement.getContext', | |
47 'HTMLTableElement.createTBody', | |
48 'IDBDatabase.transaction', | |
49 'KeyboardEvent.initKeyboardEvent', | |
50 'MouseEvent.offsetX', | |
51 'MouseEvent.offsetY', | |
52 'Navigator.language', | |
53 'URL.createObjectURL', | |
54 'URL.revokeObjectURL', | |
55 'WheelEvent.wheelDeltaX', | |
56 'WheelEvent.wheelDeltaY', | |
57 'Window.cancelAnimationFrame', | |
58 'Window.console', | |
59 'Window.document', | |
60 'Window.indexedDB', | |
61 'Window.location', | |
62 'Window.open', | |
63 'Window.requestAnimationFrame', | |
64 'Window.webkitCancelAnimationFrame', | |
65 'Window.webkitRequestAnimationFrame', | |
66 'WorkerContext.indexedDB', | |
67 ]) | |
68 | |
69 | |
70 # Classes that offer only static methods, and therefore we should suppress | |
71 # constructor creation. | |
72 _static_classes = set(['Url']) | |
73 | |
74 # Information for generating element constructors. | |
75 # | |
76 # TODO(sra): maybe remove all the argument complexity and use cascades. | |
77 # | |
78 # var c = new CanvasElement(width: 100, height: 70); | |
79 # var c = new CanvasElement()..width = 100..height = 70; | |
80 # | |
81 class ElementConstructorInfo(object): | |
82 def __init__(self, name=None, tag=None, | |
83 params=[], opt_params=[], | |
84 factory_provider_name='document'): | |
85 self.name = name # The constructor name 'h1' in 'HeadingElement.h1' | |
86 self.tag = tag or name # The HTML or SVG tag | |
87 self.params = params | |
88 self.opt_params = opt_params | |
89 self.factory_provider_name = factory_provider_name | |
90 | |
91 def ConstructorInfo(self, interface_name): | |
92 info = OperationInfo() | |
93 info.overloads = None | |
94 info.declared_name = interface_name | |
95 info.name = interface_name | |
96 info.constructor_name = self.name | |
97 info.js_name = None | |
98 info.type_name = interface_name | |
99 info.param_infos = map(lambda tXn: ParamInfo(tXn[1], tXn[0], True), | |
100 self.opt_params) | |
101 info.requires_named_arguments = True | |
102 info.factory_parameters = ['"%s"' % self.tag] | |
103 info.pure_dart_constructor = True | |
104 return info | |
105 | |
106 _html_element_constructors = { | |
107 'AnchorElement' : | |
108 ElementConstructorInfo(tag='a', opt_params=[('DOMString', 'href')]), | |
109 'AreaElement': 'area', | |
110 'ButtonElement': 'button', | |
111 'BRElement': 'br', | |
112 'BaseElement': 'base', | |
113 'BodyElement': 'body', | |
114 'ButtonElement': 'button', | |
115 'CanvasElement': | |
116 ElementConstructorInfo(tag='canvas', | |
117 opt_params=[('int', 'width'), ('int', 'height')]), | |
118 'ContentElement': 'content', | |
119 'DataListElement': 'datalist', | |
120 'DListElement': 'dl', | |
121 'DetailsElement': 'details', | |
122 'DivElement': 'div', | |
123 'EmbedElement': 'embed', | |
124 'FieldSetElement': 'fieldset', | |
125 'FormElement': 'form', | |
126 'HRElement': 'hr', | |
127 'HeadElement': 'head', | |
128 'HeadingElement': [ElementConstructorInfo('h1'), | |
129 ElementConstructorInfo('h2'), | |
130 ElementConstructorInfo('h3'), | |
131 ElementConstructorInfo('h4'), | |
132 ElementConstructorInfo('h5'), | |
133 ElementConstructorInfo('h6')], | |
134 'HtmlElement': 'html', | |
135 'IFrameElement': 'iframe', | |
136 'ImageElement': | |
137 ElementConstructorInfo(tag='img', | |
138 opt_params=[('DOMString', 'src'), | |
139 ('int', 'width'), ('int', 'height')]), | |
140 'KeygenElement': 'keygen', | |
141 'LIElement': 'li', | |
142 'LabelElement': 'label', | |
143 'LegendElement': 'legend', | |
144 'LinkElement': 'link', | |
145 'MapElement': 'map', | |
146 'MenuElement': 'menu', | |
147 'MeterElement': 'meter', | |
148 'OListElement': 'ol', | |
149 'ObjectElement': 'object', | |
150 'OptGroupElement': 'optgroup', | |
151 'OutputElement': 'output', | |
152 'ParagraphElement': 'p', | |
153 'ParamElement': 'param', | |
154 'PreElement': 'pre', | |
155 'ProgressElement': 'progress', | |
156 'ScriptElement': 'script', | |
157 'SelectElement': 'select', | |
158 'SourceElement': 'source', | |
159 'SpanElement': 'span', | |
160 'StyleElement': 'style', | |
161 'TableCaptionElement': 'caption', | |
162 'TableCellElement': 'td', | |
163 'TableColElement': 'col', | |
164 'TableElement': 'table', | |
165 'TableRowElement': 'tr', | |
166 #'TableSectionElement' <thead> <tbody> <tfoot> | |
167 'TextAreaElement': 'textarea', | |
168 'TitleElement': 'title', | |
169 'TrackElement': 'track', | |
170 'UListElement': 'ul', | |
171 'VideoElement': 'video' | |
172 } | |
173 | |
174 _svg_element_constructors = { | |
175 'AElement': 'a', | |
176 'AnimateColorElement': 'animateColor', | |
177 'AnimateElement': 'animate', | |
178 'AnimateMotionElement': 'animateMotion', | |
179 'AnimateTransformElement': 'animateTransform', | |
180 'AnimationElement': 'animation', | |
181 'CircleElement': 'circle', | |
182 'ClipPathElement': 'clipPath', | |
183 'CursorElement': 'cursor', | |
184 'DefsElement': 'defs', | |
185 'DescElement': 'desc', | |
186 'EllipseElement': 'ellipse', | |
187 'FilterElement': 'filter', | |
188 'FontElement': 'font', | |
189 'FontFaceElement': 'font-face', | |
190 'FontFaceFormatElement': 'font-face-format', | |
191 'FontFaceNameElement': 'font-face-name', | |
192 'FontFaceSrcElement': 'font-face-src', | |
193 'FontFaceUriElement': 'font-face-uri', | |
194 'ForeignObjectElement': 'foreignObject', | |
195 'GlyphElement': 'glyph', | |
196 'GElement': 'g', | |
197 'HKernElement': 'hkern', | |
198 'ImageElement': 'image', | |
199 'LinearGradientElement': 'linearGradient', | |
200 'LineElement': 'line', | |
201 'MarkerElement': 'marker', | |
202 'MaskElement': 'mask', | |
203 'MPathElement': 'mpath', | |
204 'PathElement': 'path', | |
205 'PatternElement': 'pattern', | |
206 'PolygonElement': 'polygon', | |
207 'PolylineElement': 'polyline', | |
208 'RadialGradientElement': 'radialGradient', | |
209 'RectElement': 'rect', | |
210 'ScriptElement': 'script', | |
211 'SetElement': 'set', | |
212 'StopElement': 'stop', | |
213 'StyleElement': 'style', | |
214 'SwitchElement': 'switch', | |
215 'SymbolElement': 'symbol', | |
216 'TextElement': 'text', | |
217 'TitleElement': 'title', | |
218 'TRefElement': 'tref', | |
219 'TSpanElement': 'tspan', | |
220 'UseElement': 'use', | |
221 'ViewElement': 'view', | |
222 'VKernElement': 'vkern', | |
223 } | |
224 | |
225 _element_constructors = { | |
226 'html': _html_element_constructors, | |
227 'indexed_db': {}, | |
228 'svg': _svg_element_constructors, | |
229 'web_audio': {}, | |
230 } | |
231 | |
232 _factory_ctr_strings = { | |
233 'html': { | |
234 'provider_name': 'document', | |
235 'constructor_name': '$dom_createElement' | |
236 }, | |
237 'indexed_db': { | |
238 'provider_name': 'document', | |
239 'constructor_name': '$dom_createElement' | |
240 }, | |
241 'svg': { | |
242 'provider_name': '_SvgElementFactoryProvider', | |
243 'constructor_name': 'createSvgElement_tag', | |
244 }, | |
245 'web_audio': { | |
246 'provider_name': 'document', | |
247 'constructor_name': '$dom_createElement' | |
248 }, | |
249 } | |
250 | |
251 def ElementConstructorInfos(typename, element_constructors, | |
252 factory_provider_name='_Elements'): | |
253 """Returns list of ElementConstructorInfos about the convenience constructors | |
254 for an Element or SvgElement.""" | |
255 # TODO(sra): Handle multiple and named constructors. | |
256 if typename not in element_constructors: | |
257 return [] | |
258 infos = element_constructors[typename] | |
259 if isinstance(infos, str): | |
260 infos = ElementConstructorInfo(tag=infos, | |
261 factory_provider_name=factory_provider_name) | |
262 if not isinstance(infos, list): | |
263 infos = [infos] | |
264 return infos | |
265 | |
266 # ------------------------------------------------------------------------------ | |
267 | |
268 class HtmlDartInterfaceGenerator(object): | |
269 """Generates dart interface and implementation for the DOM IDL interface.""" | |
270 | |
271 def __init__(self, options, library_emitter, event_generator, interface, | |
272 backend): | |
273 self._renamer = options.renamer | |
274 self._database = options.database | |
275 self._template_loader = options.templates | |
276 self._type_registry = options.type_registry | |
277 self._options = options | |
278 self._library_emitter = library_emitter | |
279 self._event_generator = event_generator | |
280 self._interface = interface | |
281 self._backend = backend | |
282 self._interface_type_info = self._type_registry.TypeInfo(self._interface.id) | |
283 self._library_name = self._renamer.GetLibraryName(self._interface) | |
284 | |
285 def Generate(self): | |
286 if 'Callback' in self._interface.ext_attrs: | |
287 self.GenerateCallback() | |
288 else: | |
289 self.GenerateInterface() | |
290 | |
291 def GenerateCallback(self): | |
292 """Generates a typedef for the callback interface.""" | |
293 handlers = [operation for operation in self._interface.operations | |
294 if operation.id == 'handleEvent'] | |
295 info = AnalyzeOperation(self._interface, handlers) | |
296 code = self._library_emitter.FileEmitter(self._interface.id, | |
297 self._library_name) | |
298 code.Emit(self._template_loader.Load('callback.darttemplate')) | |
299 | |
300 typedef_name = self._renamer.RenameInterface(self._interface) | |
301 code.Emit('typedef void $NAME($PARAMS);\n', | |
302 NAME=typedef_name, | |
303 PARAMS=info.ParametersDeclaration(self._DartType)) | |
304 self._backend.GenerateCallback(info) | |
305 | |
306 def GenerateInterface(self): | |
307 interface_name = self._interface_type_info.interface_name() | |
308 | |
309 factory_provider = None | |
310 if interface_name in interface_factories: | |
311 factory_provider = interface_factories[interface_name] | |
312 factory_constructor_name = None | |
313 | |
314 constructors = [] | |
315 if interface_name in _static_classes: | |
316 constructor_info = None | |
317 else: | |
318 constructor_info = AnalyzeConstructor(self._interface) | |
319 if constructor_info: | |
320 constructors.append(constructor_info) | |
321 # TODO(antonm): consider removing it later. | |
322 factory_provider = interface_name | |
323 | |
324 # HTML Elements and SVG Elements have convenience constructors. | |
325 infos = ElementConstructorInfos(interface_name, | |
326 _element_constructors[self._library_name], factory_provider_name= | |
327 _factory_ctr_strings[self._library_name]['provider_name']) | |
328 | |
329 if infos: | |
330 factory_constructor_name = _factory_ctr_strings[ | |
331 self._library_name]['constructor_name'] | |
332 | |
333 for info in infos: | |
334 constructors.append(info.ConstructorInfo(self._interface.id)) | |
335 if factory_provider: | |
336 assert factory_provider == info.factory_provider_name | |
337 else: | |
338 factory_provider = info.factory_provider_name | |
339 | |
340 implementation_emitter = self._ImplementationEmitter() | |
341 | |
342 base_type_info = None | |
343 if self._interface.parents: | |
344 supertype = self._interface.parents[0].type.id | |
345 if not IsDartCollectionType(supertype) and not IsPureInterface(supertype): | |
346 base_type_info = self._type_registry.TypeInfo(supertype) | |
347 if base_type_info.merged_into() \ | |
348 and self._backend.ImplementsMergedMembers(): | |
349 base_type_info = self._type_registry.TypeInfo( | |
350 base_type_info.merged_into()) | |
351 | |
352 if base_type_info: | |
353 base_class = base_type_info.implementation_name() | |
354 else: | |
355 base_class = self._backend.RootClassName() | |
356 | |
357 implements = self._backend.AdditionalImplementedInterfaces() | |
358 for parent in self._interface.parents: | |
359 parent_type_info = self._type_registry.TypeInfo(parent.type.id) | |
360 if parent_type_info.interface_name() != base_class and \ | |
361 parent_type_info != base_type_info: | |
362 implements.append(parent_type_info.interface_name()) | |
363 | |
364 secure_base_name = self._backend.SecureBaseName(interface_name) | |
365 if secure_base_name: | |
366 implements.append(secure_base_name) | |
367 | |
368 implements_str = '' | |
369 if implements: | |
370 implements_str = ' implements ' + ', '.join(set(implements)) | |
371 | |
372 annotations = FindCommonAnnotations(self._interface.doc_js_name) | |
373 annotations_str = '' | |
374 if annotations: | |
375 annotations_str = '\n' + '\n'.join(annotations) | |
376 | |
377 self._implementation_members_emitter = implementation_emitter.Emit( | |
378 self._backend.ImplementationTemplate(), | |
379 LIBRARYNAME=self._library_name, | |
380 ANNOTATIONS=annotations_str, | |
381 CLASSNAME=self._interface_type_info.implementation_name(), | |
382 EXTENDS=' extends %s' % base_class if base_class else '', | |
383 IMPLEMENTS=implements_str, | |
384 DOMNAME=self._interface.doc_js_name, | |
385 NATIVESPEC=self._backend.NativeSpec()) | |
386 self._backend.StartInterface(self._implementation_members_emitter) | |
387 self._backend.EmitHelpers(base_class) | |
388 self._backend.AddConstructors( | |
389 constructors, factory_provider, factory_constructor_name) | |
390 | |
391 events_class_name = self._event_generator.ProcessInterface( | |
392 self._interface, interface_name, | |
393 self._backend.CustomJSMembers(), | |
394 implementation_emitter) | |
395 if events_class_name: | |
396 self._backend.EmitEventGetter(events_class_name) | |
397 | |
398 merged_interface = self._interface_type_info.merged_interface() | |
399 if merged_interface: | |
400 self._backend.AddMembers(self._database.GetInterface(merged_interface), | |
401 not self._backend.ImplementsMergedMembers()) | |
402 | |
403 self._backend.AddMembers(self._interface) | |
404 self._backend.AddSecondaryMembers(self._interface) | |
405 self._backend.FinishInterface() | |
406 | |
407 def _ImplementationEmitter(self): | |
408 basename = self._interface_type_info.implementation_name() | |
409 if (self._interface_type_info.merged_into() and | |
410 self._backend.ImplementsMergedMembers()): | |
411 # Merged members are implemented in target interface implementation. | |
412 return emitter.Emitter() | |
413 return self._library_emitter.FileEmitter(basename, self._library_name) | |
414 | |
415 def _DartType(self, type_name): | |
416 return self._type_registry.DartType(type_name) | |
417 | |
418 | |
419 # ------------------------------------------------------------------------------ | |
420 | |
421 class Dart2JSBackend(HtmlDartGenerator): | |
422 """Generates a dart2js class for the dart:html library from a DOM IDL | |
423 interface. | |
424 """ | |
425 | |
426 def __init__(self, interface, options): | |
427 super(Dart2JSBackend, self).__init__(interface, options) | |
428 | |
429 self._database = options.database | |
430 self._template_loader = options.templates | |
431 self._type_registry = options.type_registry | |
432 self._renamer = options.renamer | |
433 self._interface_type_info = self._type_registry.TypeInfo(self._interface.id) | |
434 self._current_secondary_parent = None | |
435 | |
436 def ImplementsMergedMembers(self): | |
437 return True | |
438 | |
439 def GenerateCallback(self, info): | |
440 pass | |
441 | |
442 def RootClassName(self): | |
443 return None | |
444 | |
445 def AdditionalImplementedInterfaces(self): | |
446 implements = super(Dart2JSBackend, self).AdditionalImplementedInterfaces() | |
447 if self._interface_type_info.list_item_type(): | |
448 implements.append('JavaScriptIndexingBehavior') | |
449 return implements | |
450 | |
451 def NativeSpec(self): | |
452 native_spec = MakeNativeSpec(self._interface.javascript_binding_name) | |
453 return ' native "%s"' % native_spec | |
454 | |
455 def ImplementationTemplate(self): | |
456 if IsPureInterface(self._interface.id): | |
457 return self._template_loader.Load('pure_interface.darttemplate') | |
458 | |
459 template_file = ('impl_%s.darttemplate' % | |
460 self._interface.doc_js_name) | |
461 return (self._template_loader.TryLoad(template_file) or | |
462 self._template_loader.Load('dart2js_impl.darttemplate')) | |
463 | |
464 def StartInterface(self, members_emitter): | |
465 self._members_emitter = members_emitter | |
466 | |
467 def FinishInterface(self): | |
468 pass | |
469 | |
470 def EmitStaticFactory(self, constructor_info): | |
471 arguments = constructor_info.ParametersAsArgumentList() | |
472 if arguments: | |
473 arguments = ', ' + arguments | |
474 self._members_emitter.Emit( | |
475 " static $INTERFACE_NAME _create($PARAMETERS_DECLARATION) => JS(" | |
476 "'$INTERFACE_NAME', " | |
477 "'new $CONSTRUCTOR_NAME($ARGUMENTS_PATTERN)'$ARGUMENTS);\n", | |
478 INTERFACE_NAME=self._interface_type_info.interface_name(), | |
479 PARAMETERS_DECLARATION=constructor_info.ParametersDeclaration( | |
480 self._DartType), | |
481 CONSTRUCTOR_NAME=constructor_info.name or self._interface.doc_js_name, | |
482 ARGUMENTS_PATTERN=','.join(['#'] * len(constructor_info.param_infos)), | |
483 ARGUMENTS=arguments) | |
484 | |
485 def SecondaryContext(self, interface): | |
486 if interface is not self._current_secondary_parent: | |
487 self._current_secondary_parent = interface | |
488 self._members_emitter.Emit('\n // From $WHERE\n', WHERE=interface.id) | |
489 | |
490 def AddIndexer(self, element_type): | |
491 """Adds all the methods required to complete implementation of List.""" | |
492 # We would like to simply inherit the implementation of everything except | |
493 # length, [], and maybe []=. It is possible to extend from a base | |
494 # array implementation class only when there is no other implementation | |
495 # inheritance. There might be no implementation inheritance other than | |
496 # DOMBaseWrapper for many classes, but there might be some where the | |
497 # array-ness is introduced by a non-root interface: | |
498 # | |
499 # interface Y extends X, List<T> ... | |
500 # | |
501 # In the non-root case we have to choose between: | |
502 # | |
503 # class YImpl extends XImpl { add List<T> methods; } | |
504 # | |
505 # and | |
506 # | |
507 # class YImpl extends ListBase<T> { copies of transitive XImpl methods; } | |
508 # | |
509 self._members_emitter.Emit( | |
510 '\n' | |
511 ' $TYPE operator[](int index) => JS("$TYPE", "#[#]", this, index);\n', | |
512 TYPE=self.SecureOutputType(element_type)) | |
513 | |
514 if 'CustomIndexedSetter' in self._interface.ext_attrs: | |
515 self._members_emitter.Emit( | |
516 '\n' | |
517 ' void operator[]=(int index, $TYPE value) {' | |
518 ' JS("void", "#[#] = #", this, index, value); }', | |
519 TYPE=self._NarrowInputType(element_type)) | |
520 else: | |
521 self._members_emitter.Emit( | |
522 '\n' | |
523 ' void operator[]=(int index, $TYPE value) {\n' | |
524 ' throw new UnsupportedError("Cannot assign element of immutable Li
st.");\n' | |
525 ' }\n', | |
526 TYPE=self._NarrowInputType(element_type)) | |
527 | |
528 self.EmitListMixin(self._DartType(element_type)) | |
529 | |
530 def EmitAttribute(self, attribute, html_name, read_only): | |
531 if self._HasCustomImplementation(attribute.id): | |
532 return | |
533 | |
534 if IsPureInterface(self._interface.id): | |
535 self._AddInterfaceAttribute(attribute, html_name) | |
536 return | |
537 | |
538 # If the attribute is shadowing, we can't generate a shadowing | |
539 # field (Issue 1633). | |
540 # TODO(sra): _FindShadowedAttribute does not take into account the html | |
541 # renaming. we should be looking for another attribute that has the same | |
542 # html_name. Two attributes with the same IDL name might not match if one | |
543 # is renamed. | |
544 (super_attribute, super_attribute_interface) = self._FindShadowedAttribute( | |
545 attribute) | |
546 if super_attribute: | |
547 if read_only: | |
548 if attribute.type.id == super_attribute.type.id: | |
549 # Compatible attribute, use the superclass property. This works | |
550 # because JavaScript will do its own dynamic dispatch. | |
551 self._members_emitter.Emit( | |
552 '\n' | |
553 ' // Use implementation from $SUPER.\n' | |
554 ' // final $TYPE $NAME;\n', | |
555 SUPER=super_attribute_interface, | |
556 NAME=html_name, | |
557 TYPE=self.SecureOutputType(attribute.type.id)) | |
558 return | |
559 self._members_emitter.Emit('\n // Shadowing definition.') | |
560 self._AddAttributeUsingProperties(attribute, html_name, read_only) | |
561 return | |
562 | |
563 # If the type has a conversion we need a getter or setter to contain the | |
564 # conversion code. | |
565 if (self._OutputConversion(attribute.type.id, attribute.id) or | |
566 self._InputConversion(attribute.type.id, attribute.id)): | |
567 self._AddAttributeUsingProperties(attribute, html_name, read_only) | |
568 return | |
569 | |
570 output_type = self.SecureOutputType(attribute.type.id) | |
571 input_type = self._NarrowInputType(attribute.type.id) | |
572 annotations = self._Annotations(attribute.type.id, attribute.id) | |
573 rename = self._RenamingAnnotation(attribute.id, html_name) | |
574 self.EmitAttributeDocumentation(attribute) | |
575 if not read_only: | |
576 self._members_emitter.Emit( | |
577 '\n $RENAME$ANNOTATIONS$TYPE $NAME;' | |
578 '\n', | |
579 RENAME=rename, | |
580 ANNOTATIONS=annotations, | |
581 NAME=html_name, | |
582 TYPE=output_type) | |
583 else: | |
584 template = '\n $RENAME$(ANNOTATIONS)final $TYPE $NAME;\n' | |
585 # Need to use a getter for list.length properties so we can add a | |
586 # setter which throws an exception, satisfying List API. | |
587 if self._interface_type_info.list_item_type() and html_name == 'length': | |
588 template = ('\n $RENAME$(ANNOTATIONS)$TYPE get $NAME => ' + | |
589 'JS("$TYPE", "#.$NAME", this);\n') | |
590 self._members_emitter.Emit( | |
591 template, | |
592 RENAME=rename, | |
593 ANNOTATIONS=annotations, | |
594 NAME=html_name, | |
595 TYPE=output_type) | |
596 | |
597 def _AddAttributeUsingProperties(self, attribute, html_name, read_only): | |
598 self._AddRenamingGetter(attribute, html_name) | |
599 if not read_only: | |
600 self._AddRenamingSetter(attribute, html_name) | |
601 | |
602 def _AddInterfaceAttribute(self, attribute, html_name): | |
603 self._members_emitter.Emit( | |
604 '\n $TYPE $NAME;' | |
605 '\n', | |
606 NAME=html_name, | |
607 TYPE=self.SecureOutputType(attribute.type.id)) | |
608 | |
609 def _AddRenamingGetter(self, attr, html_name): | |
610 self.EmitAttributeDocumentation(attr) | |
611 | |
612 conversion = self._OutputConversion(attr.type.id, attr.id) | |
613 if conversion: | |
614 return self._AddConvertingGetter(attr, html_name, conversion) | |
615 return_type = self.SecureOutputType(attr.type.id) | |
616 native_type = self._NarrowToImplementationType(attr.type.id) | |
617 self._members_emitter.Emit( | |
618 # TODO(sra): Use metadata to provide native name. | |
619 '\n $TYPE get $HTML_NAME => JS("$NATIVE_TYPE", "#.$NAME", this);' | |
620 '\n', | |
621 HTML_NAME=html_name, | |
622 NAME=attr.id, | |
623 TYPE=return_type, | |
624 NATIVE_TYPE=native_type) | |
625 | |
626 def _AddRenamingSetter(self, attr, html_name): | |
627 self.EmitAttributeDocumentation(attr) | |
628 | |
629 conversion = self._InputConversion(attr.type.id, attr.id) | |
630 if conversion: | |
631 return self._AddConvertingSetter(attr, html_name, conversion) | |
632 self._members_emitter.Emit( | |
633 # TODO(sra): Use metadata to provide native name. | |
634 '\n void set $HTML_NAME($TYPE value) {' | |
635 '\n JS("void", "#.$NAME = #", this, value);' | |
636 '\n }' | |
637 '\n', | |
638 HTML_NAME=html_name, | |
639 NAME=attr.id, | |
640 TYPE=self._NarrowInputType(attr.type.id)) | |
641 | |
642 def _AddConvertingGetter(self, attr, html_name, conversion): | |
643 self._members_emitter.Emit( | |
644 '\n $RETURN_TYPE get $HTML_NAME => $CONVERT(this._$(HTML_NAME));' | |
645 "\n @JSName('$NAME')" | |
646 '\n $(ANNOTATIONS)final $NATIVE_TYPE _$HTML_NAME;' | |
647 '\n', | |
648 ANNOTATIONS=self._Annotations(attr.type.id, html_name), | |
649 CONVERT=conversion.function_name, | |
650 HTML_NAME=html_name, | |
651 NAME=attr.id, | |
652 RETURN_TYPE=conversion.output_type, | |
653 NATIVE_TYPE=conversion.input_type) | |
654 | |
655 def _AddConvertingSetter(self, attr, html_name, conversion): | |
656 self._members_emitter.Emit( | |
657 # TODO(sra): Use metadata to provide native name. | |
658 '\n void set $HTML_NAME($INPUT_TYPE value) {' | |
659 '\n this._$HTML_NAME = $CONVERT(value);' | |
660 '\n }' | |
661 '\n void set _$HTML_NAME(/*$NATIVE_TYPE*/ value) {' | |
662 '\n JS("void", "#.$NAME = #", this, value);' | |
663 '\n }' | |
664 '\n', | |
665 CONVERT=conversion.function_name, | |
666 HTML_NAME=html_name, | |
667 NAME=attr.id, | |
668 INPUT_TYPE=conversion.input_type, | |
669 NATIVE_TYPE=conversion.output_type) | |
670 | |
671 def AmendIndexer(self, element_type): | |
672 pass | |
673 | |
674 def EmitOperation(self, info, html_name): | |
675 """ | |
676 Arguments: | |
677 info: An OperationInfo object. | |
678 """ | |
679 if self._HasCustomImplementation(info.name): | |
680 return | |
681 | |
682 self.EmitOperationDocumentation(info) | |
683 | |
684 if IsPureInterface(self._interface.id): | |
685 self._AddInterfaceOperation(info, html_name) | |
686 elif any(self._OperationRequiresConversions(op) for op in info.overloads): | |
687 # Any conversions needed? | |
688 self._AddOperationWithConversions(info, html_name) | |
689 else: | |
690 self._AddDirectNativeOperation(info, html_name) | |
691 | |
692 def _AddDirectNativeOperation(self, info, html_name): | |
693 self._members_emitter.Emit( | |
694 '\n' | |
695 ' $RENAME$ANNOTATIONS$MODIFIERS$TYPE $NAME($PARAMS) native;\n', | |
696 RENAME=self._RenamingAnnotation(info.declared_name, html_name), | |
697 ANNOTATIONS=self._Annotations(info.type_name, info.declared_name), | |
698 MODIFIERS='static ' if info.IsStatic() else '', | |
699 TYPE=self.SecureOutputType(info.type_name), | |
700 NAME=html_name, | |
701 PARAMS=info.ParametersDeclaration(self._NarrowInputType)) | |
702 | |
703 def _AddOperationWithConversions(self, info, html_name): | |
704 # Assert all operations have same return type. | |
705 assert len(set([op.type.id for op in info.operations])) == 1 | |
706 output_conversion = self._OutputConversion(info.type_name, | |
707 info.declared_name) | |
708 if output_conversion: | |
709 return_type = output_conversion.output_type | |
710 native_return_type = output_conversion.input_type | |
711 else: | |
712 return_type = self._NarrowInputType(info.type_name) | |
713 native_return_type = return_type | |
714 | |
715 def InputType(type_name): | |
716 conversion = self._InputConversion(type_name, info.declared_name) | |
717 if conversion: | |
718 return conversion.input_type | |
719 else: | |
720 return self._NarrowInputType(type_name) if type_name else 'dynamic' | |
721 | |
722 body = self._members_emitter.Emit( | |
723 '\n' | |
724 ' $MODIFIERS$TYPE $(HTML_NAME)($PARAMS) {\n' | |
725 '$!BODY' | |
726 ' }\n', | |
727 MODIFIERS='static ' if info.IsStatic() else '', | |
728 TYPE=return_type, | |
729 HTML_NAME=html_name, | |
730 PARAMS=info.ParametersDeclaration(InputType)) | |
731 | |
732 parameter_names = [param_info.name for param_info in info.param_infos] | |
733 parameter_types = [InputType(param_info.type_id) | |
734 for param_info in info.param_infos] | |
735 operations = info.operations | |
736 | |
737 method_version = [0] | |
738 temp_version = [0] | |
739 | |
740 def GenerateCall(operation, argument_count, checks): | |
741 if checks: | |
742 (stmts_emitter, call_emitter) = body.Emit( | |
743 ' if ($CHECKS) {\n$!STMTS$!CALL }\n', | |
744 INDENT=' ', | |
745 CHECKS=' &&\n '.join(checks)) | |
746 else: | |
747 (stmts_emitter, call_emitter) = body.Emit('$!A$!B', INDENT=' '); | |
748 | |
749 method_version[0] += 1 | |
750 target = '_%s_%d' % (html_name, method_version[0]); | |
751 arguments = [] | |
752 target_parameters = [] | |
753 for position, arg in enumerate(operation.arguments[:argument_count]): | |
754 conversion = self._InputConversion(arg.type.id, operation.id) | |
755 param_name = operation.arguments[position].id | |
756 if conversion: | |
757 temp_version[0] += 1 | |
758 temp_name = '%s_%s' % (param_name, temp_version[0]) | |
759 temp_type = conversion.output_type | |
760 stmts_emitter.Emit( | |
761 '$(INDENT)$TYPE $NAME = $CONVERT($ARG);\n', | |
762 TYPE=TypeOrVar(temp_type), | |
763 NAME=temp_name, | |
764 CONVERT=conversion.function_name, | |
765 ARG=parameter_names[position]) | |
766 arguments.append(temp_name) | |
767 param_type = temp_type | |
768 verified_type = temp_type # verified by assignment in checked mode. | |
769 else: | |
770 arguments.append(parameter_names[position]) | |
771 param_type = self._NarrowInputType(arg.type.id) | |
772 # Verified by argument checking on entry to the dispatcher. | |
773 | |
774 verified_type = InputType(info.param_infos[position].type_id) | |
775 # The native method does not need an argument type if we know the type
. | |
776 # But we do need the native methods to have correct function types, so | |
777 # be conservative. | |
778 if param_type == verified_type: | |
779 if param_type in ['String', 'num', 'int', 'double', 'bool', 'Object'
]: | |
780 param_type = 'dynamic' | |
781 | |
782 target_parameters.append( | |
783 '%s%s' % (TypeOrNothing(param_type), param_name)) | |
784 | |
785 argument_list = ', '.join(arguments) | |
786 # TODO(sra): If the native method has zero type checks, we can 'inline' is | |
787 # and call it directly with a JS-expression. | |
788 call = '%s(%s)' % (target, argument_list) | |
789 | |
790 if output_conversion: | |
791 call = '%s(%s)' % (output_conversion.function_name, call) | |
792 | |
793 if operation.type.id == 'void': | |
794 call_emitter.Emit('$(INDENT)$CALL;\n$(INDENT)return;\n', | |
795 CALL=call) | |
796 else: | |
797 call_emitter.Emit('$(INDENT)return $CALL;\n', CALL=call) | |
798 | |
799 self._members_emitter.Emit( | |
800 ' $RENAME$ANNOTATIONS$MODIFIERS$TYPE$TARGET($PARAMS) native;\n', | |
801 RENAME=self._RenamingAnnotation(info.declared_name, target), | |
802 ANNOTATIONS=self._Annotations(info.type_name, info.declared_name), | |
803 MODIFIERS='static ' if info.IsStatic() else '', | |
804 TYPE=TypeOrNothing(native_return_type), | |
805 TARGET=target, | |
806 PARAMS=', '.join(target_parameters)) | |
807 | |
808 def GenerateChecksAndCall(operation, argument_count): | |
809 checks = [] | |
810 for i in range(0, argument_count): | |
811 argument = operation.arguments[i] | |
812 parameter_name = parameter_names[i] | |
813 test_type = self._DartType(argument.type.id) | |
814 if test_type in ['dynamic', 'Object']: | |
815 checks.append('?%s' % parameter_name) | |
816 elif test_type != parameter_types[i]: | |
817 checks.append('(?%s && (%s is %s || %s == null))' % ( | |
818 parameter_name, parameter_name, test_type, parameter_name)) | |
819 | |
820 checks.extend(['!?%s' % name for name in parameter_names[argument_count:]]
) | |
821 # There can be multiple presence checks. We need them all since a later | |
822 # optional argument could have been passed by name, leaving 'holes'. | |
823 GenerateCall(operation, argument_count, checks) | |
824 | |
825 # TODO: Optimize the dispatch to avoid repeated checks. | |
826 if len(operations) > 1: | |
827 for operation in operations: | |
828 for position, argument in enumerate(operation.arguments): | |
829 if self._IsOptional(operation, argument): | |
830 GenerateChecksAndCall(operation, position) | |
831 GenerateChecksAndCall(operation, len(operation.arguments)) | |
832 body.Emit( | |
833 ' throw new ArgumentError("Incorrect number or type of arguments");
' | |
834 '\n'); | |
835 else: | |
836 operation = operations[0] | |
837 argument_count = len(operation.arguments) | |
838 for position, argument in list(enumerate(operation.arguments))[::-1]: | |
839 if self._IsOptional(operation, argument): | |
840 check = '?%s' % parameter_names[position] | |
841 GenerateCall(operation, position + 1, [check]) | |
842 argument_count = position | |
843 GenerateCall(operation, argument_count, []) | |
844 | |
845 def _AddInterfaceOperation(self, info, html_name): | |
846 self._members_emitter.Emit( | |
847 '\n' | |
848 ' $TYPE $NAME($PARAMS);\n', | |
849 TYPE=self.SecureOutputType(info.type_name), | |
850 NAME=info.name, | |
851 PARAMS=info.ParametersDeclaration(self._NarrowInputType)) | |
852 | |
853 def _IsOptional(self, operation, argument): | |
854 return IsOptional(argument) | |
855 | |
856 | |
857 def _OperationRequiresConversions(self, operation): | |
858 return (self._OperationRequiresOutputConversion(operation) or | |
859 self._OperationRequiresInputConversions(operation)) | |
860 | |
861 def _OperationRequiresOutputConversion(self, operation): | |
862 return self._OutputConversion(operation.type.id, operation.id) | |
863 | |
864 def _OperationRequiresInputConversions(self, operation): | |
865 return any(self._InputConversion(arg.type.id, operation.id) | |
866 for arg in operation.arguments) | |
867 | |
868 def _OutputConversion(self, idl_type, member): | |
869 return FindConversion(idl_type, 'get', self._interface.id, member) | |
870 | |
871 def _InputConversion(self, idl_type, member): | |
872 return FindConversion(idl_type, 'set', self._interface.id, member) | |
873 | |
874 def _HasCustomImplementation(self, member_name): | |
875 member_name = '%s.%s' % (self._interface.doc_js_name, member_name) | |
876 return member_name in _js_custom_members | |
877 | |
878 def _RenamingAnnotation(self, idl_name, member_name): | |
879 if member_name != idl_name: | |
880 return "@JSName('%s')\n " % idl_name | |
881 return '' | |
882 | |
883 def _Annotations(self, idl_type, idl_member_name): | |
884 annotations = FindDart2JSAnnotations(idl_type, self._interface.id, | |
885 idl_member_name) | |
886 if annotations: | |
887 return '%s\n ' % annotations | |
888 return_type = self.SecureOutputType(idl_type) | |
889 native_type = self._NarrowToImplementationType(idl_type) | |
890 if native_type != return_type: | |
891 return "@Returns('%s') @Creates('%s')\n " % (native_type, native_type) | |
892 else: | |
893 return '' | |
894 | |
895 def CustomJSMembers(self): | |
896 return _js_custom_members | |
897 | |
898 def _NarrowToImplementationType(self, type_name): | |
899 return self._type_registry.TypeInfo(type_name).narrow_dart_type() | |
900 | |
901 def _NarrowInputType(self, type_name): | |
902 return self._NarrowToImplementationType(type_name) | |
903 | |
904 def _FindShadowedAttribute(self, attr): | |
905 """Returns (attribute, superinterface) or (None, None).""" | |
906 def FindInParent(interface): | |
907 """Returns matching attribute in parent, or None.""" | |
908 if interface.parents: | |
909 parent = interface.parents[0] | |
910 if IsDartCollectionType(parent.type.id): | |
911 return (None, None) | |
912 if IsPureInterface(parent.type.id): | |
913 return (None, None) | |
914 if self._database.HasInterface(parent.type.id): | |
915 interfaces_to_search_in = [] | |
916 parent_interface_name = parent.type.id | |
917 interfaces_to_search_in.append(parent_interface_name) | |
918 parent_type_info = self._type_registry.TypeInfo(parent_interface_name) | |
919 if parent_type_info.merged_into(): | |
920 # IDL parent was merged into another interface, which became a | |
921 # parent interface in Dart. | |
922 parent_interface_name = parent_type_info.merged_into() | |
923 interfaces_to_search_in.append(parent_interface_name) | |
924 elif parent_type_info.merged_interface(): | |
925 # IDL parent has another interface that was merged into it. | |
926 interfaces_to_search_in.append(parent_type_info.merged_interface()) | |
927 | |
928 for interface_name in interfaces_to_search_in: | |
929 interface = self._database.GetInterface(interface_name) | |
930 attr2 = FindMatchingAttribute(interface, attr) | |
931 if attr2: | |
932 return (attr2, parent_interface_name) | |
933 | |
934 return FindInParent( | |
935 self._database.GetInterface(parent_interface_name)) | |
936 return (None, None) | |
937 | |
938 return FindInParent(self._interface) if attr else (None, None) | |
939 | |
940 def _DartType(self, type_name): | |
941 return self._type_registry.DartType(type_name) | |
942 | |
943 # ------------------------------------------------------------------------------ | |
944 | |
945 class DartLibraryEmitter(): | |
946 def __init__(self, multiemitter, dart_sources_dir, dart_libraries): | |
947 self._multiemitter = multiemitter | |
948 self._dart_sources_dir = dart_sources_dir | |
949 self._path_to_emitter = {} | |
950 self._dart_libraries = dart_libraries | |
951 | |
952 def FileEmitter(self, basename, library_name, template=None): | |
953 aux_dir = os.path.join(self._dart_sources_dir, library_name) | |
954 path = os.path.join(aux_dir, '%s.dart' % basename) | |
955 if not path in self._path_to_emitter: | |
956 emitter = self._multiemitter.FileEmitter(path) | |
957 if not template is None: | |
958 emitter = emitter.Emit(template) | |
959 self._path_to_emitter[path] = emitter | |
960 | |
961 self._dart_libraries.AddFile(basename, library_name, path) | |
962 return self._path_to_emitter[path] | |
963 | |
964 def EmitLibraries(self, auxiliary_dir): | |
965 self._dart_libraries.Emit(self._multiemitter, auxiliary_dir) | |
966 | |
967 # ------------------------------------------------------------------------------ | |
968 class DartLibrary(): | |
969 def __init__(self, name, template_loader, library_type, output_dir): | |
970 self._template = template_loader.Load( | |
971 '%s_%s.darttemplate' % (name, library_type)) | |
972 self._dart_path = os.path.join( | |
973 output_dir, '%s_%s.dart' % (name, library_type)) | |
974 self._paths = [] | |
975 | |
976 def AddFile(self, path): | |
977 self._paths.append(path) | |
978 | |
979 def Emit(self, emitter, auxiliary_dir): | |
980 def massage_path(path): | |
981 # The most robust way to emit path separators is to use / always. | |
982 return path.replace('\\', '/') | |
983 | |
984 library_emitter = emitter.FileEmitter(self._dart_path) | |
985 library_file_dir = os.path.dirname(self._dart_path) | |
986 auxiliary_dir = os.path.relpath(auxiliary_dir, library_file_dir) | |
987 imports_emitter = library_emitter.Emit( | |
988 self._template, AUXILIARY_DIR=massage_path(auxiliary_dir)) | |
989 | |
990 for path in sorted(self._paths): | |
991 relpath = os.path.relpath(path, library_file_dir) | |
992 imports_emitter.Emit( | |
993 "part '$PATH';\n", PATH=massage_path(relpath)) | |
994 | |
995 # ------------------------------------------------------------------------------ | |
996 | |
997 class DartLibraries(): | |
998 def __init__(self, libraries, template_loader, library_type, output_dir): | |
999 self._libraries = {} | |
1000 for library_name in libraries: | |
1001 self._libraries[library_name] = DartLibrary( | |
1002 library_name, template_loader, library_type, output_dir) | |
1003 | |
1004 def AddFile(self, basename, library_name, path): | |
1005 self._libraries[library_name].AddFile(path) | |
1006 | |
1007 def Emit(self, emitter, auxiliary_dir): | |
1008 for lib in self._libraries.values(): | |
1009 lib.Emit(emitter, auxiliary_dir) | |
OLD | NEW |