Reapply zone tasks.

This reverts commit 34d3c37233f3beb6d1dfb5b23d23c0a674419661.

Committed: https://github.com/dart-lang/sdk/commit/f746f8f77e93fd93cb4ff3b4ed644c96a432947a

[email protected]

Review URL: https://codereview.chromium.org/2119243002 .

Reverted: https://github.com/dart-lang/sdk/commit/dae392291595fa394d601d2d0baa77b66d85533f
diff --git a/sdk/lib/async/timer.dart b/sdk/lib/async/timer.dart
index 1bbb65b..135c868 100644
--- a/sdk/lib/async/timer.dart
+++ b/sdk/lib/async/timer.dart
@@ -4,6 +4,84 @@
 
 part of dart.async;
 
+abstract class _TimerTask implements Timer {
+  final Zone _zone;
+  final Timer _nativeTimer;
+
+  _TimerTask(this._nativeTimer, this._zone);
+
+  void cancel() {
+    _nativeTimer.cancel();
+  }
+
+  bool get isActive => _nativeTimer.isActive;
+}
+
+class _SingleShotTimerTask extends _TimerTask {
+  // TODO(floitsch): the generic argument should be 'void'.
+  final ZoneCallback<dynamic> _callback;
+
+  _SingleShotTimerTask(Timer timer, this._callback, Zone zone)
+      : super(timer, zone);
+}
+
+class _PeriodicTimerTask extends _TimerTask {
+  // TODO(floitsch): the first generic argument should be 'void'.
+  final ZoneUnaryCallback<dynamic, Timer> _callback;
+
+  _PeriodicTimerTask(Timer timer, this._callback, Zone zone)
+      : super(timer, zone);
+}
+
+/**
+ * A task specification for a single-shot timer.
+ *
+ * *Experimental*. Might disappear without notice.
+ */
+class SingleShotTimerTaskSpecification implements TaskSpecification {
+  static const String specificationName = "dart.async.timer";
+
+  /** The duration after which the timer should invoke the [callback]. */
+  final Duration duration;
+
+  /** The callback that should be run when the timer triggers. */
+  // TODO(floitsch): the generic argument should be void.
+  final ZoneCallback<dynamic> callback;
+
+  SingleShotTimerTaskSpecification(this.duration, void this.callback());
+
+  @override
+  String get name => specificationName;
+
+  @override
+  bool get isOneShot => true;
+}
+
+/**
+ * A task specification for a periodic timer.
+ *
+ * *Experimental*. Might disappear without notice.
+ */
+class PeriodicTimerTaskSpecification implements TaskSpecification {
+  static const String specificationName = "dart.async.periodic-timer";
+
+  /** The interval at which the periodic timer should invoke the [callback]. */
+  final Duration duration;
+
+  /** The callback that should be run when the timer triggers. */
+  // TODO(floitsch): the first generic argument should be void.
+  final ZoneUnaryCallback<dynamic, Timer> callback;
+
+  PeriodicTimerTaskSpecification(
+      this.duration, void this.callback(Timer timer));
+
+  @override
+  String get name => specificationName;
+
+  @override
+  bool get isOneShot => false;
+}
+
 /**
  * A count-down timer that can be configured to fire once or repeatedly.
  *
@@ -47,10 +125,15 @@
     if (Zone.current == Zone.ROOT) {
       // No need to bind the callback. We know that the root's timer will
       // be invoked in the root zone.
-      return Zone.current.createTimer(duration, callback);
+      return Timer._createTimer(duration, callback);
     }
-    return Zone.current.createTimer(
-        duration, Zone.current.bindCallback(callback, runGuarded: true));
+    return Zone.current.createTimer(duration, callback);
+  }
+
+  factory Timer._task(Zone zone, Duration duration, void callback()) {
+    SingleShotTimerTaskSpecification specification =
+        new SingleShotTimerTaskSpecification(duration, callback);
+    return zone.createTask(_createSingleShotTimerTask, specification);
   }
 
   /**
@@ -70,17 +153,65 @@
    * scheduled for - even if the actual callback was delayed.
    */
   factory Timer.periodic(Duration duration,
-                         void callback(Timer timer)) {
+      void callback(Timer timer)) {
     if (Zone.current == Zone.ROOT) {
       // No need to bind the callback. We know that the root's timer will
       // be invoked in the root zone.
-      return Zone.current.createPeriodicTimer(duration, callback);
+      return Timer._createPeriodicTimer(duration, callback);
     }
+    return Zone.current.createPeriodicTimer(duration, callback);
+  }
+
+  factory Timer._periodicTask(Zone zone, Duration duration,
+      void callback(Timer timer)) {
+    PeriodicTimerTaskSpecification specification =
+        new PeriodicTimerTaskSpecification(duration, callback);
+    return zone.createTask(_createPeriodicTimerTask, specification);
+  }
+
+  static Timer _createSingleShotTimerTask(
+      SingleShotTimerTaskSpecification specification, Zone zone) {
+    ZoneCallback registeredCallback = identical(_ROOT_ZONE, zone)
+        ? specification.callback
+        : zone.registerCallback(specification.callback);
+
+    _TimerTask timerTask;
+
+    Timer nativeTimer = Timer._createTimer(specification.duration, () {
+      timerTask._zone.runTask(_runSingleShotCallback, timerTask, null);
+    });
+
+    timerTask = new _SingleShotTimerTask(nativeTimer, registeredCallback, zone);
+    return timerTask;
+  }
+
+  static void _runSingleShotCallback(_SingleShotTimerTask timerTask, Object _) {
+    timerTask._callback();
+  }
+
+  static Timer _createPeriodicTimerTask(
+      PeriodicTimerTaskSpecification specification, Zone zone) {
     // TODO(floitsch): the return type should be 'void', and the type
     // should be inferred.
-    var boundCallback = Zone.current.bindUnaryCallback/*<dynamic, Timer>*/(
-        callback, runGuarded: true);
-    return Zone.current.createPeriodicTimer(duration, boundCallback);
+    ZoneUnaryCallback<dynamic, Timer> registeredCallback =
+        identical(_ROOT_ZONE, zone)
+        ? specification.callback
+        : zone.registerUnaryCallback/*<dynamic, Timer>*/(
+            specification.callback);
+
+    _TimerTask timerTask;
+
+    Timer nativeTimer =
+        Timer._createPeriodicTimer(specification.duration, (Timer _) {
+      timerTask._zone.runTask(_runPeriodicCallback, timerTask, null);
+    });
+
+    timerTask = new _PeriodicTimerTask(nativeTimer, registeredCallback, zone);
+    return timerTask;
+  }
+
+  static void _runPeriodicCallback(_PeriodicTimerTask timerTask, Object _) {
+    timerTask._callback(timerTask);
   }
 
   /**
diff --git a/sdk/lib/async/zone.dart b/sdk/lib/async/zone.dart
index 24f83f8..e0c3e4b 100644
--- a/sdk/lib/async/zone.dart
+++ b/sdk/lib/async/zone.dart
@@ -8,6 +8,13 @@
 typedef R ZoneUnaryCallback<R, T>(T arg);
 typedef R ZoneBinaryCallback<R, T1, T2>(T1 arg1, T2 arg2);
 
+/// *Experimental*. Might disappear without warning.
+typedef T TaskCreate<T, S extends TaskSpecification>(
+    S specification, Zone zone);
+/// *Experimental*. Might disappear without warning.
+typedef void TaskRun<T, A>(T task, A arg);
+
+
 // TODO(floitsch): we are abusing generic typedefs as typedefs for generic
 // functions.
 /*ABUSE*/
@@ -33,19 +40,31 @@
     Zone self, ZoneDelegate parent, Zone zone, R f(T1 arg1, T2 arg2));
 typedef AsyncError ErrorCallbackHandler(Zone self, ZoneDelegate parent,
     Zone zone, Object error, StackTrace stackTrace);
+/// *Experimental*. Might disappear without warning.
+/*ABUSE*/
+typedef T CreateTaskHandler<T, S extends TaskSpecification>(
+    Zone self, ZoneDelegate parent, Zone zone,
+    TaskCreate<T, S> create, S taskSpecification);
+/// *Experimental*. Might disappear without warning.
+/*ABUSE*/
+typedef void RunTaskHandler<T, A>(Zone self, ZoneDelegate parent, Zone zone,
+    TaskRun<T, A> run, T task, A arg);
 typedef void ScheduleMicrotaskHandler(
     Zone self, ZoneDelegate parent, Zone zone, void 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 void PrintHandler(
     Zone self, ZoneDelegate parent, Zone zone, String line);
 typedef Zone ForkHandler(Zone self, ZoneDelegate parent, Zone zone,
                          ZoneSpecification specification,
                          Map zoneValues);
 
+// The following typedef declarations are used by functionality which
+// will be removed and replaced by tasksif the task experiment is successful.
+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));
+
 /** Pair of error and stack trace. Returned by [Zone.errorCallback]. */
 class AsyncError implements Error {
   final Object error;
@@ -56,10 +75,41 @@
   String toString() => '$error';
 }
 
+/**
+ * A task specification contains the necessary information to create a task.
+ *
+ * See [Zone.createTask] for how a specification is used to create a task.
+ *
+ * Task specifications should be public and it should be possible to create
+ * new instances as a user. That is, custom zones should be able to replace
+ * an existing specification with a modified one.
+ *
+ * *Experimental*. This class might disappear without warning.
+ */
+abstract class TaskSpecification {
+  /**
+   * Description of the task.
+   *
+   * This string is unused by the root-zone, but might be used for debugging,
+   * and testing. As such, it should be relatively unique in its category.
+   *
+   * As a general guideline we recommend: "package-name.library.action".
+   */
+  String get name;
+
+  /**
+   * Whether the scheduled task triggers at most once.
+   *
+   * If the task is not a one-shot task, it may need to be canceled to prevent
+   * further iterations of the task.
+   */
+  bool get isOneShot;
+}
 
 class _ZoneFunction<T extends Function> {
   final _Zone zone;
   final T function;
+
   const _ZoneFunction(this.zone, this.function);
 }
 
@@ -85,6 +135,9 @@
 abstract class ZoneSpecification {
   /**
    * Creates a specification with the provided handlers.
+   *
+   * The task-related parameters ([createTask] and [runTask]) are experimental
+   * and might be removed without warning.
    */
   const factory ZoneSpecification({
       HandleUncaughtErrorHandler handleUncaughtError,
@@ -96,7 +149,11 @@
       RegisterBinaryCallbackHandler registerBinaryCallback,
       ErrorCallbackHandler errorCallback,
       ScheduleMicrotaskHandler scheduleMicrotask,
+      CreateTaskHandler createTask,
+      RunTaskHandler runTask,
+      // TODO(floitsch): mark as deprecated once tasks are non-experimental.
       CreateTimerHandler createTimer,
+      // TODO(floitsch): mark as deprecated once tasks are non-experimental.
       CreatePeriodicTimerHandler createPeriodicTimer,
       PrintHandler print,
       ForkHandler fork
@@ -105,6 +162,9 @@
   /**
    * Creates a specification from [other] with the provided handlers overriding
    * the ones in [other].
+   *
+   * The task-related parameters ([createTask] and [runTask]) are experimental
+   * and might be removed without warning.
    */
   factory ZoneSpecification.from(ZoneSpecification other, {
       HandleUncaughtErrorHandler handleUncaughtError: null,
@@ -116,7 +176,11 @@
       RegisterBinaryCallbackHandler registerBinaryCallback: null,
       ErrorCallbackHandler errorCallback: null,
       ScheduleMicrotaskHandler scheduleMicrotask: null,
+      CreateTaskHandler createTask: null,
+      RunTaskHandler runTask: null,
+      // TODO(floitsch): mark as deprecated once tasks are non-experimental.
       CreateTimerHandler createTimer: null,
+      // TODO(floitsch): mark as deprecated once tasks are non-experimental.
       CreatePeriodicTimerHandler createPeriodicTimer: null,
       PrintHandler print: null,
       ForkHandler fork: null
@@ -132,11 +196,14 @@
       registerBinaryCallback: registerBinaryCallback ??
                               other.registerBinaryCallback,
       errorCallback: errorCallback ?? other.errorCallback,
+
+      createTask: createTask ?? other.createTask,
+      runTask: runTask ?? other.runTask,
+      print : print ?? other.print,
+      fork: fork ?? other.fork,
       scheduleMicrotask: scheduleMicrotask ?? other.scheduleMicrotask,
       createTimer : createTimer ?? other.createTimer,
-      createPeriodicTimer: createPeriodicTimer ?? other.createPeriodicTimer,
-      print : print ?? other.print,
-      fork: fork ?? other.fork);
+      createPeriodicTimer: createPeriodicTimer ?? other.createPeriodicTimer);
   }
 
   HandleUncaughtErrorHandler get handleUncaughtError;
@@ -148,10 +215,17 @@
   RegisterBinaryCallbackHandler get registerBinaryCallback;
   ErrorCallbackHandler get errorCallback;
   ScheduleMicrotaskHandler get scheduleMicrotask;
-  CreateTimerHandler get createTimer;
-  CreatePeriodicTimerHandler get createPeriodicTimer;
+  /// *Experimental*. Might disappear without warning.
+  CreateTaskHandler get createTask;
+  /// *Experimental*. Might disappear without warning.
+  RunTaskHandler get runTask;
   PrintHandler get print;
   ForkHandler get fork;
+
+  // TODO(floitsch): deprecate once tasks are non-experimental.
+  CreateTimerHandler get createTimer;
+  // TODO(floitsch): deprecate once tasks are non-experimental.
+  CreatePeriodicTimerHandler get createPeriodicTimer;
 }
 
 /**
@@ -172,10 +246,14 @@
     this.registerBinaryCallback: null,
     this.errorCallback: null,
     this.scheduleMicrotask: null,
-    this.createTimer: null,
-    this.createPeriodicTimer: null,
+    this.createTask: null,
+    this.runTask: null,
     this.print: null,
-    this.fork: null
+    this.fork: null,
+    // TODO(floitsch): deprecate once tasks are non-experimental.
+    this.createTimer: null,
+    // TODO(floitsch): deprecate once tasks are non-experimental.
+    this.createPeriodicTimer: null
   });
 
   final HandleUncaughtErrorHandler handleUncaughtError;
@@ -187,10 +265,15 @@
   final RegisterBinaryCallbackHandler registerBinaryCallback;
   final ErrorCallbackHandler errorCallback;
   final ScheduleMicrotaskHandler scheduleMicrotask;
-  final CreateTimerHandler createTimer;
-  final CreatePeriodicTimerHandler createPeriodicTimer;
+  final CreateTaskHandler createTask;
+  final RunTaskHandler runTask;
   final PrintHandler print;
   final ForkHandler fork;
+
+  // TODO(floitsch): deprecate once tasks are non-experimental.
+  final CreateTimerHandler createTimer;
+  // TODO(floitsch): deprecate once tasks are non-experimental.
+  final CreatePeriodicTimerHandler createPeriodicTimer;
 }
 
 /**
@@ -217,10 +300,23 @@
       Zone zone, /*=R*/ f(/*=T1*/ arg1, /*=T2*/ arg2));
   AsyncError errorCallback(Zone zone, Object error, StackTrace stackTrace);
   void scheduleMicrotask(Zone zone, void f());
-  Timer createTimer(Zone zone, Duration duration, void f());
-  Timer createPeriodicTimer(Zone zone, Duration period, void f(Timer timer));
+
+  /// *Experimental*. Might disappear without notice.
+  Object/*=T*/ createTask/*<T, S extends TaskSpecification>*/(
+      Zone zone, TaskCreate/*<T, S>*/ create,
+      TaskSpecification/*=S*/ specification);
+  /// *Experimental*. Might disappear without notice.
+  void runTask/*<T, A>*/(
+      Zone zone, TaskRun/*<T, A>*/ run, Object/*=T*/ task,
+      Object/*=A*/ argument);
+
   void print(Zone zone, String line);
   Zone fork(Zone zone, ZoneSpecification specification, Map zoneValues);
+
+  // TODO(floitsch): deprecate once tasks are non-experimental.
+  Timer createTimer(Zone zone, Duration duration, void f());
+  // TODO(floitsch): deprecate once tasks are non-experimental.
+  Timer createPeriodicTimer(Zone zone, Duration period, void f(Timer timer));
 }
 
 /**
@@ -411,13 +507,102 @@
   void scheduleMicrotask(void f());
 
   /**
+   * Creates a task in the current zone.
+   *
+   * A task represents an asynchronous operation or process that reports back
+   * through the event loop.
+   *
+   * This function allows the zone to intercept the initialization of the
+   * task while the [runTask] function is invoked when the task reports back.
+   *
+   * By default, in the root zone, the [create] function is invoked with the
+   * [specification] as argument. It returns a task object which is used for all
+   * future interactions between the zone and the task. The object is
+   * a unique instance representing the task. It is generally returned to
+   * whoever initiated the task.
+   * For example, the HTML library uses the returned [StreamSubscription] as
+   * task object when users register an event listener.
+   *
+   * Tasks are created when the program starts an operation that reports back
+   * through the event loop. For example, a timer or an HTTP request both
+   * return through the event loop and are therefore tasks.
+   *
+   * If the [create] function is not invoked (because a custom zone has
+   * replaced or intercepted it), then the operation is *not* started. This
+   * means that a custom zone can intercept tasks, like HTTP requests.
+   *
+   * A task goes through the following steps:
+   * - a user invokes a library function that should eventually return through
+   *   the event loop.
+   * - the library function creates a [TaskSpecification] that contains the
+   *   necessary information to start the operation, and invokes
+   *   `Zone.current.createTask` with the specification and a [create] closure.
+   *   The closure, when invoked, uses the specification to start the operation
+   *   (usually by interacting with the underlying system, or as a native
+   *   extension), and returns a task object that identifies the running task.
+   * - custom zones handle the request and (unless completely intercepted and
+   *   aborted), end up calling the root zone's [createTask] which runs the
+   *   provided `create` closure, which may have been replaced at this point.
+   * - later, the asynchronous operation returns through the event loop.
+   *   It invokes [Zone.runTask] on the zone in which the task should run
+   *   (and which was originally passed to the `create` function by
+   *   `createTask`). The [runTask] function receives the
+   *   task object, a `run` function and an argument. As before, custom zones
+   *   may intercept this call. Eventually (unless aborted), the `run` function
+   *   is invoked. This last step may happen multiple times for tasks that are
+   *   not oneshot tasks (see [ZoneSpecification.isOneShot]).
+   *
+   * Custom zones may replace the [specification] with a different one, thus
+   * modifying the task parameters. An operation that wishes to be an
+   * interceptable task must publicly specify the types that intercepting code
+   * sees:
+   * - The specification type (extending [TaskSpecification]) which holds the
+   *   information available when intercepting the `createTask` call.
+   * - The task object type, returned by `createTask` and [create]. This object
+   *   may simply be typed as [Object].
+   * - The argument type, if [runTask] takes a meaningful argument.
+   *
+   * *Experimental*. Might disappear without notice.
+   */
+  Object/*=T*/ createTask/*<T, S extends TaskSpecification>*/(
+      /*=T*/ create(TaskSpecification/*=S*/ specification, Zone zone),
+      TaskSpecification/*=S*/ specification);
+
+  /**
+   * Runs a task callback.
+   *
+   * This function is invoked when an operation, started through [createTask],
+   * generates an event.
+   *
+   * Generally, tasks schedule Dart code in the global event loop when the
+   * [createTask] function is invoked. Since the
+   * event loop does not expect any return value from the code it runs, the
+   * [runTask] function is a void function.
+   *
+   * The [task] object must be the same as the one created with [createTask].
+   *
+   * It is good practice that task operations provide a meaningful [argument],
+   * so that custom zones can interact with it. They might want to log or
+   * replace the argument before calling the [run] function.
+   *
+   * See [createTask].
+   *
+   * *Experimental*. Might disappear without notice.
+   */
+  void runTask/*<T, A>*/(
+      /*=T*/ run(/*=T*/ task, /*=A*/ argument), Object/*=T*/ task,
+      Object/*=A*/ argument);
+
+  /**
    * Creates a Timer where the callback is executed in this zone.
    */
+  // TODO(floitsch): deprecate once tasks are non-experimental.
   Timer createTimer(Duration duration, void callback());
 
   /**
    * Creates a periodic Timer where the callback is executed in this zone.
    */
+  // TODO(floitsch): deprecate once tasks are non-experimental.
   Timer createPeriodicTimer(Duration period, void callback(Timer timer));
 
   /**
@@ -523,7 +708,7 @@
     // TODO(floitsch): make this a generic method call on '<R>' once it's
     // supported. Remove the unnecessary cast.
     return handler(implZone, _parentDelegate(implZone), zone, f)
-        as Object/*=ZoneCallback<R>*/;
+        as dynamic/*=ZoneCallback<R>*/;
   }
 
   ZoneUnaryCallback/*<R, T>*/ registerUnaryCallback/*<R, T>*/(
@@ -534,7 +719,7 @@
     // TODO(floitsch): make this a generic method call on '<R, T>' once it's
     // supported. Remove the unnecessary cast.
     return handler(implZone, _parentDelegate(implZone), zone, f)
-        as Object/*=ZoneUnaryCallback<R, T>*/;
+        as dynamic/*=ZoneUnaryCallback<R, T>*/;
   }
 
   ZoneBinaryCallback/*<R, T1, T2>*/ registerBinaryCallback/*<R, T1, T2>*/(
@@ -545,7 +730,7 @@
     // TODO(floitsch): make this a generic method call on '<R, T1, T2>' once
     // it's supported. Remove the unnecessary cast.
     return handler(implZone, _parentDelegate(implZone), zone, f)
-        as Object/*=ZoneBinaryCallback<R, T1, T2>*/;
+        as dynamic/*=ZoneBinaryCallback<R, T1, T2>*/;
   }
 
   AsyncError errorCallback(Zone zone, Object error, StackTrace stackTrace) {
@@ -564,18 +749,25 @@
     handler(implZone, _parentDelegate(implZone), zone, f);
   }
 
