| #!/usr/bin/env python3 |
| # |
| # Copyright (c) 2014, 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. |
| """Generates sdk/lib/_blink/dartium/_blink_dartium.dart file.""" |
| |
| import os |
| from sets import Set |
| from generator import AnalyzeOperation, AnalyzeConstructor |
| |
| # This is list of all methods with native c++ implementations |
| # If performing a dartium merge, the best practice is to comment out this list, |
| # ensure everything runs, and then uncomment this list which might possibly |
| # introduce breaking changes due to changes to these method signatures. |
| _js_custom_members = Set([ |
| 'Document.createElement', |
| 'Element.id', |
| 'Element.tagName', |
| 'Element.className', |
| 'Element.setAttribute', |
| 'Element.getAttribute', |
| # Consider adding this method so there is a fast path to access only |
| # element children. |
| # 'NonDocumentTypeChildNode.nextElementSibling', |
| 'Node.appendChild', # actually not removed, just native implementation. |
| 'Node.cloneNode', |
| 'Node.insertBefore', |
| 'Node.lastChild', |
| 'Node.firstChild', |
| 'Node.parentElement', |
| 'Node.parentNode', |
| 'Node.childNodes', |
| 'Node.removeChild', |
| 'Node.contains', |
| 'Node.nextSibling', |
| 'Node.previousSibling', |
| 'ChildNode.remove', |
| 'Document.createTextNode', |
| 'Window.location', |
| 'Location.href', |
| 'Location.hash', |
| 'Node.querySelector', |
| 'HTMLElement.hidden', |
| 'HTMLElement.style', |
| 'Element.attributes', |
| 'Window.innerWidth', |
| 'NodeList.length', |
| 'NodeList.item', |
| 'ParentNode.children', |
| 'ParentNode.firstElementChild', |
| 'ParentNode.lastElementChild', |
| 'Event.target', |
| 'MouseEvent.clientY', |
| 'MouseEvent.clientX', |
| 'Node.nodeType', |
| 'Node.textContent', |
| 'HTMLCollection.length', |
| 'HTMLCollection.item', |
| 'Node.lastElementChild', |
| 'Node.firstElementChild', |
| 'HTMLElement_tabIndex', |
| 'Element.clientWidth', |
| 'Element.clientHeight', |
| 'Document.body', |
| 'Element.removeAttribute', |
| 'Element.getBoundingClientRect', |
| 'CSSStyleDeclaration.getPropertyValue', |
| 'CSSStyleDeclaration.setProperty', |
| 'CSSStyleDeclaration.__propertyQuery__', |
| |
| # TODO(jacobr): consider implementing these methods as well as they show |
| # up in benchmarks for some sample applications. |
| #'Document.createEvent', |
| #'Document.initEvent', |
| #'EventTarget.dispatchEvent', |
| ]) |
| |
| # Uncomment out this line to short circuited native methods and run all of |
| # dart:html through JS interop except for createElement which is slightly more |
| # tightly natively wired. |
| # _js_custom_members = Set([]) |
| |
| # Expose built-in methods support by an instance that is not shown in the IDL. |
| _additional_methods = { |
| # Support propertyIsEnumerable (available on all objects only needed by |
| # CSSStyleDeclaration decides if style property is supported (handling |
| # camelcase and inject hyphens between camelcase). |
| # Format of dictionary is 'operation name', arguments, returns value (True or False) |
| 'CSSStyleDeclaration': ('propertyIsEnumerable', 1, True), |
| } |
| |
| HEADER = """/* Copyright (c) 2014, 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. |
| * |
| * DO NOT EDIT |
| * Auto-generated _blink library. |
| */ |
| library dart.dom._blink; |
| |
| import 'dart:async'; |
| import 'dart:js' as js; |
| import 'dart:html' show DomException; |
| import 'dart:_internal' as internal; |
| // This is a place to put custom renames if we need them. |
| final resolverMap = { |
| }; |
| |
| dynamic resolver(String s) { |
| """ |
| |
| END_RESOLVER = """ |
| // Failed to find it, check for custom renames |
| dynamic obj = resolverMap[s]; |
| if (obj != null) return obj; |
| throw("No such interface exposed in blink: ${s}"); |
| } |
| |
| """ |
| |
| BLINK_UTILS = """ |
| // _Utils native entry points |
| class Blink_Utils { |
| static window() native "Utils_window"; |
| |
| static forwardingPrint(message) native "Utils_forwardingPrint"; |
| |
| static spawnDomUri(uri) native "Utils_spawnDomUri"; |
| |
| static void spawnDomHelper(Function f, int replyTo) native "Utils_spawnDomHelper"; |
| |
| static register(document, tag, customType, extendsTagName) native "Utils_register"; |
| |
| // Below code sets up VMLibraryHooks for resolvePackageUri. |
| static Uri resolvePackageUri(Uri packageUri) native "Utils_resolvePackageUri"; |
| static Future<Uri> _resolvePackageUriFuture(Uri packageUri) async { |
| return resolvePackageUri(packageUri); |
| } |
| static void _setupHooks() { |
| internal.VMLibraryHooks.resolvePackageUriFuture = _resolvePackageUriFuture; |
| } |
| |
| // Defines an interceptor if there is an appropriate JavaScript prototype to define it on. |
| // In any case, returns a typed JS wrapper compatible with dart:html and the new |
| // typed JS Interop. |
| static defineInterceptorCustomElement(jsObject, Type type) native "Utils_defineInterceptorCustomElement"; |
| static defineInterceptor(jsObject, Type type) native "Utils_defineInterceptor"; |
| static setInstanceInterceptor(o, Type type, {bool customElement: false}) native "Utils_setInstanceInterceptor"; |
| static setInstanceInterceptorCustomUpgrade(o) native "Utils_setInstanceInterceptorCustomUpgrade"; |
| |
| // This method will throw if the element isn't actually a real Element. |
| static initializeCustomElement(element) native "Utils_initializeCustomElement"; |
| } |
| |
| class Blink_DOMStringMap { |
| // _DOMStringMap native entry points |
| static containsKey(_DOMStringMap, key) native "DOMStringMap_containsKey_Callback"; |
| |
| static item(_DOMStringMap, key) native "DOMStringMap_item_Callback"; |
| |
| static setItem(_DOMStringMap, key, value) native "DOMStringMap_setItem_Callback"; |
| |
| static remove(_DOMStringMap, key) native "DOMStringMap_remove_Callback"; |
| |
| static get_keys(_DOMStringMap) native "DOMStringMap_getKeys_Callback"; |
| } |
| |
| // Calls through JsNative but returns DomException instead of error strings. |
| class Stats { |
| Stats(this.name) { |
| counts = new Map<String, int>(); |
| } |
| |
| String name; |
| Map<String, int> counts; |
| clear() { |
| counts.clear(); |
| } |
| |
| track(String v) { |
| counts[v] = counts.putIfAbsent(v, ()=> 0) + 1; |
| } |
| toString() { |
| StringBuffer sb = new StringBuffer(); |
| sb.write('================'); |
| sb.write('$name ${counts.length}'); |
| var keys = counts.keys.toList(); |
| keys.sort((a,b) => counts[b].compareTo(counts[a])); |
| for (var key in keys) { |
| print("$key => ${counts[key]}"); |
| } |
| sb.write('---------------'); |
| sb.write('================'); |
| return sb; |
| } |
| } |
| |
| bool TRACK_STATS = true; |
| dumpStats() { |
| print("------------ STATS ----------------"); |
| print(Blink_JsNative_DomException.getPropertyStats.toString()); |
| print(Blink_JsNative_DomException.setPropertyStats.toString()); |
| print(Blink_JsNative_DomException.callMethodStats.toString()); |
| print(Blink_JsNative_DomException.constructorStats.toString()); |
| print("-----------------------------------"); |
| } |
| |
| clearStats() { |
| Blink_JsNative_DomException.getPropertyStats.clear(); |
| Blink_JsNative_DomException.setPropertyStats.clear(); |
| Blink_JsNative_DomException.callMethodStats.clear(); |
| Blink_JsNative_DomException.constructorStats.clear(); |
| } |
| |
| class Blink_JsNative_DomException { |
| static var getPropertyStats = new Stats('get property'); |
| static var setPropertyStats = new Stats('set property'); |
| static var callMethodStats = new Stats('call method'); |
| static var constructorStats = new Stats('constructor'); |
| |
| static var constructors = new Map<String, dynamic>(); |
| |
| static getProperty(o, String name) { |
| try { |
| if (TRACK_STATS) getPropertyStats.track(name); |
| return js.JsNative.getProperty(o, name); |
| } catch (e) { |
| // Re-throw any errors (returned as a string) as a DomException. |
| throw new DomException.jsInterop(e); |
| } |
| } |
| |
| static propertyQuery(o, String name) { |
| try { |
| if (TRACK_STATS) getPropertyStats.track('__propertyQuery__'); |
| return js.JsNative.getProperty(o, name); |
| } catch (e) { |
| // Re-throw any errors (returned as a string) as a DomException. |
| throw new DomException.jsInterop(e); |
| } |
| } |
| |
| static callConstructor0(String name) { |
| try { |
| if (TRACK_STATS) constructorStats.track(name); |
| var constructor = constructors.putIfAbsent(name, () => js.context[name]); |
| return js.JsNative.callConstructor0(constructor); |
| } catch (e) { |
| // Re-throw any errors (returned as a string) as a DomException. |
| throw new DomException.jsInterop(e); |
| } |
| } |
| |
| static callConstructor(String name, List args) { |
| try { |
| if (TRACK_STATS) constructorStats.track(name); |
| var constructor = constructors.putIfAbsent(name, () => js.context[name]); |
| return js.JsNative.callConstructor(constructor, args); |
| } catch (e) { |
| // Re-throw any errors (returned as a string) as a DomException. |
| throw new DomException.jsInterop(e); |
| } |
| } |
| |
| static setProperty(o, String name, value) { |
| try { |
| if (TRACK_STATS) setPropertyStats.track(name); |
| return js.JsNative.setProperty(o, name, value); |
| } catch (e) { |
| // Re-throw any errors (returned as a string) as a DomException. |
| throw new DomException.jsInterop(e); |
| } |
| } |
| |
| static callMethod(o, String method, List args) { |
| try { |
| if (TRACK_STATS) callMethodStats.track(method); |
| return js.JsNative.callMethod(o, method, args); |
| } catch (e) { |
| // Re-throw any errors (returned as a string) as a DomException. |
| throw new DomException.jsInterop(e); |
| } |
| } |
| }""" |
| |
| CLASS_DEFINITION = """class Blink%s { |
| static final instance = new Blink%s(); |
| |
| """ |
| |
| CLASS_DEFINITION_EXTENDS = """class Blink%s extends Blink%s { |
| static final instance = new Blink%s(); |
| |
| """ |
| |
| #(interface_name) |
| |
| # |
| CONSTRUCTOR_0 = [ |
| ' constructorCallback_0_()', |
| ' => Blink_JsNative_DomException.callConstructor0("%s");\n\n', |
| ' native "Blink_Constructor_%s";\n\n' |
| ] |
| |
| #(argument_count, arguments, interface_name, arguments) |
| CONSTRUCTOR_ARGS = [ |
| ' constructorCallback_%s_(%s)', |
| ' => Blink_JsNative_DomException.callConstructor("%s", [%s]);\n\n', |
| ' native "Blink_Constructor_Args_%s" /* %s */;\n\n' |
| ] |
| |
| #(attribute_name, attribute_name) |
| ATTRIBUTE_GETTER = [ |
| ' %s_Getter_(mthis)', |
| ' => Blink_JsNative_DomException.getProperty(mthis /* %s */, "%s");\n\n', |
| ' native "Blink_Getter_%s_%s";\n\n' |
| ] |
| |
| ATTRIBUTE_SETTER = [ |
| ' %s_Setter_(mthis, __arg_0)', |
| ' => Blink_JsNative_DomException.setProperty(mthis /* %s */, "%s", __arg_0);\n\n', |
| ' native "Blink_Setter_%s_%s";\n\n' |
| ] |
| |
| #(operation_name, operationName) |
| OPERATION_0 = [ |
| ' %s_Callback_0_(mthis)', |
| ' => Blink_JsNative_DomException.callMethod(mthis /* %s */, "%s", []);\n\n', |
| ' native "Blink_Operation_0_%s_%s";\n\n' |
| ] |
| |
| # getter, setter, deleter, propertyQuery code, and propertyIsEnumerable |
| OPERATION_1 = [ |
| ' $%s_Callback_1_(mthis, __arg_0)', |
| ' => Blink_JsNative_DomException.callMethod(mthis /* %s */, "%s", [__arg_0]);\n\n', |
| ' native "Blink_Operation_1_%s_%s";\n\n' |
| ] |
| |
| OPERATION_2 = [ |
| ' $%s_Callback_2_(mthis, __arg_0, __arg_1)', |
| ' => Blink_JsNative_DomException.callMethod(mthis /* %s */, "%s", [__arg_0, __arg_1]);\n\n', |
| ' native "Blink_Operation_2_%s_%s";\n\n' |
| ] |
| |
| OPERATION_PQ = [ |
| ' $%s_Callback_1_(mthis, __arg_0)', |
| ' => Blink_JsNative_DomException.propertyQuery(mthis, __arg_0); /* %s */ \n\n', |
| ' native "Blink_Operation_PQ_%s";\n\n' |
| ] |
| |
| #(operation_name, argument_count, arguments, operation_name, arguments) |
| ARGUMENT_NUM = "__arg_%s" |
| OPERATION_ARGS = [ |
| ' %s_Callback_%s_(mthis, %s)', |
| ' => Blink_JsNative_DomException.callMethod(mthis /* %s */, "%s", [%s]);\n\n', |
| ' native "Blink_Operation_%s_%s"; /* %s */\n\n' |
| ] |
| |
| # get class property to make static call. |
| CLASS_STATIC = 'Blink_JsNative_DomException.getProperty(js.context, "%s")' |
| |
| # name, classname_getproperty, name |
| STATIC_ATTRIBUTE_GETTER = [ |
| ' %s_Getter_()', |
| ' => Blink_JsNative_DomException.getProperty(%s /* %s */, "%s");\n\n', |
| ' /* %s */ native "Blink_Static_getter_%s_%s"' |
| ] |
| |
| # name, classname_getproperty, name |
| STATIC_OPERATION_0 = [ |
| ' %s_Callback_0_()', |
| ' => Blink_JsNative_DomException.callMethod(%s /* %s */, "%s", []);\n\n', |
| ' /* %s */ native "Blink_Static_Operation_0_%s_%s' |
| ] |
| |
| # name, argsCount, args, classname_getproperty, name, args |
| STATIC_OPERATION_ARGS = [ |
| ' %s_Callback_%s_(%s)', |
| ' => Blink_JsNative_DomException.callMethod(%s /* %s */, "%s", [%s]);\n\n', |
| ' /* %s */ native "Blink_Static_Operations_%s_%s" /* %s */ \n\n' |
| ] |
| |
| CLASS_DEFINITION_END = """} |
| |
| """ |
| |
| |
| def ConstantOutputOrder(a, b): |
| """Canonical output ordering for constants.""" |
| return (a.id > b.id) - (a.id < b.id) |
| |
| |
| def generate_parameter_entries(param_infos): |
| optional_default_args = 0 |
| for argument in param_infos: |
| if argument.is_optional: |
| optional_default_args += 1 |
| |
| arg_count = len(param_infos) |
| min_arg_count = arg_count - optional_default_args |
| lb = min_arg_count - 2 if min_arg_count > 2 else 0 |
| return (lb, arg_count + 1) |
| |
| |
| constructor_renames = { |
| 'RTCPeerConnection': 'webkitRTCPeerConnection', |
| 'SpeechRecognition': 'webkitSpeechRecognition', |
| } |
| |
| |
| def rename_constructor(name): |
| return constructor_renames[name] if name in constructor_renames else name |
| |
| |
| def _Find_Match(interface_id, member, member_prefix, candidates): |
| member_name = interface_id + '.' + member |
| if member_name in candidates: |
| return member_name |
| member_name = interface_id + '.' + member_prefix + member |
| if member_name in candidates: |
| return member_name |
| member_name = interface_id + '.*' |
| if member_name in candidates: |
| return member_name |
| |
| |
| def _Is_Native(interface, member): |
| return _Find_Match(interface, member, '', _js_custom_members) |
| |
| |
| def Select_Stub(template, is_native): |
| if is_native: |
| return template[0] + template[2] |
| else: |
| return template[0] + template[1] |
| |
| |
| def Generate_Blink(output_dir, database, type_registry): |
| blink_filename = os.path.join(output_dir, '_blink_dartium.dart') |
| blink_file = open(blink_filename, 'w') |
| |
| blink_file.write(HEADER) |
| |
| interfaces = database.GetInterfaces() |
| for interface in interfaces: |
| name = interface.id |
| resolver_entry = ' if (s == "%s") return Blink%s.instance;\n' % (name, |
| name) |
| blink_file.write(resolver_entry) |
| |
| blink_file.write(END_RESOLVER) |
| |
| for interface in interfaces: |
| name = interface.id |
| |
| if interface.parents and len( |
| interface.parents) > 0 and interface.parents[0].id: |
| extends = interface.parents[0].id |
| class_def = CLASS_DEFINITION_EXTENDS % (name, extends, name) |
| else: |
| class_def = CLASS_DEFINITION % (name, name) |
| blink_file.write(class_def) |
| |
| analyzed_constructors = AnalyzeConstructor(interface) |
| if analyzed_constructors: |
| _Emit_Blink_Constructors(blink_file, analyzed_constructors) |
| elif 'Constructor' in interface.ext_attrs: |
| # Zero parameter constructor. |
| blink_file.write( |
| Select_Stub(CONSTRUCTOR_0, _Is_Native(name, 'constructor')) % |
| rename_constructor(name)) |
| |
| _Process_Attributes(blink_file, interface, interface.attributes) |
| _Process_Operations(blink_file, interface, interface.operations, True) |
| |
| _Emit_Extra_Operations(blink_file, name) |
| |
| secondary_parents = database.TransitiveSecondaryParents( |
| interface, False) |
| for secondary in secondary_parents: |
| _Process_Attributes(blink_file, secondary, secondary.attributes) |
| _Process_Operations(blink_file, secondary, secondary.operations, |
| False) |
| |
| blink_file.write(CLASS_DEFINITION_END) |
| |
| blink_file.write(BLINK_UTILS) |
| |
| blink_file.close() |
| |
| |
| def _Emit_Extra_Operations(blink_file, interface_name): |
| if (interface_name in _additional_methods): |
| (name, arg_count, return_value) = _additional_methods[interface_name] |
| exposed_name = ''.join(['__get', '___', name]) if return_value else name |
| blink_file.write( |
| Select_Stub(OPERATION_1, False) % (exposed_name, interface_name, |
| name)) |
| |
| |
| def _Emit_Blink_Constructors(blink_file, analyzed_constructors): |
| (arg_min_count, arg_max_count) = generate_parameter_entries( |
| analyzed_constructors.param_infos) |
| name = analyzed_constructors.js_name |
| if not (name): |
| name = analyzed_constructors.type_name |
| |
| for callback_index in range(arg_min_count, arg_max_count): |
| if callback_index == 0: |
| blink_file.write( |
| Select_Stub(CONSTRUCTOR_0, _Is_Native(name, 'constructor')) % |
| (rename_constructor(name))) |
| else: |
| arguments = [] |
| for i in range(0, callback_index): |
| arguments.append(ARGUMENT_NUM % i) |
| argument_list = ', '.join(arguments) |
| blink_file.write( |
| Select_Stub(CONSTRUCTOR_ARGS, _Is_Native(name, 'constructor')) % |
| (callback_index, argument_list, rename_constructor(name), |
| argument_list)) |
| |
| |
| def _Process_Attributes(blink_file, interface, attributes): |
| # Emit an interface's attributes and operations. |
| for attribute in sorted(attributes, ConstantOutputOrder): |
| name = attribute.id |
| is_native = _Is_Native(interface.id, name) |
| if attribute.is_read_only: |
| if attribute.is_static: |
| class_property = CLASS_STATIC % interface.id |
| blink_file.write( |
| Select_Stub(STATIC_ATTRIBUTE_GETTER, is_native) % |
| (name, class_property, interface.id, name)) |
| else: |
| blink_file.write( |
| Select_Stub(ATTRIBUTE_GETTER, is_native) % |
| (name, interface.id, name)) |
| else: |
| blink_file.write( |
| Select_Stub(ATTRIBUTE_GETTER, is_native) % (name, interface.id, |
| name)) |
| blink_file.write( |
| Select_Stub(ATTRIBUTE_SETTER, is_native) % (name, interface.id, |
| name)) |
| |
| |
| def _Process_Operations(blink_file, |
| interface, |
| operations, |
| primary_interface=False): |
| analyzeOperations = [] |
| |
| for operation in sorted(operations, ConstantOutputOrder): |
| if len(analyzeOperations) == 0: |
| analyzeOperations.append(operation) |
| else: |
| if analyzeOperations[0].id == operation.id: |
| # Handle overloads |
| analyzeOperations.append(operation) |
| else: |
| _Emit_Blink_Operation(blink_file, interface, analyzeOperations, |
| primary_interface) |
| analyzeOperations = [operation] |
| if len(analyzeOperations) > 0: |
| _Emit_Blink_Operation(blink_file, interface, analyzeOperations, |
| primary_interface) |
| |
| |
| # List of DartName operations to not emit (e.g., For now only WebGL2RenderingContextBase |
| # has readPixels in both WebGLRenderingContextBase and WebGL2RenderingContextBase. |
| # Furthermore, readPixels has the exact same number of arguments - in Javascript |
| # there is no typing so they're the same. |
| suppressed_operations = { |
| 'WebGL2RenderingContextBase': ['readPixels2', 'texImage2D2'], |
| } |
| |
| |
| def _Suppress_Secondary_Interface_Operation(interface, analyzed): |
| if interface.id in suppressed_operations: |
| # Should this DartName (name property) be suppressed on this interface? |
| return analyzed.name in suppressed_operations[interface.id] |
| return False |
| |
| |
| def _Emit_Blink_Operation(blink_file, interface, analyzeOperations, |
| primary_interface): |
| analyzed = AnalyzeOperation(interface, analyzeOperations) |
| |
| if not (primary_interface) and _Suppress_Secondary_Interface_Operation( |
| interface, analyzed): |
| return |
| |
| (arg_min_count, |
| arg_max_count) = generate_parameter_entries(analyzed.param_infos) |
| name = analyzed.js_name |
| |
| is_native = _Is_Native(interface.id, name) |
| |
| operation = analyzeOperations[0] |
| if (name.startswith('__') and \ |
| ('getter' in operation.specials or \ |
| 'setter' in operation.specials or \ |
| 'deleter' in operation.specials)): |
| if name == '__propertyQuery__': |
| blink_file.write( |
| Select_Stub(OPERATION_PQ, is_native) % (name, interface.id)) |
| else: |
| arg_min_count = arg_max_count |
| if arg_max_count == 2: |
| blink_file.write( |
| Select_Stub(OPERATION_1, is_native) % (name, interface.id, |
| name)) |
| elif arg_max_count == 3: |
| blink_file.write( |
| Select_Stub(OPERATION_2, is_native) % (name, interface.id, |
| name)) |
| else: |
| print("FATAL ERROR: _blink emitter operator %s.%s" % |
| (interface.id, name)) |
| exit |
| |
| return |
| |
| for callback_index in range(arg_min_count, arg_max_count): |
| if callback_index == 0: |
| if operation.is_static: |
| class_property = CLASS_STATIC % interface.id |
| blink_file.write( |
| Select_Stub(STATIC_OPERATION_0, is_native) % |
| (name, class_property, interface.id, name)) |
| else: |
| blink_file.write( |
| Select_Stub(OPERATION_0, is_native) % (name, interface.id, |
| name)) |
| else: |
| arguments = [] |
| for i in range(0, callback_index): |
| arguments.append(ARGUMENT_NUM % i) |
| argument_list = ', '.join(arguments) |
| if operation.is_static: |
| class_property = CLASS_STATIC % interface.id |
| blink_file.write( |
| Select_Stub(STATIC_OPERATION_ARGS, is_native) % |
| (name, callback_index, argument_list, class_property, |
| interface.id, name, argument_list)) |
| else: |
| blink_file.write( |
| Select_Stub(OPERATION_ARGS, is_native) % |
| (name, callback_index, argument_list, interface.id, name, |
| argument_list)) |