11library angular.zone;
22
3- import 'dart:async' as async ;
3+ import 'dart:async' ;
44import 'package:stack_trace/stack_trace.dart' show Chain;
55
66/**
@@ -15,49 +15,97 @@ import 'package:stack_trace/stack_trace.dart' show Chain;
1515 * The wrapper maintains an "inner" and "outer" `Zone` . The application code will executes
1616 * in the "inner" zone unless `runOutsideAngular` is explicitely called.
1717 *
18- * A typical application will create a singleton `VmTurnZone` whose outer `Zone` is the root `Zone`
19- * and whose default `onTurnDone` runs the Angular digest .
18+ * A typical application will create a singleton `VmTurnZone` . The outer `Zone` is a fork of the root
19+ * `Zone` . The default `onTurnDone` runs the Angular change detection .
2020 */
2121class VmTurnZone {
2222 Function _onTurnStart;
2323 Function _onTurnDone;
24- Function _onScheduleMicrotask;
2524 Function _onErrorHandler;
2625
27- async .Zone _outerZone;
28- async .Zone _innerZone;
29-
30- int _nestedRunCounter;
26+ // Code executed in _outerZone does not trigger the onTurnDone.
27+ Zone _outerZone;
28+ // _innerZone is the child of _outerZone. Any code executed in this zone will trigger the
29+ // onTurnDone hook at the end of the current VM turn.
30+ Zone _innerZone;
31+
32+ // Number of microtasks pending from _outerZone (& descendants)
33+ int _pendingMicrotasks = 0 ;
34+ // Whether some code has been executed in the _innerZone (& descendants) in the current turn
35+ bool _hasExecutedCodeInInnerZone = false ;
36+ // Whether the onTurnStart hook is executing
37+ bool _inTurnStart = false ;
38+ // _outerRun() call depth. 0 at the end of a macrotask
39+ // zone.run(() => { // top-level call
40+ // zone.run(() => {}); // nested call -> in-turn
41+ // }); // we should only check for the end of a turn once the top-level run ends
42+ int _nestedRun = 0 ;
3143
3244 /**
3345 * Associates with this
3446 *
35- * - an "outer" ` Zone` , which is the one that created this.
36- * - an "inner" ` Zone` , which is a child of the outer ` Zone` .
47+ * - an "outer" [ Zone] , which is a child of the one that created this.
48+ * - an "inner" [ Zone] , which is a child of the outer [ Zone] .
3749 *
3850 * @param {bool} enableLongStackTrace whether to enable long stack trace. They should only be
3951 * enabled in development mode as they significantly impact perf.
4052 */
4153 VmTurnZone ({bool enableLongStackTrace}) {
42- _nestedRunCounter = 0 ;
43- _outerZone = async .Zone .current;
44- _innerZone = _createInnerZoneWithErrorHandling (enableLongStackTrace);
54+ // The _outerZone captures microtask scheduling so that we can run onTurnDone when the queue
55+ // is exhausted and code has been executed in the _innerZone.
56+ if (enableLongStackTrace) {
57+ _outerZone = Chain .capture (
58+ () {
59+ return Zone .current.fork (
60+ specification: new ZoneSpecification (
61+ scheduleMicrotask: _scheduleMicrotask,
62+ run: _outerRun,
63+ runUnary: _outerRunUnary,
64+ runBinary: _outerRunBinary
65+ ),
66+ zoneValues: {'_name' : 'outer' }
67+ );
68+ }, onError: _onErrorWithLongStackTrace);
69+ } else {
70+ _outerZone = Zone .current.fork (
71+ specification: new ZoneSpecification (
72+ scheduleMicrotask: _scheduleMicrotask,
73+ run: _outerRun,
74+ runUnary: _outerRunUnary,
75+ runBinary: _outerRunBinary,
76+ handleUncaughtError: (Zone self, ZoneDelegate parent, Zone zone, error, StackTrace trace) =>
77+ _onErrorWithoutLongStackTrace (error, trace)
78+ ),
79+ zoneValues: {'_name' : 'outer' }
80+ );
81+ }
82+
83+ // Instruments the inner [Zone] to detect when code is executed in this (or a descendant) zone.
84+ // Also runs the onTurnStart hook the first time this zone executes some code in each turn.
85+ _innerZone = _outerZone.fork (
86+ specification: new ZoneSpecification (
87+ run: _innerRun,
88+ runUnary: _innerRunUnary,
89+ runBinary: _innerRunBinary
90+ ),
91+ zoneValues: {'_name' : 'inner' });
4592 }
4693
4794 /**
4895 * Initializes the zone hooks.
4996 *
97+ * The given error handler should re-throw the passed exception. Otherwise, exceptions will not
98+ * propagate outside of the [VmTurnZone] and can alter the application execution flow.
99+ * Not re-throwing could be used to help testing the code or advanced use cases.
100+ *
50101 * @param {Function} onTurnStart called before code executes in the inner zone for each VM turn
51102 * @param {Function} onTurnDone called at the end of a VM turn if code has executed in the inner zone
52- * @param {Function} onScheduleMicrotask
53103 * @param {Function} onErrorHandler called when an exception is thrown by a macro or micro task
54104 */
55- initCallbacks ({Function onTurnStart, Function onTurnDone,
56- Function onScheduleMicrotask, Function onErrorHandler}) {
57- this ._onTurnStart = onTurnStart;
58- this ._onTurnDone = onTurnDone;
59- this ._onScheduleMicrotask = onScheduleMicrotask;
60- this ._onErrorHandler = onErrorHandler;
105+ void initCallbacks ({Function onTurnStart, Function onTurnDone, Function onErrorHandler}) {
106+ _onTurnStart = onTurnStart;
107+ _onTurnDone = onTurnDone;
108+ _onErrorHandler = onErrorHandler;
61109 }
62110
63111 /**
@@ -76,7 +124,12 @@ class VmTurnZone {
76124 * }
77125 * ```
78126 */
79- dynamic run (fn ()) => _innerZone.run (fn);
127+ dynamic run (fn ()) {
128+ // Using runGuarded() is required when executing sync code with Dart otherwise handleUncaughtError()
129+ // would not be called on exceptions.
130+ // see https://code.google.com/p/dart/issues/detail?id=19566 for details.
131+ return _innerZone.runGuarded (fn);
132+ }
80133
81134 /**
82135 * Runs `fn` in the outer zone and returns whatever it returns.
@@ -97,81 +150,94 @@ class VmTurnZone {
97150 * }
98151 * ```
99152 */
100- dynamic runOutsideAngular (fn ()) => _outerZone.run (fn);
153+ dynamic runOutsideAngular (fn ()) {
154+ return _outerZone.runGuarded (fn);
155+ }
101156
102- async .Zone _createInnerZoneWithErrorHandling (bool enableLongStackTrace) {
103- if (enableLongStackTrace) {
104- return Chain .capture (() {
105- return _createInnerZone (async .Zone .current);
106- }, onError: this ._onErrorWithLongStackTrace);
107- } else {
108- return async .runZoned (() {
109- return _createInnerZone (async .Zone .current);
110- }, onError: this ._onErrorWithoutLongStackTrace);
111- }
157+ // Executes code in the [_innerZone] & trigger the onTurnStart hook when code is executed for the
158+ // first time in a turn.
159+ dynamic _innerRun (Zone self, ZoneDelegate parent, Zone zone, fn ()) {
160+ _maybeStartVmTurn (parent, zone);
161+ return parent.run (zone, fn);
112162 }
113163
114- async .Zone _createInnerZone (async .Zone zone) {
115- return zone.fork (
116- specification: new async .ZoneSpecification (
117- run: _onRun,
118- runUnary: _onRunUnary,
119- scheduleMicrotask: _onMicrotask));
164+ dynamic _innerRunUnary (Zone self, ZoneDelegate parent, Zone zone, fn (arg), arg) {
165+ _maybeStartVmTurn (parent, zone);
166+ return parent.runUnary (zone, fn, arg);
120167 }
121168
122- dynamic _onRunBase (
123- async .Zone self, async .ZoneDelegate delegate, async .Zone zone, fn ()) {
124- _nestedRunCounter++ ;
125- try {
126- if (_nestedRunCounter == 1 && _onTurnStart != null ) delegate.run (
127- zone, _onTurnStart);
128- return fn ();
129- } catch (e, s) {
130- if (_onErrorHandler != null && _nestedRunCounter == 1 ) {
131- _onErrorHandler (e, [s.toString ()]);
132- } else {
133- rethrow ;
169+ dynamic _innerRunBinary (Zone self, ZoneDelegate parent, Zone zone, fn (arg1, arg2), arg1, arg2) {
170+ _maybeStartVmTurn (parent, zone);
171+ return parent.runBinary (zone, fn, arg1, arg2);
172+ }
173+
174+ void _maybeStartVmTurn (ZoneDelegate parent, Zone zone) {
175+ if (! _hasExecutedCodeInInnerZone) {
176+ _hasExecutedCodeInInnerZone = true ;
177+ if (_onTurnStart != null ) {
178+ _inTurnStart = true ;
179+ parent.run (zone, _onTurnStart);
134180 }
181+ }
182+ }
183+
184+ dynamic _outerRun (Zone self, ZoneDelegate parent, Zone zone, fn ()) {
185+ try {
186+ _nestedRun++ ;
187+ return parent.run (zone, fn);
135188 } finally {
136- _nestedRunCounter-- ;
137- if (_nestedRunCounter == 0 && _onTurnDone != null ) _finishTurn (
138- zone, delegate);
189+ _nestedRun-- ;
190+ // If there are no more pending microtasks, we are at the end of a VM turn (or in onTurnStart)
191+ // _nestedRun will be 0 at the end of a macrotasks (it could be > 0 when there are nested calls
192+ // to _outerRun()).
193+ if (_pendingMicrotasks == 0 && _nestedRun == 0 ) {
194+ if (_onTurnDone != null && ! _inTurnStart && _hasExecutedCodeInInnerZone) {
195+ // Trigger onTurnDone at the end of a turn if _innerZone has executed some code
196+ try {
197+ parent.run (_innerZone, _onTurnDone);
198+ } finally {
199+ _hasExecutedCodeInInnerZone = false ;
200+ }
201+ }
202+ }
203+ _inTurnStart = false ;
139204 }
140205 }
141206
142- dynamic _onRun ( async . Zone self, async . ZoneDelegate delegate, async . Zone zone,
143- fn ()) => _onRunBase ( self, delegate , zone, () => delegate. run (zone, fn ));
207+ dynamic _outerRunUnary ( Zone self, ZoneDelegate parent, Zone zone, fn (arg), arg) =>
208+ _outerRun ( self, parent , zone, () => fn (arg ));
144209
145- dynamic _onRunUnary (async .Zone self, async .ZoneDelegate delegate,
146- async .Zone zone, fn (args), args) =>
147- _onRunBase (self, delegate, zone, () => delegate.runUnary (zone, fn, args));
210+ dynamic _outerRunBinary (Zone self, ZoneDelegate parent, Zone zone, fn (arg1, arg2), arg1, arg2) =>
211+ _outerRun (self, parent, zone, () => fn (arg1, arg2));
148212
149- void _finishTurn (zone, delegate) {
150- delegate.run (zone, _onTurnDone);
213+ void _scheduleMicrotask (Zone self, ZoneDelegate parent, Zone zone, fn) {
214+ _pendingMicrotasks++ ;
215+ var microtask = () {
216+ try {
217+ fn ();
218+ } finally {
219+ _pendingMicrotasks-- ;
220+ }
221+ };
222+ parent.scheduleMicrotask (zone, microtask);
151223 }
152224
153- _onMicrotask (
154- async .Zone self, async .ZoneDelegate delegate, async .Zone zone, fn) {
155- if (this ._onScheduleMicrotask != null ) {
156- _onScheduleMicrotask (fn);
225+ // Called by Chain.capture() on errors when long stack traces are enabled
226+ void _onErrorWithLongStackTrace (error, Chain chain) {
227+ if (_onErrorHandler != null ) {
228+ final traces = chain.terse.traces.map ((t) => t.toString ()).toList ();
229+ _onErrorHandler (error, traces);
157230 } else {
158- delegate. scheduleMicrotask (zone, fn) ;
231+ throw error ;
159232 }
160233 }
161234
162- _onErrorWithLongStackTrace (exception, Chain chain) {
163- final traces = chain.terse.traces.map ((t) => t.toString ()).toList ();
164- _onError (exception, traces, chain.traces[0 ]);
165- }
166- _onErrorWithoutLongStackTrace (exception, StackTrace trace) {
167- _onError (exception, [trace.toString ()], trace);
168- }
169-
170- _onError (exception, List <String > traces, StackTrace singleTrace) {
235+ // Outer zone handleUnchaughtError when long stack traces are not used
236+ void _onErrorWithoutLongStackTrace (error, StackTrace trace) {
171237 if (_onErrorHandler != null ) {
172- _onErrorHandler (exception, traces );
238+ _onErrorHandler (error, [trace. toString ()] );
173239 } else {
174- _outerZone. handleUncaughtError (exception, singleTrace) ;
240+ throw error ;
175241 }
176242 }
177243}
0 commit comments