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 var jsPolymer = new JsObject.fromBrowserObject( |
| 285 document.createElement('polymer-element')); |
| 286 |
| 287 var proto = js.context['Object'].callMethod('getPrototypeOf', [jsPolymer]); |
| 288 if (proto is Node) { |
| 289 proto = new JsObject.fromBrowserObject(proto); |
| 290 } |
| 291 |
| 292 JsFunction originalRegister = proto['register']; |
| 293 if (originalRegister == null) { |
| 294 throw new StateError('polymer.js must expose "register" function on ' |
| 295 'polymer-element to enable polymer.dart to interoperate.'); |
| 296 } |
| 297 |
| 298 registerDart(jsElem, String name, String extendee) { |
| 299 // By the time we get here, we'll know for sure if it is a Dart object |
| 300 // or not, because polymer-element will wait for us to notify that |
| 301 // the @CustomTag was found. |
| 302 final type = _getRegisteredType(name); |
| 303 if (type != null) { |
| 304 final extendsDecl = _getDeclaration(extendee); |
| 305 return zone.run(() => |
| 306 new PolymerDeclaration(jsElem, name, type, extendsDecl).register()); |
| 307 } |
| 308 // It's a JavaScript polymer element, fall back to the original register. |
| 309 return originalRegister.apply([name, extendee], thisArg: jsElem); |
| 310 } |
| 311 |
| 312 proto['register'] = new JsFunction.withThis(registerDart); |
| 313 } |
OLD | NEW |