OLD | NEW |
(Empty) | |
| 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 |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 part of polymer; |
| 6 |
| 7 /// Annotation used to automatically register polymer elements. |
| 8 class CustomTag { |
| 9 final String tagName; |
| 10 const CustomTag(this.tagName); |
| 11 } |
| 12 |
| 13 /// Metadata used to label static or top-level methods that are called |
| 14 /// automatically when loading the library of a custom element. |
| 15 const initMethod = const InitMethodAnnotation(); |
| 16 |
| 17 /// Implementation behind [initMethod]. Only exposed for internal implementation |
| 18 /// details |
| 19 class InitMethodAnnotation { |
| 20 const InitMethodAnnotation(); |
| 21 } |
| 22 |
| 23 /// Initializes a polymer application as follows: |
| 24 /// * if running in development mode, set up a dirty-checking zone that polls |
| 25 /// for observable changes |
| 26 /// * initialize template binding and polymer-element |
| 27 /// * for each library included transitively from HTML and HTML imports, |
| 28 /// register custom elements declared there (labeled with [CustomTag]) and |
| 29 /// invoke the initialization method on it (top-level functions annotated with |
| 30 /// [initMethod]). |
| 31 Zone initPolymer() { |
| 32 _initializeLogging(); |
| 33 if (loader.deployMode) { |
| 34 startPolymer(loader.initializers, loader.deployMode); |
| 35 return Zone.current; |
| 36 } |
| 37 return dirtyCheckZone()..run( |
| 38 () => startPolymer(loader.initializers, loader.deployMode)); |
| 39 } |
| 40 |
| 41 /// True if we're in deployment mode. |
| 42 bool _deployMode = false; |
| 43 |
| 44 bool _startPolymerCalled = false; |
| 45 |
| 46 /// Starts polymer by running all [initializers] and hooking the polymer.js |
| 47 /// code. **Note**: this function is not meant to be invoked directly by |
| 48 /// application developers. It is invoked either by [initPolymer] or, if you are |
| 49 /// using the experimental bootstrap API, this would be invoked by an entry |
| 50 /// point that is automatically generated somehow. In particular, during |
| 51 /// development, the entry point would be generated dynamically in `boot.js`. |
| 52 /// Similarly, pub-build would generate the entry point for deployment. |
| 53 void startPolymer(List<Function> initializers, [bool deployMode = true]) { |
| 54 if (_startPolymerCalled) throw 'Initialization was already done.'; |
| 55 _startPolymerCalled = true; |
| 56 _hookJsPolymer(); |
| 57 _deployMode = deployMode; |
| 58 |
| 59 if (initializers == null) { |
| 60 throw 'Missing initialization of polymer elements. ' |
| 61 'Please check that the list of entry points in your pubspec.yaml ' |
| 62 'is correct. If you are using pub-serve, you may need to restart it.'; |
| 63 } |
| 64 |
| 65 Polymer.registerSync('auto-binding-dart', AutoBindingElement, |
| 66 extendsTag: 'template'); |
| 67 |
| 68 for (var initializer in initializers) { |
| 69 initializer(); |
| 70 } |
| 71 |
| 72 _watchWaitingFor(); |
| 73 } |
| 74 |
| 75 /// Configures [initPolymer] making it optimized for deployment to the internet. |
| 76 /// With this setup the initializer list is supplied instead of searched for |
| 77 /// at runtime. Additionally, after this method is called [initPolymer] omits |
| 78 /// the [Zone] that automatically invokes [Observable.dirtyCheck]. |
| 79 void configureForDeployment(List<Function> initializers) { |
| 80 loader.initializers = initializers; |
| 81 loader.deployMode = true; |
| 82 } |
| 83 |
| 84 /// To ensure Dart can interoperate with polymer-element registered by |
| 85 /// polymer.js, we need to be able to execute Dart code if we are registering |
| 86 /// a Dart class for that element. We trigger Dart logic by patching |
| 87 /// polymer-element's register function and: |
| 88 /// |
| 89 /// * if it has a Dart class, run PolymerDeclaration's register. |
| 90 /// * otherwise it is a JS prototype, run polymer-element's normal register. |
| 91 void _hookJsPolymer() { |
| 92 var polymerJs = js.context['Polymer']; |
| 93 if (polymerJs == null) { |
| 94 throw new StateError('polymer.js must be loaded before polymer.dart, please' |
| 95 ' add <link rel="import" href="packages/polymer/polymer.html"> to your' |
| 96 ' <head> before any Dart scripts. Alternatively you can get a different' |
| 97 ' version of polymer.js by following the instructions at' |
| 98 ' http://www.polymer-project.org.'); |
| 99 } |
| 100 |
| 101 // TODO(jmesserly): dart:js appears to not callback in the correct zone: |
| 102 // https://code.google.com/p/dart/issues/detail?id=17301 |
| 103 var zone = Zone.current; |
| 104 |
| 105 polymerJs.callMethod('whenPolymerReady', |
| 106 [zone.bindCallback(() => Polymer._onReady.complete())]); |
| 107 |
| 108 JsFunction originalRegister = _polymerElementProto['register']; |
| 109 if (originalRegister == null) { |
| 110 throw new StateError('polymer.js must expose "register" function on ' |
| 111 'polymer-element to enable polymer.dart to interoperate.'); |
| 112 } |
| 113 |
| 114 registerDart(jsElem, String name, String extendee) { |
| 115 // By the time we get here, we'll know for sure if it is a Dart object |
| 116 // or not, because polymer-element will wait for us to notify that |
| 117 // the @CustomTag was found. |
| 118 final type = _getRegisteredType(name); |
| 119 if (type != null) { |
| 120 final extendsDecl = _getDeclaration(extendee); |
| 121 return zone.run(() => |
| 122 new PolymerDeclaration(jsElem, name, type, extendsDecl).register()); |
| 123 } |
| 124 // It's a JavaScript polymer element, fall back to the original register. |
| 125 return originalRegister.apply([name, extendee], thisArg: jsElem); |
| 126 } |
| 127 |
| 128 _polymerElementProto['register'] = new JsFunction.withThis(registerDart); |
| 129 } |
| 130 |
| 131 // Note: we cache this so we can use it later to look up 'init'. |
| 132 // See registerSync. |
| 133 JsObject _polymerElementProto = () { |
| 134 var polyElem = document.createElement('polymer-element'); |
| 135 var proto = new JsObject.fromBrowserObject(polyElem)['__proto__']; |
| 136 if (proto is Node) proto = new JsObject.fromBrowserObject(proto); |
| 137 return proto; |
| 138 }(); |
| 139 |
| 140 // Add support for the polymer js style of enabling logging. The global logging |
| 141 // level is respected for specified loggers (see http://goo.gl/btfDe1). All |
| 142 // other loggers will be set to [Level.OFF]. Logs will also be printed to the |
| 143 // console automatically if any are supplied. |
| 144 void _initializeLogging() { |
| 145 hierarchicalLoggingEnabled = true; |
| 146 var webComponents = js.context['WebComponents']; |
| 147 var logFlags = (webComponents == null || webComponents['flags'] == null) ? {} |
| 148 : webComponents['flags']['log']; |
| 149 if (logFlags == null) logFlags = {}; |
| 150 var loggers = |
| 151 [_observeLog, _eventsLog, _unbindLog, _bindLog, _watchLog, _readyLog]; |
| 152 var polymerLogger = new Logger('polymer'); |
| 153 |
| 154 // If no loggers specified then disable globally and return. |
| 155 if (!loggers.any((logger) => logFlags[logger.name] == true)) { |
| 156 polymerLogger.level = Level.OFF; |
| 157 return; |
| 158 } |
| 159 |
| 160 // Disable the loggers that were not specified. |
| 161 loggers.where((logger) => logFlags[logger.name] != true) |
| 162 .forEach((logger) {logger.level = Level.OFF;}); |
| 163 |
| 164 // Listen to the polymer logs and print them to the console. |
| 165 polymerLogger.onRecord.listen((rec) {print(rec);}); |
| 166 } |
| 167 |
| 168 /// Watches the waitingFor queue and if it fails to make progress then prints |
| 169 /// a message to the console. |
| 170 void _watchWaitingFor() { |
| 171 int lastWaiting = Polymer.waitingFor.length; |
| 172 int lastAlert; |
| 173 new Timer.periodic(new Duration(seconds: 1), (Timer timer) { |
| 174 var waiting = Polymer.waitingFor; |
| 175 // Done, cancel timer. |
| 176 if (waiting.isEmpty) { |
| 177 timer.cancel(); |
| 178 return; |
| 179 } |
| 180 // Made progress, don't alert. |
| 181 if (waiting.length != lastWaiting) { |
| 182 lastWaiting = waiting.length; |
| 183 return; |
| 184 } |
| 185 // Only alert once per waiting state. |
| 186 if (lastAlert == lastWaiting) return; |
| 187 lastAlert = lastWaiting; |
| 188 |
| 189 print('No elements registered in a while, but still waiting on ' |
| 190 '${waiting.length} elements to be registered. Check that you have a ' |
| 191 'class with an @CustomTag annotation for each of the following tags: ' |
| 192 '${waiting.map((e) => "'${e.attributes['name']}'").join(', ')}'); |
| 193 }); |
| 194 } |
OLD | NEW |