-  Timer createTimer(Zone zone, Duration duration, void f()) {
-    var implementation = _delegationTarget._createTimer;
+  Object/*=T*/ createTask/*<T, S extends TaskSpecification>*/(
+      Zone zone, TaskCreate/*<T, S>*/ create, TaskSpecification/*=S*/ specification) {
+    var implementation = _delegationTarget._createTask;
     _Zone implZone = implementation.zone;
-    CreateTimerHandler handler = implementation.function;
-    return handler(implZone, _parentDelegate(implZone), zone, duration, f);
+    // TODO(floitsch): make the handler call a generic method call on '<T, S>'
+    // once it's supported. Remove the unnecessary cast.
+    var handler =
+        implementation.function as CreateTaskHandler/*<T, S>*/;
+    return handler(
+        implZone, _parentDelegate(implZone), zone, create, specification);
   }
 
-  Timer createPeriodicTimer(Zone zone, Duration period, void f(Timer timer)) {
-    var implementation = _delegationTarget._createPeriodicTimer;
+  void runTask/*<T, A>*/(Zone zone, TaskRun run, Object /*=T*/ task,
+      Object /*=A*/ argument) {
+    var implementation = _delegationTarget._runTask;
     _Zone implZone = implementation.zone;
-    CreatePeriodicTimerHandler handler = implementation.function;
-    return handler(implZone, _parentDelegate(implZone), zone, period, f);
+    RunTaskHandler handler = implementation.function;
+    // TODO(floitsch): make this a generic call on '<T, A>'.
+    handler(implZone, _parentDelegate(implZone), zone, run, task, argument);
   }
 
   void print(Zone zone, String line) {
@@ -593,6 +785,22 @@
     return handler(
         implZone, _parentDelegate(implZone), zone, specification, zoneValues);
   }
+
+  // TODO(floitsch): deprecate once tasks are non-experimental.
+  Timer createTimer(Zone zone, Duration duration, void f()) {
+    var implementation = _delegationTarget._createTimer;
+    _Zone implZone = implementation.zone;
+    CreateTimerHandler handler = implementation.function;
+    return handler(implZone, _parentDelegate(implZone), zone, duration, f);
+  }
+
+  // TODO(floitsch): deprecate once tasks are non-experimental.
+  Timer createPeriodicTimer(Zone zone, Duration period, void f(Timer timer)) {
+    var implementation = _delegationTarget._createPeriodicTimer;
+    _Zone implZone = implementation.zone;
+    CreatePeriodicTimerHandler handler = implementation.function;
+    return handler(implZone, _parentDelegate(implZone), zone, period, f);
+  }
 }
 
 
@@ -610,11 +818,17 @@
   _ZoneFunction<RegisterBinaryCallbackHandler> get _registerBinaryCallback;
   _ZoneFunction<ErrorCallbackHandler> get _errorCallback;
   _ZoneFunction<ScheduleMicrotaskHandler> get _scheduleMicrotask;
-  _ZoneFunction<CreateTimerHandler> get _createTimer;
-  _ZoneFunction<CreatePeriodicTimerHandler> get _createPeriodicTimer;
+  _ZoneFunction<CreateTaskHandler> get _createTask;
+  _ZoneFunction<RunTaskHandler> get _runTask;
   _ZoneFunction<PrintHandler> get _print;
   _ZoneFunction<ForkHandler> get _fork;
   _ZoneFunction<HandleUncaughtErrorHandler> get _handleUncaughtError;
+
+  // TODO(floitsch): deprecate once tasks are non-experimental.
+  _ZoneFunction<CreateTimerHandler> get _createTimer;
+  // TODO(floitsch): deprecate once tasks are non-experimental.
+  _ZoneFunction<CreatePeriodicTimerHandler> get _createPeriodicTimer;
+
   _Zone get parent;
   ZoneDelegate get _delegate;
   Map get _map;
@@ -636,12 +850,17 @@
   _ZoneFunction<RegisterBinaryCallbackHandler> _registerBinaryCallback;
   _ZoneFunction<ErrorCallbackHandler> _errorCallback;
   _ZoneFunction<ScheduleMicrotaskHandler> _scheduleMicrotask;
-  _ZoneFunction<CreateTimerHandler> _createTimer;
-  _ZoneFunction<CreatePeriodicTimerHandler> _createPeriodicTimer;
+  _ZoneFunction<CreateTaskHandler> _createTask;
+  _ZoneFunction<RunTaskHandler> _runTask;
   _ZoneFunction<PrintHandler> _print;
   _ZoneFunction<ForkHandler> _fork;
   _ZoneFunction<HandleUncaughtErrorHandler> _handleUncaughtError;
 
+  // TODO(floitsch): deprecate once tasks are non-experimental.
+  _ZoneFunction<CreateTimerHandler> _createTimer;
+  // TODO(floitsch): deprecate once tasks are non-experimental.
+  _ZoneFunction<CreatePeriodicTimerHandler> _createPeriodicTimer;
+
   // A cached delegate to this zone.
   ZoneDelegate _delegateCache;
 
@@ -692,13 +911,14 @@
         ? new _ZoneFunction<ScheduleMicrotaskHandler>(
             this, specification.scheduleMicrotask)
         : parent._scheduleMicrotask;
-    _createTimer = (specification.createTimer != null)
-        ? new _ZoneFunction<CreateTimerHandler>(this, specification.createTimer)
-        : parent._createTimer;
-    _createPeriodicTimer = (specification.createPeriodicTimer != null)
-        ? new _ZoneFunction<CreatePeriodicTimerHandler>(
-            this, specification.createPeriodicTimer)
-        : parent._createPeriodicTimer;
+    _createTask = (specification.createTask != null)
+        ? new _ZoneFunction<CreateTaskHandler>(
+            this, specification.createTask)
+        : parent._createTask;
+    _runTask = (specification.runTask != null)
+        ? new _ZoneFunction<RunTaskHandler>(
+            this, specification.runTask)
+        : parent._runTask;
     _print = (specification.print != null)
         ? new _ZoneFunction<PrintHandler>(this, specification.print)
         : parent._print;
@@ -709,6 +929,16 @@
         ? new _ZoneFunction<HandleUncaughtErrorHandler>(
             this, specification.handleUncaughtError)
         : parent._handleUncaughtError;
+
+    // Deprecated fields, once tasks are non-experimental.
+    _createTimer = (specification.createTimer != null)
+        ? new _ZoneFunction<CreateTimerHandler>(
+            this, specification.createTimer)
+        : parent._createTimer;
+    _createPeriodicTimer = (specification.createPeriodicTimer != null)
+        ? new _ZoneFunction<CreatePeriodicTimerHandler>(
+            this, specification.createPeriodicTimer)
+        : parent._createPeriodicTimer;
   }
 
   /**
@@ -859,7 +1089,7 @@
     // TODO(floitsch): make this a generic method call on '<R>' once it's
     // supported. Remove the unnecessary cast.
     return handler(implementation.zone, parentDelegate, this, callback)
-        as Object/*=ZoneCallback<R>*/;
+        as dynamic/*=ZoneCallback<R>*/;
   }
 
   ZoneUnaryCallback/*<R, T>*/ registerUnaryCallback/*<R, T>*/(
@@ -871,7 +1101,7 @@
     // TODO(floitsch): make this a generic method call on '<R, T>' once it's
     // supported. Remove the unnecessary cast.
     return handler(implementation.zone, parentDelegate, this, callback)
-        as Object/*=ZoneUnaryCallback<R, T>*/;
+        as dynamic/*=ZoneUnaryCallback<R, T>*/;
   }
 
   ZoneBinaryCallback/*<R, T1, T2>*/ registerBinaryCallback/*<R, T1, T2>*/(
@@ -883,7 +1113,7 @@
     // TODO(floitsch): make this a generic method call on '<R, T1, T2>' once
     // it's supported. Remove the unnecessary cast.
     return handler(implementation.zone, parentDelegate, this, callback)
-        as Object/*=ZoneBinaryCallback<R, T1, T2>*/;
+        as dynamic/*=ZoneBinaryCallback<R, T1, T2>*/;
   }
 
   AsyncError errorCallback(Object error, StackTrace stackTrace) {
@@ -902,24 +1132,29 @@
     assert(implementation != null);
     ZoneDelegate parentDelegate = _parentDelegate(implementation.zone);
     ScheduleMicrotaskHandler handler = implementation.function;
-    return handler(implementation.zone, parentDelegate, this, f);
+    handler(implementation.zone, parentDelegate, this, f);
   }
 
-  Timer createTimer(Duration duration, void f()) {
-    var implementation = this._createTimer;
-    assert(implementation != null);
+  Object/*=T*/ createTask/*<T, S extends TaskSpecification>*/(
+      TaskCreate/*<T, S>*/ create, TaskSpecification/*=S*/ specification) {
+    var implementation = this._createTask;
     ZoneDelegate parentDelegate = _parentDelegate(implementation.zone);
-    CreateTimerHandler handler = implementation.function;
-    return handler(implementation.zone, parentDelegate, this, duration, f);
-  }
-
-  Timer createPeriodicTimer(Duration duration, void f(Timer timer)) {
-    var implementation = this._createPeriodicTimer;
-    assert(implementation != null);
-    ZoneDelegate parentDelegate = _parentDelegate(implementation.zone);
-    CreatePeriodicTimerHandler handler = implementation.function;
+    // TODO(floitsch): make the handler call a generic method call on '<T, S>'
+    // once it's supported. Remove the unnecessary cast.
+    var handler =
+        implementation.function as CreateTaskHandler/*<T, S>*/;
     return handler(
-        implementation.zone, parentDelegate, this, duration, f);
+        implementation.zone, parentDelegate, this, create, specification);
+  }
+
+  void runTask/*<T, A>*/(
+      TaskRun/*<T, A>*/ run, Object/*=T*/ task, Object/*=A*/ arg1) {
+    var implementation = this._runTask;
+    ZoneDelegate parentDelegate = _parentDelegate(implementation.zone);
+    RunTaskHandler handler = implementation.function;
+    // TODO(floitsch): make this a generic method call on '<T, A>' once it's
+    // supported.
+    handler(implementation.zone, parentDelegate, this, run, task, arg1);
   }
 
   void print(String line) {
@@ -929,6 +1164,25 @@
     PrintHandler handler = implementation.function;
     return handler(implementation.zone, parentDelegate, this, line);
   }
+
+  // TODO(floitsch): deprecate once tasks are non-experimental.
+  Timer createTimer(Duration duration, void f()) {
+    var implementation = this._createTimer;
+    assert(implementation != null);
+    ZoneDelegate parentDelegate = _parentDelegate(implementation.zone);
+    CreateTimerHandler handler = implementation.function;
+    return handler(implementation.zone, parentDelegate, this, duration, f);
+  }
+
+  // TODO(floitsch): deprecate once tasks are non-experimental.
+  Timer createPeriodicTimer(Duration duration, void f(Timer timer)) {
+    var implementation = this._createPeriodicTimer;
+    assert(implementation != null);
+    ZoneDelegate parentDelegate = _parentDelegate(implementation.zone);
+    CreatePeriodicTimerHandler handler = implementation.function;
+    return handler(
+        implementation.zone, parentDelegate, this, duration, f);
+  }
 }
 
 /*=R*/ _rootHandleUncaughtError/*<R>*/(
@@ -1006,22 +1260,39 @@
   _scheduleAsyncCallback(f);
 }
 
+Object/*=T*/ _rootCreateTask/*<T, S extends TaskSpecification>*/(
+    Zone self, ZoneDelegate parent, Zone zone,
+    TaskCreate/*<T, S>*/ create, TaskSpecification/*=S*/ specification) {
+  return create(specification, zone);
+}
+
+void _rootRunTask/*<T, A>*/(
+    Zone self, ZoneDelegate parent, Zone zone, TaskRun run/*<T, A>*/,
+    Object/*=T*/ task, Object/*=A*/ arg) {
+  if (Zone._current == zone) {
+    run(task, arg);
+    return;
+  }
+
+  Zone old = Zone._enter(zone);
+  try {
+    run(task, arg);
+  } catch (e, s) {
+    zone.handleUncaughtError/*<dynamic>*/(e, s);
+  } finally {
+    Zone._leave(old);
+  }
+}
+
 Timer _rootCreateTimer(Zone self, ZoneDelegate parent, Zone zone,
                        Duration duration, void callback()) {
-  if (!identical(_ROOT_ZONE, zone)) {
-    callback = zone.bindCallback(callback);
-  }
-  return Timer._createTimer(duration, callback);
+  return new Timer._task(zone, duration, callback);
 }
 
 Timer _rootCreatePeriodicTimer(
     Zone self, ZoneDelegate parent, Zone zone,
     Duration duration, void callback(Timer timer)) {
-  if (!identical(_ROOT_ZONE, zone)) {
-    // TODO(floitsch): the return type should be 'void'.
-    callback = zone.bindUnaryCallback/*<dynamic, Timer>*/(callback);
-  }
-  return Timer._createPeriodicTimer(duration, callback);
+  return new Timer._periodicTask(zone, duration, callback);
 }
 
 void _rootPrint(Zone self, ZoneDelegate parent, Zone zone, String line) {
@@ -1082,10 +1353,10 @@
   _ZoneFunction<ScheduleMicrotaskHandler> get _scheduleMicrotask =>
       const _ZoneFunction<ScheduleMicrotaskHandler>(
           _ROOT_ZONE, _rootScheduleMicrotask);
-  _ZoneFunction<CreateTimerHandler> get _createTimer =>
-      const _ZoneFunction<CreateTimerHandler>(_ROOT_ZONE, _rootCreateTimer);
-  _ZoneFunction<CreatePeriodicTimerHandler> get _createPeriodicTimer =>
-      const _ZoneFunction<CreatePeriodicTimerHandler>(_ROOT_ZONE, _rootCreatePeriodicTimer);
+  _ZoneFunction<CreateTaskHandler> get _createTask =>
+      const _ZoneFunction<CreateTaskHandler>(_ROOT_ZONE, _rootCreateTask);
+  _ZoneFunction<RunTaskHandler> get _runTask =>
+      const _ZoneFunction<RunTaskHandler>(_ROOT_ZONE, _rootRunTask);
   _ZoneFunction<PrintHandler> get _print =>
       const _ZoneFunction<PrintHandler>(_ROOT_ZONE, _rootPrint);
   _ZoneFunction<ForkHandler> get _fork =>
@@ -1094,6 +1365,14 @@
       const _ZoneFunction<HandleUncaughtErrorHandler>(
           _ROOT_ZONE, _rootHandleUncaughtError);
 
+  // TODO(floitsch): deprecate once tasks are non-experimental.
+  _ZoneFunction<CreateTimerHandler> get _createTimer =>
+      const _ZoneFunction<CreateTimerHandler>(_ROOT_ZONE, _rootCreateTimer);
+  // TODO(floitsch): deprecate once tasks are non-experimental.
+  _ZoneFunction<CreatePeriodicTimerHandler> get _createPeriodicTimer =>
+      const _ZoneFunction<CreatePeriodicTimerHandler>(
+          _ROOT_ZONE, _rootCreatePeriodicTimer);
+
   // The parent zone.
   _Zone get parent => null;
 
@@ -1225,6 +1504,16 @@
     _rootScheduleMicrotask(null, null, this, f);
   }
 
+  Object/*=T*/ createTask/*<T, S extends TaskSpecification>*/(
+      TaskCreate/*<T, S>*/ create, TaskSpecification/*=S*/ specification) {
+    return _rootCreateTask/*<T, S>*/(null, null, this, create, specification);
+  }
+
+  void runTask/*<T, A>*/(
+      TaskRun/*<T, A>*/ run, Object/*=T*/ task, Object/*=A*/ arg) {
+    _rootRunTask/*<T, A>*/(null, null, this, run, task, arg);
+  }
+
   Timer createTimer(Duration duration, void f()) {
     return Timer._createTimer(duration, f);
   }
diff --git a/sdk/lib/html/dart2js/html_dart2js.dart b/sdk/lib/html/dart2js/html_dart2js.dart
index 0bc3749..e5eae90 100644
--- a/sdk/lib/html/dart2js/html_dart2js.dart
+++ b/sdk/lib/html/dart2js/html_dart2js.dart
@@ -19240,6 +19240,109 @@
 // BSD-style license that can be found in the LICENSE file.
 
 
+/**
+ * A task specification for HTTP requests.
+ *
+ * This specification is not available when an HTTP request is sent through
+ * direct use of [HttpRequest.send]. See [HttpRequestSendTaskSpecification].
+ *
+ * A task created from this specification is a `Future<HttpRequest>`.
+ *
+ * *Experimental*. This class may disappear without notice.
+ */
+class HttpRequestTaskSpecification extends TaskSpecification {
+  /// The URL of the request.
+  final String url;
+
+  /// The HTTP request method.
+  ///
+  /// By default (when `null`) this is a `"GET"` request. Alternatively, the
+  /// method can be `"POST"`, `"PUT"`, `"DELETE"`, etc.
+  final String method;
+
+  /// Whether the request should send credentials. Credentials are only useful
+  /// for cross-origin requests.
+  ///
+  /// See [HttpRequest.request] for more information.
+  final bool withCredentials;
+
+  /// The desired response format.
+  ///
+  /// Supported types are:
+  /// - `""`: (same as `"text"`),
+  /// - `"arraybuffer"`,
+  /// - `"blob"`,
+  /// - `"document"`,
+  /// - `"json"`,
+  /// - `"text"`
+  ///
+  /// When no value is provided (when equal to `null`) defaults to `""`.
+  final String responseType;
+
+  /// The desired MIME type.
+  ///
+  /// This overrides the default MIME type which is set up to transfer textual
+  /// data.
+  final String mimeType;
+
+  /// The request headers that should be sent with the request.
+  final Map<String, String> requestHeaders;
+
+  /// The data that is sent with the request.
+  ///
+  /// When data is provided (the value is not `null`), it must be a
+  /// [ByteBuffer], [Blob], [Document], [String], or [FormData].
+  final dynamic sendData;
+
+  /// The function that is invoked on progress updates. This function is
+  /// registered as an event listener on the created [HttpRequest] object, and
+  /// thus has its own task. Further invocations of the progress function do
+  /// *not* use the HTTP request task as task object.
+  ///
+  /// Creating an HTTP request automatically registers the on-progress listener.
+  final ZoneUnaryCallback<dynamic, ProgressEvent> onProgress;
+
+  HttpRequestTaskSpecification(this.url,
+      {String this.method, bool this.withCredentials, String this.responseType,
+      String this.mimeType, Map<String, String> this.requestHeaders,
+      this.sendData,
+      void this.onProgress(ProgressEvent e)});
+
+  String get name => "dart.html.http-request";
+  bool get isOneShot => true;
+}
+
+/**
+ * A task specification for HTTP requests that are initiated through a direct
+ * invocation of [HttpRequest.send].
+ *
+ * This specification serves as signal to zones that an HTTP request has been
+ * initiated. The created task is the [request] object itself, and
+ * no callback is ever executed in this task.
+ *
+ * Note that event listeners on the HTTP request are also registered in the
+ * zone (although with their own task creations), and that a zone can thus
+ * detect when the HTTP request returns.
+ *
+ * HTTP requests that are initiated through `request` methods don't use
+ * this class but use [HttpRequestTaskSpecification].
+ *
+ * *Experimental*. This class may disappear without notice.
+ */
+class HttpRequestSendTaskSpecification extends TaskSpecification {
+  final HttpRequest request;
+  final dynamic sendData;
+
+  HttpRequestSendTaskSpecification(this.request, this.sendData);
+
+  String get name => "dart.html.http-request-send";
+
+  /**
+   * No callback is ever executed in an HTTP request send task.
+   */
+  bool get isOneShot => false;
+}
+
  /**
   * A client-side XHR request for getting data from a URL,
   * formally known as XMLHttpRequest.
@@ -19428,7 +19531,34 @@
       {String method, bool withCredentials, String responseType,
       String mimeType, Map<String, String> requestHeaders, sendData,
       void onProgress(ProgressEvent e)}) {
+    var spec = new HttpRequestTaskSpecification(
+        url, method: method,
+        withCredentials: withCredentials,
+        responseType: responseType,
+        mimeType: mimeType,
+        requestHeaders: requestHeaders,
+        sendData: sendData,
+        onProgress: onProgress);
+
+    if (identical(Zone.current, Zone.ROOT)) {
+      return _createHttpRequestTask(spec, null);
+    }
+    return Zone.current.createTask(_createHttpRequestTask, spec);
+  }
+
+  static Future<HttpRequest> _createHttpRequestTask(
+      HttpRequestTaskSpecification spec, Zone zone) {
+    String url = spec.url;
+    String method = spec.method;
+    bool withCredentials = spec.withCredentials;
+    String responseType = spec.responseType;
+    String mimeType = spec.mimeType;
+    Map<String, String> requestHeaders = spec.requestHeaders;
+    var sendData = spec.sendData;
+    var onProgress = spec.onProgress;
+
     var completer = new Completer<HttpRequest>();
+    var task = completer.future;
 
     var xhr = new HttpRequest();
     if (method == null) {
@@ -19468,23 +19598,42 @@
       // redirect case will be handled by the browser before it gets to us,
       // so if we see it we should pass it through to the user.
       var unknownRedirect = xhr.status > 307 && xhr.status < 400;
-      
-      if (accepted || fileUri || notModified || unknownRedirect) {
+
+      var isSuccessful = accepted || fileUri || notModified || unknownRedirect;
+
+      if (zone == null && isSuccessful) {
         completer.complete(xhr);
-      } else {
+      } else if (zone == null) {
         completer.completeError(e);
+      } else if (isSuccessful) {
+        zone.runTask((task, value) {
+          completer.complete(value);
+        }, task, xhr);
+      } else {
+        zone.runTask((task, error) {
+          completer.completeError(error);
+        }, task, e);
       }
     });
 
-    xhr.onError.listen(completer.completeError);
-
-    if (sendData != null) {
-      xhr.send(sendData);
+    if (zone == null) {
+      xhr.onError.listen(completer.completeError);
     } else {
-      xhr.send();
+      xhr.onError.listen((error) {
+        zone.runTask((task, error) {
+          completer.completeError(error);
+        }, task, error);
+      });
     }
 
-    return completer.future;
+    if (sendData != null) {
+      // TODO(floitsch): should we go through 'send()' and have nested tasks?
+      xhr._send(sendData);
+    } else {
+      xhr._send();
+    }
+
+    return task;
   }
 
   /**
@@ -19538,6 +19687,9 @@
         return xhr.responseText;
       });
     }
+    // TODO(floitsch): the following code doesn't go through task zones.
+    // Since 'XDomainRequest' is an IE9 feature we should probably just remove
+    // it.
     var completer = new Completer<String>();
     if (method == null) {
       method = 'GET';
@@ -19616,13 +19768,43 @@
    *
    * Note: Most simple HTTP requests can be accomplished using the [getString],
    * [request], [requestCrossOrigin], or [postFormData] methods. Use of this
-   * `open` method is intended only for more complext HTTP requests where
+   * `open` method is intended only for more complex HTTP requests where
    * finer-grained control is needed.
    */
   @DomName('XMLHttpRequest.open')
   @DocsEditable()
   void open(String method, String url, {bool async, String user, String password}) native;
 
+  /**
+   * Sends the request with any given `data`.
+   *
+   * Note: Most simple HTTP requests can be accomplished using the [getString],
+   * [request], [requestCrossOrigin], or [postFormData] methods. Use of this
+   * `send` method is intended only for more complex HTTP requests where
+   * finer-grained control is needed.
+   *
+   * ## Other resources
+   *
+   * * [XMLHttpRequest.send](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#send%28%29)
+   *   from MDN.
+   */
+  @DomName('XMLHttpRequest.send')
+  @DocsEditable()
+  void send([body_OR_data]) {
+    if (identical(Zone.current, Zone.ROOT)) {
+      _send(body_OR_data);
+    } else {
+      Zone.current.createTask(_createHttpRequestSendTask,
+          new HttpRequestSendTaskSpecification(this, body_OR_data));
+    }
+  }
+
+  static HttpRequest _createHttpRequestSendTask(
+      HttpRequestSendTaskSpecification spec, Zone zone) {
+    spec.request._send(spec.sendData);
+    return spec.request;
+  }
+
   // To suppress missing implicit constructor warnings.
   factory HttpRequest._() { throw new UnsupportedError("Not supported"); }
 
