OLD | NEW |
1 part of angular.core; | 1 part of angular.core_internal; |
2 | 2 |
3 typedef void ZoneOnTurn(); | 3 /** |
| 4 * Handles an [VmTurnZone] onTurnDone event. |
| 5 */ |
| 6 typedef void ZoneOnTurnDone(); |
| 7 |
| 8 /** |
| 9 * Handles an [VmTurnZone] onTurnDone event. |
| 10 */ |
| 11 typedef void ZoneOnTurnStart(); |
| 12 |
| 13 /** |
| 14 * Handles an [VmTurnZone] onError event. |
| 15 */ |
4 typedef void ZoneOnError(dynamic error, dynamic stacktrace, | 16 typedef void ZoneOnError(dynamic error, dynamic stacktrace, |
5 LongStackTrace longStacktrace); | 17 LongStackTrace longStacktrace); |
6 | 18 |
7 /** | 19 /** |
8 * Contains the locations of runAsync calls across VM turns. | 20 * Contains the locations of async calls across VM turns. |
9 */ | 21 */ |
10 class LongStackTrace { | 22 class LongStackTrace { |
11 final String reason; | 23 final String reason; |
12 final dynamic stacktrace; | 24 final dynamic stacktrace; |
13 final LongStackTrace parent; | 25 final LongStackTrace parent; |
14 | 26 |
15 LongStackTrace(this.reason, this.stacktrace, this.parent); | 27 LongStackTrace(this.reason, this.stacktrace, this.parent); |
16 | 28 |
17 toString() { | 29 toString() { |
18 List<String> frames = '${this.stacktrace}'.split('\n') | 30 List<String> frames = '${this.stacktrace}'.split('\n') |
19 .where((frame) => | 31 .where((frame) => |
20 frame.indexOf('(dart:') == -1 && // skip dart runtime libs | 32 frame.indexOf('(dart:') == -1 && // skip dart runtime libs |
21 frame.indexOf('(package:angular/zone.dart') == -1 // skip angular zo
ne | 33 frame.indexOf('(package:angular/zone.dart') == -1 // skip angular zo
ne |
22 ).toList()..insert(0, reason); | 34 ).toList()..insert(0, reason); |
23 var parent = this.parent == null ? '' : this.parent; | 35 var parent = this.parent == null ? '' : this.parent; |
24 return '${frames.join("\n ")}\n$parent'; | 36 return '${frames.join("\n ")}\n$parent'; |
25 } | 37 } |
26 } | 38 } |
27 | 39 |
28 /** | 40 /** |
29 * A better zone API which implements onTurnDone. | 41 * A [Zone] wrapper that lets you schedule tasks after its private microtask |
| 42 * queue is exhausted but before the next "turn", i.e. event loop iteration. |
| 43 * This lets you freely schedule microtasks that prepare data, and set an |
| 44 * [onTurnDone] handler that will consume that data after it's ready but before |
| 45 * the browser has a chance to re-render. |
| 46 * The wrapper maintains an "inner" and "outer" [Zone] and a private queue of |
| 47 * all the microtasks scheduled on the inner [Zone]. |
| 48 * |
| 49 * In a typical app, [ngDynamicApp] or [ngStaticApp] will create a singleton |
| 50 * [VmTurnZone] whose outer [Zone] is the root [Zone] and whose default [onTurnD
one] |
| 51 * runs the Angular digest. A component may want to inject this singleton if it |
| 52 * needs to run code _outside_ the Angular digest. |
30 */ | 53 */ |
31 class NgZone { | 54 class VmTurnZone { |
32 final async.Zone _outerZone; | 55 /// an "outer" [Zone], which is the one that created this. |
33 async.Zone _zone; | 56 async.Zone _outerZone; |
34 | 57 |
35 NgZone() | 58 /// an "inner" [Zone], which is a child of the outer [Zone]. |
36 : _outerZone = async.Zone.current | 59 async.Zone _innerZone; |
37 { | 60 |
38 _zone = _outerZone.fork(specification: new async.ZoneSpecification( | 61 /** |
| 62 * Associates with this |
| 63 * |
| 64 * Defaults [onError] to forward errors to the outer [Zone]. |
| 65 * Defaults [onTurnDone] to a no-op. |
| 66 */ |
| 67 VmTurnZone() { |
| 68 _outerZone = async.Zone.current; |
| 69 _innerZone = _outerZone.fork(specification: new async.ZoneSpecification( |
39 run: _onRun, | 70 run: _onRun, |
40 runUnary: _onRunUnary, | 71 runUnary: _onRunUnary, |
41 scheduleMicrotask: _onScheduleMicrotask, | 72 scheduleMicrotask: _onScheduleMicrotask, |
42 handleUncaughtError: _uncaughtError | 73 handleUncaughtError: _uncaughtError |
43 )); | 74 )); |
| 75 onError = _defaultOnError; |
| 76 onTurnDone = _defaultOnTurnDone; |
| 77 onTurnStart = _defaultOnTurnStart; |
44 } | 78 } |
45 | 79 |
46 | |
47 List _asyncQueue = []; | 80 List _asyncQueue = []; |
48 bool _errorThrownFromOnRun = false; | 81 bool _errorThrownFromOnRun = false; |
49 | 82 |
| 83 var _currentlyInTurn = false; |
50 _onRunBase(async.Zone self, async.ZoneDelegate delegate, async.Zone zone, fn()
) { | 84 _onRunBase(async.Zone self, async.ZoneDelegate delegate, async.Zone zone, fn()
) { |
51 _runningInTurn++; | 85 _runningInTurn++; |
52 try { | 86 try { |
| 87 if (!_currentlyInTurn) { |
| 88 _currentlyInTurn = true; |
| 89 delegate.run(zone, onTurnStart); |
| 90 } |
53 return fn(); | 91 return fn(); |
54 } catch (e, s) { | 92 } catch (e, s) { |
55 onError(e, s, _longStacktrace); | 93 onError(e, s, _longStacktrace); |
56 _errorThrownFromOnRun = true; | 94 _errorThrownFromOnRun = true; |
57 rethrow; | 95 rethrow; |
58 } finally { | 96 } finally { |
59 _runningInTurn--; | 97 _runningInTurn--; |
60 if (_runningInTurn == 0) _finishTurn(zone, delegate); | 98 if (_runningInTurn == 0) _finishTurn(zone, delegate); |
61 } | 99 } |
62 } | 100 } |
63 // Called from the parent zone. | 101 // Called from the parent zone. |
64 _onRun(async.Zone self, async.ZoneDelegate delegate, async.Zone zone, fn()) => | 102 _onRun(async.Zone self, async.ZoneDelegate delegate, async.Zone zone, fn()) => |
65 _onRunBase(self, delegate, zone, () => delegate.run(zone, fn)); | 103 _onRunBase(self, delegate, zone, () => delegate.run(zone, fn)); |
66 | 104 |
67 _onRunUnary(async.Zone self, async.ZoneDelegate delegate, async.Zone zone, | 105 _onRunUnary(async.Zone self, async.ZoneDelegate delegate, async.Zone zone, |
68 fn(args), args) => | 106 fn(args), args) => |
69 _onRunBase(self, delegate, zone, () => delegate.runUnary(zone, fn, args)); | 107 _onRunBase(self, delegate, zone, () => delegate.runUnary(zone, fn, args)); |
70 | 108 |
71 _onScheduleMicrotask(async.Zone self, async.ZoneDelegate delegate, | 109 _onScheduleMicrotask(async.Zone self, async.ZoneDelegate delegate, |
72 async.Zone zone, fn()) { | 110 async.Zone zone, fn()) { |
73 _asyncQueue.add(() => delegate.run(zone, fn)); | 111 _asyncQueue.add(() => delegate.run(zone, fn)); |
74 if (_runningInTurn == 0 && !_inFinishTurn) _finishTurn(zone, delegate); | 112 if (_runningInTurn == 0 && !_inFinishTurn) _finishTurn(zone, delegate); |
75 } | 113 } |
76 | 114 |
77 _uncaughtError(async.Zone self, async.ZoneDelegate delegate, async.Zone zone, | 115 _uncaughtError(async.Zone self, async.ZoneDelegate delegate, async.Zone zone, |
78 e, StackTrace s) { | 116 e, StackTrace s) { |
79 if (!_errorThrownFromOnRun) onError(e, s, _longStacktrace); | 117 if (!_errorThrownFromOnRun) onError(e, s, _longStacktrace); |
80 _errorThrownFromOnRun = false; | 118 _errorThrownFromOnRun = false; |
81 } | 119 } |
82 | 120 |
83 var _inFinishTurn = false; | 121 var _inFinishTurn = false; |
84 _finishTurn(zone, delegate) { | 122 _finishTurn(zone, delegate) { |
85 if (_inFinishTurn) return; | 123 if (_inFinishTurn) return; |
86 _inFinishTurn = true; | 124 _inFinishTurn = true; |
87 try { | 125 try { |
88 // Two loops here: the inner one runs all queued microtasks, | 126 // Two loops here: the inner one runs all queued microtasks, |
89 // the outer runs onTurnDone (e.g. scope.digest) and then | 127 // the outer runs onTurnDone (e.g. scope.digest) and then |
90 // any microtasks which may have been queued from onTurnDone. | 128 // any microtasks which may have been queued from onTurnDone. |
| 129 // If any microtasks were scheduled during onTurnDone, onTurnStart |
| 130 // will be executed before those microtasks. |
91 do { | 131 do { |
| 132 if (!_currentlyInTurn) { |
| 133 _currentlyInTurn = true; |
| 134 delegate.run(zone, onTurnStart); |
| 135 } |
92 while (!_asyncQueue.isEmpty) { | 136 while (!_asyncQueue.isEmpty) { |
93 delegate.run(zone, _asyncQueue.removeAt(0)); | 137 delegate.run(zone, _asyncQueue.removeAt(0)); |
94 } | 138 } |
95 delegate.run(zone, onTurnDone); | 139 delegate.run(zone, onTurnDone); |
| 140 _currentlyInTurn = false; |
96 } while (!_asyncQueue.isEmpty); | 141 } while (!_asyncQueue.isEmpty); |
97 } catch (e, s) { | 142 } catch (e, s) { |
98 onError(e, s, _longStacktrace); | 143 onError(e, s, _longStacktrace); |
99 _errorThrownFromOnRun = true; | 144 _errorThrownFromOnRun = true; |
100 rethrow; | 145 rethrow; |
101 } finally { | 146 } finally { |
102 _inFinishTurn = false; | 147 _inFinishTurn = false; |
103 } | 148 } |
104 } | 149 } |
105 | 150 |
106 int _runningInTurn = 0; | 151 int _runningInTurn = 0; |
107 | 152 |
108 /** | 153 /** |
109 * A function called with any errors from the zone. | 154 * Called with any errors from the inner zone. |
110 */ | 155 */ |
111 var onError = (e, s, ls) => null; | 156 ZoneOnError onError; |
| 157 |
| 158 /// Prevent silently ignoring uncaught exceptions by forwarding such exception
s to the outer zone. |
| 159 void _defaultOnError(dynamic e, dynamic s, LongStackTrace ls) => |
| 160 _outerZone.handleUncaughtError(e, s); |
112 | 161 |
113 /** | 162 /** |
114 * A function that is called at the end of each VM turn in which the | 163 * Called at the beginning of each VM turn in which inner zone code runs. |
115 * in-zone code or any runAsync callbacks were run. | 164 * "At the beginning" means before any of the microtasks from the private |
| 165 * microtask queue of the inner zone is executed. Notes |
| 166 * - [onTurnStart] runs repeatedly until no more microstasks are scheduled |
| 167 * within [onTurnStart], [run] or [onTurnDone]. You usually don't want it to |
| 168 * schedule any. For example, if its first line of code is `new Future.valu
e()`, |
| 169 * the turn will _never_ end. |
116 */ | 170 */ |
117 var onTurnDone = () => null; // Type was ZoneOnTurn: dartbug 13519 | 171 ZoneOnTurnStart onTurnStart; |
| 172 void _defaultOnTurnStart() => null; |
| 173 |
118 | 174 |
119 /** | 175 /** |
120 * A function that is called when uncaught errors are thrown inside the zone. | 176 * Called at the end of each VM turn in which inner zone code runs. |
| 177 * "At the end" means after the private microtask queue of the inner zone is |
| 178 * exhausted but before the next VM turn. Notes |
| 179 * - This won't wait for microtasks scheduled in zones other than the inner |
| 180 * zone, e.g. those scheduled with [runOutsideAngular]. |
| 181 * - [onTurnDone] runs repeatedly until no more tasks are scheduled within |
| 182 * [onTurnStart], [run] or [onTurnDone]. You usually don't want it to |
| 183 * schedule any. For example, if its first line of code is `new Future.valu
e()`, |
| 184 * the turn will _never_ end. |
121 */ | 185 */ |
122 // var onError = (dynamic e, dynamic s, LongStackTrace ls) => print('EXCEPTION
: $e\n$s\n$ls'); | 186 ZoneOnTurnDone onTurnDone; |
123 // Type was ZoneOnError: dartbug 13519 | 187 void _defaultOnTurnDone() => null; |
124 | 188 |
125 LongStackTrace _longStacktrace = null; | 189 LongStackTrace _longStacktrace = null; |
126 | 190 |
127 LongStackTrace _getLongStacktrace(name) { | 191 LongStackTrace _getLongStacktrace(name) { |
128 var shortStacktrace = 'Long-stacktraces supressed in production.'; | 192 var shortStacktrace = 'Long-stacktraces supressed in production.'; |
129 assert((shortStacktrace = _getStacktrace()) != null); | 193 assert((shortStacktrace = _getStacktrace()) != null); |
130 return new LongStackTrace(name, shortStacktrace, _longStacktrace); | 194 return new LongStackTrace(name, shortStacktrace, _longStacktrace); |
131 } | 195 } |
132 | 196 |
133 _getStacktrace() { | 197 StackTrace _getStacktrace() { |
134 try { | 198 try { |
135 throw []; | 199 throw []; |
136 } catch (e, s) { | 200 } catch (e, s) { |
137 return s; | 201 return s; |
138 } | 202 } |
139 } | 203 } |
140 | 204 |
141 /** | 205 /** |
142 * Runs the provided function in the zone. Any runAsync calls (e.g. futures) | 206 * Runs [body] in the inner zone and returns whatever it returns. |
143 * will also be run in this zone. | |
144 * | |
145 * Returns the return value of body. | |
146 */ | 207 */ |
147 run(body()) => _zone.run(body); | 208 dynamic run(body()) => _innerZone.run(body); |
148 | 209 |
149 /** | 210 /** |
150 * Allows one to escape the auto-digest mechanism of Angular. | 211 * Runs [body] in the outer zone and returns whatever it returns. |
| 212 * In a typical app where the inner zone is the Angular zone, this allows |
| 213 * one to escape Angular's auto-digest mechanism. |
151 * | 214 * |
152 * myFunction(NgZone zone, Element element) { | 215 * myFunction(VmTurnZone zone, Element element) { |
153 * element.onClick.listen(() { | 216 * element.onClick.listen(() { |
154 * // auto-digest will run after element click. | 217 * // auto-digest will run after element click. |
155 * }); | 218 * }); |
156 * zone.runOutsideAngular(() { | 219 * zone.runOutsideAngular(() { |
157 * element.onMouseMove.listen(() { | 220 * element.onMouseMove.listen(() { |
158 * // auto-digest will NOT run after mouse move | 221 * // auto-digest will NOT run after mouse move |
159 * }); | 222 * }); |
160 * }); | 223 * }); |
161 * } | 224 * } |
162 */ | 225 */ |
163 runOutsideAngular(body()) => _outerZone.run(body); | 226 dynamic runOutsideAngular(body()) => _outerZone.run(body); |
164 | 227 |
165 assertInTurn() { | 228 /** |
| 229 * Throws an [AssertionError] if no task is currently running in the inner |
| 230 * zone. In a typical app where the inner zone is the Angular zone, this can |
| 231 * be used to assert that the digest will indeed run at the end of the current |
| 232 * turn. |
| 233 */ |
| 234 void assertInTurn() { |
166 assert(_runningInTurn > 0 || _inFinishTurn); | 235 assert(_runningInTurn > 0 || _inFinishTurn); |
167 } | 236 } |
168 | 237 |
169 assertInZone() { | 238 /** |
| 239 * Same as [assertInTurn]. |
| 240 */ |
| 241 void assertInZone() { |
170 assertInTurn(); | 242 assertInTurn(); |
171 } | 243 } |
172 } | 244 } |
OLD | NEW |