| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, 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 /// Contains logic to initialize polymer apps during development. This |
| 6 /// implementation uses dart:mirrors to load each library as they are discovered |
| 7 /// through HTML imports. This is only meant to be during development in |
| 8 /// dartium, and the polymer transformers replace this implementation with |
| 9 /// codege generation in the polymer-build steps. |
| 10 library polymer.src.mirror_loader; |
| 6 | 11 |
| 7 /// Annotation used to automatically register polymer elements. | 12 import 'dart:async'; |
| 8 class CustomTag { | 13 import 'dart:html'; |
| 9 final String tagName; | |
| 10 const CustomTag(this.tagName); | |
| 11 } | |
| 12 | 14 |
| 13 /// Metadata used to label static or top-level methods that are called | 15 // Technically, we shouldn't need any @MirrorsUsed, since this is for |
| 14 /// automatically when loading the library of a custom element. | 16 // development only, but our test bots don't yet run pub-build. See more details |
| 15 const initMethod = const _InitMethodAnnotation(); | 17 // on the comments of the mirrors import in `lib/polymer.dart`. |
| 18 @MirrorsUsed(metaTargets: |
| 19 const [CustomTag, InitMethodAnnotation], |
| 20 override: const ['smoke.mirrors', 'polymer.src.mirror_loader']) |
| 21 import 'dart:mirrors'; |
| 16 | 22 |
| 17 /// Initializes a polymer application as follows: | 23 import 'package:logging/logging.dart' show Logger; |
| 18 /// * set up up polling for observable changes | 24 import 'package:polymer/polymer.dart' show |
| 19 /// * initialize Model-Driven Views | 25 InitMethodAnnotation, CustomTag, initMethod, Polymer; |
| 20 /// * Include some style to prevent flash of unstyled content (FOUC) | |
| 21 /// * for each library included transitively from HTML and HTML imports, | |
| 22 /// register custom elements declared there (labeled with [CustomTag]) and | |
| 23 /// invoke the initialization method on it (top-level functions annotated with | |
| 24 /// [initMethod]). | |
| 25 Zone initPolymer() { | |
| 26 // We use this pattern, and not the inline lazy initialization pattern, so we | |
| 27 // can help dart2js detect that _discoverInitializers can be tree-shaken for | |
| 28 // deployment (and hence all uses of dart:mirrors from this loading logic). | |
| 29 // TODO(sigmund): fix polymer's transformers so they can replace initPolymer | |
| 30 // by initPolymerOptimized. | |
| 31 if (_initializers == null) _initializers = _discoverInitializers(); | |
| 32 | 26 |
| 33 // In deployment mode, we rely on change notifiers instead of dirty checking. | |
| 34 if (!_deployMode) { | |
| 35 return dirtyCheckZone()..run(initPolymerOptimized); | |
| 36 } | |
| 37 | 27 |
| 38 return initPolymerOptimized(); | 28 /// Set of initializers that are invoked by `initPolymer`. This is computed the |
| 39 } | 29 /// list by crawling HTML imports, searching for script tags, and including an |
| 40 | |
| 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 | |
| 43 /// automatically invokes [Observable.dirtyCheck], and the list of initializers | |
| 44 /// must be supplied instead of being dynamically searched for at runtime using | |
| 45 /// mirrors. | |
| 46 Zone initPolymerOptimized() { | |
| 47 // TODO(sigmund): refactor this so we can replace it by codegen. | |
| 48 smoke.useMirrors(); | |
| 49 _hookJsPolymer(); | |
| 50 | |
| 51 for (var initializer in _initializers) { | |
| 52 initializer(); | |
| 53 } | |
| 54 | |
| 55 return Zone.current; | |
| 56 } | |
| 57 | |
| 58 /// Configures [initPolymer] making it optimized for deployment to the internet. | |
| 59 /// With this setup the initializer list is supplied instead of searched for | |
| 60 /// at runtime. Additionally, after this method is called [initPolymer] omits | |
| 61 /// the [Zone] that automatically invokes [Observable.dirtyCheck]. | |
| 62 void configureForDeployment(List<Function> initializers) { | |
| 63 _initializers = initializers; | |
| 64 _deployMode = true; | |
| 65 } | |
| 66 | |
| 67 /// List of initializers that by default will be executed when calling | |
| 68 /// initPolymer. If null, initPolymer will compute the list of initializers by | |
| 69 /// crawling HTML imports, searchfing for script tags, and including an | |
| 70 /// initializer for each type tagged with a [CustomTag] annotation and for each | 30 /// initializer for each type tagged with a [CustomTag] annotation and for each |
| 71 /// top-level method annotated with [initMethod]. The value of this field is | 31 /// top-level method annotated with [initMethod]. |
| 72 /// assigned programatically by the code generated from the polymer deploy | 32 List<Function> initializers = _discoverInitializers(); |
| 73 /// scripts. | |
| 74 List<Function> _initializers; | |
| 75 | 33 |
| 76 /// True if we're in deployment mode. | 34 /// True if we're in deployment mode. |
| 77 bool _deployMode = false; | 35 bool deployMode = false; |
| 78 | 36 |
| 37 /// Discovers what script tags are loaded from HTML pages and collects the |
| 38 /// initializers of their corresponding libraries. |
| 79 List<Function> _discoverInitializers() { | 39 List<Function> _discoverInitializers() { |
| 80 var initializers = []; | 40 var initializers = []; |
| 81 var librariesToLoad = _discoverScripts(document, window.location.href); | 41 var librariesToLoad = _discoverScripts(document, window.location.href); |
| 82 for (var lib in librariesToLoad) { | 42 for (var lib in librariesToLoad) { |
| 83 try { | 43 try { |
| 84 _loadLibrary(lib, initializers); | 44 _loadLibrary(lib, initializers); |
| 85 } catch (e, s) { | 45 } catch (e, s) { |
| 86 // Deliver errors async, so if a single library fails it doesn't prevent | 46 // Deliver errors async, so if a single library fails it doesn't prevent |
| 87 // other things from loading. | 47 // other things from loading. |
| 88 new Completer().completeError(e, s); | 48 new Completer().completeError(e, s); |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 125 return scripts; | 85 return scripts; |
| 126 } | 86 } |
| 127 | 87 |
| 128 /// All libraries in the current isolate. | 88 /// All libraries in the current isolate. |
| 129 final _libs = currentMirrorSystem().libraries; | 89 final _libs = currentMirrorSystem().libraries; |
| 130 | 90 |
| 131 // TODO(sigmund): explore other (cheaper) ways to resolve URIs relative to the | 91 // TODO(sigmund): explore other (cheaper) ways to resolve URIs relative to the |
| 132 // root library (see dartbug.com/12612) | 92 // root library (see dartbug.com/12612) |
| 133 final _rootUri = currentMirrorSystem().isolate.rootLibrary.uri; | 93 final _rootUri = currentMirrorSystem().isolate.rootLibrary.uri; |
| 134 | 94 |
| 135 final Logger _loaderLog = new Logger('polymer.loader'); | 95 final Logger _loaderLog = new Logger('polymer.src.mirror_loader'); |
| 136 | 96 |
| 137 bool _isHttpStylePackageUrl(Uri uri) { | 97 bool _isHttpStylePackageUrl(Uri uri) { |
| 138 var uriPath = uri.path; | 98 var uriPath = uri.path; |
| 139 return uri.scheme == _rootUri.scheme && | 99 return uri.scheme == _rootUri.scheme && |
| 140 // Don't process cross-domain uris. | 100 // Don't process cross-domain uris. |
| 141 uri.authority == _rootUri.authority && | 101 uri.authority == _rootUri.authority && |
| 142 uriPath.endsWith('.dart') && | 102 uriPath.endsWith('.dart') && |
| 143 (uriPath.contains('/packages/') || uriPath.startsWith('packages/')); | 103 (uriPath.contains('/packages/') || uriPath.startsWith('packages/')); |
| 144 } | 104 } |
| 145 | 105 |
| (...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 244 " ${method.simpleName} is not."); | 204 " ${method.simpleName} is not."); |
| 245 return; | 205 return; |
| 246 } | 206 } |
| 247 if (!method.parameters.where((p) => !p.isOptional).isEmpty) { | 207 if (!method.parameters.where((p) => !p.isOptional).isEmpty) { |
| 248 print("warning: methods marked with @initMethod should take no " | 208 print("warning: methods marked with @initMethod should take no " |
| 249 "arguments, ${method.simpleName} expects some."); | 209 "arguments, ${method.simpleName} expects some."); |
| 250 return; | 210 return; |
| 251 } | 211 } |
| 252 initializers.add(() => obj.invoke(method.simpleName, const [])); | 212 initializers.add(() => obj.invoke(method.simpleName, const [])); |
| 253 } | 213 } |
| 254 | |
| 255 class _InitMethodAnnotation { | |
| 256 const _InitMethodAnnotation(); | |
| 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 |