@@ -19893,12 +20075,13 @@
   @SupportedBrowser(SupportedBrowser.SAFARI)
   void overrideMimeType(String mime) native;
 
+  @JSName('send')
   /**
    * Send the request with any given `data`.
    *
    * Note: Most simple HTTP requests can be accomplished using the [getString],
    * [request], [requestCrossOrigin], or [postFormData] methods. Use of this
-   * `send` method is intended only for more complext HTTP requests where
+   * `send` method is intended only for more complex HTTP requests where
    * finer-grained control is needed.
    *
    * ## Other resources
@@ -19908,7 +20091,7 @@
    */
   @DomName('XMLHttpRequest.send')
   @DocsEditable()
-  void send([body_OR_data]) native;
+  void _send([body_OR_data]) native;
 
   /**
    * Sets the value of an HTTP requst header.
@@ -34485,6 +34668,99 @@
 // BSD-style license that can be found in the LICENSE file.
 
 
+typedef void RemoveFrameRequestMapping(int id);
+
+/**
+ * The task object representing animation-frame requests.
+ *
+ * For historical reasons, [Window.requestAnimationFrame] returns an integer
+ * to users. However, zone tasks must be unique objects, and an integer can
+ * therefore not be used as task object. The [Window] class thus keeps a mapping
+ * from the integer ID to the corresponding task object. All zone related
+ * operations work on this task object, whereas users of
+ * [Window.requestAnimationFrame] only see the integer ID.
+ *
+ * Since this mapping takes up space, it must be removed when the
+ * animation-frame task has triggered. The default implementation does this
+ * automatically, but intercepting implementations of `requestAnimationFrame`
+ * must make sure to call the [AnimationFrameTask.removeMapping]
+ * function that is provided in the task specification.
+ *
+ * *Experimental*. This class may disappear without notice.
+ */
+abstract class AnimationFrameTask {
+  /** The ID that is returned to users. */
+  int get id;
+
+  /** The zone in which the task will run. */
+  Zone get zone;
+
+  /**
+   * Cancels the animation-frame request.
+   *
+   * A call to [Window.cancelAnimationFrame] with an `id` argument equal to [id]
+   * forwards the request to this function.
+   *
+   * Zones that intercept animation-frame requests implement this method so
+   * that they can react to cancelation requests.
+   */
+  void cancel(Window window);
+
+  /**
+   * Maps animation-frame request IDs to their task objects.
+   */
+  static final Map<int, _AnimationFrameTask> _tasks = {};
+
+  /**
+   * Removes the mapping from [id] to [AnimationFrameTask].
+   *
+   * This function must be invoked by user-implemented animation-frame
+   * tasks, before running [callback].
+   *
+   * See [AnimationFrameTask].
+   */
+  static void removeMapping(int id) {
+    _tasks.remove(id);
+  }
+}
+
+class _AnimationFrameTask implements AnimationFrameTask {
+  final int id;
+  final Zone zone;
+  final FrameRequestCallback _callback;
+
+  _AnimationFrameTask(this.id, this.zone, this._callback);
+
+  void cancel(Window window) {
+    window._cancelAnimationFrame(this.id);
+  }
+}
+
+/**
+ * The task specification for an animation-frame request.
+ *
+ * *Experimental*. This class may disappear without notice.
+ */
+class AnimationFrameRequestSpecification implements TaskSpecification {
+  /**
+   * The window on which [Window.requestAnimationFrame] was invoked.
+   */
+  final Window window;
+
+  /**
+   * The callback that is executed when the animation-frame is ready.
+   *
+   * Note that the callback hasn't been registered in any zone when the `create`
+   * function (passed to [Zone.createTask]) is invoked.
+   */
+  final FrameRequestCallback callback;
+
+  AnimationFrameRequestSpecification(this.window, this.callback);
+
+  String get name => "dart.html.request-animation-frame";
+  bool get isOneShot => true;
+}
+
 @DocsEditable()
 /**
  * Top-level container for the current browser tab or window.
@@ -34540,9 +34816,7 @@
    */
   Future<num> get animationFrame {
     var completer = new Completer<num>.sync();
-    requestAnimationFrame((time) {
-      completer.complete(time);
-    });
+    requestAnimationFrame(completer.complete);
     return completer.future;
   }
 
@@ -34625,7 +34899,30 @@
   @DomName('Window.requestAnimationFrame')
   int requestAnimationFrame(FrameRequestCallback callback) {
     _ensureRequestAnimationFrame();
-    return _requestAnimationFrame(_wrapZone/*<num, dynamic>*/(callback));
+    if (identical(Zone.current, Zone.ROOT)) {
+      return _requestAnimationFrame(callback);
+    }
+    var spec = new AnimationFrameRequestSpecification(this, callback);
+    var task = Zone.current.createTask/*<AnimationFrameTask>*/(
+        _createAnimationFrameTask, spec);
+    AnimationFrameTask._tasks[task.id] = task;
+    return task.id;
+  }
+
+  static _AnimationFrameTask _createAnimationFrameTask(
+      AnimationFrameRequestSpecification spec, Zone zone) {
+    var task;
+    var id = spec.window._requestAnimationFrame((num time) {
+      AnimationFrameTask.removeMapping(task.id);
+      zone.runTask(_runAnimationFrame, task, time);
+    });
+    var callback = zone.registerUnaryCallback(spec.callback);
+    task = new _AnimationFrameTask(id, zone, callback);
+    return task;
+  }
+
+  static void _runAnimationFrame(_AnimationFrameTask task, num time) {
+    task._callback(time);
   }
 
   /**
@@ -34638,7 +34935,13 @@
    */
   void cancelAnimationFrame(int id) {
     _ensureRequestAnimationFrame();
-    _cancelAnimationFrame(id);
+    var task = AnimationFrameTask._tasks.remove(id);
+    if (task == null) {
+      // Assume that the animation frame request wasn't intercepted by a zone.
+      _cancelAnimationFrame(id);
+      return;
+    }
+    task.cancel(this);
   }
 
   @JSName('requestAnimationFrame')
@@ -39964,6 +40267,41 @@
   StreamSubscription<T> capture(void onData(T event));
 }
 
+/// Task specification for DOM Events.
+///
+/// *Experimental*. May disappear without notice.
+class EventSubscriptionSpecification<T extends Event>
+    implements TaskSpecification {
+  @override
+  final String name;
+  @override
+  final bool isOneShot;
+
+  final EventTarget target;
+  /// The event-type of the event. For example 'click' for click events.
+  final String eventType;
+  // TODO(floitsch): the first generic argument should be 'void'.
+  final ZoneUnaryCallback<dynamic, T> onData;
+  final bool useCapture;
+
+  EventSubscriptionSpecification({this.name, this.isOneShot, this.target,
+      this.eventType, void this.onData(T event), this.useCapture});
+
+  /// Returns a copy of this instance, with every non-null argument replaced
+  /// by the given value.
+  EventSubscriptionSpecification<T> replace(
+      {String name, bool isOneShot, EventTarget target,
+       String eventType, void onData(T event), bool useCapture}) {
+    return new EventSubscriptionSpecification<T>(
+        name: name ?? this.name,
+        isOneShot: isOneShot ?? this.isOneShot,
+        target: target ?? this.target,
+        eventType: eventType ?? this.eventType,
+        onData: onData ?? this.onData,
+        useCapture: useCapture ?? this.useCapture);
+  }
+}
+
 /**
  * Adapter for exposing DOM events as Dart streams.
  */
@@ -39971,8 +40309,16 @@
   final EventTarget _target;
   final String _eventType;
   final bool _useCapture;
+  /// The name that is used in the task specification.
+  final String _name;
+  /// Whether the stream can trigger multiple times.
+  final bool _isOneShot;
 
-  _EventStream(this._target, this._eventType, this._useCapture);
+  _EventStream(this._target, String eventType, this._useCapture,
+      {String name, bool isOneShot: false})
+      : _eventType = eventType,
+        _isOneShot = isOneShot,
+        _name = name ?? "dart.html.event.$eventType";
 
   // DOM events are inherently multi-subscribers.
   Stream<T> asBroadcastStream({void onListen(StreamSubscription<T> subscription),
@@ -39980,13 +40326,31 @@
       => this;
   bool get isBroadcast => true;
 
+  StreamSubscription<T> _listen(
+      void onData(T event), {bool useCapture}) {
+
+    if (identical(Zone.current, Zone.ROOT)) {
+      return new _EventStreamSubscription<T>(
+          this._target, this._eventType, onData, this._useCapture,
+          Zone.current);
+    }
+
+    var specification = new EventSubscriptionSpecification<T>(
+        name: this._name, isOneShot: this._isOneShot,
+        target: this._target, eventType: this._eventType,
+        onData: onData, useCapture: useCapture);
+    // We need to wrap the _createStreamSubscription call, since a tear-off
+    // would not bind the generic type 'T'.
+    return Zone.current.createTask((spec, Zone zone) {
+      return _createStreamSubscription/*<T>*/(spec, zone);
+    }, specification);
+  }
+
   StreamSubscription<T> listen(void onData(T event),
       { Function onError,
         void onDone(),
         bool cancelOnError}) {
-
-    return new _EventStreamSubscription<T>(
-        this._target, this._eventType, onData, this._useCapture);
+    return _listen(onData, useCapture: this._useCapture);
   }
 }
 
@@ -40001,8 +40365,9 @@
  */
 class _ElementEventStreamImpl<T extends Event> extends _EventStream<T>
     implements ElementStream<T> {
-  _ElementEventStreamImpl(target, eventType, useCapture) :
-      super(target, eventType, useCapture);
+  _ElementEventStreamImpl(target, eventType, useCapture,
+      {String name, bool isOneShot: false}) :
+      super(target, eventType, useCapture, name: name, isOneShot: isOneShot);
 
   Stream<T> matches(String selector) => this.where(
       (event) => _matchesWithAncestors(event, selector)).map((e) {
@@ -40010,9 +40375,9 @@
         return e;
       });
 
-  StreamSubscription<T> capture(void onData(T event)) =>
-    new _EventStreamSubscription<T>(
-        this._target, this._eventType, onData, true);
+  StreamSubscription<T> capture(void onData(T event)) {
+    return _listen(onData, useCapture: true);
+  }
 }
 
 /**
@@ -40061,7 +40426,13 @@
   bool get isBroadcast => true;
 }
 
-// We would like this to just be EventListener<T> but that typdef cannot
+StreamSubscription/*<T>*/ _createStreamSubscription/*<T>*/(
+    EventSubscriptionSpecification/*<T>*/ spec, Zone zone) {
+  return new _EventStreamSubscription/*<T>*/(spec.target, spec.eventType,
+      spec.onData, spec.useCapture, zone);
+}
+
+// We would like this to just be EventListener<T> but that typedef cannot
 // use generics until dartbug/26276 is fixed.
 typedef _EventListener<T extends Event>(T event);
 
@@ -40070,15 +40441,19 @@
   EventTarget _target;
   final String _eventType;
   EventListener _onData;
+  EventListener _domCallback;
   final bool _useCapture;
+  final Zone _zone;
 
   // TODO(jacobr): for full strong mode correctness we should write
-  // _onData = onData == null ? null : _wrapZone/*<Event, dynamic>*/((e) => onData(e as T))
+  // _onData = onData == null ? null : _wrapZone/*<dynamic, Event>*/((e) => onData(e as T))
   // but that breaks 114 co19 tests as well as multiple html tests as it is reasonable
   // to pass the wrong type of event object to an event listener as part of a
   // test.
   _EventStreamSubscription(this._target, this._eventType, void onData(T event),
-      this._useCapture) : _onData = _wrapZone/*<Event, dynamic>*/(onData) {
+      this._useCapture, Zone zone)
+      : _zone = zone,
+        _onData = _registerZone/*<dynamic, Event>*/(zone, onData) {
     _tryResume();
   }
 
@@ -40100,7 +40475,7 @@
     }
     // Remove current event listener.
     _unlisten();
-    _onData = _wrapZone/*<Event, dynamic>*/(handleData);
+    _onData = _registerZone/*<dynamic, Event>*/(_zone, handleData);
     _tryResume();
   }
 
