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 |