OLD | NEW |
---|---|
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 part of polymer; | 5 part of polymer; |
6 | 6 |
7 /// Annotation used to automatically register polymer elements. | 7 /// Annotation used to automatically register polymer elements. |
8 class CustomTag { | 8 class CustomTag { |
9 final String tagName; | 9 final String tagName; |
10 const CustomTag(this.tagName); | 10 const CustomTag(this.tagName); |
(...skipping 28 matching lines...) Expand all Loading... | |
39 } | 39 } |
40 | 40 |
41 /// Same as [initPolymer], but runs the version that is optimized for deployment | 41 /// Same as [initPolymer], but runs the version that is optimized for deployment |
42 /// to the internet. The biggest difference is it omits the [Zone] that | 42 /// to the internet. The biggest difference is it omits the [Zone] that |
43 /// automatically invokes [Observable.dirtyCheck], and the list of initializers | 43 /// automatically invokes [Observable.dirtyCheck], and the list of initializers |
44 /// must be supplied instead of being dynamically searched for at runtime using | 44 /// must be supplied instead of being dynamically searched for at runtime using |
45 /// mirrors. | 45 /// mirrors. |
46 Zone initPolymerOptimized() { | 46 Zone initPolymerOptimized() { |
47 // TODO(sigmund): refactor this so we can replace it by codegen. | 47 // TODO(sigmund): refactor this so we can replace it by codegen. |
48 smoke.useMirrors(); | 48 smoke.useMirrors(); |
49 // TODO(jmesserly): there is some code in src/declaration/polymer-element.js, | 49 _hookJsPolymer(); |
50 // git version 37eea00e13b9f86ab21c85a955585e8e4237e3d2, right before | |
51 // it registers polymer-element, which uses Platform.deliverDeclarations to | |
52 // coordinate with HTML Imports. I don't think we need it so skipping. | |
53 document.register(PolymerDeclaration._TAG, PolymerDeclaration); | |
54 | 50 |
55 for (var initializer in _initializers) { | 51 for (var initializer in _initializers) { |
56 initializer(); | 52 initializer(); |
57 } | 53 } |
58 | 54 |
59 // Run this after user code so they can add to Polymer.veiledElements | |
60 _preventFlashOfUnstyledContent(); | |
61 | |
62 customElementsReady.then((_) => Polymer._ready.complete()); | |
63 return Zone.current; | 55 return Zone.current; |
64 } | 56 } |
65 | 57 |
66 /// Configures [initPolymer] making it optimized for deployment to the internet. | 58 /// Configures [initPolymer] making it optimized for deployment to the internet. |
67 /// With this setup the initializer list is supplied instead of searched for | 59 /// With this setup the initializer list is supplied instead of searched for |
68 /// at runtime. Additionally, after this method is called [initPolymer] omits | 60 /// at runtime. Additionally, after this method is called [initPolymer] omits |
69 /// the [Zone] that automatically invokes [Observable.dirtyCheck]. | 61 /// the [Zone] that automatically invokes [Observable.dirtyCheck]. |
70 void configureForDeployment(List<Function> initializers) { | 62 void configureForDeployment(List<Function> initializers) { |
71 _initializers = initializers; | 63 _initializers = initializers; |
72 _deployMode = true; | 64 _deployMode = true; |
(...skipping 108 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
181 if (lib == null) { | 173 if (lib == null) { |
182 _loaderLog.info('$uri library not found'); | 174 _loaderLog.info('$uri library not found'); |
183 return; | 175 return; |
184 } | 176 } |
185 | 177 |
186 // Search top-level functions marked with @initMethod | 178 // Search top-level functions marked with @initMethod |
187 for (var f in lib.declarations.values.where((d) => d is MethodMirror)) { | 179 for (var f in lib.declarations.values.where((d) => d is MethodMirror)) { |
188 _addInitMethod(lib, f, initializers); | 180 _addInitMethod(lib, f, initializers); |
189 } | 181 } |
190 | 182 |
183 | |
184 // Dart note: we don't get back @CustomTags in a reliable order from mirrors, | |
185 // at least on Dart VM. So we need to sort them so base classes are registered | |
186 // first, which ensures that document.register will work correctly for a | |
187 // set of types within in the same library. | |
188 var customTags = new LinkedHashMap<Type, Function>(); | |
191 for (var c in lib.declarations.values.where((d) => d is ClassMirror)) { | 189 for (var c in lib.declarations.values.where((d) => d is ClassMirror)) { |
192 // Search for @CustomTag on classes | 190 _loadCustomTags(lib, c, customTags); |
193 for (var m in c.metadata) { | |
194 var meta = m.reflectee; | |
195 if (meta is CustomTag) { | |
196 initializers.add(() => Polymer.register(meta.tagName, c.reflectedType)); | |
197 } | |
198 } | |
199 | |
200 // TODO(sigmund): check also static methods marked with @initMethod. | 191 // TODO(sigmund): check also static methods marked with @initMethod. |
201 // This is blocked on two bugs: | 192 // This is blocked on two bugs: |
202 // - dartbug.com/12133 (static methods are incorrectly listed as top-level | 193 // - dartbug.com/12133 (static methods are incorrectly listed as top-level |
203 // in dart2js, so they end up being called twice) | 194 // in dart2js, so they end up being called twice) |
204 // - dartbug.com/12134 (sometimes "method.metadata" throws an exception, | 195 // - dartbug.com/12134 (sometimes "method.metadata" throws an exception, |
205 // we could wrap and hide those exceptions, but it's not ideal). | 196 // we could wrap and hide those exceptions, but it's not ideal). |
206 } | 197 } |
198 | |
199 initializers.addAll(customTags.values); | |
200 } | |
201 | |
202 void _loadCustomTags(LibraryMirror lib, ClassMirror cls, | |
203 LinkedHashMap registerFns) { | |
204 if (cls == null || cls.reflectedType == HtmlElement) return; | |
205 | |
206 // Register superclass first. | |
207 _loadCustomTags(lib, cls.superclass, registerFns); | |
208 | |
209 if (cls.owner != lib) { | |
210 // Don't register classes from different libraries. | |
211 // TODO(jmesserly): @CustomTag does not currently respect re-export, because | |
212 // LibraryMirror.declarations doesn't include these. | |
213 return; | |
214 } | |
215 | |
216 var meta = _getCustomTagMetadata(cls); | |
217 if (meta == null) return; | |
218 | |
219 registerFns.putIfAbsent(cls.reflectedType, () => | |
220 () => Polymer.register(meta.tagName, cls.reflectedType)); | |
221 } | |
222 | |
223 /// Search for @CustomTag on a classemirror | |
224 CustomTag _getCustomTagMetadata(ClassMirror c) { | |
225 for (var m in c.metadata) { | |
226 var meta = m.reflectee; | |
227 if (meta is CustomTag) return meta; | |
228 } | |
229 return null; | |
207 } | 230 } |
208 | 231 |
209 void _addInitMethod(ObjectMirror obj, MethodMirror method, | 232 void _addInitMethod(ObjectMirror obj, MethodMirror method, |
210 List<Function> initializers) { | 233 List<Function> initializers) { |
211 var annotationFound = false; | 234 var annotationFound = false; |
212 for (var meta in method.metadata) { | 235 for (var meta in method.metadata) { |
213 if (identical(meta.reflectee, initMethod)) { | 236 if (identical(meta.reflectee, initMethod)) { |
214 annotationFound = true; | 237 annotationFound = true; |
215 break; | 238 break; |
216 } | 239 } |
217 } | 240 } |
218 if (!annotationFound) return; | 241 if (!annotationFound) return; |
219 if (!method.isStatic) { | 242 if (!method.isStatic) { |
220 print("warning: methods marked with @initMethod should be static," | 243 print("warning: methods marked with @initMethod should be static," |
221 " ${method.simpleName} is not."); | 244 " ${method.simpleName} is not."); |
222 return; | 245 return; |
223 } | 246 } |
224 if (!method.parameters.where((p) => !p.isOptional).isEmpty) { | 247 if (!method.parameters.where((p) => !p.isOptional).isEmpty) { |
225 print("warning: methods marked with @initMethod should take no " | 248 print("warning: methods marked with @initMethod should take no " |
226 "arguments, ${method.simpleName} expects some."); | 249 "arguments, ${method.simpleName} expects some."); |
227 return; | 250 return; |
228 } | 251 } |
229 initializers.add(() => obj.invoke(method.simpleName, const [])); | 252 initializers.add(() => obj.invoke(method.simpleName, const [])); |
230 } | 253 } |
231 | 254 |
232 class _InitMethodAnnotation { | 255 class _InitMethodAnnotation { |
233 const _InitMethodAnnotation(); | 256 const _InitMethodAnnotation(); |
234 } | 257 } |
258 | |
259 /// To ensure Dart can interoperate with polymer-element registered by | |
260 /// polymer.js, we need to be able to execute Dart code if we are registering | |
261 /// a Dart class for that element. We trigger Dart logic by patching | |
262 /// polymer-element's register function and: | |
263 /// | |
264 /// * if it has a Dart class, run PolymerDeclaration's register. | |
265 /// * otherwise it is a JS prototype, run polymer-element's normal register. | |
266 void _hookJsPolymer() { | |
267 var polymerJs = js.context['Polymer']; | |
268 if (polymerJs == null) { | |
269 throw new StateError('polymer.js must be loaded before polymer.dart, please' | |
270 ' add <link rel="import" href="packages/polymer/polymer.html"> to your' | |
271 ' <head> before any Dart scripts. Alternatively you can get a different' | |
272 ' version of polymer.js by following the instructions at' | |
273 ' http://www.polymer-project.org; if you do that be sure to include' | |
274 ' the platform polyfills.'); | |
275 } | |
276 | |
277 // TODO(jmesserly): dart:js appears to not callback in the correct zone: | |
278 // https://code.google.com/p/dart/issues/detail?id=17301 | |
279 var zone = Zone.current; | |
280 | |
281 polymerJs.callMethod('whenPolymerReady', | |
282 [zone.bindCallback(() => Polymer._ready.complete())]); | |
283 | |
284 // Note: we need to call createElement through JavaScript to get the right | |
285 // JavaScript prototype. | |
286 // TODO(jmesserly): what's going on here? Are we getting a strange mix of | |
Siggi Cherem (dart-lang)
2014/03/06 18:18:08
not sure I understand the TODO, what does this app
Jennifer Messerly
2014/03/06 21:05:08
Well, at the time I didn't understand the problem
| |
287 // polyfilled things in JS and native in Dart? | |
288 var jsPolymer = new JsObject.fromBrowserObject(document) | |
289 .callMethod('createElement', ['polymer-element']); | |
290 | |
291 var proto = js.context['Object'].callMethod('getPrototypeOf', [jsPolymer]); | |
292 if (proto is Node) { | |
293 proto = new JsObject.fromBrowserObject(proto); | |
294 } | |
295 | |
296 JsFunction originalRegister = proto['register']; | |
297 if (originalRegister == null) { | |
298 throw new StateError('polymer.js must expose "register" function on ' | |
299 'polymer-element to enable polymer.dart to interoperate.'); | |
300 } | |
301 | |
302 registerDart(jsElem, String name, String extendee) { | |
303 // By the time we get here, we'll know for sure if it is a Dart object | |
304 // or not, because polymer-element will wait for us to notify that | |
305 // the @CustomTag was found. | |
306 final type = _getRegisteredType(name); | |
307 if (type != null) { | |
308 final extendsDecl = _getDeclaration(extendee); | |
309 return zone.run(() => | |
310 new PolymerDeclaration(jsElem, name, type, extendsDecl).register()); | |
311 } | |
312 // It's a JavaScript polymer element, fall back to the original register. | |
313 return originalRegister.apply([name, extendee], thisArg: jsElem); | |
314 } | |
315 | |
316 proto['register'] = new JsFunction.withThis(registerDart); | |
Siggi Cherem (dart-lang)
2014/03/06 18:18:08
wow -- I knew you were going to do this, was just
Jennifer Messerly
2014/03/06 21:05:08
Yeah, it's kinda neat we can do it. It's a lot cle
| |
317 } | |
OLD | NEW |