@@ -40129,14 +40504,25 @@
   }
 
   void _tryResume() {
-    if (_onData != null && !isPaused) {
-      _target.addEventListener(_eventType, _onData, _useCapture);
+    if (_onData == null || isPaused) return;
+    if (identical(_zone, Zone.ROOT)) {
+      _domCallback = _onData;
+    } else {
+      _domCallback = (event) {
+        _zone.runTask(_runEventNotification, this, event);
+      };
     }
+    _target.addEventListener(_eventType, _domCallback, _useCapture);
+  }
+
+  static void _runEventNotification/*<T>*/(
+      _EventStreamSubscription/*<T>*/ subscription, /*=T*/ event) {
+    subscription._onData(event);
   }
 
   void _unlisten() {
     if (_onData != null) {
-      _target.removeEventListener(_eventType, _onData, _useCapture);
+      _target.removeEventListener(_eventType, _domCallback, _useCapture);
     }
   }
 
@@ -43370,31 +43756,26 @@
 // BSD-style license that can be found in the LICENSE file.
 
 
-// TODO(jacobr): remove these typedefs when dart:async supports generic types.
-typedef R _wrapZoneCallback<A, R>(A a);
-typedef R _wrapZoneBinaryCallback<A, B, R>(A a, B b);
-
-_wrapZoneCallback/*<A, R>*/ _wrapZone/*<A, R>*/(_wrapZoneCallback/*<A, R>*/ callback) {
-  // For performance reasons avoid wrapping if we are in the root zone.
-  if (Zone.current == Zone.ROOT) return callback;
+ZoneUnaryCallback/*<R, T>*/ _registerZone/*<R, T>*/(Zone zone,
+    ZoneUnaryCallback/*<R, T>*/ callback) {
+  // For performance reasons avoid registering if we are in the root zone.
+  if (identical(zone, Zone.ROOT)) return callback;
   if (callback == null) return null;
-  // TODO(jacobr): we cast to _wrapZoneCallback/*<A, R>*/ to hack around missing
-  // generic method support in zones.
-  // ignore: STRONG_MODE_DOWN_CAST_COMPOSITE
-  _wrapZoneCallback/*<A, R>*/ wrapped =
-      Zone.current.bindUnaryCallback(callback, runGuarded: true);
-  return wrapped;
+  return zone.registerUnaryCallback(callback);
 }
 
-_wrapZoneBinaryCallback/*<A, B, R>*/ _wrapBinaryZone/*<A, B, R>*/(_wrapZoneBinaryCallback/*<A, B, R>*/ callback) {
-  if (Zone.current == Zone.ROOT) return callback;
+ZoneUnaryCallback/*<R, T>*/ _wrapZone/*<R, T>*/(ZoneUnaryCallback/*<R, T>*/ callback) {
+  // For performance reasons avoid wrapping if we are in the root zone.
+  if (identical(Zone.current, Zone.ROOT)) return callback;
   if (callback == null) return null;
-  // We cast to _wrapZoneBinaryCallback/*<A, B, R>*/ to hack around missing
-  // generic method support in zones.
-  // ignore: STRONG_MODE_DOWN_CAST_COMPOSITE
-  _wrapZoneBinaryCallback/*<A, B, R>*/ wrapped =
-      Zone.current.bindBinaryCallback(callback, runGuarded: true);
-  return wrapped;
+  return Zone.current.bindUnaryCallback(callback, runGuarded: true);
+}
+
+ZoneBinaryCallback/*<R, A, B>*/ _wrapBinaryZone/*<R, A, B>*/(
+    ZoneBinaryCallback/*<R, A, B>*/ callback) {
+  if (identical(Zone.current, Zone.ROOT)) return callback;
+  if (callback == null) return null;
+  return Zone.current.bindBinaryCallback(callback, runGuarded: true);
 }
 
 /**
diff --git a/sdk/lib/html/dartium/html_dartium.dart b/sdk/lib/html/dartium/html_dartium.dart
index bc92d6d..35b620a 100644
--- a/sdk/lib/html/dartium/html_dartium.dart
+++ b/sdk/lib/html/dartium/html_dartium.dart
@@ -20997,6 +20997,109 @@
 // BSD-style license that can be found in the LICENSE file.
 
 
+/**
+ * A task specification for HTTP requests.
+ *
+ * This specification is not available when an HTTP request is sent through
+ * direct use of [HttpRequest.send]. See [HttpRequestSendTaskSpecification].
+ *
+ * A task created from this specification is a `Future<HttpRequest>`.
+ *
+ * *Experimental*. This class may disappear without notice.
+ */
+class HttpRequestTaskSpecification extends TaskSpecification {
+  /// The URL of the request.
+  final String url;
+
+  /// The HTTP request method.
+  ///
+  /// By default (when `null`) this is a `"GET"` request. Alternatively, the
+  /// method can be `"POST"`, `"PUT"`, `"DELETE"`, etc.
+  final String method;
+
+  /// Whether the request should send credentials. Credentials are only useful
+  /// for cross-origin requests.
+  ///
+  /// See [HttpRequest.request] for more information.
+  final bool withCredentials;
+
+  /// The desired response format.
+  ///
+  /// Supported types are:
+  /// - `""`: (same as `"text"`),
+  /// - `"arraybuffer"`,
+  /// - `"blob"`,
+  /// - `"document"`,
+  /// - `"json"`,
+  /// - `"text"`
+  ///
+  /// When no value is provided (when equal to `null`) defaults to `""`.
+  final String responseType;
+
+  /// The desired MIME type.
+  ///
+  /// This overrides the default MIME type which is set up to transfer textual
+  /// data.
+  final String mimeType;
+
+  /// The request headers that should be sent with the request.
+  final Map<String, String> requestHeaders;
+
+  /// The data that is sent with the request.
+  ///
+  /// When data is provided (the value is not `null`), it must be a
+  /// [ByteBuffer], [Blob], [Document], [String], or [FormData].
+  final dynamic sendData;
+
+  /// The function that is invoked on progress updates. This function is
+  /// registered as an event listener on the created [HttpRequest] object, and
+  /// thus has its own task. Further invocations of the progress function do
+  /// *not* use the HTTP request task as task object.
+  ///
+  /// Creating an HTTP request automatically registers the on-progress listener.
+  final ZoneUnaryCallback<dynamic, ProgressEvent> onProgress;
+
+  HttpRequestTaskSpecification(this.url,
+      {String this.method, bool this.withCredentials, String this.responseType,
+      String this.mimeType, Map<String, String> this.requestHeaders,
+      this.sendData,
+      void this.onProgress(ProgressEvent e)});
+
+  String get name => "dart.html.http-request";
+  bool get isOneShot => true;
+}
+
+/**
+ * A task specification for HTTP requests that are initiated through a direct
+ * invocation of [HttpRequest.send].
+ *
+ * This specification serves as signal to zones that an HTTP request has been
+ * initiated. The created task is the [request] object itself, and
+ * no callback is ever executed in this task.
+ *
+ * Note that event listeners on the HTTP request are also registered in the
+ * zone (although with their own task creations), and that a zone can thus
+ * detect when the HTTP request returns.
+ *
+ * HTTP requests that are initiated through `request` methods don't use
+ * this class but use [HttpRequestTaskSpecification].
+ *
+ * *Experimental*. This class may disappear without notice.
+ */
+class HttpRequestSendTaskSpecification extends TaskSpecification {
+  final HttpRequest request;
+  final dynamic sendData;
+
+  HttpRequestSendTaskSpecification(this.request, this.sendData);
+
+  String get name => "dart.html.http-request-send";
+
+  /**
+   * No callback is ever executed in an HTTP request send task.
+   */
+  bool get isOneShot => false;
+}
+
  /**
   * A client-side XHR request for getting data from a URL,
   * formally known as XMLHttpRequest.
@@ -21184,7 +21287,34 @@
       {String method, bool withCredentials, String responseType,
       String mimeType, Map<String, String> requestHeaders, sendData,
       void onProgress(ProgressEvent e)}) {
+    var spec = new HttpRequestTaskSpecification(
+        url, method: method,
+        withCredentials: withCredentials,
+        responseType: responseType,
+        mimeType: mimeType,
+        requestHeaders: requestHeaders,
+        sendData: sendData,
+        onProgress: onProgress);
+
+    if (identical(Zone.current, Zone.ROOT)) {
+      return _createHttpRequestTask(spec, null);
+    }
+    return Zone.current.createTask(_createHttpRequestTask, spec);
+  }
+
+  static Future<HttpRequest> _createHttpRequestTask(
+      HttpRequestTaskSpecification spec, Zone zone) {
+    String url = spec.url;
+    String method = spec.method;
+    bool withCredentials = spec.withCredentials;
+    String responseType = spec.responseType;
+    String mimeType = spec.mimeType;
+    Map<String, String> requestHeaders = spec.requestHeaders;
+    var sendData = spec.sendData;
+    var onProgress = spec.onProgress;
+
     var completer = new Completer<HttpRequest>();
+    var task = completer.future;
 
     var xhr = new HttpRequest();
     if (method == null) {
@@ -21224,23 +21354,42 @@
       // redirect case will be handled by the browser before it gets to us,
       // so if we see it we should pass it through to the user.
       var unknownRedirect = xhr.status > 307 && xhr.status < 400;
-      
-      if (accepted || fileUri || notModified || unknownRedirect) {
+
+      var isSuccessful = accepted || fileUri || notModified || unknownRedirect;
+
+      if (zone == null && isSuccessful) {
         completer.complete(xhr);
-      } else {
+      } else if (zone == null) {
         completer.completeError(e);
+      } else if (isSuccessful) {
+        zone.runTask((task, value) {
+          completer.complete(value);
+        }, task, xhr);
+      } else {
+        zone.runTask((task, error) {
+          completer.completeError(error);
+        }, task, e);
       }
     });
 
-    xhr.onError.listen(completer.completeError);
-
-    if (sendData != null) {
-      xhr.send(sendData);
+    if (zone == null) {
+      xhr.onError.listen(completer.completeError);
     } else {
-      xhr.send();
+      xhr.onError.listen((error) {
+        zone.runTask((task, error) {
+          completer.completeError(error);
+        }, task, error);
+      });
     }
 
-    return completer.future;
+    if (sendData != null) {
+      // TODO(floitsch): should we go through 'send()' and have nested tasks?
+      xhr._send(sendData);
+    } else {
+      xhr._send();
+    }
+
+    return task;
   }
 
   /**
@@ -21290,6 +21439,9 @@
         return xhr.responseText;
       });
     }
+    // TODO(floitsch): the following code doesn't go through task zones.
+    // Since 'XDomainRequest' is an IE9 feature we should probably just remove
+    // it.
   }
 
   /**
@@ -21340,7 +21492,7 @@
    *
    * Note: Most simple HTTP requests can be accomplished using the [getString],
    * [request], [requestCrossOrigin], or [postFormData] methods. Use of this
-   * `open` method is intended only for more complext HTTP requests where
+   * `open` method is intended only for more complex HTTP requests where
    * finer-grained control is needed.
    */
   @DomName('XMLHttpRequest.open')
@@ -21353,6 +21505,36 @@
     }
   }
 
+  /**
+   * Sends the request with any given `data`.
+   *
+   * Note: Most simple HTTP requests can be accomplished using the [getString],
+   * [request], [requestCrossOrigin], or [postFormData] methods. Use of this
+   * `send` method is intended only for more complex HTTP requests where
+   * finer-grained control is needed.
+   *
+   * ## Other resources
+   *
+   * * [XMLHttpRequest.send](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#send%28%29)
+   *   from MDN.
+   */
+  @DomName('XMLHttpRequest.send')
+  @DocsEditable()
+  void send([body_OR_data]) {
+    if (identical(Zone.current, Zone.ROOT)) {
+      _send(body_OR_data);
+    } else {
+      Zone.current.createTask(_createHttpRequestSendTask,
+          new HttpRequestSendTaskSpecification(this, body_OR_data));
+    }
+  }
+
+  static HttpRequest _createHttpRequestSendTask(
+      HttpRequestSendTaskSpecification spec, Zone zone) {
+    spec.request._send(spec.sendData);
+    return spec.request;
+  }
+
   // To suppress missing implicit constructor warnings.
   factory HttpRequest._() { throw new UnsupportedError("Not supported"); }
 
@@ -21656,7 +21838,7 @@
   @SupportedBrowser(SupportedBrowser.SAFARI)
   void overrideMimeType(String mime) => _blink.BlinkXMLHttpRequest.instance.overrideMimeType_Callback_1_(this, mime);
   
-  void send([body_OR_data]) {
+  void _send([body_OR_data]) {
     if (body_OR_data != null) {
       _blink.BlinkXMLHttpRequest.instance.send_Callback_1_(this, body_OR_data);
       return;
@@ -37662,10 +37844,10 @@
     if ((blob_OR_source_OR_stream is Blob || blob_OR_source_OR_stream == null)) {
       return _blink.BlinkURL.instance.createObjectURL_Callback_1_(blob_OR_source_OR_stream);
     }
-    if ((blob_OR_source_OR_stream is MediaStream)) {
+    if ((blob_OR_source_OR_stream is MediaSource)) {
       return _blink.BlinkURL.instance.createObjectURL_Callback_1_(blob_OR_source_OR_stream);
     }
-    if ((blob_OR_source_OR_stream is MediaSource)) {
+    if ((blob_OR_source_OR_stream is MediaStream)) {
       return _blink.BlinkURL.instance.createObjectURL_Callback_1_(blob_OR_source_OR_stream);
     }
     throw new ArgumentError("Incorrect number or type of arguments");
@@ -39131,6 +39313,99 @@
 // BSD-style license that can be found in the LICENSE file.
 
 
+typedef void RemoveFrameRequestMapping(int id);
+
+/**
+ * The task object representing animation-frame requests.
+ *
+ * For historical reasons, [Window.requestAnimationFrame] returns an integer
+ * to users. However, zone tasks must be unique objects, and an integer can
+ * therefore not be used as task object. The [Window] class thus keeps a mapping
+ * from the integer ID to the corresponding task object. All zone related
+ * operations work on this task object, whereas users of
+ * [Window.requestAnimationFrame] only see the integer ID.
+ *
+ * Since this mapping takes up space, it must be removed when the
+ * animation-frame task has triggered. The default implementation does this
+ * automatically, but intercepting implementations of `requestAnimationFrame`
+ * must make sure to call the [AnimationFrameTask.removeMapping]
+ * function that is provided in the task specification.
+ *
+ * *Experimental*. This class may disappear without notice.
+ */
+abstract class AnimationFrameTask {
+  /** The ID that is returned to users. */
+  int get id;
+
+  /** The zone in which the task will run. */
+  Zone get zone;
+
+  /**
+   * Cancels the animation-frame request.
+   *
+   * A call to [Window.cancelAnimationFrame] with an `id` argument equal to [id]
+   * forwards the request to this function.
+   *
+   * Zones that intercept animation-frame requests implement this method so
+   * that they can react to cancelation requests.
+   */
+  void cancel(Window window);
+
+  /**
+   * Maps animation-frame request IDs to their task objects.
+   */
+  static final Map<int, _AnimationFrameTask> _tasks = {};
+
+  /**
+   * Removes the mapping from [id] to [AnimationFrameTask].
+   *
+   * This function must be invoked by user-implemented animation-frame
+   * tasks, before running [callback].
+   *
+   * See [AnimationFrameTask].
+   */
+  static void removeMapping(int id) {
+    _tasks.remove(id);
+  }
+}
+
+class _AnimationFrameTask implements AnimationFrameTask {
+  final int id;
+  final Zone zone;
+  final FrameRequestCallback _callback;
+
+  _AnimationFrameTask(this.id, this.zone, this._callback);
+
+  void cancel(Window window) {
+    window._cancelAnimationFrame(this.id);
+  }
+}
+
+/**
+ * The task specification for an animation-frame request.
+ *
+ * *Experimental*. This class may disappear without notice.
+ */
+class AnimationFrameRequestSpecification implements TaskSpecification {
+  /**
+   * The window on which [Window.requestAnimationFrame] was invoked.
+   */
+  final Window window;
+
+  /**
+   * The callback that is executed when the animation-frame is ready.
+   *
+   * Note that the callback hasn't been registered in any zone when the `create`
+   * function (passed to [Zone.createTask]) is invoked.
+   */
+  final FrameRequestCallback callback;
+
+  AnimationFrameRequestSpecification(this.window, this.callback);
+
+  String get name => "dart.html.request-animation-frame";
+  bool get isOneShot => true;
+}
+
 @DocsEditable()
 /**
  * Top-level container for the current browser tab or window.
@@ -39185,9 +39460,7 @@
    */
   Future<num> get animationFrame {
     var completer = new Completer<num>.sync();
-    requestAnimationFrame((time) {
-      completer.complete(time);
-    });
+    requestAnimationFrame(completer.complete);
     return completer.future;
   }
 
@@ -44492,6 +44765,41 @@
   StreamSubscription<T> capture(void onData(T event));
 }
 
+/// Task specification for DOM Events.
+///
+/// *Experimental*. May disappear without notice.
+class EventSubscriptionSpecification<T extends Event>
+    implements TaskSpecification {
+  @override
+  final String name;
+  @override
+  final bool isOneShot;
+
+  final EventTarget target;
+  /// The event-type of the event. For example 'click' for click events.
+  final String eventType;
+  // TODO(floitsch): the first generic argument should be 'void'.
+  final ZoneUnaryCallback<dynamic, T> onData;
+  final bool useCapture;
+
+  EventSubscriptionSpecification({this.name, this.isOneShot, this.target,
+      this.eventType, void this.onData(T event), this.useCapture});
+
+  /// Returns a copy of this instance, with every non-null argument replaced
+  /// by the given value.
+  EventSubscriptionSpecification<T> replace(
+      {String name, bool isOneShot, EventTarget target,
+       String eventType, void onData(T event), bool useCapture}) {
+    return new EventSubscriptionSpecification<T>(
+        name: name ?? this.name,
+        isOneShot: isOneShot ?? this.isOneShot,
+        target: target ?? this.target,
+        eventType: eventType ?? this.eventType,
+        onData: onData ?? this.onData,
+        useCapture: useCapture ?? this.useCapture);
+  }
+}
+
 /**
  * Adapter for exposing DOM events as Dart streams.
  */
@@ -44499,8 +44807,16 @@
   final EventTarget _target;
   final String _eventType;
   final bool _useCapture;
+  /// The name that is used in the task specification.
+  final String _name;
+  /// Whether the stream can trigger multiple times.
+  final bool _isOneShot;
 
-  _EventStream(this._target, this._eventType, this._useCapture);
+  _EventStream(this._target, String eventType, this._useCapture,
+      {String name, bool isOneShot: false})
+      : _eventType = eventType,
+        _isOneShot = isOneShot,
+        _name = name ?? "dart.html.event.$eventType";
 
   // DOM events are inherently multi-subscribers.
   Stream<T> asBroadcastStream({void onListen(StreamSubscription<T> subscription),
@@ -44508,13 +44824,31 @@
       => this;
   bool get isBroadcast => true;
 
+  StreamSubscription<T> _listen(
+      void onData(T event), {bool useCapture}) {
+
+    if (identical(Zone.current, Zone.ROOT)) {
+      return new _EventStreamSubscription<T>(
+          this._target, this._eventType, onData, this._useCapture,
+          Zone.current);
+    }
+
+    var specification = new EventSubscriptionSpecification<T>(
+        name: this._name, isOneShot: this._isOneShot,
+        target: this._target, eventType: this._eventType,
+        onData: onData, useCapture: useCapture);
+    // We need to wrap the _createStreamSubscription call, since a tear-off
+    // would not bind the generic type 'T'.
+    return Zone.current.createTask((spec, Zone zone) {
+      return _createStreamSubscription/*<T>*/(spec, zone);
+    }, specification);
+  }
+
   StreamSubscription<T> listen(void onData(T event),
       { Function onError,
         void onDone(),
         bool cancelOnError}) {
-
-    return new _EventStreamSubscription<T>(
-        this._target, this._eventType, onData, this._useCapture);
+    return _listen(onData, useCapture: this._useCapture);
   }
 }
 
@@ -44529,8 +44863,9 @@
  */
 class _ElementEventStreamImpl<T extends Event> extends _EventStream<T>
     implements ElementStream<T> {
-  _ElementEventStreamImpl(target, eventType, useCapture) :
-      super(target, eventType, useCapture);
+  _ElementEventStreamImpl(target, eventType, useCapture,
+      {String name, bool isOneShot: false}) :
+      super(target, eventType, useCapture, name: name, isOneShot: isOneShot);
 
   Stream<T> matches(String selector) => this.where(
       (event) => _matchesWithAncestors(event, selector)).map((e) {
@@ -44538,9 +44873,9 @@
         return e;
       });
 
-  StreamSubscription<T> capture(void onData(T event)) =>
-    new _EventStreamSubscription<T>(
-        this._target, this._eventType, onData, true);
+  StreamSubscription<T> capture(void onData(T event)) {
+    return _listen(onData, useCapture: true);
+  }
 }
 
 /**
@@ -44589,7 +44924,13 @@
   bool get isBroadcast => true;
 }
 
-// We would like this to just be EventListener<T> but that typdef cannot
+StreamSubscription/*<T>*/ _createStreamSubscription/*<T>*/(
+    EventSubscriptionSpecification/*<T>*/ spec, Zone zone) {
+  return new _EventStreamSubscription/*<T>*/(spec.target, spec.eventType,
+      spec.onData, spec.useCapture, zone);
+}
+
+// We would like this to just be EventListener<T> but that typedef cannot
 // use generics until dartbug/26276 is fixed.
 typedef _EventListener<T extends Event>(T event);
 
@@ -44598,15 +44939,19 @@
   EventTarget _target;
   final String _eventType;
   EventListener _onData;
+  EventListener _domCallback;
   final bool _useCapture;
+  final Zone _zone;
 
   // TODO(jacobr): for full strong mode correctness we should write
-  // _onData = onData == null ? null : _wrapZone/*<Event, dynamic>*/((e) => onData(e as T))
+  // _onData = onData == null ? null : _wrapZone/*<dynamic, Event>*/((e) => onData(e as T))
   // but that breaks 114 co19 tests as well as multiple html tests as it is reasonable
   // to pass the wrong type of event object to an event listener as part of a
   // test.
   _EventStreamSubscription(this._target, this._eventType, void onData(T event),
-      this._useCapture) : _onData = _wrapZone/*<Event, dynamic>*/(onData) {
+      this._useCapture, Zone zone)
+      : _zone = zone,
+        _onData = _registerZone/*<dynamic, Event>*/(zone, onData) {
     _tryResume();
   }
 
@@ -44628,7 +44973,7 @@
     }
     // Remove current event listener.
     _unlisten();
-    _onData = _wrapZone/*<Event, dynamic>*/(handleData);
+    _onData = _registerZone/*<dynamic, Event>*/(_zone, handleData);
     _tryResume();
   }
 
@@ -44657,14 +45002,25 @@
   }
 
   void _tryResume() {
-    if (_onData != null && !isPaused) {
-      _target.addEventListener(_eventType, _onData, _useCapture);
+    if (_onData == null || isPaused) return;
+    if (identical(_zone, Zone.ROOT)) {
+      _domCallback = _onData;
+    } else {
+      _domCallback = (event) {
+        _zone.runTask(_runEventNotification, this, event);
+      };
     }
+    _target.addEventListener(_eventType, _domCallback, _useCapture);
+  }
+
+  static void _runEventNotification/*<T>*/(
+      _EventStreamSubscription/*<T>*/ subscription, /*=T*/ event) {
+    subscription._onData(event);
   }
 
   void _unlisten() {
     if (_onData != null) {
-      _target.removeEventListener(_eventType, _onData, _useCapture);
+      _target.removeEventListener(_eventType, _domCallback, _useCapture);
     }
   }
 
@@ -47856,31 +48212,26 @@
 // BSD-style license that can be found in the LICENSE file.
 
 
-// TODO(jacobr): remove these typedefs when dart:async supports generic types.
-typedef R _wrapZoneCallback<A, R>(A a);
-typedef R _wrapZoneBinaryCallback<A, B, R>(A a, B b);
-
-_wrapZoneCallback/*<A, R>*/ _wrapZone/*<A, R>*/(_wrapZoneCallback/*<A, R>*/ callback) {
-  // For performance reasons avoid wrapping if we are in the root zone.
-  if (Zone.current == Zone.ROOT) return callback;
+ZoneUnaryCallback/*<R, T>*/ _registerZone/*<R, T>*/(Zone zone,
+    ZoneUnaryCallback/*<R, T>*/ callback) {
+  // For performance reasons avoid registering if we are in the root zone.
+  if (identical(zone, Zone.ROOT)) return callback;
   if (callback == null) return null;
-  // TODO(jacobr): we cast to _wrapZoneCallback/*<A, R>*/ to hack around missing
-  // generic method support in zones.
-  // ignore: STRONG_MODE_DOWN_CAST_COMPOSITE
-  _wrapZoneCallback/*<A, R>*/ wrapped =
-      Zone.current.bindUnaryCallback(callback, runGuarded: true);
-  return wrapped;
+  return zone.registerUnaryCallback(callback);
 }
 
-_wrapZoneBinaryCallback/*<A, B, R>*/ _wrapBinaryZone/*<A, B, R>*/(_wrapZoneBinaryCallback/*<A, B, R>*/ callback) {
-  if (Zone.current == Zone.ROOT) return callback;
+ZoneUnaryCallback/*<R, T>*/ _wrapZone/*<R, T>*/(ZoneUnaryCallback/*<R, T>*/ callback) {
+  // For performance reasons avoid wrapping if we are in the root zone.
+  if (identical(Zone.current, Zone.ROOT)) return callback;
   if (callback == null) return null;
-  // We cast to _wrapZoneBinaryCallback/*<A, B, R>*/ to hack around missing
-  // generic method support in zones.
-  // ignore: STRONG_MODE_DOWN_CAST_COMPOSITE
-  _wrapZoneBinaryCallback/*<A, B, R>*/ wrapped =
-      Zone.current.bindBinaryCallback(callback, runGuarded: true);
-  return wrapped;
+  return Zone.current.bindUnaryCallback(callback, runGuarded: true);
+}
+
+ZoneBinaryCallback/*<R, A, B>*/ _wrapBinaryZone/*<R, A, B>*/(
+    ZoneBinaryCallback/*<R, A, B>*/ callback) {
+  if (identical(Zone.current, Zone.ROOT)) return callback;
+  if (callback == null) return null;
+  return Zone.current.bindBinaryCallback(callback, runGuarded: true);
 }
 
 /**
diff --git a/tests/html/event_subscription_specification_test.dart b/tests/html/event_subscription_specification_test.dart
new file mode 100644
index 0000000..406ed37
--- /dev/null
+++ b/tests/html/event_subscription_specification_test.dart
@@ -0,0 +1,121 @@
+// Copyright (c) 2016, 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.
+
+library EventTaskZoneTest;
+
+import 'package:unittest/unittest.dart';
+import 'package:unittest/html_config.dart';
+import 'dart:async';
+import 'dart:html';
+
+// Tests event-subscription specifications.
+
+main() {
+  useHtmlConfiguration();
+
+  var defaultTarget = new Element.div();
+  var defaultOnData = (x) => null;
+
+  EventSubscriptionSpecification createSpec({useCapture, isOneShot}) {
+    return new EventSubscriptionSpecification(
+        name: "name",
+        target: defaultTarget,
+        useCapture: useCapture,
+        isOneShot: isOneShot,
+        onData: defaultOnData,
+        eventType: "eventType");
+  }
+
+  for (var useCapture in [true, false]) {
+    for (var isOneShot in [true, false]) {
+      var spec = createSpec(useCapture: useCapture, isOneShot: isOneShot);
+
+      test(
+          "EventSubscriptionSpecification - constructor "
+          "useCapture: $useCapture isOneShot: $isOneShot", () {
+        var replaced = spec.replace(eventType: 'replace-eventType');
+        expect(replaced.name, "name");
+        expect(replaced.target, defaultTarget);
+        expect(replaced.useCapture, useCapture);
+        expect(replaced.isOneShot, isOneShot);
+        expect(replaced.onData, equals(defaultOnData));
+        expect(replaced.eventType, "replace-eventType");
+      });
+
+      test(
+          "replace name "
+          "useCapture: $useCapture isOneShot: $isOneShot", () {
+        var replaced = spec.replace(name: 'replace-name');
+        expect(replaced.name, "replace-name");
+        expect(replaced.target, defaultTarget);
+        expect(replaced.useCapture, useCapture);
+        expect(replaced.isOneShot, isOneShot);
+        expect(replaced.onData, equals(defaultOnData));
+        expect(replaced.eventType, "eventType");
+      });
+
+      test(
+          "replace target "
+          "useCapture: $useCapture isOneShot: $isOneShot", () {
+        var replacementTarget = new Element.a();
+        var replaced = spec.replace(target: replacementTarget);
+        expect(replaced.name, "name");
+        expect(replaced.target, replacementTarget);
+        expect(replaced.useCapture, useCapture);
+        expect(replaced.isOneShot, isOneShot);
+        expect(replaced.onData, equals(defaultOnData));
+        expect(replaced.eventType, "eventType");
+      });
+
+      test(
+          "replace useCapture "
+              "useCapture: $useCapture isOneShot: $isOneShot", () {
+        var replaced = spec.replace(useCapture: !useCapture);
+        expect(replaced.name, "name");
+        expect(replaced.target, defaultTarget);
+        expect(replaced.useCapture, !useCapture);
+        expect(replaced.isOneShot, isOneShot);
+        expect(replaced.onData, equals(defaultOnData));
+        expect(replaced.eventType, "eventType");
+      });
+
+      test(
+          "replace isOneShot "
+              "useCapture: $useCapture isOneShot: $isOneShot", () {
+        var replaced = spec.replace(isOneShot: !isOneShot);
+        expect(replaced.name, "name");
+        expect(replaced.target, defaultTarget);
+        expect(replaced.useCapture, useCapture);
+        expect(replaced.isOneShot, !isOneShot);
+        expect(replaced.onData, equals(defaultOnData));
+        expect(replaced.eventType, "eventType");
+      });
+
+      test(
+          "replace onData "
+              "useCapture: $useCapture isOneShot: $isOneShot", () {
+        var replacementOnData = (x) {};
+        var replaced = spec.replace(onData: replacementOnData);
+        expect(replaced.name, "name");
+        expect(replaced.target, defaultTarget);
+        expect(replaced.useCapture, useCapture);
+        expect(replaced.isOneShot, isOneShot);
+        expect(replaced.onData, equals(replacementOnData));
+        expect(replaced.eventType, "eventType");
+      });
+
+      test(
+          "replace eventType "
+          "useCapture: $useCapture isOneShot: $isOneShot", () {
+        var replaced = spec.replace(eventType: 'replace-eventType');
+        expect(replaced.name, "name");
+        expect(replaced.target, defaultTarget);
+        expect(replaced.useCapture, useCapture);
+        expect(replaced.isOneShot, isOneShot);
+        expect(replaced.onData, equals(defaultOnData));
+        expect(replaced.eventType, "replace-eventType");
+      });
+    }
+  }
+}
diff --git a/tests/html/event_zone_task_test.dart b/tests/html/event_zone_task_test.dart
new file mode 100644
index 0000000..a0e85c8
--- /dev/null
+++ b/tests/html/event_zone_task_test.dart
@@ -0,0 +1,239 @@
+// Copyright (c) 2016, 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.
+
+library EventTaskZoneTest;
+
+import 'package:unittest/unittest.dart';
+import 'package:unittest/html_config.dart';
+import 'dart:async';
+import 'dart:html';
+
+// Tests zone tasks with DOM events.
+
+class AbortedEventStreamSubscription implements StreamSubscription<Event> {
+  final Zone zone;
+
+  AbortedEventStreamSubscription(this.zone);
+
+  @override
+  Future asFuture([futureValue]) {
+    throw new UnsupportedError("asFuture");
+  }
+
+  @override
+  Future cancel() {
+    return null;
+  }
+
+  @override
+  bool get isPaused => throw new UnsupportedError("pause");
+
+  @override
+  void onData(void handleData(Event data)) {
+    throw new UnsupportedError("cancel");
+  }
+
+  @override
+  void onDone(void handleDone()) {
+    throw new UnsupportedError("onDone");
+  }
+
+  @override
+  void onError(Function handleError) {
+    throw new UnsupportedError("onError");
+  }
+
+  @override
+  void pause([Future resumeSignal]) {
+    throw new UnsupportedError("pause");
+  }
+
+  @override
+  void resume() {
+    throw new UnsupportedError("resume");
+  }
+
+  static AbortedEventStreamSubscription _create(
+      EventSubscriptionSpecification spec, Zone zone) {
+    return new AbortedEventStreamSubscription(zone);
+  }
+}
+
+eventTest(String name, Event eventFn(), void validate(Event event),
+    void validateSpec(EventSubscriptionSpecification spec),
+    {String type: 'foo',
+    bool abortCreation: false,
+    EventSubscriptionSpecification modifySpec(
+        EventSubscriptionSpecification spec),
+    bool abortEvent: false,
+    Event modifyEvent(Event event)}) {
+  test(name, () {
+    var lastSpec;
+    var lastTask;
+    var lastEvent;
+
+    Object createTaskHandler(Zone self, ZoneDelegate parent, Zone zone,
+        TaskCreate create, TaskSpecification specification) {
+      if (specification is EventSubscriptionSpecification) {
+        if (abortCreation) {
+          create = AbortedEventStreamSubscription._create;
+        }
+        if (modifySpec != null) {
+          specification = modifySpec(specification);
+        }
+        lastSpec = specification;
+        return lastTask = parent.createTask(zone, create, specification);
+      }
+      return parent.createTask(zone, create, specification);
+    }
+
+    void runTaskHandler(Zone self, ZoneDelegate parent, Zone zone, TaskRun run,
+        Object task, Object arg) {
+      if (identical(task, lastTask)) {
+        if (abortEvent) return;
+        if (modifyEvent != null) {
+          arg = modifyEvent(arg);
+        }
+        parent.runTask(zone, run, task, arg);
+        return;
+      }
+      parent.runTask(zone, run, task, arg);
+    }
+
+    runZoned(() {
+      final el = new Element.tag('div');
+      var fired = false;
+      var sub = el.on[type].listen((ev) {
+        lastEvent = ev;
+        fired = true;
+      });
+      el.dispatchEvent(eventFn());
+
+      validateSpec(lastSpec);
+      validate(lastEvent);
+
+      if (abortEvent || abortCreation) {
+        expect(fired, isFalse, reason: 'Expected event to be intercepted.');
+      } else {
+        expect(fired, isTrue, reason: 'Expected event to be dispatched.');
+      }
+
+      sub.cancel();
+    },
+        zoneSpecification: new ZoneSpecification(
+            createTask: createTaskHandler,
+            runTask: runTaskHandler));
+  });
+}
+
+Function checkSpec(
+    [String expectedType = 'foo', bool expectedUseCapture = false]) {
+  return (EventSubscriptionSpecification spec) {
+    expect(spec.eventType, expectedType);
+    expect(spec.useCapture, expectedUseCapture);
+  };
+}
+
+main() {
+  useHtmlConfiguration();
+
+  eventTest('Event', () => new Event('foo'), (ev) {
+    expect(ev.type, equals('foo'));
+  }, checkSpec('foo'));
+
+  eventTest(
+      'WheelEvent',
+      () => new WheelEvent("mousewheel",
+          deltaX: 1,
+          deltaY: 0,
+          detail: 4,
+          screenX: 3,
+          screenY: 4,
+          clientX: 5,
+          clientY: 6,
+          ctrlKey: true,
+          altKey: true,
+          shiftKey: true,
+          metaKey: true), (ev) {
+    expect(ev.deltaX, 1);
+    expect(ev.deltaY, 0);
+    expect(ev.screen.x, 3);
+    expect(ev.screen.y, 4);
+    expect(ev.client.x, 5);
+    expect(ev.client.y, 6);
+    expect(ev.ctrlKey, isTrue);
+    expect(ev.altKey, isTrue);
+    expect(ev.shiftKey, isTrue);
+    expect(ev.metaKey, isTrue);
+  }, checkSpec('mousewheel'), type: 'mousewheel');
+
+  eventTest('Event - no-create', () => new Event('foo'), (ev) {
+    expect(ev, isNull);
+  }, checkSpec('foo'), abortCreation: true);
+
+  eventTest(
+      'WheelEvent - no-create',
+      () => new WheelEvent("mousewheel",
+          deltaX: 1,
+          deltaY: 0,
+          detail: 4,
+          screenX: 3,
+          screenY: 4,
+          clientX: 5,
+          clientY: 6,
+          ctrlKey: true,
+          altKey: true,
+          shiftKey: true,
+          metaKey: true), (ev) {
+    expect(ev, isNull);
+  }, checkSpec('mousewheel'), type: 'mousewheel', abortCreation: true);
+
+  eventTest('Event - no-run', () => new Event('foo'), (ev) {
+    expect(ev, isNull);
+  }, checkSpec('foo'), abortEvent: true);
+
+  eventTest(
+      'WheelEvent - no-run',
+      () => new WheelEvent("mousewheel",
+          deltaX: 1,
+          deltaY: 0,
+          detail: 4,
+          screenX: 3,
+          screenY: 4,
+          clientX: 5,
+          clientY: 6,
+          ctrlKey: true,
+          altKey: true,
+          shiftKey: true,
+          metaKey: true), (ev) {
+    expect(ev, isNull);
+  }, checkSpec('mousewheel'), type: 'mousewheel', abortEvent: true);
+
+  // Register for 'foo', but receive a 'bar' event, because the specification
+  // is rewritten.
+  eventTest(
+      'Event - replace eventType',
+      () => new Event('bar'),
+      (ev) {
+        expect(ev.type, equals('bar'));
+      },
+      checkSpec('bar'),
+      type: 'foo',
+      modifySpec: (EventSubscriptionSpecification spec) {
+        return spec.replace(eventType: 'bar');
+      });
+
+  // Intercept the 'foo' event and replace it with a 'bar' event.
+  eventTest(
+      'Event - intercept result',
+      () => new Event('foo'),
+      (ev) {
+        expect(ev.type, equals('bar'));
+      },
+      checkSpec('foo'),
+      type: 'foo',
+      modifyEvent: (Event event) {
+        return new Event('bar');
+      });
+}
diff --git a/tests/html/html.status b/tests/html/html.status
index 4d57f42..65ff46f 100644
--- a/tests/html/html.status
+++ b/tests/html/html.status
@@ -142,6 +142,7 @@
 event_test: RuntimeError # Issue 23437. Only three failures, but hard to break them out.
 wheelevent_test: RuntimeError # Issue 23437
 text_event_test: RuntimeError # Issue 23437
+event_zone_task_test: RuntimeError # Issue 23437
 transition_event_test/functional: Skip # Times out. Issue 22167
 request_animation_frame_test: Skip # Times out. Issue 22167
 
@@ -185,6 +186,7 @@
 webgl_1_test/supported: Fail
 websql_test/supported: Fail
 xhr_test/json: Fail # IE10 returns string, not JSON object
+xhr_task_test/json: Fail # IE10 returns string, not JSON object
 xhr_test/supported_overrideMimeType: Fail
 xsltprocessor_test/supported: Fail
 worker_test/functional: Fail # IE uses incorrect security context for Blob URIs.
@@ -242,7 +244,8 @@
 touchevent_test/supported: Fail # IE does not support TouchEvents
 webgl_1_test/functional: Fail
 websql_test/supported: Fail
-xhr_test/json: Fail # IE10 returns string, not JSON object
+xhr_test/json: Fail # IE11 returns string, not JSON object
+xhr_task_test/json: Fail # IE11 returns string, not JSON object
 xsltprocessor_test/supported: Fail
 
 [ $runtime == ie10 ]
@@ -361,6 +364,7 @@
 
 [ (($runtime == dartium || $runtime == drt) && $system == macos) || $system == windows ]
 xhr_test/xhr: Skip # Times out.  Issue 21527
+xhr_task_test/xhr: Skip # Times out.  Issue 21527
 
 [ $compiler == dart2analyzer ]
 custom/document_register_basic_test: StaticWarning
diff --git a/tests/html/request_animation_task_test.dart b/tests/html/request_animation_task_test.dart
new file mode 100644
index 0000000..3d388dd
--- /dev/null
+++ b/tests/html/request_animation_task_test.dart
@@ -0,0 +1,170 @@
+// Copyright (c) 2016, 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.
+
+library EventTaskZoneTest;
+
+import 'package:unittest/unittest.dart';
+import 'package:unittest/html_config.dart';
+import 'dart:async';
+import 'dart:html';
+
+// Tests zone tasks with window.requestAnimationFrame.
+
+class MockAnimationFrameTask implements AnimationFrameTask {
+  static int taskId = 499;
+
+  final int id;
+  final Zone zone;
+  bool _isCanceled = false;
+  Function _callback;
+
+  MockAnimationFrameTask(
+      this.id, this.zone, this._callback);
+
+  void cancel(Window window) {
+    _isCanceled = true;
+  }
+
+  trigger(num stamp) {
+    zone.runTask(run, this, stamp);
+  }
+
+  static create(AnimationFrameRequestSpecification spec, Zone zone) {
+    var callback = zone.registerUnaryCallback(spec.callback);
+    return new MockAnimationFrameTask(
+        taskId++, zone, callback);
+  }
+
+  static run(MockAnimationFrameTask task, num arg) {
+    AnimationFrameTask.removeMapping(task.id);
+    task._callback(arg);
+  }
+}
+
+animationFrameTest() {
+  test("animationFrameTest - no intercept", () async {
+    AnimationFrameTask lastTask;
+    bool sawRequest = false;
+    int id;
+    num providedArg;
+
+    Object createTaskHandler(Zone self, ZoneDelegate parent, Zone zone,
+        TaskCreate create, TaskSpecification specification) {
+      if (specification is AnimationFrameRequestSpecification) {
+        sawRequest = true;
+        lastTask = parent.createTask(zone, create, specification);
+        id = lastTask.id;
+        return lastTask;
+      }
+      return parent.createTask(zone, create, specification);
+    }
+
+    void runTaskHandler(Zone self, ZoneDelegate parent, Zone zone, TaskRun run,
+        Object task, Object arg) {
+      if (identical(task, lastTask)) {
+        providedArg = arg;
+      }
+      parent.runTask(zone, run, task, arg);
+    }
+
+    var completer = new Completer();
+    var publicId;
+    runZoned(() {
+      publicId = window.requestAnimationFrame((num stamp) {
+        completer.complete(stamp);
+      });
+    },
+        zoneSpecification: new ZoneSpecification(
+            createTask: createTaskHandler, runTask: runTaskHandler));
+
+    var referenceCompleter = new Completer();
+    window.requestAnimationFrame((num stamp) {
+      referenceCompleter.complete(stamp);
+    });
+
+    var callbackStamp = await completer.future;
+    var referenceStamp = await referenceCompleter.future;
+
+    expect(callbackStamp, equals(referenceStamp));
+    expect(providedArg, equals(callbackStamp));
+    expect(sawRequest, isTrue);
+    expect(publicId, isNotNull);
+    expect(publicId, equals(id));
+  });
+}
+
+interceptedAnimationFrameTest() {
+  test("animationFrameTest - intercepted", () {
+    List<MockAnimationFrameTask> tasks = [];
+    List<num> loggedRuns = [];
+    int executedTaskId;
+    num executedStamp;
+
+    Object createTaskHandler(Zone self, ZoneDelegate parent, Zone zone,
+        TaskCreate create, TaskSpecification specification) {
+      if (specification is AnimationFrameRequestSpecification) {
+        var task = parent.createTask(
+            zone, MockAnimationFrameTask.create, specification);
+        tasks.add(task);
+        return task;
+      }
+      return parent.createTask(zone, create, specification);
+    }
+
+    void runTaskHandler(Zone self, ZoneDelegate parent, Zone zone, TaskRun run,
+        Object task, Object arg) {
+      if (tasks.contains(task)) {
+        loggedRuns.add(arg);
+      }
+      parent.runTask(zone, run, task, arg);
+    }
+
+    var id0, id1, id2;
+
+    runZoned(() {
+      id0 = window.requestAnimationFrame((num stamp) {
+        executedTaskId = id0;
+        executedStamp = stamp;
+      });
+      id1 = window.requestAnimationFrame((num stamp) {
+        executedTaskId = id1;
+        executedStamp = stamp;
+      });
+      id2 = window.requestAnimationFrame((num stamp) {
+        executedTaskId = id2;
+        executedStamp = stamp;
+      });
+    },
+        zoneSpecification: new ZoneSpecification(
+            createTask: createTaskHandler, runTask: runTaskHandler));
+
+    expect(tasks.length, 3);
+    expect(executedTaskId, isNull);
+    expect(executedStamp, isNull);
+    expect(loggedRuns.isEmpty, isTrue);
+
+    tasks[0].trigger(123.1);
+    expect(executedTaskId, id0);
+    expect(executedStamp, 123.1);
+
+    tasks[1].trigger(123.2);
+    expect(executedTaskId, id1);
+    expect(executedStamp, 123.2);
+
+    expect(loggedRuns, equals([123.1, 123.2]));
+
+    window.cancelAnimationFrame(id2);
+    expect(tasks[2]._isCanceled, isTrue);
+    // Cancel it a second time. Should not crash.
+    window.cancelAnimationFrame(id2);
+    expect(tasks[2]._isCanceled, isTrue);
+  });
+}
+
+main() {
+  useHtmlConfiguration();
+
+  animationFrameTest();
+  interceptedAnimationFrameTest();
+}
diff --git a/tests/html/xhr_task2_test.dart b/tests/html/xhr_task2_test.dart
new file mode 100644
index 0000000..6dee69e
--- /dev/null
+++ b/tests/html/xhr_task2_test.dart
@@ -0,0 +1,280 @@
+// Copyright (c) 2016, 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.
+
+library XHRTask2Test;
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:html';
+import 'dart:typed_data';
+import 'package:unittest/html_individual_config.dart';
+import 'package:unittest/unittest.dart';
+
+class MockProgressEvent implements ProgressEvent {
+  final target;
+  MockProgressEvent(this.target);
+
+  noSuchMethod(Invocation invocation) {
+    throw "missing function in MockProgressEvent";
+  }
+}
+
+class MockHttpRequestTask implements Future<HttpRequest> {
+  final Completer completer = new Completer<HttpRequest>();
+  final HttpRequestTaskSpecification spec;
+  final Zone zone;
+
+  MockHttpRequestTask(this.spec, this.zone);
+
+  void trigger(response) {
+    var xhr = new MockHttpRequest(spec, response);
+    var arg;
+    if (spec.url == "NonExistingFile") {
+      arg = new MockProgressEvent(xhr);
+    } else {
+      arg = xhr;
+    }
+    zone.runTask(run, this, arg);
+  }
+
+  then(onData, {onError}) => completer.future.then(onData, onError: onError);
+  catchError(f, {test}) => completer.future.catchError(f, test: test);
+  whenComplete(f) => completer.future.whenComplete(f);
+  asStream() => completer.future.asStream();
+  timeout(timeLimit, {onTimeout}) =>
+      completer.future.timeout(timeLimit, onTimeout: onTimeout);
+
+  static create(HttpRequestTaskSpecification spec, Zone zone) {
+    return new MockHttpRequestTask(spec, zone);
+  }
+
+  static run(MockHttpRequestTask task, value) {
+    if (value is HttpRequest) {
+      task.completer.complete(value);
+    } else {
+      task.completer.completeError(value);
+    }
+  }
+}
+
+class MockHttpRequest implements HttpRequest {
+  final HttpRequestTaskSpecification spec;
+  final response;
+
+  MockHttpRequest(this.spec, this.response);
+
+  noSuchMethod(Invocation invocation) {
+    print("isGetter: ${invocation.isGetter}");
+    print("isMethod: ${invocation.isMethod}");
+    print("memberName: ${invocation.memberName}");
+  }
+
+  int get status => spec.url == "NonExistingFile" ? 404 : 200;
+
+  get readyState => HttpRequest.DONE;
+  get responseText => "$response";
+
+  Map get responseHeaders => {'content-type': 'text/plain; charset=utf-8',};
+}
+
+main() {
+  useHtmlIndividualConfiguration();
+  unittestConfiguration.timeout = const Duration(milliseconds: 800);
+
+  var urlExpando = new Expando();
+
+  var url = 'some/url.html';
+
+  Function buildCreateTaskHandler(List log, List tasks) {
+    Object createTaskHandler(Zone self, ZoneDelegate parent, Zone zone,
+        TaskCreate create, TaskSpecification spec) {
+      if (spec is HttpRequestTaskSpecification) {
+        var url = spec.url;
+        var method = spec.method;
+        var withCredentials = spec.withCredentials;
+        var responseType = spec.responseType;
+        var mimeType = spec.mimeType;
+        var data = spec.sendData;
+
+        log.add("request $url");
+        var dataLog = data is List<int> ? "binary ${data.length}" : "$data";
+        log.add("  method: $method withCredentials: $withCredentials "
+            "responseType: $responseType mimeType: $mimeType data: $dataLog");
+        var task = parent.createTask(zone, MockHttpRequestTask.create, spec);
+        urlExpando[task] = url;
+        tasks.add(task);
+        return task;
+      }
+      if (spec is EventSubscriptionSpecification) {
+        EventSubscriptionSpecification eventSpec = spec;
+        if (eventSpec.target is HttpRequest) {
+          HttpRequest target = eventSpec.target;
+          log.add("event listener on http-request ${eventSpec.eventType}");
+          if (eventSpec.eventType == "readystatechange") {
+            var oldOnData = eventSpec.onData;
+            spec = eventSpec.replace(onData: (event) {
+              oldOnData(event);
+              if (target.readyState == HttpRequest.DONE) {
+                log.add("unknown request done");
+              }
+            });
+          }
+        }
+      }
+      return parent.createTask(zone, create, spec);
+    }
+
+    return createTaskHandler;
+  }
+
+  Function buildRunTaskHandler(List log, List tasks) {
+    void runTaskHandler(Zone self, ZoneDelegate parent, Zone zone, TaskRun run,
+        Object task, Object arg) {
+      if (tasks.contains(task)) {
+        var url = urlExpando[task];
+        if (arg is Error || arg is Exception) {
+          log.add("failed $url");
+        } else {
+          if (arg is ProgressEvent) {
+            log.add("success $url with progress-event");
+          } else if (arg is HttpRequest) {
+            log.add("success $url with http-request");
+          } else {
+            log.add("success $url (unknown arg)");
+          }
+        }
+      }
+      parent.runTask(zone, run, task, arg);
+    }
+
+    return runTaskHandler;
+  }
+
+  Future<List> runMocked(response, fun) async {
+    var log = [];
+    var tasks = [];
+    var future = runZoned(fun,
+        zoneSpecification: new ZoneSpecification(
+            createTask: buildCreateTaskHandler(log, tasks),
+            runTask: buildRunTaskHandler(log, tasks)));
+    // Wait a full cycle to make sure things settle.
+    await new Future(() {});
+    var beforeTriggerLog = log.toList();
+    log.clear();
+    expect(tasks.length, 1);
+    tasks.single.trigger(response);
+    await future;
+    return [beforeTriggerLog, log];
+  }
+
+  void validate200Response(xhr) {
+    expect(xhr.status, equals(200));
+    var data = JSON.decode(xhr.responseText);
+    expect(data, contains('feed'));
+    expect(data['feed'], contains('entry'));
+    expect(data, isMap);
+  }
+
+  void validate404(xhr) {
+    expect(xhr.status, equals(404));
+    // We cannot say much about xhr.responseText, most HTTP servers will
+    // include an HTML page explaining the error to a human.
+    String responseText = xhr.responseText;
+    expect(responseText, isNotNull);
+  }
+
+  group('xhr', () {
+    test('XHR.request No file', () async {
+      var log = await runMocked("404", () {
+        var completer = new Completer();
+        HttpRequest.request('NonExistingFile').then((_) {
+          fail('Request should not have succeeded.');
+        }, onError: expectAsync((error) {
+          var xhr = error.target;
+          expect(xhr.readyState, equals(HttpRequest.DONE));
+          validate404(xhr);
+          completer.complete('done');
+        }));
+        return completer.future;
+      });
+      expect(
+          log,
+          equals([
+            [
+              'request NonExistingFile',
+              '  method: null withCredentials: null responseType: null '
+                  'mimeType: null data: null',
+            ],
+            ['success NonExistingFile with progress-event']
+          ]));
+    });
+
+    test('XHR.request file', () async {
+      var log = await runMocked('{"feed": {"entry": 499}}', () {
+        var completer = new Completer();
+        HttpRequest.request(url).then(expectAsync((xhr) {
+          expect(xhr.readyState, equals(HttpRequest.DONE));
+          validate200Response(xhr);
+          completer.complete('done');
+        }));
+        return completer.future;
+      });
+      expect(
+          log,
+          equals([
+            [
+              'request $url',
+              '  method: null withCredentials: null responseType: null '
+                  'mimeType: null data: null'
+            ],
+            ['success $url with http-request']
+          ]));
+    });
+
+    test('XHR.getString file', () async {
+      var log = await runMocked("foo", () {
+        return HttpRequest.getString(url).then(expectAsync((str) {}));
+      });
+      expect(
+          log,
+          equals([
+            [
+              'request $url',
+              '  method: null withCredentials: null responseType: null '
+                  'mimeType: null data: null'
+            ],
+            ['success $url with http-request']
+          ]));
+    });
+
+    test('XHR.request responseType arraybuffer', () async {
+      if (Platform.supportsTypedData) {
+        var data = new Uint8List(128);
+        var log = await runMocked(data.buffer, () {
+          return HttpRequest.request(url,
+              responseType: 'arraybuffer',
+              requestHeaders: {
+                'Content-Type': 'text/xml'
+              }).then(expectAsync((xhr) {
+            expect(xhr.status, equals(200));
+            var byteBuffer = xhr.response;
+            expect(byteBuffer, new isInstanceOf<ByteBuffer>());
+            expect(byteBuffer, isNotNull);
+          }));
+        });
+        expect(
+            log,
+            equals([
+              [
+                'request $url',
+                '  method: null withCredentials: null responseType: arraybuffer'
+                    ' mimeType: null data: null'
+              ],
+              ['success $url with http-request']
+            ]));
+      }
+      ;
+    });
+  });
+}
diff --git a/tests/html/xhr_task_test.dart b/tests/html/xhr_task_test.dart
new file mode 100644
index 0000000..c315ae6
--- /dev/null
+++ b/tests/html/xhr_task_test.dart
@@ -0,0 +1,508 @@
+// Copyright (c) 2016, 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.
+
+library XHRTaskTest;
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:html';
+import 'dart:typed_data';
+import 'package:unittest/html_individual_config.dart';
+import 'package:unittest/unittest.dart';
+
+main() {
+  useHtmlIndividualConfiguration();
+
+  // Cache blocker is a workaround for:
+  // https://code.google.com/p/dart/issues/detail?id=11834
+  var cacheBlocker = new DateTime.now().millisecondsSinceEpoch;
+  var url = '/root_dart/tests/html/xhr_cross_origin_data.txt?'
+      'cacheBlock=$cacheBlocker';
+
+  var urlExpando = new Expando();
+
+  Function buildCreateTaskHandler(List log, List tasks) {
+    Object createTaskHandler(Zone self, ZoneDelegate parent, Zone zone,
+        TaskCreate create, TaskSpecification spec) {
+      if (spec is HttpRequestTaskSpecification) {
+        var url = spec.url;
+        var method = spec.method;
+        var withCredentials = spec.withCredentials;
+        var responseType = spec.responseType;
+        var mimeType = spec.mimeType;
+        var data = spec.sendData;
+
+        log.add("request $url");
+        var dataLog = data is List<int> ? "binary ${data.length}" : "$data";
+        log.add("  method: $method withCredentials: $withCredentials "
+            "responseType: $responseType mimeType: $mimeType data: $dataLog");
+        var task = parent.createTask(zone, create, spec);
+        urlExpando[task] = url;
+        tasks.add(task);
+        return task;
+      }
+      if (spec is HttpRequestSendTaskSpecification) {
+        var data = spec.sendData;
+        var dataLog = data is List<int> ? "binary ${data.length}" : "$data";
+        log.add("http-request (no info), data: $dataLog");
+        var task = parent.createTask(zone, create, spec);
+        tasks.add(task);
+        urlExpando[task] = "unknown";
+        return task;
+      }
+      if (spec is EventSubscriptionSpecification) {
+        EventSubscriptionSpecification eventSpec = spec;
+        if (eventSpec.target is HttpRequest) {
+          HttpRequest target = eventSpec.target;
+          log.add("event listener on http-request ${eventSpec.eventType}");
+          if (eventSpec.eventType == "readystatechange") {
+            var oldOnData = eventSpec.onData;
+            spec = eventSpec.replace(onData: (event) {
+              oldOnData(event);
+              if (target.readyState == HttpRequest.DONE) {
+                log.add("unknown request done");
+              }
+            });
+          }
+        }
+      }
+      return parent.createTask(zone, create, spec);
+    }
+
+    return createTaskHandler;
+  }
+
+  Function buildRunTaskHandler(List log, List tasks) {
+    void runTaskHandler(Zone self, ZoneDelegate parent, Zone zone,
+        TaskRun run, Object task, Object arg) {
+      if (tasks.contains(task)) {
+        var url = urlExpando[task];
+        if (arg is Error || arg is Exception) {
+          log.add("failed $url");
+        } else {
+          if (arg is ProgressEvent) {
+            log.add("success $url with progress-event");
+          } else if (arg is HttpRequest){
+            log.add("success $url with http-request");
+          } else {
+            log.add("success $url (unknown arg)");
+          }
+        }
+      }
+      parent.runTask(zone, run, task, arg);
+    }
+
+    return runTaskHandler;
+  }
+
+  Future<List> runWithLogging(fun) async {
+    var log = [];
+    var tasks = [];
+    await runZoned(fun, zoneSpecification: new ZoneSpecification(
+        createTask: buildCreateTaskHandler(log, tasks),
+        runTask: buildRunTaskHandler(log, tasks)));
+    return log;
+  }
+
+  void validate200Response(xhr) {
+    expect(xhr.status, equals(200));
+    var data = JSON.decode(xhr.responseText);
+    expect(data, contains('feed'));
+    expect(data['feed'], contains('entry'));
+    expect(data, isMap);
+  }
+
+  void validate404(xhr) {
+    expect(xhr.status, equals(404));
+    // We cannot say much about xhr.responseText, most HTTP servers will
+    // include an HTML page explaining the error to a human.
+    String responseText = xhr.responseText;
+    expect(responseText, isNotNull);
+  }
+
+  group('xhr', () {
+    test('XHR No file', () async {
+      var log = await runWithLogging(() {
+        var completer = new Completer();
+        HttpRequest xhr = new HttpRequest();
+        xhr.open("GET", "NonExistingFile", async: true);
+        xhr.onReadyStateChange.listen(expectAsyncUntil((event) {
+          if (xhr.readyState == HttpRequest.DONE) {
+            validate404(xhr);
+            completer.complete("done");
+          }
+        }, () => xhr.readyState == HttpRequest.DONE));
+        xhr.send();
+        return completer.future;
+      });
+      expect(log, equals([
+        'event listener on http-request readystatechange',
+        'http-request (no info), data: null',
+        'unknown request done'
+      ]));
+    });
+
+    test('XHR_file', () async {
+      var log = await runWithLogging(() {
+        var completer = new Completer();
+        var loadEndCalled = false;
+
+        var xhr = new HttpRequest();
+        xhr.open('GET', url, async: true);
+        xhr.onReadyStateChange.listen(expectAsyncUntil((e) {
+          if (xhr.readyState == HttpRequest.DONE) {
+            validate200Response(xhr);
+
+            Timer.run(expectAsync(() {
+              expect(loadEndCalled, HttpRequest.supportsLoadEndEvent);
+              completer.complete("done");
+            }));
+          }
+        }, () => xhr.readyState == HttpRequest.DONE));
+
+        xhr.onLoadEnd.listen((ProgressEvent e) {
+          loadEndCalled = true;
+        });
+        xhr.send();
+        return completer.future;
+      });
+      expect(log, equals([
+        'event listener on http-request readystatechange',
+        'event listener on http-request loadend',
+        'http-request (no info), data: null',
+        'unknown request done'
+      ]));
+    });
+
+    test('XHR.request No file', () async {
+      var log = await runWithLogging(() {
+        var completer = new Completer();
+        HttpRequest.request('NonExistingFile').then(
+            (_) { fail('Request should not have succeeded.'); },
+            onError: expectAsync((error) {
+              var xhr = error.target;
+              expect(xhr.readyState, equals(HttpRequest.DONE));
+              validate404(xhr);
+              completer.complete('done');
+            }));
+        return completer.future;
+      });
+      expect(log, equals([
+        'request NonExistingFile',
+        '  method: null withCredentials: null responseType: null '
+            'mimeType: null data: null',
+        'event listener on http-request load',
+        'event listener on http-request error',
+        'success NonExistingFile with progress-event'
+      ]));
+    });
+
+    test('XHR.request file', () async {
+      var log = await runWithLogging(() {
+        var completer = new Completer();
+        HttpRequest.request(url).then(expectAsync((xhr) {
+          expect(xhr.readyState, equals(HttpRequest.DONE));
+          validate200Response(xhr);
+          completer.complete('done');
+        }));
+        return completer.future;
+      });
+      expect(log, equals([
+        'request $url',
+        '  method: null withCredentials: null responseType: null '
+            'mimeType: null data: null',
+        'event listener on http-request load',
+        'event listener on http-request error',
+        'success $url with http-request'
+      ]));
+    });
+
+    test('XHR.request onProgress', () async {
+      var log = await runWithLogging(() {
+        var completer = new Completer();
+        var progressCalled = false;
+        HttpRequest.request(url,
+            onProgress: (_) {
+              progressCalled = true;
+            }).then(expectAsync(
+            (xhr) {
+              expect(xhr.readyState, equals(HttpRequest.DONE));
+              expect(progressCalled, HttpRequest.supportsProgressEvent);
+              validate200Response(xhr);
+              completer.complete("done");
+        }));
+        return completer.future;
+      });
+      expect(log, equals([
+        'request $url',
+        '  method: null withCredentials: null responseType: null '
+            'mimeType: null data: null',
+        'event listener on http-request progress',
+        'event listener on http-request load',
+        'event listener on http-request error',
+        'success $url with http-request'
+      ]));
+    });
+
+    test('XHR.request withCredentials No file', () async {
+      var log = await runWithLogging(() {
+        var completer = new Completer();
+        HttpRequest.request('NonExistingFile', withCredentials: true).then(
+            (_) { fail('Request should not have succeeded.'); },
+            onError: expectAsync((error) {
+              var xhr = error.target;
+              expect(xhr.readyState, equals(HttpRequest.DONE));
+              validate404(xhr);
+              completer.complete("done");
+            }));
+        return completer.future;
+      });
+      expect(log, equals([
+        'request NonExistingFile',
+        '  method: null withCredentials: true responseType: null '
+            'mimeType: null data: null',
+        'event listener on http-request load',
+        'event listener on http-request error',
+        'success NonExistingFile with progress-event'
+      ]));
+    });
+
+
+    test('XHR.request withCredentials file', () async {
+      var log = await runWithLogging(() {
+        var completer = new Completer();
+        HttpRequest.request(url, withCredentials: true).then(expectAsync((xhr) {
+          expect(xhr.readyState, equals(HttpRequest.DONE));
+          validate200Response(xhr);
+          completer.complete("done");
+        }));
+        return completer.future;
+      });
+      expect(log, equals([
+        'request $url',
+        '  method: null withCredentials: true responseType: null '
+            'mimeType: null data: null',
+        'event listener on http-request load',
+        'event listener on http-request error',
+        'success $url with http-request'
+      ]));
+    });
+
+    test('XHR.getString file', () async {
+      var log = await runWithLogging(() {
+        return HttpRequest.getString(url).then(expectAsync((str) {}));
+      });
+      expect(log, equals([
+        'request $url',
+        '  method: null withCredentials: null responseType: null '
+            'mimeType: null data: null',
+        'event listener on http-request load',
+        'event listener on http-request error',
+        'success $url with http-request'
+      ]));
+    });
+
+    test('XHR.getString No file', () async {
+      var log = await runWithLogging(() {
+        return HttpRequest.getString('NonExistingFile').then(
+            (_) { fail('Succeeded for non-existing file.'); },
+            onError: expectAsync((error) {
+              validate404(error.target);
+            }));
+      });
+      expect(log, equals([
+        'request NonExistingFile',
+        '  method: null withCredentials: null responseType: null '
+            'mimeType: null data: null',
+        'event listener on http-request load',
+        'event listener on http-request error',
+        'success NonExistingFile with progress-event'
+      ]));
+    });
+
+    test('XHR.request responseType arraybuffer', () async {
+      if (Platform.supportsTypedData) {
+        var log = await runWithLogging(() {
+          return HttpRequest.request(url, responseType: 'arraybuffer',
+              requestHeaders: {'Content-Type': 'text/xml'}).then(
+              expectAsync((xhr) {
+                expect(xhr.status, equals(200));
+                var byteBuffer = xhr.response;
+                expect(byteBuffer, new isInstanceOf<ByteBuffer>());
+                expect(byteBuffer, isNotNull);
+              }));
+        });
+        expect(log, equals([
+          'request $url',
+          '  method: null withCredentials: null responseType: arraybuffer '
+              'mimeType: null data: null',
+          'event listener on http-request load',
+          'event listener on http-request error',
+          'success $url with http-request'
+        ]));
+      };
+    });
+
+    test('overrideMimeType', () async {
+      var expectation =
+          HttpRequest.supportsOverrideMimeType ? returnsNormally : throws;
+
+      var log = await runWithLogging(() {
+        var completer = new Completer();
+        expect(() {
+          HttpRequest.request(url, mimeType: 'application/binary')
+              .whenComplete(completer.complete);
+        }, expectation);
+        return completer.future;
+      });
+      expect(log, equals([
+        'request $url',
+        '  method: null withCredentials: null responseType: null '
+            'mimeType: application/binary data: null',
+        'event listener on http-request load',
+        'event listener on http-request error',
+        'success $url with http-request'
+      ]));
+    });
+
+    if (Platform.supportsTypedData) {
+      test('xhr upload', () async {
+        var log = await runWithLogging(() {
+          var xhr = new HttpRequest();
+          var progressCalled = false;
+          xhr.upload.onProgress.listen((e) {
+            progressCalled = true;
+          });
+
+          xhr.open('POST',
+              '${window.location.protocol}//${window.location.host}/echo');
+
+          // 10MB of payload data w/ a bit of data to make sure it
+          // doesn't get compressed to nil.
+          var data = new Uint8List(1 * 1024 * 1024);
+          for (var i = 0; i < data.length; ++i) {
+            data[i] = i & 0xFF;
+          }
+          xhr.send(new Uint8List.view(data.buffer));
+
+          return xhr.onLoad.first.then((_) {
+            expect(
+                progressCalled, isTrue, reason: 'onProgress should be fired');
+          });
+        });
+        expect(log, equals([
+          'http-request (no info), data: binary 1048576',
+          'event listener on http-request load',
+        ]));
+      });
+    }
+
+    test('xhr postFormData', () async {
+      var url = '${window.location.protocol}//${window.location.host}/echo';
+      var log = await runWithLogging(() {
+        var data = { 'name': 'John', 'time': '2 pm'};
+
+        var parts = [];
+        for (var key in data.keys) {
+          parts.add('${Uri.encodeQueryComponent(key)}='
+              '${Uri.encodeQueryComponent(data[key])}');
+        }
+        var encodedData = parts.join('&');
+
+        return HttpRequest.postFormData(url, data).then((xhr) {
+          expect(xhr.responseText, encodedData);
+        });
+      });
+      expect(log, equals([
+        'request $url',
+        '  method: POST withCredentials: null responseType: null '
+            'mimeType: null data: name=John&time=2+pm',
+        'event listener on http-request load',
+        'event listener on http-request error',
+        'success $url with http-request'
+      ]));
+    });
+  });
+
+  group('xhr_requestBlob', () {
+    test('XHR.request responseType blob', () async {
+      if (Platform.supportsTypedData) {
+        var log = await runWithLogging(() {
+          return HttpRequest.request(url, responseType: 'blob').then(
+              (xhr) {
+            expect(xhr.status, equals(200));
+            var blob = xhr.response;
+            expect(blob is Blob, isTrue);
+            expect(blob, isNotNull);
+          });
+        });
+        expect(log, equals([
+          'request $url',
+          '  method: null withCredentials: null responseType: blob '
+              'mimeType: null data: null',
+          'event listener on http-request load',
+          'event listener on http-request error',
+          'success $url with http-request'
+        ]));
+      }
+    });
+  });
+
+  group('json', () {
+    test('xhr responseType json', () async {
+      var url = '${window.location.protocol}//${window.location.host}/echo';
+      var log = await runWithLogging(() {
+        var completer = new Completer();
+        var data = {
+          'key': 'value',
+          'a': 'b',
+          'one': 2,
+        };
+
+        HttpRequest.request(url,
+            method: 'POST',
+            sendData: JSON.encode(data),
+            responseType: 'json').then(
+            expectAsync((xhr) {
+              expect(xhr.status, equals(200));
+              var json = xhr.response;
+              expect(json, equals(data));
+              completer.complete("done");
+            }));
+        return completer.future;
+      });
+      expect(log, equals([
+        'request $url',
+        '  method: POST withCredentials: null responseType: json mimeType: null'
+            ' data: {"key":"value","a":"b","one":2}',
+        'event listener on http-request load',
+        'event listener on http-request error',
+        'success $url with http-request'
+      ]));
+    });
+  });
+
+  group('headers', () {
+    test('xhr responseHeaders', () async {
+      var log = await runWithLogging(() {
+        return HttpRequest.request(url).then(
+            (xhr) {
+          var contentTypeHeader = xhr.responseHeaders['content-type'];
+          expect(contentTypeHeader, isNotNull);
+          // Should be like: 'text/plain; charset=utf-8'
+          expect(contentTypeHeader.contains('text/plain'), isTrue);
+          expect(contentTypeHeader.contains('charset=utf-8'), isTrue);
+        });
+      });
+      expect(log, equals([
+        'request $url',
+        '  method: null withCredentials: null responseType: null'
+            ' mimeType: null data: null',
+        'event listener on http-request load',
+        'event listener on http-request error',
+        'success $url with http-request'
+      ]));
+    });
+  });
+}
diff --git a/tests/lib/async/zone_task_test.dart b/tests/lib/async/zone_task_test.dart
new file mode 100644
index 0000000..aa9b008
--- /dev/null
+++ b/tests/lib/async/zone_task_test.dart
@@ -0,0 +1,310 @@
+// Copyright (c) 2016, 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.
+
+// Tests basic functionality of tasks in zones.
+
+import 'package:expect/expect.dart';
+import 'package:async_helper/async_helper.dart';
+import 'dart:async';
+
+List log = [];
+
+class MySpecification extends TaskSpecification {
+  final Function callback;
+  final bool isOneShot;
+  final int value;
+
+  MySpecification(void this.callback(), this.isOneShot, this.value);
+
+  String get name => "test.specification-name";
+}
+
+class MyTask {
+  final Zone zone;
+  final Function callback;
+  final int id;
+  int invocationCount = 0;
+  bool shouldStop = false;
+
+  MyTask(this.zone, void this.callback(), this.id);
+}
+
+void runMyTask(MyTask task, int value) {
+  log.add("running "
+      "zone: ${Zone.current['name']} "
+      "task-id: ${task.id} "
+      "invocation-count: ${task.invocationCount} "
+      "value: $value");
+  task.callback();
+  task.invocationCount++;
+}
+
+MyTask createMyTask(MySpecification spec, Zone zone) {
+  var task = new MyTask(zone, spec.callback, spec.value);
+  log.add("creating task: ${spec.value} oneshot?: ${spec.isOneShot}");
+  if (spec.isOneShot) {
+    Timer.run(() {
+      zone.runTask(runMyTask, task, task.id);
+    });
+  } else {
+    new Timer.periodic(const Duration(milliseconds: 10), (Timer timer) {
+      zone.runTask(runMyTask, task, task.id);
+      if (task.shouldStop) {
+        timer.cancel();
+      }
+    });
+  }
+  return task;
+}
+
+MyTask startTask(f, bool oneShot, int value) {
+  var spec = new MySpecification(f, oneShot, value);
+  return Zone.current.createTask(createMyTask, spec);
+}
+
+/// Makes sure things are working in a simple setting.
+/// No interceptions, changes, ...
+Future testCustomTask() {
+  var testCompleter = new Completer();
+  asyncStart();
+
+  Object createTaskHandler(Zone self, ZoneDelegate parent, Zone zone,
+      TaskCreate create, TaskSpecification specification) {
+    if (specification is MySpecification) {
+      log.add("create enter "
+          "zone: ${self['name']} "
+          "spec-value: ${specification.value} "
+          "spec-oneshot?: ${specification.isOneShot}");
+      MyTask result = parent.createTask(zone, create, specification);
+      log.add("create leave");
+      return result;
+    }
+    return parent.createTask(zone, create, specification);
+  }
+
+  void runTaskHandler(Zone self, ZoneDelegate parent, Zone zone, TaskRun run,
+      Object task, Object arg) {
+    if (task is MyTask) {
+      log.add("run enter "
+          "zone: ${self['name']} "
+          "task-id: ${task.id} "
+          "invocation-count: ${task.invocationCount} "
+          "arg: $arg");
+      parent.runTask(zone, run, task, arg);
+      log.add("run leave invocation-count: ${task.invocationCount}");
+      return;
+    }
+    parent.runTask(zone, run, task, arg);
+  }
+
+  runZoned(() async {
+    var completer0 = new Completer();
+    startTask(() {
+      completer0.complete("done");
+    }, true, 0);
+    await completer0.future;
+
+    Expect.listEquals([
+      'create enter zone: custom zone spec-value: 0 spec-oneshot?: true',
+      'creating task: 0 oneshot?: true',
+      'create leave',
+      'run enter zone: custom zone task-id: 0 invocation-count: 0 arg: 0',
+      'running zone: custom zone task-id: 0 invocation-count: 0 value: 0',
+      'run leave invocation-count: 1'
+    ], log);
+    log.clear();
+
+    var completer1 = new Completer();
+    MyTask task1;
+    task1 = startTask(() {
+      if (task1.invocationCount == 1) {
+        task1.shouldStop = true;
+        completer1.complete("done");
+      }
+    }, false, 1);
+    await completer1.future;
+
+    Expect.listEquals([
+      'create enter zone: custom zone spec-value: 1 spec-oneshot?: false',
+      'creating task: 1 oneshot?: false',
+      'create leave',
+      'run enter zone: custom zone task-id: 1 invocation-count: 0 arg: 1',
+      'running zone: custom zone task-id: 1 invocation-count: 0 value: 1',
+      'run leave invocation-count: 1',
+      'run enter zone: custom zone task-id: 1 invocation-count: 1 arg: 1',
+      'running zone: custom zone task-id: 1 invocation-count: 1 value: 1',
+      'run leave invocation-count: 2',
+    ], log);
+    log.clear();
+
+    testCompleter.complete("done");
+    asyncEnd();
+  },
+      zoneValues: {'name': 'custom zone'},
+      zoneSpecification: new ZoneSpecification(
+          createTask: createTaskHandler,
+          runTask: runTaskHandler));
+
+  return testCompleter.future;
+}
+
+/// More complicated zone, that intercepts...
+Future testCustomTask2() {
+  var testCompleter = new Completer();
+  asyncStart();
+
+  Object createTaskHandler(Zone self, ZoneDelegate parent, Zone zone,
+      TaskCreate create, TaskSpecification specification) {
+    if (specification is MySpecification) {
+      log.add("create enter "
+          "zone: ${self['name']} "
+          "spec-value: ${specification.value} "
+          "spec-oneshot?: ${specification.isOneShot}");
+      var replacement = new MySpecification(specification.callback,
+          specification.isOneShot, specification.value + 1);
+      MyTask result = parent.createTask(zone, create, replacement);
+      log.add("create leave");
+      return result;
+    }
+    return parent.createTask(zone, create, specification);
+  }
+
+  void runTaskHandler(Zone self, ZoneDelegate parent, Zone zone, TaskRun run,
+      Object task, Object arg) {
+    if (task is MyTask) {
+      log.add("run enter "
+          "zone: ${self['name']} "
+          "task-id: ${task.id} "
+          "invocation-count: ${task.invocationCount} "
+          "arg: $arg");
+      int value = arg;
+      parent.runTask(zone, run, task, value + 101);
+      log.add("run leave invocation-count: ${task.invocationCount}");
+      return;
+    }
+    parent.runTask(zone, run, task, arg);
+  }
+
+  runZoned(() async {
+    var completer0 = new Completer();
+    startTask(() {
+      completer0.complete("done");
+    }, true, 0);
+    await completer0.future;
+
+    Expect.listEquals([
+      'create enter zone: outer-zone spec-value: 0 spec-oneshot?: true',
+      'creating task: 1 oneshot?: true',
+      'create leave',
+      'run enter zone: outer-zone task-id: 1 invocation-count: 0 arg: 1',
+      'running zone: outer-zone task-id: 1 invocation-count: 0 value: 102',
+      'run leave invocation-count: 1'
+    ], log);
+    log.clear();
+
+    var completer1 = new Completer();
+    MyTask task1;
+    task1 = startTask(() {
+      if (task1.invocationCount == 1) {
+        task1.shouldStop = true;
+        completer1.complete("done");
+      }
+    }, false, 1);
+    await completer1.future;
+
+    Expect.listEquals([
+      'create enter zone: outer-zone spec-value: 1 spec-oneshot?: false',
+      'creating task: 2 oneshot?: false',
+      'create leave',
+      'run enter zone: outer-zone task-id: 2 invocation-count: 0 arg: 2',
+      'running zone: outer-zone task-id: 2 invocation-count: 0 value: 103',
+      'run leave invocation-count: 1',
+      'run enter zone: outer-zone task-id: 2 invocation-count: 1 arg: 2',
+      'running zone: outer-zone task-id: 2 invocation-count: 1 value: 103',
+      'run leave invocation-count: 2',
+    ], log);
+    log.clear();
+
+    var nestedCompleter = new Completer();
+
+    runZoned(() async {
+      var completer0 = new Completer();
+      startTask(() {
+        completer0.complete("done");
+      }, true, 0);
+      await completer0.future;
+
+      Expect.listEquals([
+        'create enter zone: inner-zone spec-value: 0 spec-oneshot?: true',
+        'create enter zone: outer-zone spec-value: 1 spec-oneshot?: true',
+        'creating task: 2 oneshot?: true',
+        'create leave',
+        'create leave',
+        'run enter zone: inner-zone task-id: 2 invocation-count: 0 arg: 2',
+        'run enter zone: outer-zone task-id: 2 invocation-count: 0 arg: 103',
+        'running zone: inner-zone task-id: 2 invocation-count: 0 value: 204',
+        'run leave invocation-count: 1',
+        'run leave invocation-count: 1'
+      ], log);
+      log.clear();
+
+      var completer1 = new Completer();
+      MyTask task1;
+      task1 = startTask(() {
+        if (task1.invocationCount == 1) {
+          task1.shouldStop = true;
+          completer1.complete("done");
+        }
+      }, false, 1);
+      await completer1.future;
+
+      Expect.listEquals([
+        'create enter zone: inner-zone spec-value: 1 spec-oneshot?: false',
+        'create enter zone: outer-zone spec-value: 2 spec-oneshot?: false',
+        'creating task: 3 oneshot?: false',
+        'create leave',
+        'create leave',
+        'run enter zone: inner-zone task-id: 3 invocation-count: 0 arg: 3',
+        'run enter zone: outer-zone task-id: 3 invocation-count: 0 arg: 104',
+        'running zone: inner-zone task-id: 3 invocation-count: 0 value: 205',
+        'run leave invocation-count: 1',
+        'run leave invocation-count: 1',
+        'run enter zone: inner-zone task-id: 3 invocation-count: 1 arg: 3',
+        'run enter zone: outer-zone task-id: 3 invocation-count: 1 arg: 104',
+        'running zone: inner-zone task-id: 3 invocation-count: 1 value: 205',
+        'run leave invocation-count: 2',
+        'run leave invocation-count: 2',
+      ], log);
+      log.clear();
+
+      nestedCompleter.complete("done");
+    },
+        zoneValues: {'name': 'inner-zone'},
+        zoneSpecification: new ZoneSpecification(
+            createTask: createTaskHandler,
+            runTask: runTaskHandler));
+
+    await nestedCompleter.future;
+    testCompleter.complete("done");
+    asyncEnd();
+  },
+      zoneValues: {'name': 'outer-zone'},
+      zoneSpecification: new ZoneSpecification(
+          createTask: createTaskHandler,
+          runTask: runTaskHandler));
+
+  return testCompleter.future;
+}
+
+runTests() async {
+  await testCustomTask();
+  await testCustomTask2();
+}
+
+main() {
+  asyncStart();
+  runTests().then((_) {
+    asyncEnd();
+  });
+}
diff --git a/tests/lib/async/zone_timer_task_test.dart b/tests/lib/async/zone_timer_task_test.dart
new file mode 100644
index 0000000..310f7ca
--- /dev/null
+++ b/tests/lib/async/zone_timer_task_test.dart
@@ -0,0 +1,515 @@
+// Copyright (c) 2016, 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.
+
+// Tests timer tasks.
+
+import 'package:expect/expect.dart';
+import 'package:async_helper/async_helper.dart';
+import 'dart:async';
+import 'dart:collection';
+
+class MyTimerSpecification implements SingleShotTimerTaskSpecification {
+  final Function callback;
+  final Duration duration;
+
+  MyTimerSpecification(this.callback, this.duration);
+
+  bool get isOneShot => true;
+  String get name => "test.timer-override";
+}
+
+class MyPeriodicTimerSpecification implements PeriodicTimerTaskSpecification {
+  final Function callback;
+  final Duration duration;
+
+  MyPeriodicTimerSpecification(this.callback, this.duration);
+
+  bool get isOneShot => true;
+  String get name => "test.periodic-timer-override";
+}
+
+/// Makes sure things are working in a simple setting.
+/// No interceptions, changes, ...
+Future testTimerTask() {
+  List log = [];
+
+  var testCompleter = new Completer();
+  asyncStart();
+
+  int taskIdCounter = 0;
+
+  Object createTaskHandler(Zone self, ZoneDelegate parent, Zone zone,
+      TaskCreate create, TaskSpecification specification) {
+    var taskMap = self['taskMap'];
+    var taskIdMap = self['taskIdMap'];
+    if (specification is SingleShotTimerTaskSpecification) {
+      log.add("create enter "
+          "zone: ${self['name']} "
+          "spec-duration: ${specification.duration} "
+          "spec-oneshot?: ${specification.isOneShot}");
+      var result = parent.createTask(zone, create, specification);
+      taskMap[result] = specification;
+      taskIdMap[specification] = taskIdCounter++;
+      log.add("create leave");
+      return result;
+    } else if (specification is PeriodicTimerTaskSpecification) {
+      log.add("create enter "
+          "zone: ${self['name']} "
+          "spec-duration: ${specification.duration} "
+          "spec-oneshot?: ${specification.isOneShot}");
+      var result = parent.createTask(zone, create, specification);
+      taskMap[result] = specification;
+      taskIdMap[specification] = taskIdCounter++;
+      log.add("create leave");
+      return result;
+    }
+    return parent.createTask(zone, create, specification);
+  }
+
+  void runTaskHandler(Zone self, ZoneDelegate parent, Zone zone, TaskRun run,
+      Object task, Object arg) {
+    var taskMap = self['taskMap'];
+    var taskIdMap = self['taskIdMap'];
+    if (taskMap.containsKey(task)) {
+      var spec = taskMap[task];
+      log.add("run enter "
+          "zone: ${self['name']} "
+          "task-id: ${taskIdMap[spec]} "
+          "arg: $arg");
+      parent.runTask(zone, run, task, arg);
+      log.add("run leave");
+      return;
+    }
+    parent.runTask(zone, run, task, arg);
+  }
+
+  runZoned(() async {
+    var completer0 = new Completer();
+    Timer.run(() {
+      completer0.complete("done");
+    });
+    await completer0.future;
+
+    Expect.listEquals([
+      'create enter zone: custom zone spec-duration: 0:00:00.000000 '
+          'spec-oneshot?: true',
+      'create leave',
+      'run enter zone: custom zone task-id: 0 arg: null',
+      'run leave'
+    ], log);
+    log.clear();
+
+    var completer1 = new Completer();
+    var counter1 = 0;
+    new Timer.periodic(const Duration(milliseconds: 5), (Timer timer) {
+      if (counter1++ > 1) {
+        timer.cancel();
+        completer1.complete("done");
+      }
+    });
+    await completer1.future;
+
+    Expect.listEquals([
+      'create enter zone: custom zone spec-duration: 0:00:00.005000 '
+          'spec-oneshot?: false',
+      'create leave',
+      'run enter zone: custom zone task-id: 1 arg: null',
+      'run leave',
+      'run enter zone: custom zone task-id: 1 arg: null',
+      'run leave',
+      'run enter zone: custom zone task-id: 1 arg: null',
+      'run leave'
+    ], log);
+    log.clear();
+
+    testCompleter.complete("done");
+    asyncEnd();
+  },
+      zoneValues: {'name': 'custom zone', 'taskMap': {}, 'taskIdMap': {}},
+      zoneSpecification: new ZoneSpecification(
+          createTask: createTaskHandler,
+          runTask: runTaskHandler));
+
+  return testCompleter.future;
+}
+
+/// More complicated zone, that intercepts...
+Future testTimerTask2() {
+  List log = [];
+
+  var testCompleter = new Completer();
+  asyncStart();
+
+  int taskIdCounter = 0;
+
+  Object createTaskHandler(Zone self, ZoneDelegate parent, Zone zone,
+      TaskCreate create, TaskSpecification specification) {
+    var taskMap = self['taskMap'];
+    var taskIdMap = self['taskIdMap'];
+    if (specification is SingleShotTimerTaskSpecification) {
+      log.add("create enter "
+          "zone: ${self['name']} "
+          "spec-duration: ${specification.duration} "
+          "spec-oneshot?: ${specification.isOneShot}");
+      var mySpec = new MyTimerSpecification(specification.callback,
+          specification.duration + const Duration(milliseconds: 2));
+      var result = parent.createTask(zone, create, mySpec);
+      taskMap[result] = specification;
+      taskIdMap[specification] = taskIdCounter++;
+      log.add("create leave");
+      return result;
+    } else if (specification is PeriodicTimerTaskSpecification) {
+      log.add("create enter "
+          "zone: ${self['name']} "
+          "spec-duration: ${specification.duration} "
+          "spec-oneshot?: ${specification.isOneShot}");
+      var mySpec = new MyPeriodicTimerSpecification(specification.callback,
+          specification.duration + const Duration(milliseconds: 2));
+      var result = parent.createTask(zone, create, specification);
+      taskMap[result] = specification;
+      taskIdMap[specification] = taskIdCounter++;
+      log.add("create leave");
+      return result;
+    }
+    return parent.createTask(zone, create, specification);
+  }
+
+  void runTaskHandler(Zone self, ZoneDelegate parent, Zone zone, TaskRun run,
+      Object task, Object arg) {
+    var taskMap = self['taskMap'];
+    var taskIdMap = self['taskIdMap'];
+    if (taskMap.containsKey(task)) {
+      var spec = taskMap[task];
+      log.add("run enter "
+          "zone: ${self['name']} "
+          "task-id: ${taskIdMap[spec]} "
+          "arg: $arg");
+      parent.runTask(zone, run, task, arg);
+      log.add("run leave");
+      return;
+    }
+    parent.runTask(zone, run, task, arg);
+  }
+
+  runZoned(() async {
+    var completer0 = new Completer();
+    Timer.run(() {
+      completer0.complete("done");
+    });
+    await completer0.future;
+
+    // No visible change (except for the zone name) in the log, compared to the
+    // simple invocations.
+    Expect.listEquals([
+      'create enter zone: outer-zone spec-duration: 0:00:00.000000 '
+          'spec-oneshot?: true',
+      'create leave',
+      'run enter zone: outer-zone task-id: 0 arg: null',
+      'run leave'
+    ], log);
+    log.clear();
+
+    var completer1 = new Completer();
+    var counter1 = 0;
+    new Timer.periodic(const Duration(milliseconds: 5), (Timer timer) {
+      if (counter1++ > 1) {
+        timer.cancel();
+        completer1.complete("done");
+      }
+    });
+    await completer1.future;
+
+    // No visible change (except for the zone nome) in the log, compared to the
+    // simple invocations.
+    Expect.listEquals([
+      'create enter zone: outer-zone spec-duration: 0:00:00.005000 '
+          'spec-oneshot?: false',
+      'create leave',
+      'run enter zone: outer-zone task-id: 1 arg: null',
+      'run leave',
+      'run enter zone: outer-zone task-id: 1 arg: null',
+      'run leave',
+      'run enter zone: outer-zone task-id: 1 arg: null',
+      'run leave'
+    ], log);
+    log.clear();
+
+    var nestedCompleter = new Completer();
+
+    runZoned(() async {
+      var completer0 = new Completer();
+      Timer.run(() {
+        completer0.complete("done");
+      });
+      await completer0.future;
+
+      // The outer zone sees the duration change of the inner zone.
+      Expect.listEquals([
+        'create enter zone: inner-zone spec-duration: 0:00:00.000000 '
+            'spec-oneshot?: true',
+        'create enter zone: outer-zone spec-duration: 0:00:00.002000 '
+            'spec-oneshot?: true',
+        'create leave',
+        'create leave',
+        'run enter zone: inner-zone task-id: 3 arg: null',
+        'run enter zone: outer-zone task-id: 2 arg: null',
+        'run leave',
+        'run leave'
+      ], log);
+      log.clear();
+
+      var completer1 = new Completer();
+      var counter1 = 0;
+      new Timer.periodic(const Duration(milliseconds: 5), (Timer timer) {
+        if (counter1++ > 1) {
+          timer.cancel();
+          completer1.complete("done");
+        }
+      });
+      await completer1.future;
+
+      // The outer zone sees the duration change of the inner zone.
+      Expect.listEquals([
+        'create enter zone: inner-zone spec-duration: 0:00:00.005000 '
+            'spec-oneshot?: false',
+        'create enter zone: outer-zone spec-duration: 0:00:00.005000 '
+            'spec-oneshot?: false',
+        'create leave',
+        'create leave',
+        'run enter zone: inner-zone task-id: 5 arg: null',
+        'run enter zone: outer-zone task-id: 4 arg: null',
+        'run leave',
+        'run leave',
+        'run enter zone: inner-zone task-id: 5 arg: null',
+        'run enter zone: outer-zone task-id: 4 arg: null',
+        'run leave',
+        'run leave',
+        'run enter zone: inner-zone task-id: 5 arg: null',
+        'run enter zone: outer-zone task-id: 4 arg: null',
+        'run leave',
+        'run leave'
+      ], log);
+      log.clear();
+
+      nestedCompleter.complete("done");
+    },
+        zoneValues: {'name': 'inner-zone', 'taskMap': {}, 'taskIdMap': {}},
+        zoneSpecification: new ZoneSpecification(
+            createTask: createTaskHandler,
+            runTask: runTaskHandler));
+
+    await nestedCompleter.future;
+    testCompleter.complete("done");
+    asyncEnd();
+  },
+      zoneValues: {'name': 'outer-zone', 'taskMap': {}, 'taskIdMap': {}},
+      zoneSpecification: new ZoneSpecification(
+          createTask: createTaskHandler,
+          runTask: runTaskHandler));
+
+  return testCompleter.future;
+}
+
+class TimerEntry {
+  final int time;
+  final SimulatedTimer timer;
+
+  TimerEntry(this.time, this.timer);
+}
+
+class SimulatedTimer implements Timer {
+  static int _idCounter = 0;
+
+  Zone _zone;
+  final int _id = _idCounter++;
+  final Duration _duration;
+  final Function _callback;
+  final bool _isPeriodic;
+  bool _isActive = true;
+
+  SimulatedTimer(this._zone, this._duration, this._callback, this._isPeriodic);
+
+  bool get isActive => _isActive;
+
+  void cancel() {
+    _isActive = false;
+  }
+
+  void _run() {
+    if (!isActive) return;
+    _zone.runTask(_runTimer, this, null);
+  }
+
+  static void _runTimer(SimulatedTimer timer, _) {
+    if (timer._isPeriodic) {
+      timer._callback(timer);
+    } else {
+      timer._callback();
+    }
+  }
+}
+
+testSimulatedTimer() {
+  List log = [];
+
+  var currentTime = 0;
+  // Using a simple list as queue. Not very efficient, but the test has only
+  // very few timers running at the same time.
+  var queue = new DoubleLinkedQueue<TimerEntry>();
+
+  // Schedules the given callback at now + duration.
+  void schedule(int scheduledTime, SimulatedTimer timer) {
+    log.add("scheduling timer ${timer._id} for $scheduledTime");
+    if (queue.isEmpty) {
+      queue.add(new TimerEntry(scheduledTime, timer));
+    } else {
+      DoubleLinkedQueueEntry current = queue.firstEntry();
+      while (current != null) {
+        if (current.element.time <= scheduledTime) {
+          current = current.nextEntry();
+        } else {
+          current.prepend(new TimerEntry(scheduledTime, timer));
+          break;
+        }
+      }
+      if (current == null) {
+        queue.add(new TimerEntry(scheduledTime, timer));
+      }
+    }
+  }
+
+  void runQueue() {
+    while (queue.isNotEmpty) {
+      var item = queue.removeFirst();
+      // If multiple callbacks were scheduled at the same time, increment the
+      // current time instead of staying at the same time.
+      currentTime = item.time > currentTime ? item.time : currentTime + 1;
+      SimulatedTimer timer = item.timer;
+      log.add("running timer ${timer._id} at $currentTime "
+          "(active?: ${timer.isActive})");
+      if (!timer.isActive) continue;
+      if (timer._isPeriodic) {
+        schedule(currentTime + timer._duration.inMilliseconds, timer);
+      }
+      item.timer._run();
+    }
+  }
+
+  SimulatedTimer createSimulatedOneShotTimer(
+      SingleShotTimerTaskSpecification spec, Zone zone) {
+    var timer = new SimulatedTimer(zone, spec.duration, spec.callback, false);
+    schedule(currentTime + spec.duration.inMilliseconds, timer);
+    return timer;
+  }
+
+  SimulatedTimer createSimulatedPeriodicTimer(
+      PeriodicTimerTaskSpecification spec, Zone zone) {
+    var timer = new SimulatedTimer(zone, spec.duration, spec.callback, true);
+    schedule(currentTime + spec.duration.inMilliseconds, timer);
+    return timer;
+  }
+
+  Object createSimulatedTaskHandler(Zone self, ZoneDelegate parent, Zone zone,
+      TaskCreate create, TaskSpecification specification) {
+    var taskMap = self['taskMap'];
+    var taskIdMap = self['taskIdMap'];
+    if (specification is SingleShotTimerTaskSpecification) {
+      log.add("create enter "
+          "zone: ${self['name']} "
+          "spec-duration: ${specification.duration} "
+          "spec-oneshot?: ${specification.isOneShot}");
+      var result =
+          parent.createTask(zone, createSimulatedOneShotTimer, specification);
+      log.add("create leave");
+      return result;
+    }
+    if (specification is PeriodicTimerTaskSpecification) {
+      log.add("create enter "
+          "zone: ${self['name']} "
+          "spec-duration: ${specification.duration} "
+          "spec-oneshot?: ${specification.isOneShot}");
+      var result =
+          parent.createTask(zone, createSimulatedPeriodicTimer, specification);
+      log.add("create leave");
+      return result;
+    }
+    return parent.createTask(zone, create, specification);
+  }
+
+  runZoned(() {
+    Timer.run(() {
+      log.add("running Timer.run");
+    });
+
+    var timer0;
+
+    new Timer(const Duration(milliseconds: 10), () {
+      log.add("running Timer(10)");
+      timer0.cancel();
+      log.add("canceled timer0");
+    });
+
+    timer0 = new Timer(const Duration(milliseconds: 15), () {
+      log.add("running Timer(15)");
+    });
+
+    var counter1 = 0;
+    new Timer.periodic(const Duration(milliseconds: 5), (Timer timer) {
+      log.add("running periodic timer $counter1");
+      if (counter1++ > 1) {
+        timer.cancel();
+      }
+    });
+  },
+      zoneSpecification:
+          new ZoneSpecification(createTask: createSimulatedTaskHandler));
+
+  runQueue();
+
+  Expect.listEquals([
+    'create enter zone: null spec-duration: 0:00:00.000000 spec-oneshot?: true',
+    'scheduling timer 0 for 0',
+    'create leave',
+    'create enter zone: null spec-duration: 0:00:00.010000 spec-oneshot?: true',
+    'scheduling timer 1 for 10',
+    'create leave',
+    'create enter zone: null spec-duration: 0:00:00.015000 spec-oneshot?: true',
+    'scheduling timer 2 for 15',
+    'create leave',
+    'create enter zone: null spec-duration: 0:00:00.005000 '
+        'spec-oneshot?: false',
+    'scheduling timer 3 for 5',
+    'create leave',
+    'running timer 0 at 1 (active?: true)',
+    'running Timer.run',
+    'running timer 3 at 5 (active?: true)',
+    'scheduling timer 3 for 10',
+    'running periodic timer 0',
+    'running timer 1 at 10 (active?: true)',
+    'running Timer(10)',
+    'canceled timer0',
+    'running timer 3 at 11 (active?: true)',
+    'scheduling timer 3 for 16',
+    'running periodic timer 1',
+    'running timer 2 at 15 (active?: false)',
+    'running timer 3 at 16 (active?: true)',
+    'scheduling timer 3 for 21',
+    'running periodic timer 2',
+    'running timer 3 at 21 (active?: false)'
+  ], log);
+  log.clear();
+}
+
+runTests() async {
+  await testTimerTask();
+  await testTimerTask2();
+  testSimulatedTimer();
+}
+
+main() {
+  asyncStart();
+  runTests().then((_) {
+    asyncEnd();
+  });
+}
diff --git a/tests/lib/lib.status b/tests/lib/lib.status
index 8d3dcee..ecfc10d 100644
--- a/tests/lib/lib.status
+++ b/tests/lib/lib.status
@@ -167,6 +167,8 @@
 async/stream_transformation_broadcast_test: RuntimeError # Timer interface not supported: Issue 7728.
 async/stream_controller_test: Fail # Timer interface not supported: Issue 7728.
 async/future_constructor2_test: Fail # Timer interface not supported: Issue 7728.
+async/zone_timer_task_test: Fail # Timer interface not supported: Issue 7728.
+async/zone_task_test: Fail # Timer interface not supported: Issue 7728.
 mirrors/mirrors_reader_test: Skip # Running in v8 suffices. Issue 16589 - RuntimeError.  Issue 22130 - Crash (out of memory).
 
 [ $compiler == dart2js && $checked ]
diff --git a/tools/dom/docs/docs.json b/tools/dom/docs/docs.json
index 123447d..98455fd 100644
--- a/tools/dom/docs/docs.json
+++ b/tools/dom/docs/docs.json
@@ -4201,7 +4201,7 @@
           "   *",
           "   * Note: Most simple HTTP requests can be accomplished using the [getString],",
           "   * [request], [requestCrossOrigin], or [postFormData] methods. Use of this",
-          "   * `open` method is intended only for more complext HTTP requests where",
+          "   * `open` method is intended only for more complex HTTP requests where",
           "   * finer-grained control is needed.",
           "   */"
         ],
@@ -4298,7 +4298,7 @@
           "   *",
           "   * Note: Most simple HTTP requests can be accomplished using the [getString],",
           "   * [request], [requestCrossOrigin], or [postFormData] methods. Use of this",
-          "   * `send` method is intended only for more complext HTTP requests where",
+          "   * `send` method is intended only for more complex HTTP requests where",
           "   * finer-grained control is needed.",
           "   *",
           "   * ## Other resources",
diff --git a/tools/dom/scripts/htmlrenamer.py b/tools/dom/scripts/htmlrenamer.py
index 856da50..7ec0685 100644
--- a/tools/dom/scripts/htmlrenamer.py
+++ b/tools/dom/scripts/htmlrenamer.py
@@ -404,6 +404,8 @@
   'Window.requestAnimationFrame',
   'Window.setInterval',
   'Window.setTimeout',
+
+  'XMLHttpRequest.send',
 ])
 
 # Members from the standard dom that exist in the dart:html library with
diff --git a/tools/dom/src/EventStreamProvider.dart b/tools/dom/src/EventStreamProvider.dart
index 474a143..b6c32fc 100644
--- a/tools/dom/src/EventStreamProvider.dart
+++ b/tools/dom/src/EventStreamProvider.dart
@@ -118,6 +118,41 @@
   StreamSubscription<T> capture(void onData(T event));
 }
 
+/// Task specification for DOM Events.
+///
+/// *Experimental*. May disappear without notice.
+class EventSubscriptionSpecification<T extends Event>
+    implements TaskSpecification {
+  @override
+  final String name;
+  @override
+  final bool isOneShot;
+
+  final EventTarget target;
+  /// The event-type of the event. For example 'click' for click events.
+  final String eventType;
+  // TODO(floitsch): the first generic argument should be 'void'.
+  final ZoneUnaryCallback<dynamic, T> onData;
+  final bool useCapture;
+
+  EventSubscriptionSpecification({this.name, this.isOneShot, this.target,
+      this.eventType, void this.onData(T event), this.useCapture});
+
+  /// Returns a copy of this instance, with every non-null argument replaced
+  /// by the given value.
+  EventSubscriptionSpecification<T> replace(
+      {String name, bool isOneShot, EventTarget target,
+       String eventType, void onData(T event), bool useCapture}) {
+    return new EventSubscriptionSpecification<T>(
+        name: name ?? this.name,
+        isOneShot: isOneShot ?? this.isOneShot,
+        target: target ?? this.target,
+        eventType: eventType ?? this.eventType,
+        onData: onData ?? this.onData,
+        useCapture: useCapture ?? this.useCapture);
+  }
+}
+
 /**
  * Adapter for exposing DOM events as Dart streams.
  */
@@ -125,8 +160,16 @@
   final EventTarget _target;
   final String _eventType;
   final bool _useCapture;
+  /// The name that is used in the task specification.
+  final String _name;
+  /// Whether the stream can trigger multiple times.
+  final bool _isOneShot;
 
-  _EventStream(this._target, this._eventType, this._useCapture);
+  _EventStream(this._target, String eventType, this._useCapture,
+      {String name, bool isOneShot: false})
+      : _eventType = eventType,
+        _isOneShot = isOneShot,
+        _name = name ?? "dart.html.event.$eventType";
 
   // DOM events are inherently multi-subscribers.
   Stream<T> asBroadcastStream({void onListen(StreamSubscription<T> subscription),
@@ -134,13 +177,31 @@
       => this;
   bool get isBroadcast => true;
 
+  StreamSubscription<T> _listen(
+      void onData(T event), {bool useCapture}) {
+
+    if (identical(Zone.current, Zone.ROOT)) {
+      return new _EventStreamSubscription<T>(
+          this._target, this._eventType, onData, this._useCapture,
+          Zone.current);
+    }
+
+    var specification = new EventSubscriptionSpecification<T>(
+        name: this._name, isOneShot: this._isOneShot,
+        target: this._target, eventType: this._eventType,
+        onData: onData, useCapture: useCapture);
+    // We need to wrap the _createStreamSubscription call, since a tear-off
+    // would not bind the generic type 'T'.
+    return Zone.current.createTask((spec, Zone zone) {
+      return _createStreamSubscription/*<T>*/(spec, zone);
+    }, specification);
+  }
+
   StreamSubscription<T> listen(void onData(T event),
       { Function onError,
         void onDone(),
         bool cancelOnError}) {
-
-    return new _EventStreamSubscription<T>(
-        this._target, this._eventType, onData, this._useCapture);
+    return _listen(onData, useCapture: this._useCapture);
   }
 }
 
@@ -155,8 +216,9 @@
  */
 class _ElementEventStreamImpl<T extends Event> extends _EventStream<T>
     implements ElementStream<T> {
-  _ElementEventStreamImpl(target, eventType, useCapture) :
-      super(target, eventType, useCapture);
+  _ElementEventStreamImpl(target, eventType, useCapture,
+      {String name, bool isOneShot: false}) :
+      super(target, eventType, useCapture, name: name, isOneShot: isOneShot);
 
   Stream<T> matches(String selector) => this.where(
       (event) => _matchesWithAncestors(event, selector)).map((e) {
@@ -164,9 +226,9 @@
         return e;
       });
 
-  StreamSubscription<T> capture(void onData(T event)) =>
-    new _EventStreamSubscription<T>(
-        this._target, this._eventType, onData, true);
+  StreamSubscription<T> capture(void onData(T event)) {
+    return _listen(onData, useCapture: true);
+  }
 }
 
 /**
@@ -215,7 +277,13 @@
   bool get isBroadcast => true;
 }
 
-// We would like this to just be EventListener<T> but that typdef cannot
+StreamSubscription/*<T>*/ _createStreamSubscription/*<T>*/(
+    EventSubscriptionSpecification/*<T>*/ spec, Zone zone) {
+  return new _EventStreamSubscription/*<T>*/(spec.target, spec.eventType,
+      spec.onData, spec.useCapture, zone);
+}
+
+// We would like this to just be EventListener<T> but that typedef cannot
 // use generics until dartbug/26276 is fixed.
 typedef _EventListener<T extends Event>(T event);
 
@@ -224,15 +292,19 @@
   EventTarget _target;
   final String _eventType;
   EventListener _onData;
+  EventListener _domCallback;
   final bool _useCapture;
+  final Zone _zone;
 
   // TODO(jacobr): for full strong mode correctness we should write
-  // _onData = onData == null ? null : _wrapZone/*<Event, dynamic>*/((e) => onData(e as T))
+  // _onData = onData == null ? null : _wrapZone/*<dynamic, Event>*/((e) => onData(e as T))
   // but that breaks 114 co19 tests as well as multiple html tests as it is reasonable
   // to pass the wrong type of event object to an event listener as part of a
   // test.
   _EventStreamSubscription(this._target, this._eventType, void onData(T event),
-      this._useCapture) : _onData = _wrapZone/*<Event, dynamic>*/(onData) {
+      this._useCapture, Zone zone)
+      : _zone = zone,
+        _onData = _registerZone/*<dynamic, Event>*/(zone, onData) {
     _tryResume();
   }
 
@@ -254,7 +326,7 @@
     }
     // Remove current event listener.
     _unlisten();
-    _onData = _wrapZone/*<Event, dynamic>*/(handleData);
+    _onData = _registerZone/*<dynamic, Event>*/(_zone, handleData);
     _tryResume();
   }
 
@@ -283,14 +355,25 @@
   }
 
   void _tryResume() {
-    if (_onData != null && !isPaused) {
-      _target.addEventListener(_eventType, _onData, _useCapture);
+    if (_onData == null || isPaused) return;
+    if (identical(_zone, Zone.ROOT)) {
+      _domCallback = _onData;
+    } else {
+      _domCallback = (event) {
+        _zone.runTask(_runEventNotification, this, event);
+      };
     }
+    _target.addEventListener(_eventType, _domCallback, _useCapture);
+  }
+
+  static void _runEventNotification/*<T>*/(
+      _EventStreamSubscription/*<T>*/ subscription, /*=T*/ event) {
+    subscription._onData(event);
   }
 
   void _unlisten() {
     if (_onData != null) {
-      _target.removeEventListener(_eventType, _onData, _useCapture);
+      _target.removeEventListener(_eventType, _domCallback, _useCapture);
     }
   }
 
diff --git a/tools/dom/src/shared_html.dart b/tools/dom/src/shared_html.dart
index 7342cdf..f2c32f3 100644
--- a/tools/dom/src/shared_html.dart
+++ b/tools/dom/src/shared_html.dart
@@ -4,31 +4,26 @@
 
 part of dart.dom.html;
 
-// TODO(jacobr): remove these typedefs when dart:async supports generic types.
-typedef R _wrapZoneCallback<A, R>(A a);
-typedef R _wrapZoneBinaryCallback<A, B, R>(A a, B b);
-
-_wrapZoneCallback/*<A, R>*/ _wrapZone/*<A, R>*/(_wrapZoneCallback/*<A, R>*/ callback) {
-  // For performance reasons avoid wrapping if we are in the root zone.
-  if (Zone.current == Zone.ROOT) return callback;
+ZoneUnaryCallback/*<R, T>*/ _registerZone/*<R, T>*/(Zone zone,
+    ZoneUnaryCallback/*<R, T>*/ callback) {
+  // For performance reasons avoid registering if we are in the root zone.
+  if (identical(zone, Zone.ROOT)) return callback;
   if (callback == null) return null;
-  // TODO(jacobr): we cast to _wrapZoneCallback/*<A, R>*/ to hack around missing
-  // generic method support in zones.
-  // ignore: STRONG_MODE_DOWN_CAST_COMPOSITE
-  _wrapZoneCallback/*<A, R>*/ wrapped =
-      Zone.current.bindUnaryCallback(callback, runGuarded: true);
-  return wrapped;
+  return zone.registerUnaryCallback(callback);
 }
 
-_wrapZoneBinaryCallback/*<A, B, R>*/ _wrapBinaryZone/*<A, B, R>*/(_wrapZoneBinaryCallback/*<A, B, R>*/ callback) {
-  if (Zone.current == Zone.ROOT) return callback;
+ZoneUnaryCallback/*<R, T>*/ _wrapZone/*<R, T>*/(ZoneUnaryCallback/*<R, T>*/ callback) {
+  // For performance reasons avoid wrapping if we are in the root zone.
+  if (identical(Zone.current, Zone.ROOT)) return callback;
   if (callback == null) return null;
-  // We cast to _wrapZoneBinaryCallback/*<A, B, R>*/ to hack around missing
-  // generic method support in zones.
-  // ignore: STRONG_MODE_DOWN_CAST_COMPOSITE
-  _wrapZoneBinaryCallback/*<A, B, R>*/ wrapped =
-      Zone.current.bindBinaryCallback(callback, runGuarded: true);
-  return wrapped;
+  return Zone.current.bindUnaryCallback(callback, runGuarded: true);
+}
+
+ZoneBinaryCallback/*<R, A, B>*/ _wrapBinaryZone/*<R, A, B>*/(
+    ZoneBinaryCallback/*<R, A, B>*/ callback) {
+  if (identical(Zone.current, Zone.ROOT)) return callback;
+  if (callback == null) return null;
+  return Zone.current.bindBinaryCallback(callback, runGuarded: true);
 }
 
 /**
diff --git a/tools/dom/templates/html/impl/impl_Window.darttemplate b/tools/dom/templates/html/impl/impl_Window.darttemplate
index 8abac37..fe4db3d 100644
--- a/tools/dom/templates/html/impl/impl_Window.darttemplate
+++ b/tools/dom/templates/html/impl/impl_Window.darttemplate
@@ -4,6 +4,99 @@
 
 part of $LIBRARYNAME;
 
+typedef void RemoveFrameRequestMapping(int id);
+
+/**
+ * The task object representing animation-frame requests.
+ *
+ * For historical reasons, [Window.requestAnimationFrame] returns an integer
+ * to users. However, zone tasks must be unique objects, and an integer can
+ * therefore not be used as task object. The [Window] class thus keeps a mapping
+ * from the integer ID to the corresponding task object. All zone related
+ * operations work on this task object, whereas users of
+ * [Window.requestAnimationFrame] only see the integer ID.
+ *
+ * Since this mapping takes up space, it must be removed when the
+ * animation-frame task has triggered. The default implementation does this
+ * automatically, but intercepting implementations of `requestAnimationFrame`
+ * must make sure to call the [AnimationFrameTask.removeMapping]
+ * function that is provided in the task specification.
+ *
+ * *Experimental*. This class may disappear without notice.
+ */
+abstract class AnimationFrameTask {
+  /** The ID that is returned to users. */
+  int get id;
+
+  /** The zone in which the task will run. */
+  Zone get zone;
+
+  /**
+   * Cancels the animation-frame request.
+   *
+   * A call to [Window.cancelAnimationFrame] with an `id` argument equal to [id]
+   * forwards the request to this function.
+   *
+   * Zones that intercept animation-frame requests implement this method so
+   * that they can react to cancelation requests.
+   */
+  void cancel(Window window);
+
+  /**
+   * Maps animation-frame request IDs to their task objects.
+   */
+  static final Map<int, _AnimationFrameTask> _tasks = {};
+
+  /**
+   * Removes the mapping from [id] to [AnimationFrameTask].
+   *
+   * This function must be invoked by user-implemented animation-frame
+   * tasks, before running [callback].
+   *
+   * See [AnimationFrameTask].
+   */
+  static void removeMapping(int id) {
+    _tasks.remove(id);
+  }
+}
+
+class _AnimationFrameTask implements AnimationFrameTask {
+  final int id;
+  final Zone zone;
+  final FrameRequestCallback _callback;
+
+  _AnimationFrameTask(this.id, this.zone, this._callback);
+
+  void cancel(Window window) {
+    window._cancelAnimationFrame(this.id);
+  }
+}
+
+/**
+ * The task specification for an animation-frame request.
+ *
+ * *Experimental*. This class may disappear without notice.
+ */
+class AnimationFrameRequestSpecification implements TaskSpecification {
+  /**
+   * The window on which [Window.requestAnimationFrame] was invoked.
+   */
+  final Window window;
+
+  /**
+   * The callback that is executed when the animation-frame is ready.
+   *
+   * Note that the callback hasn't been registered in any zone when the `create`
+   * function (passed to [Zone.createTask]) is invoked.
+   */
+  final FrameRequestCallback callback;
+
+  AnimationFrameRequestSpecification(this.window, this.callback);
+
+  String get name => "dart.html.request-animation-frame";
+  bool get isOneShot => true;
+}
+
 @DocsEditable()
 $if DART2JS
 $(ANNOTATIONS)@Native("Window,DOMWindow")
@@ -29,9 +122,7 @@
    */
   Future<num> get animationFrame {
     var completer = new Completer<num>.sync();
-    requestAnimationFrame((time) {
-      completer.complete(time);
-    });
+    requestAnimationFrame(completer.complete);
     return completer.future;
   }
 
@@ -115,7 +206,30 @@
   @DomName('Window.requestAnimationFrame')
   int requestAnimationFrame(FrameRequestCallback callback) {
     _ensureRequestAnimationFrame();
-    return _requestAnimationFrame(_wrapZone/*<num, dynamic>*/(callback));
+    if (identical(Zone.current, Zone.ROOT)) {
+      return _requestAnimationFrame(callback);
+    }
+    var spec = new AnimationFrameRequestSpecification(this, callback);
+    var task = Zone.current.createTask/*<AnimationFrameTask>*/(
+        _createAnimationFrameTask, spec);
+    AnimationFrameTask._tasks[task.id] = task;
+    return task.id;
+  }
+
+  static _AnimationFrameTask _createAnimationFrameTask(
+      AnimationFrameRequestSpecification spec, Zone zone) {
+    var task;
+    var id = spec.window._requestAnimationFrame((num time) {
+      AnimationFrameTask.removeMapping(task.id);
+      zone.runTask(_runAnimationFrame, task, time);
+    });
+    var callback = zone.registerUnaryCallback(spec.callback);
+    task = new _AnimationFrameTask(id, zone, callback);
+    return task;
+  }
+
+  static void _runAnimationFrame(_AnimationFrameTask task, num time) {
+    task._callback(time);
   }
 
   /**
@@ -128,7 +242,13 @@
    */
   void cancelAnimationFrame(int id) {
     _ensureRequestAnimationFrame();
-    _cancelAnimationFrame(id);
+    var task = AnimationFrameTask._tasks.remove(id);
+    if (task == null) {
+      // Assume that the animation frame request wasn't intercepted by a zone.
+      _cancelAnimationFrame(id);
+      return;
+    }
+    task.cancel(this);
   }
 
   @JSName('requestAnimationFrame')
diff --git a/tools/dom/templates/html/impl/impl_XMLHttpRequest.darttemplate b/tools/dom/templates/html/impl/impl_XMLHttpRequest.darttemplate
index 9ad00f6..35683fc 100644
--- a/tools/dom/templates/html/impl/impl_XMLHttpRequest.darttemplate
+++ b/tools/dom/templates/html/impl/impl_XMLHttpRequest.darttemplate
@@ -4,6 +4,109 @@
 
 part of $LIBRARYNAME;
 
+/**
+ * A task specification for HTTP requests.
+ *
+ * This specification is not available when an HTTP request is sent through
+ * direct use of [HttpRequest.send]. See [HttpRequestSendTaskSpecification].
+ *
+ * A task created from this specification is a `Future<HttpRequest>`.
+ *
+ * *Experimental*. This class may disappear without notice.
+ */
+class HttpRequestTaskSpecification extends TaskSpecification {
+  /// The URL of the request.
+  final String url;
+
+  /// The HTTP request method.
+  ///
+  /// By default (when `null`) this is a `"GET"` request. Alternatively, the
+  /// method can be `"POST"`, `"PUT"`, `"DELETE"`, etc.
+  final String method;
+
+  /// Whether the request should send credentials. Credentials are only useful
+  /// for cross-origin requests.
+  ///
+  /// See [HttpRequest.request] for more information.
+  final bool withCredentials;
+
+  /// The desired response format.
+  ///
+  /// Supported types are:
+  /// - `""`: (same as `"text"`),
+  /// - `"arraybuffer"`,
+  /// - `"blob"`,
+  /// - `"document"`,
+  /// - `"json"`,
+  /// - `"text"`
+  ///
+  /// When no value is provided (when equal to `null`) defaults to `""`.
+  final String responseType;
+
+  /// The desired MIME type.
+  ///
+  /// This overrides the default MIME type which is set up to transfer textual
+  /// data.
+  final String mimeType;
+
+  /// The request headers that should be sent with the request.
+  final Map<String, String> requestHeaders;
+
+  /// The data that is sent with the request.
+  ///
+  /// When data is provided (the value is not `null`), it must be a
+  /// [ByteBuffer], [Blob], [Document], [String], or [FormData].
+  final dynamic sendData;
+
+  /// The function that is invoked on progress updates. This function is
+  /// registered as an event listener on the created [HttpRequest] object, and
+  /// thus has its own task. Further invocations of the progress function do
+  /// *not* use the HTTP request task as task object.
+  ///
+  /// Creating an HTTP request automatically registers the on-progress listener.
+  final ZoneUnaryCallback<dynamic, ProgressEvent> onProgress;
+
+  HttpRequestTaskSpecification(this.url,
+      {String this.method, bool this.withCredentials, String this.responseType,
+      String this.mimeType, Map<String, String> this.requestHeaders,
+      this.sendData,
+      void this.onProgress(ProgressEvent e)});
+
+  String get name => "dart.html.http-request";
+  bool get isOneShot => true;
+}
+
+/**
+ * A task specification for HTTP requests that are initiated through a direct
+ * invocation of [HttpRequest.send].
+ *
+ * This specification serves as signal to zones that an HTTP request has been
+ * initiated. The created task is the [request] object itself, and
+ * no callback is ever executed in this task.
+ *
+ * Note that event listeners on the HTTP request are also registered in the
+ * zone (although with their own task creations), and that a zone can thus
+ * detect when the HTTP request returns.
+ *
+ * HTTP requests that are initiated through `request` methods don't use
+ * this class but use [HttpRequestTaskSpecification].
+ *
+ * *Experimental*. This class may disappear without notice.
+ */
+class HttpRequestSendTaskSpecification extends TaskSpecification {
+  final HttpRequest request;
+  final dynamic sendData;
+
+  HttpRequestSendTaskSpecification(this.request, this.sendData);
+
+  String get name => "dart.html.http-request-send";
+
+  /**
+   * No callback is ever executed in an HTTP request send task.
+   */
+  bool get isOneShot => false;
+}
+
  /**
   * A client-side XHR request for getting data from a URL,
   * formally known as XMLHttpRequest.
@@ -190,7 +293,34 @@
       {String method, bool withCredentials, String responseType,
       String mimeType, Map<String, String> requestHeaders, sendData,
       void onProgress(ProgressEvent e)}) {
+    var spec = new HttpRequestTaskSpecification(
+        url, method: method,
+        withCredentials: withCredentials,
+        responseType: responseType,
+        mimeType: mimeType,
+        requestHeaders: requestHeaders,
+        sendData: sendData,
+        onProgress: onProgress);
+
+    if (identical(Zone.current, Zone.ROOT)) {
+      return _createHttpRequestTask(spec, null);
+    }
+    return Zone.current.createTask(_createHttpRequestTask, spec);
+  }
+
+  static Future<HttpRequest> _createHttpRequestTask(
+      HttpRequestTaskSpecification spec, Zone zone) {
+    String url = spec.url;
+    String method = spec.method;
+    bool withCredentials = spec.withCredentials;
+    String responseType = spec.responseType;
+    String mimeType = spec.mimeType;
+    Map<String, String> requestHeaders = spec.requestHeaders;
+    var sendData = spec.sendData;
+    var onProgress = spec.onProgress;
+
     var completer = new Completer<HttpRequest>();
+    var task = completer.future;
 
     var xhr = new HttpRequest();
     if (method == null) {
@@ -230,23 +360,42 @@
       // redirect case will be handled by the browser before it gets to us,
       // so if we see it we should pass it through to the user.
       var unknownRedirect = xhr.status > 307 && xhr.status < 400;
-      
-      if (accepted || fileUri || notModified || unknownRedirect) {
+
+      var isSuccessful = accepted || fileUri || notModified || unknownRedirect;
+
+      if (zone == null && isSuccessful) {
         completer.complete(xhr);
-      } else {
+      } else if (zone == null) {
         completer.completeError(e);
+      } else if (isSuccessful) {
+        zone.runTask((task, value) {
+          completer.complete(value);
+        }, task, xhr);
+      } else {
+        zone.runTask((task, error) {
+          completer.completeError(error);
+        }, task, e);
       }
     });
 
-    xhr.onError.listen(completer.completeError);
-
-    if (sendData != null) {
-      xhr.send(sendData);
+    if (zone == null) {
+      xhr.onError.listen(completer.completeError);
     } else {
-      xhr.send();
+      xhr.onError.listen((error) {
+        zone.runTask((task, error) {
+          completer.completeError(error);
+        }, task, error);
+      });
     }
 
-    return completer.future;
+    if (sendData != null) {
+      // TODO(floitsch): should we go through 'send()' and have nested tasks?
+      xhr._send(sendData);
+    } else {
+      xhr._send();
+    }
+
+    return task;
   }
 
   /**
@@ -316,6 +465,9 @@
         return xhr.responseText;
       });
     }
+    // TODO(floitsch): the following code doesn't go through task zones.
+    // Since 'XDomainRequest' is an IE9 feature we should probably just remove
+    // it.
 $if DART2JS
     var completer = new Completer<String>();
     if (method == null) {
@@ -396,7 +548,7 @@
    *
    * Note: Most simple HTTP requests can be accomplished using the [getString],
    * [request], [requestCrossOrigin], or [postFormData] methods. Use of this
-   * `open` method is intended only for more complext HTTP requests where
+   * `open` method is intended only for more complex HTTP requests where
    * finer-grained control is needed.
    */
   @DomName('XMLHttpRequest.open')
@@ -413,5 +565,35 @@
   void open(String method, String url, {bool async, String user, String password}) native;
 $endif
 
+  /**
+   * Sends the request with any given `data`.
+   *
+   * Note: Most simple HTTP requests can be accomplished using the [getString],
+   * [request], [requestCrossOrigin], or [postFormData] methods. Use of this
+   * `send` method is intended only for more complex HTTP requests where
+   * finer-grained control is needed.
+   *
+   * ## Other resources
+   *
+   * * [XMLHttpRequest.send](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#send%28%29)
+   *   from MDN.
+   */
+  @DomName('XMLHttpRequest.send')
+  @DocsEditable()
+  void send([body_OR_data]) {
+    if (identical(Zone.current, Zone.ROOT)) {
+      _send(body_OR_data);
+    } else {
+      Zone.current.createTask(_createHttpRequestSendTask,
+          new HttpRequestSendTaskSpecification(this, body_OR_data));
+    }
+  }
+
+  static HttpRequest _createHttpRequestSendTask(
+      HttpRequestSendTaskSpecification spec, Zone zone) {
+    spec.request._send(spec.sendData);
+    return spec.request;
+  }
+
 $!MEMBERS
 }