| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 String typeNameInChrome(obj) { | |
| 6 String name = JS('String', "#.constructor.name", obj); | |
| 7 if (name == 'Window') return 'DOMWindow'; | |
| 8 return name; | |
| 9 } | |
| 10 | |
| 11 String typeNameInFirefox(obj) { | |
| 12 String name = constructorNameFallback(obj); | |
| 13 if (name == 'Window') return 'DOMWindow'; | |
| 14 if (name == 'Document') return 'HTMLDocument'; | |
| 15 if (name == 'XMLDocument') return 'Document'; | |
| 16 return name; | |
| 17 } | |
| 18 | |
| 19 String typeNameInIE(obj) { | |
| 20 String name = constructorNameFallback(obj); | |
| 21 if (name == 'Window') return 'DOMWindow'; | |
| 22 // IE calls both HTML and XML documents 'Document', so we check for the | |
| 23 // xmlVersion property, which is the empty string on HTML documents. | |
| 24 if (name == 'Document' && JS('bool', '!!#.xmlVersion', obj)) return 'Document'
; | |
| 25 if (name == 'Document') return 'HTMLDocument'; | |
| 26 return name; | |
| 27 } | |
| 28 | |
| 29 String constructorNameFallback(obj) { | |
| 30 var constructor = JS('var', "#.constructor", obj); | |
| 31 if (JS('String', "typeof(#)", constructor) === 'function') { | |
| 32 // The constructor isn't null or undefined at this point. Try | |
| 33 // to grab hold of its name. | |
| 34 var name = JS('var', '#.name', constructor); | |
| 35 // If the name is a non-empty string, we use that as the type | |
| 36 // name of this object. On Firefox, we often get 'Object' as | |
| 37 // the constructor name even for more specialized objects so | |
| 38 // we have to fall through to the toString() based implementation | |
| 39 // below in that case. | |
| 40 if (JS('String', "typeof(#)", name) === 'string' | |
| 41 && !name.isEmpty() | |
| 42 && name !== 'Object') { | |
| 43 return name; | |
| 44 } | |
| 45 } | |
| 46 String string = JS('String', 'Object.prototype.toString.call(#)', obj); | |
| 47 return string.substring(8, string.length - 1); | |
| 48 } | |
| 49 | |
| 50 | |
| 51 /** | |
| 52 * Returns the function to use to get the type name of an object. | |
| 53 */ | |
| 54 Function getTypeNameOfFunction() { | |
| 55 // If we're not in the browser, we're almost certainly running on v8. | |
| 56 if (JS('String', 'typeof(navigator)') !== 'object') return typeNameInChrome; | |
| 57 | |
| 58 String userAgent = JS('String', "navigator.userAgent"); | |
| 59 if (userAgent.contains(const RegExp('Chrome|DumpRenderTree'))) { | |
| 60 return typeNameInChrome; | |
| 61 } else if (userAgent.contains('Firefox')) { | |
| 62 return typeNameInFirefox; | |
| 63 } else if (userAgent.contains('MSIE')) { | |
| 64 return typeNameInIE; | |
| 65 } else { | |
| 66 return constructorNameFallback; | |
| 67 } | |
| 68 } | |
| 69 | |
| 70 | |
| 71 /** | |
| 72 * Cached value for the function to use to get the type name of an | |
| 73 * object. | |
| 74 */ | |
| 75 Function _getTypeNameOf; | |
| 76 | |
| 77 /** | |
| 78 * Returns the type name of [obj]. | |
| 79 */ | |
| 80 String getTypeNameOf(var obj) { | |
| 81 if (_getTypeNameOf === null) _getTypeNameOf = getTypeNameOfFunction(); | |
| 82 return _getTypeNameOf(obj); | |
| 83 } | |
| 84 | |
| 85 String toStringForNativeObject(var obj) { | |
| 86 return 'Instance of ${getTypeNameOf(obj)}'; | |
| 87 } | |
| 88 | |
| 89 /** | |
| 90 * Sets a JavaScript property on an object. | |
| 91 */ | |
| 92 void defineProperty(var obj, String property, var value) { | |
| 93 JS('void', """Object.defineProperty(#, #, | |
| 94 {value: #, enumerable: false, writable: false, configurable: true});""", | |
| 95 obj, | |
| 96 property, | |
| 97 value); | |
| 98 } | |
| 99 | |
| 100 /** | |
| 101 * Helper method to throw a [NoSuchMethodException] for a invalid call | |
| 102 * on a native object. | |
| 103 */ | |
| 104 void throwNoSuchMethod(obj, name, arguments) { | |
| 105 throw new NoSuchMethodException(obj, name, arguments); | |
| 106 } | |
| 107 | |
| 108 /** | |
| 109 * This method looks up the type name of [obj] in [methods]. If it | |
| 110 * cannot find it, it looks into the [_dynamicMetadata] array. If the | |
| 111 * method can still not be found, it creates a method that will throw | |
| 112 * a [NoSuchMethodException]. | |
| 113 * | |
| 114 * Once it has a method, the prototype of [obj] is patched with that | |
| 115 * method, on the property [name]. The method is then invoked. | |
| 116 * | |
| 117 * This method returns the result of invoking the found method. | |
| 118 */ | |
| 119 dynamicBind(var obj, | |
| 120 String name, | |
| 121 var methods, | |
| 122 List arguments) { | |
| 123 String tag = getTypeNameOf(obj); | |
| 124 var method = JS('var', '#[#]', methods, tag); | |
| 125 | |
| 126 if (method === null && _dynamicMetadata !== null) { | |
| 127 for (int i = 0; i < _dynamicMetadata.length; i++) { | |
| 128 MetaInfo entry = _dynamicMetadata[i]; | |
| 129 if (entry.set.contains(tag)) { | |
| 130 method = JS('var', '#[#]', methods, entry.tag); | |
| 131 if (method !== null) break; | |
| 132 } | |
| 133 } | |
| 134 } | |
| 135 | |
| 136 if (method === null) { | |
| 137 method = JS('var', "#['Object']", methods); | |
| 138 } | |
| 139 | |
| 140 if (method === null) { | |
| 141 method = JS('var', | |
| 142 'function () {' | |
| 143 '#(#, #, Array.prototype.slice.call(arguments));' | |
| 144 '}', | |
| 145 DART_CLOSURE_TO_JS(throwNoSuchMethod), obj, name); | |
| 146 } | |
| 147 | |
| 148 var nullCheckMethod = JS('var', | |
| 149 'function() {' | |
| 150 'var res = #.apply(this, Array.prototype.slice.call(arguments));' | |
| 151 'return res === null ? (void 0) : res;' | |
| 152 '}', | |
| 153 method); | |
| 154 | |
| 155 var proto = JS('var', 'Object.getPrototypeOf(#)', obj); | |
| 156 if (JS('bool', '!#.hasOwnProperty(#)', proto, name)) { | |
| 157 defineProperty(proto, name, nullCheckMethod); | |
| 158 } | |
| 159 | |
| 160 return JS('var', '#.apply(#, #)', nullCheckMethod, obj, arguments); | |
| 161 } | |
| 162 | |
| 163 /** | |
| 164 * Code for doing the dynamic dispatch on JavaScript prototypes that are not | |
| 165 * available at compile-time. Each property of a native Dart class | |
| 166 * is registered through this function, which is called with the | |
| 167 * following pattern: | |
| 168 * | |
| 169 * dynamicFunction('propertyName').prototypeName = // JS code | |
| 170 * | |
| 171 * What this function does is: | |
| 172 * - Creates a map of { prototypeName: JS code }. | |
| 173 * - Attaches 'propertyName' to the JS Object prototype that will | |
| 174 * intercept at runtime all calls to propertyName. | |
| 175 * - Sets the value of 'propertyName' to the returned method from | |
| 176 * [dynamicBind]. | |
| 177 * | |
| 178 */ | |
| 179 dynamicFunction(name) { | |
| 180 var f = JS('var', 'Object.prototype[#]', name); | |
| 181 if (f !== null && JS('bool', '!!#.methods', f)) { | |
| 182 return JS('var', '#.methods', f); | |
| 183 } | |
| 184 | |
| 185 // TODO(ngeoffray): We could make this a map if the code we | |
| 186 // generate plays well with a Dart map. | |
| 187 var methods = JS('var', '{}'); | |
| 188 // If there is a method attached to the Dart Object class, use it as | |
| 189 // the method to call in case no method is registered for that type. | |
| 190 var dartMethod = JS('var', 'Object.getPrototypeOf(#)[#]', new Object(), name); | |
| 191 if (dartMethod !== null) JS('void', "#['Object'] = #", methods, dartMethod); | |
| 192 | |
| 193 var bind = JS('var', | |
| 194 'function() {' | |
| 195 'return #(this, #, #, Array.prototype.slice.call(arguments));' | |
| 196 '}', | |
| 197 DART_CLOSURE_TO_JS(dynamicBind), name, methods); | |
| 198 | |
| 199 JS('void', '#.methods = #', bind, methods); | |
| 200 defineProperty(JS('var', 'Object.prototype'), name, bind); | |
| 201 return methods; | |
| 202 } | |
| 203 | |
| 204 /** | |
| 205 * This class encodes the class hierarchy when we need it for dynamic | |
| 206 * dispatch. | |
| 207 */ | |
| 208 class MetaInfo { | |
| 209 /** | |
| 210 * The type name this [MetaInfo] relates to. | |
| 211 */ | |
| 212 String tag; | |
| 213 | |
| 214 /** | |
| 215 * A string containing the names of subtypes of [tag], separated by | |
| 216 * '|'. | |
| 217 */ | |
| 218 String tags; | |
| 219 | |
| 220 /** | |
| 221 * A list of names of subtypes of [tag]. | |
| 222 */ | |
| 223 Set<String> set; | |
| 224 | |
| 225 MetaInfo(this.tag, this.tags, this.set); | |
| 226 } | |
| 227 | |
| 228 List<MetaInfo> get _dynamicMetadata() { | |
| 229 if (JS('var', 'typeof(\$dynamicMetadata)') === 'undefined') { | |
| 230 _dynamicMetadata = <MetaInfo>[]; | |
| 231 } | |
| 232 return JS('var', '\$dynamicMetadata'); | |
| 233 } | |
| 234 | |
| 235 void set _dynamicMetadata(List<String> table) { | |
| 236 JS('void', '\$dynamicMetadata = #', table); | |
| 237 } | |
| 238 | |
| 239 /** | |
| 240 * Builds the metadata used for encoding the class hierarchy of native | |
| 241 * classes. The following example: | |
| 242 * | |
| 243 * class A native "*A" {} | |
| 244 * class B native "*B" {} | |
| 245 * | |
| 246 * Will generate: | |
| 247 * ['A', 'A|B'] | |
| 248 * | |
| 249 * This method turns the array into a list of [MetaInfo] objects. | |
| 250 */ | |
| 251 void dynamicSetMetadata(List<List<String>> inputTable) { | |
| 252 _dynamicMetadata = <MetaInfo>[]; | |
| 253 for (int i = 0; i < inputTable.length; i++) { | |
| 254 String tag = inputTable[i][0]; | |
| 255 String tags = inputTable[i][1]; | |
| 256 Set<String> set = new Set<String>(); | |
| 257 List<String> tagNames = tags.split('|'); | |
| 258 for (int j = 0; j < tagNames.length; j++) { | |
| 259 set.add(tagNames[j]); | |
| 260 } | |
| 261 _dynamicMetadata.add(new MetaInfo(tag, tags, set)); | |
| 262 } | |
| 263 } | |
| OLD | NEW |