blob: ac6bacd2361b60e9b607fed5a16ed3cc8f04cf14 [file] [log] [blame]
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
part of dart.async;
typedef dynamic ZoneCallback();
typedef dynamic ZoneUnaryCallback(arg);
typedef dynamic HandleUncaughtErrorHandler(
Zone self, ZoneDelegate parent, Zone zone, e);
typedef dynamic RunHandler(Zone self, ZoneDelegate parent, Zone zone, f());
typedef dynamic RunUnaryHandler(
Zone self, ZoneDelegate parent, Zone zone, f(arg), arg);
typedef ZoneCallback RegisterCallbackHandler(
Zone self, ZoneDelegate parent, Zone zone, f());
typedef ZoneUnaryCallback RegisterUnaryCallbackHandler(
Zone self, ZoneDelegate parent, Zone zone, f(arg));
typedef void ScheduleMicrotaskHandler(
Zone self, ZoneDelegate parent, Zone zone, f());
typedef Timer CreateTimerHandler(
Zone self, ZoneDelegate parent, Zone zone, Duration duration, void f());
typedef Timer CreatePeriodicTimerHandler(
Zone self, ZoneDelegate parent, Zone zone,
Duration period, void f(Timer timer));
typedef Zone ForkHandler(Zone self, ZoneDelegate parent, Zone zone,
ZoneSpecification specification,
Map<Symbol, dynamic> zoneValues);
/**
* This class provides the specification for a forked zone.
*
* When forking a new zone (see [Zone.fork]) one can override the default
* behavior of the zone by providing callbacks. These callbacks must be
* given in an instance of this class.
*
* Handlers have the same signature as the same-named methods on [Zone] but
* receive three additional arguments:
*
* 1. the zone the handlers are attached to (the "self" zone).
* 2. a [ZoneDelegate] to the parent zone.
* 3. the zone that first received the request (before the request was
* bubbled up).
*
* Handlers can either stop propagation the request (by simply not calling the
* parent handler), or forward to the parent zone, potentially modifying the
* arguments on the way.
*/
abstract class ZoneSpecification {
/**
* Creates a specification with the provided handlers.
*/
const factory ZoneSpecification({
void handleUncaughtError(
Zone self, ZoneDelegate parent, Zone zone, e): null,
dynamic run(Zone self, ZoneDelegate parent, Zone zone, f()): null,
dynamic runUnary(
Zone self, ZoneDelegate parent, Zone zone, f(arg), arg): null,
ZoneCallback registerCallback(
Zone self, ZoneDelegate parent, Zone zone, f()): null,
ZoneUnaryCallback registerUnaryCallback(
Zone self, ZoneDelegate parent, Zone zone, f(arg)): null,
void scheduleMicrotask(
Zone self, ZoneDelegate parent, Zone zone, f()): null,
Timer createTimer(Zone self, ZoneDelegate parent, Zone zone,
Duration duration, void f()): null,
Timer createPeriodicTimer(Zone self, ZoneDelegate parent, Zone zone,
Duration period, void f(Timer timer)): null,
Zone fork(Zone self, ZoneDelegate parent, Zone zone,
ZoneSpecification specification, Map zoneValues): null
}) = _ZoneSpecification;
/**
* Creates a specification from [other] with the provided handlers overriding
* the ones in [other].
*/
factory ZoneSpecification.from(ZoneSpecification other, {
void handleUncaughtError(
Zone self, ZoneDelegate parent, Zone zone, e): null,
dynamic run(Zone self, ZoneDelegate parent, Zone zone, f()): null,
dynamic runUnary(
Zone self, ZoneDelegate parent, Zone zone, f(arg), arg): null,
ZoneCallback registerCallback(
Zone self, ZoneDelegate parent, Zone zone, f()): null,
ZoneUnaryCallback registerUnaryCallback(
Zone self, ZoneDelegate parent, Zone zone, f(arg)): null,
void scheduleMicrotask(
Zone self, ZoneDelegate parent, Zone zone, f()): null,
Timer createTimer(Zone self, ZoneDelegate parent, Zone zone,
Duration duration, void f()): null,
Timer createPeriodicTimer(Zone self, ZoneDelegate parent, Zone zone,
Duration period, void f(Timer timer)): null,
Zone fork(Zone self, ZoneDelegate parent, Zone zone,
ZoneSpecification specification,
Map<Symbol, dynamic> zoneValues): null
}) {
return new ZoneSpecification(
handleUncaughtError: handleUncaughtError != null
? handleUncaughtError
: other.handleUncaughtError,
run: run != null ? run : other.run,
runUnary: runUnary != null ? runUnary : other.runUnary,
registerCallback: registerCallback != null
? registerCallback
: other.registerCallback,
registerUnaryCallback: registerUnaryCallback != null
? registerUnaryCallback
: other.registerUnaryCallback,
scheduleMicrotask: scheduleMicrotask != null
? scheduleMicrotask
: other.scheduleMicrotask,
createTimer : createTimer != null ? createTimer : other.createTimer,
createPeriodicTimer: createPeriodicTimer != null
? createPeriodicTimer
: other.createPeriodicTimer,
fork: fork != null ? fork : other.fork);
}
HandleUncaughtErrorHandler get handleUncaughtError;
RunHandler get run;
RunUnaryHandler get runUnary;
RegisterCallbackHandler get registerCallback;
RegisterUnaryCallbackHandler get registerUnaryCallback;
ScheduleMicrotaskHandler get scheduleMicrotask;
CreateTimerHandler get createTimer;
CreatePeriodicTimerHandler get createPeriodicTimer;
ForkHandler get fork;
}
/**
* Internal [ZoneSpecification] class.
*
* The implementation wants to rely on the fact that the getters cannot change
* dynamically. We thus require users to go through the redirecting
* [ZoneSpecification] constructor which instantiates this class.
*/
class _ZoneSpecification implements ZoneSpecification {
const _ZoneSpecification({
this.handleUncaughtError: null,
this.run: null,
this.runUnary: null,
this.registerCallback: null,
this.registerUnaryCallback: null,
this.scheduleMicrotask: null,
this.createTimer: null,
this.createPeriodicTimer: null,
this.fork: null
});
// TODO(13406): Enable types when dart2js supports it.
final /*HandleUncaughtErrorHandler*/ handleUncaughtError;
final /*RunHandler*/ run;
final /*RunUnaryHandler*/ runUnary;
final /*RegisterCallbackHandler*/ registerCallback;
final /*RegisterUnaryCallbackHandler*/ registerUnaryCallback;
final /*ScheduleMicrotaskHandler*/ scheduleMicrotask;
final /*CreateTimerHandler*/ createTimer;
final /*CreatePeriodicTimerHandler*/ createPeriodicTimer;
final /*ForkHandler*/ fork;
}
/**
* This class wraps zones for delegation.
*
* When forwarding to parent zones one can't just invoke the parent zone's
* exposed functions (like [Zone.run]), but one needs to provide more
* information (like the zone the `run` was initiated). Zone callbacks thus
* receive more information including this [ZoneDelegate] class. When delegating
* to the parent zone one should go through the given instance instead of
* directly invoking the parent zone.
*/
abstract class ZoneDelegate {
/// The [Zone] this class wraps.
Zone get _zone;
dynamic handleUncaughtError(Zone zone, e);
dynamic run(Zone zone, f());
dynamic runUnary(Zone zone, f(arg), arg);
ZoneCallback registerCallback(Zone zone, f());
ZoneUnaryCallback registerUnaryCallback(Zone zone, f(arg));
void scheduleMicrotask(Zone zone, f());
Timer createTimer(Zone zone, Duration duration, void f());
Timer createPeriodicTimer(Zone zone, Duration period, void f(Timer timer));
Zone fork(Zone zone, ZoneSpecification specification, Map zoneValues);
}
/**
* A Zone represents the asynchronous version of a dynamic extent. Asynchronous
* callbacks are executed in the zone they have been queued in. For example,
* the callback of a `future.then` is executed in the same zone as the one where
* the `then` was invoked.
*/
abstract class Zone {
// Private constructor so that it is not possible instantiate a Zone class.
Zone._();
/// The root zone that is implicitly created.
static const Zone ROOT = _ROOT_ZONE;
/// The currently running zone.
static Zone _current = _ROOT_ZONE;
static Zone get current => _current;
dynamic handleUncaughtError(error);
/**
* Returns the parent zone.
*
* Returns `null` if `this` is the [ROOT] zone.
*/
Zone get parent;
/**
* Returns true if `this` and [otherZone] are in the same error zone.
*
* Two zones are in the same error zone if they share the same
* [handleUncaughtError] callback.
*/
bool inSameErrorZone(Zone otherZone);
/**
* Creates a new zone as a child of `this`.
*/
Zone fork({ ZoneSpecification specification,
Map<Symbol, dynamic> zoneValues });
/**
* Executes the given function [f] in this zone.
*/
dynamic run(f());
/**
* Executes the given callback [f] with argument [arg] in this zone.
*/
dynamic runUnary(f(arg), var arg);
/**
* Executes the given function [f] in this zone.
*
* Same as [run] but catches uncaught errors and gives them to
* [handleUncaughtError].
*/
dynamic runGuarded(f());
/**
* Executes the given callback [f] in this zone.
*
* Same as [runUnary] but catches uncaught errors and gives them to
* [handleUncaughtError].
*/
dynamic runUnaryGuarded(f(arg), var arg);
ZoneCallback registerCallback(f());
ZoneUnaryCallback registerUnaryCallback(f(arg));
/**
* Equivalent to:
*
* ZoneCallback registered = registerCallback(f);
* return () => this.run(registered);
*/
ZoneCallback bindCallback(f(), { bool runGuarded });
/**
* Equivalent to:
*
* ZoneCallback registered = registerCallback1(f);
* return (arg) => this.run1(registered, arg);
*/
ZoneUnaryCallback bindUnaryCallback(f(arg), { bool runGuarded });
/**
* Runs [f] asynchronously.
*/
void scheduleMicrotask(void f());
/**
* Creates a Timer where the callback is executed in this zone.
*/
Timer createTimer(Duration duration, void callback());
/**
* Creates a periodic Timer where the callback is executed in this zone.
*/
Timer createPeriodicTimer(Duration period, void callback(Timer timer));
/**
* The error zone is the one that is responsible for dealing with uncaught
* errors. Errors are not allowed to cross zones with different error-zones.
*/
Zone get _errorZone;
/**
* Retrieves the zone-value associated with [key].
*
* If this zone does not contain the value looks up the same key in the
* parent zone. If the [key] is not found returns `null`.
*/
operator[](Symbol key);
}
class _ZoneDelegate implements ZoneDelegate {
final _CustomizedZone _degelationTarget;
Zone get _zone => _degelationTarget;
const _ZoneDelegate(this._degelationTarget);
dynamic handleUncaughtError(Zone zone, e) {
_CustomizedZone parent = _degelationTarget;
while (parent._specification.handleUncaughtError == null) {
parent = parent.parent;
}
return (parent._specification.handleUncaughtError)(
parent, new _ZoneDelegate(parent.parent), zone, e);
}
dynamic run(Zone zone, f()) {
_CustomizedZone parent = _degelationTarget;
while (parent._specification.run == null) {
parent = parent.parent;
}
return (parent._specification.run)(
parent, new _ZoneDelegate(parent.parent), zone, f);
}
dynamic runUnary(Zone zone, f(arg), arg) {
_CustomizedZone parent = _degelationTarget;
while (parent._specification.runUnary == null) {
parent = parent.parent;
}
return (parent._specification.runUnary)(
parent, new _ZoneDelegate(parent.parent), zone, f, arg);
}
ZoneCallback registerCallback(Zone zone, f()) {
_CustomizedZone parent = _degelationTarget;
while (parent._specification.registerCallback == null) {
parent = parent.parent;
}
return (parent._specification.registerCallback)(
parent, new _ZoneDelegate(parent.parent), zone, f);
}
ZoneUnaryCallback registerUnaryCallback(Zone zone, f(arg)) {
_CustomizedZone parent = _degelationTarget;
while (parent._specification.registerUnaryCallback == null) {
parent = parent.parent;
}
return (parent._specification.registerUnaryCallback)(
parent, new _ZoneDelegate(parent.parent), zone, f);
}
void scheduleMicrotask(Zone zone, f()) {
_CustomizedZone parent = _degelationTarget;
while (parent._specification.scheduleMicrotask == null) {
parent = parent.parent;
}
_ZoneDelegate grandParent = new _ZoneDelegate(parent.parent);
(parent._specification.scheduleMicrotask)(parent, grandParent, zone, f);
}
Timer createTimer(Zone zone, Duration duration, void f()) {
_CustomizedZone parent = _degelationTarget;
while (parent._specification.createTimer == null) {
parent = parent.parent;
}
return (parent._specification.createTimer)(
parent, new _ZoneDelegate(parent.parent), zone, duration, f);
}
Timer createPeriodicTimer(Zone zone, Duration period, void f(Timer timer)) {
_CustomizedZone parent = _degelationTarget;
while (parent._specification.createPeriodicTimer == null) {
parent = parent.parent;
}
return (parent._specification.createPeriodicTimer)(
parent, new _ZoneDelegate(parent.parent), zone, period, f);
}
Zone fork(Zone zone, ZoneSpecification specification,
Map<Symbol, dynamic> zoneValues) {
_CustomizedZone parent = _degelationTarget;
while (parent._specification.fork == null) {
parent = parent.parent;
}
_ZoneDelegate grandParent = new _ZoneDelegate(parent.parent);
return (parent._specification.fork)(
parent, grandParent, zone, specification, zoneValues);
}
}
/**
* Default implementation of a [Zone].
*/
class _CustomizedZone implements Zone {
/// The parent zone.
final _CustomizedZone parent;
/// The zone's handlers.
final ZoneSpecification _specification;
/// The zone's value map.
final Map<Symbol, dynamic> _map;
const _CustomizedZone(this.parent, this._specification, this._map);
Zone get _errorZone {
if (_specification.handleUncaughtError != null) return this;
return parent._errorZone;
}
bool inSameErrorZone(Zone otherZone) => _errorZone == otherZone._errorZone;
dynamic runGuarded(f()) {
try {
return run(f);
} catch (e, s) {
return handleUncaughtError(_asyncError(e, s));
}
}
dynamic runUnaryGuarded(f(arg), arg) {
try {
return runUnary(f, arg);
} catch (e, s) {
return handleUncaughtError(_asyncError(e, s));
}
}
ZoneCallback bindCallback(f(), { bool runGuarded }) {
ZoneCallback registered = registerCallback(f);
if (runGuarded) {
return () => this.runGuarded(registered);
} else {
return () => this.run(registered);
}
}
ZoneUnaryCallback bindUnaryCallback(f(arg), { bool runGuarded }) {
ZoneUnaryCallback registered = registerUnaryCallback(f);
if (runGuarded) {
return (arg) => this.runUnaryGuarded(registered, arg);
} else {
return (arg) => this.runUnary(registered, arg);
}
}
operator [](Symbol key) {
var result = _map[key];
if (result != null || _map.containsKey(key)) return result;
// If we are not the root zone look up in the parent zone.
if (parent != null) return parent[key];
assert(this == Zone.ROOT);
return null;
}
// Methods that can be customized by the zone specification.
dynamic handleUncaughtError(error) {
return new _ZoneDelegate(this).handleUncaughtError(this, error);
}
Zone fork({ZoneSpecification specification, Map zoneValues}) {
return new _ZoneDelegate(this).fork(this, specification, zoneValues);
}
dynamic run(f()) {
return new _ZoneDelegate(this).run(this, f);
}
dynamic runUnary(f(arg), arg) {
return new _ZoneDelegate(this).runUnary(this, f, arg);
}
ZoneCallback registerCallback(f()) {
return new _ZoneDelegate(this).registerCallback(this, f);
}
ZoneUnaryCallback registerUnaryCallback(f(arg)) {
return new _ZoneDelegate(this).registerUnaryCallback(this, f);
}
void scheduleMicrotask(void f()) {
new _ZoneDelegate(this).scheduleMicrotask(this, f);
}
Timer createTimer(Duration duration, void f()) {
return new _ZoneDelegate(this).createTimer(this, duration, f);
}
Timer createPeriodicTimer(Duration duration, void f(Timer timer)) {
return new _ZoneDelegate(this).createPeriodicTimer(this, duration, f);
}
}
void _rootHandleUncaughtError(
Zone self, ZoneDelegate parent, Zone zone, error) {
_scheduleAsyncCallback(() {
print("Uncaught Error: ${error}");
var trace = getAttachedStackTrace(error);
_attachStackTrace(error, null);
if (trace != null) {
print("Stack Trace:\n$trace\n");
}
throw error;
});
}
dynamic _rootRun(Zone self, ZoneDelegate parent, Zone zone, f()) {
if (Zone._current == zone) return f();
Zone old = Zone._current;
try {
Zone._current = zone;
return f();
} finally {
Zone._current = old;
}
}
dynamic _rootRunUnary(Zone self, ZoneDelegate parent, Zone zone, f(arg), arg) {
if (Zone._current == zone) return f(arg);
Zone old = Zone._current;
try {
Zone._current = zone;
return f(arg);
} finally {
Zone._current = old;
}
}
ZoneCallback _rootRegisterCallback(
Zone self, ZoneDelegate parent, Zone zone, f()) {
return f;
}
ZoneUnaryCallback _rootRegisterUnaryCallback(
Zone self, ZoneDelegate parent, Zone zone, f(arg)) {
return f;
}
void _rootScheduleMicrotask(Zone self, ZoneDelegate parent, Zone zone, f()) {
_scheduleAsyncCallback(f);
}
Timer _rootCreateTimer(Zone self, ZoneDelegate parent, Zone zone,
Duration duration, void callback()) {
return _createTimer(duration, callback);
}
Timer _rootCreatePeriodicTimer(
Zone self, ZoneDelegate parent, Zone zone,
Duration duration, void callback(Timer timer)) {
return _createPeriodicTimer(duration, callback);
}
Zone _rootFork(Zone self, ZoneDelegate parent, Zone zone,
ZoneSpecification specification,
Map<Symbol, dynamic> zoneValues) {
if (specification == null) {
specification = const ZoneSpecification();
} else if (specification is! _ZoneSpecification) {
throw new ArgumentError("ZoneSpecifications must be instantiated"
" with the provided constructor.");
}
Map<Symbol, dynamic> copiedMap = new HashMap();
if (zoneValues != null) {
zoneValues.forEach((Symbol key, value) {
if (key == null) {
throw new ArgumentError("ZoneValue key must not be null");
}
copiedMap[key] = value;
});
}
return new _CustomizedZone(zone, specification, copiedMap);
}
const _ROOT_SPECIFICATION = const ZoneSpecification(
handleUncaughtError: _rootHandleUncaughtError,
run: _rootRun,
runUnary: _rootRunUnary,
registerCallback: _rootRegisterCallback,
registerUnaryCallback: _rootRegisterUnaryCallback,
scheduleMicrotask: _rootScheduleMicrotask,
createTimer: _rootCreateTimer,
createPeriodicTimer: _rootCreatePeriodicTimer,
fork: _rootFork
);
const _ROOT_ZONE =
const _CustomizedZone(null, _ROOT_SPECIFICATION, const <Symbol, dynamic>{});
/**
* Runs [body] in its own zone.
*
* If [onError] is non-null the zone is considered an error zone. All uncaught
* errors, synchronous or asynchronous, in the zone are caught and handled
* by the callback.
*
* Errors may never cross error-zone boundaries. This is intuitive for leaving
* a zone, but it also applies for errors that would enter an error-zone.
* Errors that try to cross error-zone boundaries are considered uncaught.
*
* var future = new Future.value(499);
* runZonedExperimental(() {
* future = future.then((_) { throw "error in first error-zone"; });
* runZonedExperimental(() {
* future = future.catchError((e) { print("Never reached!"); });
* }, onError: (e) { print("unused error handler"); });
* }, onError: (e) { print("catches error of first error-zone."); });
*
* Example:
*
* runZonedExperimental(() {
* new Future(() { throw "asynchronous error"; });
* }, onError: print); // Will print "asynchronous error".
*/
dynamic runZoned(body(),
{ Map<Symbol, dynamic> zoneValues,
ZoneSpecification zoneSpecification,
void onError(error) }) {
HandleUncaughtErrorHandler errorHandler;
if (onError != null) {
errorHandler = (Zone self, ZoneDelegate parent, Zone zone, error) {
try {
return self.parent.runUnary(onError, error);
} catch(e, s) {
if (identical(e, error)) {
return parent.handleUncaughtError(zone, error);
} else {
return parent.handleUncaughtError(zone, _asyncError(e, s));
}
}
};
}
if (zoneSpecification == null) {
zoneSpecification =
new ZoneSpecification(handleUncaughtError: errorHandler);
} else if (errorHandler != null) {
zoneSpecification =
new ZoneSpecification.from(zoneSpecification,
handleUncaughtError: errorHandler);
}
Zone zone = Zone.current.fork(specification: zoneSpecification,
zoneValues: zoneValues);
if (onError != null) {
return zone.runGuarded(body);
} else {
return zone.run(body);
}
}
/**
* Deprecated. Use `runZoned` instead or create your own [ZoneSpecification].
*
* The [onRunAsync] handler (if non-null) is invoked when the [body] executes
* [runAsync]. The handler is invoked in the outer zone and can therefore
* execute [runAsync] without recursing. The given callback must be
* executed eventually. Otherwise the nested zone will not complete. It must be
* executed only once.
*
* The following example prints the stack trace whenever a callback is
* registered using [runAsync] (which is also used by [Completer]s and
* [StreamController]s.
*
* printStackTrace() { try { throw 0; } catch(e, s) { print(s); } }
* runZonedExperimental(body, onRunAsync: (callback) {
* printStackTrace();
* runAsync(callback);
* });
*
* Note: the `onDone` handler is ignored.
*/
@deprecated
runZonedExperimental(body(),
{ void onRunAsync(void callback()),
void onError(error),
void onDone() }) {
if (onRunAsync == null) {
return runZoned(body, onError: onError);
}
HandleUncaughtErrorHandler errorHandler;
if (onError != null) {
errorHandler = (Zone self, ZoneDelegate parent, Zone zone, error) {
try {
return self.parent.runUnary(onError, error);
} catch(e, s) {
if (identical(e, error)) {
return parent.handleUncaughtError(zone, error);
} else {
return parent.handleUncaughtError(zone, _asyncError(e, s));
}
}
};
}
ScheduleMicrotaskHandler asyncHandler;
if (onRunAsync != null) {
asyncHandler = (Zone self, ZoneDelegate parent, Zone zone, f()) {
self.parent.runUnary(onRunAsync, () => zone.runGuarded(f));
};
}
ZoneSpecification specification =
new ZoneSpecification(handleUncaughtError: errorHandler,
scheduleMicrotask: asyncHandler);
Zone zone = Zone.current.fork(specification: specification);
if (onError != null) {
return zone.runGuarded(body);
} else {
return zone.run(body);
}
}