Chromium Code Reviews| Index: frog/leg/lib/native_helper.dart |
| =================================================================== |
| --- frog/leg/lib/native_helper.dart (revision 0) |
| +++ frog/leg/lib/native_helper.dart (revision 0) |
| @@ -0,0 +1,277 @@ |
| +// 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. |
| + |
| +String typeNameInChrome(obj) { |
| + String name = JS('String', "#.constructor.name", obj); |
| + if (name == 'Window') return 'DOMWindow'; |
| + return name; |
| +} |
| + |
| +String typeNameInFirefox(obj) { |
| + String name = constructorNameFallback(obj); |
| + if (name == 'Window') return 'DOMWindow'; |
| + if (name == 'Document') return 'HTMLDocument'; |
| + if (name == 'XMLDocument') return 'Document'; |
| + return name; |
| +} |
| + |
| +String typeNameInIE(obj) { |
| + String name = constructorNameFallback(obj); |
| + if (name == 'Window') return 'DOMWindow'; |
| + // IE calls both HTML and XML documents 'Document', so we check for the |
| + // xmlVersion property, which is the empty string on HTML documents. |
| + if (name == 'Document' && JS('bool', '!!#.xmlVersion', obj)) return 'Document'; |
|
floitsch
2012/03/28 23:36:33
80 chars.
ngeoffray
2012/03/29 11:38:40
Done.
|
| + if (name == 'Document') return 'HTMLDocument'; |
| + return name; |
| +} |
| + |
| +String constructorNameFallback(obj) { |
| + var constructor = JS('var', "#.constructor", obj); |
| + if (JS('String', "typeof(#)", constructor) === 'function') { |
| + // The constructor isn't null or undefined at this point. Try |
| + // to grab hold of its name. |
| + var name = JS('var', '#.name', constructor); |
| + // If the name is a non-empty string, we use that as the type |
| + // name of this object. On Firefox, we often get 'Object' as |
| + // the constructor name even for more specialized objects so |
| + // we have to fall through to the toString() based implementation |
| + // below in that case. |
| + if (JS('String', "typeof(#)", name) === 'string' |
| + && !name.isEmpty() |
| + && name !== 'Object') { |
| + return name; |
| + } |
| + } |
| + String string = JS('String', 'Object.prototype.toString.call(#)', obj); |
| + return string.substring(8, string.length - 1); |
| +} |
| + |
| + |
| +/** |
| + * Returns the function to use to get the type name of an object. |
| + */ |
| +Function getTypeNameOfFunction() { |
|
floitsch
2012/03/28 23:36:33
maybe getFunctionForTypeNameOf ?
Otherwise it read
ngeoffray
2012/03/29 11:38:40
Done.
|
| + // If we're not in the browser, we're almost certainly running on v8. |
| + if (JS('String', 'typeof(navigator)') !== 'object') return typeNameInChrome; |
| + |
| + String userAgent = JS('String', "navigator.userAgent"); |
| + if (userAgent.contains(const RegExp('Chrome|DumpRenderTree'))) { |
| + return typeNameInChrome; |
| + } else if (userAgent.contains('Firefox')) { |
| + return typeNameInFirefox; |
| + } else if (userAgent.contains('MSIE')) { |
| + return typeNameInIE; |
| + } else { |
| + return constructorNameFallback; |
| + } |
| +} |
| + |
| + |
| +/** |
| + * Cached value for the function to use to get the type name of an |
| + * object. |
| + */ |
| +Function _getTypeNameOf; |
|
sra1
2012/03/27 18:05:27
Could you initialize this with the following funct
ngeoffray
2012/03/29 10:20:48
I cannot right now, as it's not valid Dart code to
|
| + |
| +/** |
| + * Returns the type name of [obj]. |
| + */ |
| +String getTypeNameOf(var obj) { |
| + if (_getTypeNameOf === null) _getTypeNameOf = getTypeNameOfFunction(); |
| + return _getTypeNameOf(obj); |
| +} |
| + |
| +/** |
| + * Sets a JavaScript property on an object. |
| + */ |
| +void defineProperty(var obj, String property, var value) { |
| + JS('void', """Object.defineProperty(#, #, |
| + {value: #, enumerable: false, writable: false, configurable: true});""", |
| + obj, |
| + property, |
| + value); |
| +} |
| + |
| +/** |
| + * Helper method to throw a [NoSuchMethodException] for a invalid call |
| + * on a native object. |
| + */ |
| +void throwNoSuchMethod(obj, name, arguments) { |
| + throw new NoSuchMethodException(obj, name, arguments); |
| +} |
| + |
| +/** |
| + * This method looks up the type name of [obj] in [methods]. If it |
| + * cannot find it, it looks into the [_dynamicMetadata] array. If the |
| + * method can still not be found, it creates a method that will throw |
| + * a [NoSuchMethodException]. |
| + * |
| + * Once it has a method, the prototype of [obj] is patched with that |
| + * method, on the property [name]. The method is then invoked. |
| + * |
| + * This method returns the result of invoking the found method. |
| + */ |
| +dynamicBind(var obj, |
| + String name, |
| + var methods, |
|
floitsch
2012/03/28 23:36:33
Add comment what the type of methods is (a native
ngeoffray
2012/03/29 11:38:40
Done.
|
| + List arguments) { |
| + String tag = getTypeNameOf(obj); |
| + var method = JS('var', '#[#]', methods, tag); |
| + |
| + if (method === null && _dynamicMetadata !== null) { |
|
floitsch
2012/03/28 23:36:33
how can _dynamicMetadata be null?
sra1
2012/03/29 01:14:23
It is not generated for programs that call only me
|
| + for (int i = 0; i < _dynamicMetadata.length; i++) { |
| + MetaInfo entry = _dynamicMetadata[i]; |
| + if (entry.set.contains(tag)) { |
| + method = JS('var', '#[#]', methods, entry.tag); |
| + if (method !== null) break; |
| + } |
| + } |
| + } |
| + |
| + if (method === null) { |
| + method = JS('var', "#['Object']", methods); |
| + } |
| + |
| + if (method === null) { |
| + method = JS('var', |
| + 'function () {' |
| + '#(#, #, Array.prototype.slice.call(arguments));' |
|
sra1
2012/03/29 01:14:23
There is a missing exact type check here.
class A
ngeoffray
2012/03/29 11:38:40
Done.
|
| + '}', |
| + DART_CLOSURE_TO_JS(throwNoSuchMethod), obj, name); |
| + } |
| + |
| + var nullCheckMethod = JS('var', |
| + 'function() {' |
| + 'var res = #.apply(this, Array.prototype.slice.call(arguments));' |
| + 'return res === null ? (void 0) : res;' |
| + '}', |
| + method); |
| + |
| + var proto = JS('var', 'Object.getPrototypeOf(#)', obj); |
| + if (JS('bool', '!#.hasOwnProperty(#)', proto, name)) { |
|
floitsch
2012/03/28 23:36:33
how can this happen?
sra1
2012/03/29 01:14:23
This prevents (new Object()).dynamic.foo() from
|
| + defineProperty(proto, name, nullCheckMethod); |
| + } |
| + |
| + return JS('var', '#.apply(#, #)', nullCheckMethod, obj, arguments); |
| +} |
| + |
| +/** |
| + * Code for doing the dynamic dispatch on JavaScript prototypes that are not |
| + * available at compile-time. Each property of a native Dart class |
| + * is registered through this function, which is called with the |
| + * following pattern: |
| + * |
| + * dynamicFunction('propertyName').prototypeName = // JS code |
| + * |
| + * What this function does is: |
| + * - Creates a map of { prototypeName: JS code }. |
| + * - Attaches 'propertyName' to the JS Object prototype that will |
| + * intercept at runtime all calls to propertyName. |
| + * - Sets the value of 'propertyName' to the returned method from |
| + * [dynamicBind]. |
| + * |
| + */ |
| +dynamicFunction(name) { |
| + var f = JS('var', 'Object.prototype[#]', name); |
| + if (f !== null && JS('bool', '!!#.methods', f)) { |
| + return JS('var', '#.methods', f); |
| + } |
| + |
| + // TODO(ngeoffray): We could make this a map if the code we |
| + // generate plays well with a Dart map. |
| + var methods = JS('var', '{}'); |
| + // If there is a method attached to the Dart Object class, use it as |
| + // the method to call in case no method is registered for that type. |
| + var dartMethod = JS('var', 'Object.getPrototypeOf(#)[#]', new Object(), name); |
|
floitsch
2012/03/28 23:36:33
const Object() would work too. No preference.
ngeoffray
2012/03/29 11:38:40
Good point. Done.
|
| + if (dartMethod !== null) JS('void', "#['Object'] = #", methods, dartMethod); |
| + |
| + var bind = JS('var', |
| + 'function() {' |
| + 'return #(this, #, #, Array.prototype.slice.call(arguments));' |
| + '}', |
| + DART_CLOSURE_TO_JS(dynamicBind), name, methods); |
| + |
| + JS('void', '#.methods = #', bind, methods); |
| + defineProperty(JS('var', 'Object.prototype'), name, bind); |
| + return methods; |
| +} |
| + |
| +/** |
| + * This class encodes the class hierarchy when we need it for dynamic |
| + * dispatch. |
| + */ |
| +class MetaInfo { |
| + /** |
| + * The type name this [MetaInfo] relates to. |
| + */ |
| + String tag; |
| + |
| + /** |
| + * A string containing the names of subtypes of [tag], separated by |
| + * '|'. |
| + */ |
| + String tags; |
| + |
| + /** |
| + * A list of names of subtypes of [tag]. |
| + */ |
| + Set<String> set; |
| + |
| + MetaInfo(this.tag, this.tags, this.set); |
| +} |
| + |
| +List<MetaInfo> get _dynamicMetadata() { |
| + if (JS('var', 'typeof(\$dynamicMetadata)') === 'undefined') { |
|
floitsch
2012/03/28 23:36:33
why is \$dynamicMetadata not a normal Dart global
ngeoffray
2012/03/29 11:38:40
Because it is shared with isolates. Making it glob
|
| + _dynamicMetadata = <MetaInfo>[]; |
| + } |
| + return JS('var', '\$dynamicMetadata'); |
| +} |
| + |
| +void set _dynamicMetadata(List<String> table) { |
| + JS('void', '\$dynamicMetadata = #', table); |
| +} |
| + |
| +/** |
| + * Builds the metadata used for encoding the class hierarchy of native |
| + * classes. The following example: |
| + * |
| + * class A native "*A" {} |
| + * class B native "*B" {} |
|
floitsch
2012/03/28 23:36:33
extends A
ngeoffray
2012/03/29 11:38:40
Done.
|
| + * |
| + * Will generate: |
|
floitsch
2012/03/28 23:36:33
Will generate a list of: (the inputTable)
|
| + * ['A', 'A|B'] |
|
floitsch
2012/03/28 23:36:33
explain. ex: where 'A' is the class the informatio
ngeoffray
2012/03/29 11:38:40
I'm reluctant on adding comments as this is copied
|
| + * |
| + * This method turns the array into a list of [MetaInfo] objects. |
| + */ |
| +void dynamicSetMetadata(List<List<String>> inputTable) { |
| + _dynamicMetadata = <MetaInfo>[]; |
| + for (int i = 0; i < inputTable.length; i++) { |
| + String tag = inputTable[i][0]; |
| + String tags = inputTable[i][1]; |
| + Set<String> set = new Set<String>(); |
| + List<String> tagNames = tags.split('|'); |
| + for (int j = 0; j < tagNames.length; j++) { |
| + set.add(tagNames[j]); |
| + } |
| + _dynamicMetadata.add(new MetaInfo(tag, tags, set)); |
| + } |
| +} |
| + |
| +// Initialized by the compiler. |
|
floitsch
2012/03/28 23:36:33
Describe type. (if I see correctly a native JS has
ngeoffray
2012/03/29 11:38:40
This code was removed.
|
| +var isChecksHelper; |
| + |
| +// This method will be called for 'is' checks on native types. |
| +// It takes the object on which the 'is' check is being done, and the |
| +// property name for the type check. The method patches the real |
| +// prototype of the object with the value from the Dart object |
| +// (see [generateNativeClass]). |
| +bool dynamicIsCheck(obj, String typeName) { |
| + if (isJsArray(obj)) return false; |
| + // Check if the Dart object corresponding to this class has the property. |
| + var res = JS('bool', '!!#[#][#]', isChecksHelper, getTypeNameOf(obj), |
| + typeName); |
| + var proto = JS('var', 'Object.getPrototypeOf(#)', obj); |
| + defineProperty(proto, typeName, res); |
| + return res; |
| +} |