Bug 1096044 - Add new actors to surface Web Animations API objects and methods to the devtools; r=harth
authorPatrick Brosset <pbrosset@mozilla.com>
Thu, 11 Dec 2014 20:08:49 +0100
changeset 219148 0812ccbf1b659304d484e186771632c834ddc558
parent 219147 c677f867a18a1c2627078263157848fb3b198406
child 219149 0d35326dfbae2d2dc62ebcd99d8696d8fedb9bbf
push id27956
push userkwierso@gmail.com
push dateFri, 12 Dec 2014 00:47:19 +0000
treeherdermozilla-central@32a2c5bd2f68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersharth
bugs1096044
milestone37.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1096044 - Add new actors to surface Web Animations API objects and methods to the devtools; r=harth
toolkit/devtools/server/actors/animation.js
toolkit/devtools/server/main.js
toolkit/devtools/server/moz.build
toolkit/devtools/server/tests/browser/animation.html
toolkit/devtools/server/tests/browser/browser.ini
toolkit/devtools/server/tests/browser/browser_animation_actors_01.js
toolkit/devtools/server/tests/browser/browser_animation_actors_02.js
toolkit/devtools/server/tests/browser/browser_animation_actors_03.js
toolkit/devtools/server/tests/browser/browser_animation_actors_04.js
toolkit/devtools/server/tests/browser/browser_timeline.js
toolkit/devtools/server/tests/browser/browser_timeline_actors.js
toolkit/devtools/server/tests/browser/browser_timeline_iframes.js
toolkit/devtools/server/tests/browser/head.js
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/actors/animation.js
@@ -0,0 +1,273 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * Set of actors that expose the Web Animations API to devtools protocol clients.
+ *
+ * The |Animations| actor is the main entry point. It is used to discover
+ * animation players on given nodes.
+ * There should only be one instance per debugger server.
+ *
+ * The |AnimationPlayer| actor provides attributes and methods to inspect an
+ * animation as well as pause/resume/seek it.
+ *
+ * The Web Animation spec implementation is ongoing in Gecko, and so this set
+ * of actors should evolve when the implementation progresses.
+ *
+ * References:
+ * - WebAnimation spec:
+ *   http://w3c.github.io/web-animations/
+ * - WebAnimation WebIDL files:
+ *   /dom/webidl/Animation*.webidl
+ */
+
+const {ActorClass, Actor,
+       FrontClass, Front,
+       Arg, method, RetVal} = require("devtools/server/protocol");
+const {NodeActor} = require("devtools/server/actors/inspector");
+
+/**
+ * The AnimationPlayerActor provides information about a given animation: its
+ * startTime, currentTime, current state, etc.
+ *
+ * Since the state of a player changes as the animation progresses it is often
+ * useful to call getCurrentState at regular intervals to get the current state.
+ *
+ * This actor also allows playing and pausing the animation.
+ */
+let AnimationPlayerActor = ActorClass({
+  typeName: "animationplayer",
+
+  /**
+   * @param {AnimationsActor} The main AnimationsActor instance
+   * @param {AnimationPlayer} The player object returned by getAnimationPlayers
+   * @param {DOMNode} The node targeted by this player
+   * @param {Number} Temporary work-around used to retrieve duration and
+   * iteration count from computed-style rather than from waapi. This is needed
+   * to know which duration to get, in case there are multiple css animations
+   * applied to the same node.
+   */
+  initialize: function(animationsActor, player, node, playerIndex) {
+    this.player = player;
+    this.node = node;
+    this.playerIndex = playerIndex;
+    this.styles = node.ownerDocument.defaultView.getComputedStyle(node);
+    Actor.prototype.initialize.call(this, animationsActor.conn);
+  },
+
+  destroy: function() {
+    this.player = this.node = this.styles = null;
+    Actor.prototype.destroy.call(this);
+  },
+
+  /**
+   * Release the actor, when it isn't needed anymore.
+   * Protocol.js uses this release method to call the destroy method.
+   */
+  release: method(function() {}, {release: true}),
+
+  form: function(detail) {
+    if (detail === "actorid") {
+      return this.actorID;
+    }
+
+    let data = this.getCurrentState();
+    data.actor = this.actorID;
+
+    return data;
+  },
+
+  /**
+   * Get the animation duration from this player, in milliseconds.
+   * Note that the Web Animations API doesn't yet offer a way to retrieve this
+   * directly from the AnimationPlayer object, so for now, a duration is only
+   * returned if found in the node's computed styles.
+   * @return {Number}
+   */
+  getDuration: function() {
+    let durationText;
+    if (this.styles.animationDuration !== "0s") {
+      durationText = this.styles.animationDuration;
+    } else if (this.styles.transitionDuration !== "0s") {
+      durationText = this.styles.transitionDuration;
+    } else {
+      return null;
+    }
+
+    if (durationText.indexOf(",") !== -1) {
+      durationText = durationText.split(",")[this.playerIndex];
+    }
+
+    return parseFloat(durationText) * 1000;
+  },
+
+  /**
+   * Get the animation iteration count for this player. That is, how many times
+   * is the animation scheduled to run.
+   * Note that the Web Animations API doesn't yet offer a way to retrieve this
+   * directly from the AnimationPlayer object, so for now, check for
+   * animationIterationCount in the node's computed styles, and return that.
+   * This style property defaults to 1 anyway.
+   * @return {Number}
+   */
+  getIterationCount: function() {
+    let iterationText = this.styles.animationIterationCount;
+    if (iterationText.indexOf(",") !== -1) {
+      iterationText = iterationText.split(",")[this.playerIndex];
+    }
+
+    return parseInt(iterationText, 10);
+  },
+
+  /**
+   * Get the current state of the AnimationPlayer (currentTime, playState, ...).
+   * Note that the initial state is returned as the form of this actor when it
+   * is initialized.
+   * @return {Object}
+   */
+  getCurrentState: method(function() {
+    return {
+      /**
+       * Return the player's current startTime value.
+       * Will be null whenever the animation is paused or waiting to start.
+       */
+      startTime: this.player.startTime,
+      currentTime: this.player.currentTime,
+      playState: this.player.playState,
+      name: this.player.source.effect.name,
+      duration: this.getDuration(),
+      iterationCount: this.getIterationCount(),
+      /**
+       * Is the animation currently running on the compositor. This is important for
+       * developers to know if their animation is hitting the fast path or not.
+       * Currently this will only be true for Firefox OS though (where we have
+       * compositor animations enabled).
+       * Returns false whenever the animation is paused as it is taken off the
+       * compositor then.
+       */
+      isRunningOnCompositor: this.player.isRunningOnCompositor
+    };
+  }, {
+    request: {},
+    response: {
+      data: RetVal("json")
+    }
+  }),
+
+  /**
+   * Pause the player.
+   */
+  pause: method(function() {
+    this.player.pause();
+  }, {
+    request: {},
+    response: {}
+  }),
+
+  /**
+   * Play the player.
+   */
+  play: method(function() {
+    this.player.play();
+  }, {
+    request: {},
+    response: {}
+  })
+});
+
+let AnimationPlayerFront = FrontClass(AnimationPlayerActor, {
+  initialize: function(conn, form, detail, ctx) {
+    Front.prototype.initialize.call(this, conn, form, detail, ctx);
+  },
+
+  form: function(form, detail) {
+    if (detail === "actorid") {
+      this.actorID = form;
+      return;
+    }
+    this._form = form;
+  },
+
+  destroy: function() {
+    Front.prototype.destroy.call(this);
+  },
+
+  /**
+   * Getter for the initial state of the player. Up to date states can be
+   * retrieved by calling the getCurrentState method.
+   */
+  get initialState() {
+    return {
+      startTime: this._form.startTime,
+      currentTime: this._form.currentTime,
+      playState: this._form.playState,
+      name: this._form.name,
+      duration: this._form.duration,
+      iterationCount: this._form.iterationCount,
+      isRunningOnCompositor: this._form.isRunningOnCompositor
+    }
+  }
+});
+
+/**
+ * The Animations actor lists animation players for a given node.
+ */
+let AnimationsActor = exports.AnimationsActor = ActorClass({
+  typeName: "animations",
+
+  initialize: function(conn, tabActor) {
+    Actor.prototype.initialize.call(this, conn);
+  },
+
+  destroy: function() {
+    Actor.prototype.destroy.call(this);
+  },
+
+  /**
+   * Since AnimationsActor doesn't have a protocol.js parent actor that takes
+   * care of its lifetime, implementing disconnect is required to cleanup.
+   */
+  disconnect: function() {
+    this.destroy();
+  },
+
+  /**
+   * Retrieve the list of AnimationPlayerActor actors corresponding to
+   * currently running animations for a given node.
+   * @param {NodeActor} nodeActor The NodeActor type is defined in
+   * /toolkit/devtools/server/actors/inspector
+   */
+  getAnimationPlayersForNode: method(function(nodeActor) {
+    let players = nodeActor.rawNode.getAnimationPlayers();
+
+    let actors = [];
+    for (let i = 0; i < players.length; i ++) {
+      // XXX: for now the index is passed along as the AnimationPlayerActor uses
+      // it to retrieve animation information from CSS.
+      actors.push(AnimationPlayerActor(this, players[i], nodeActor.rawNode, i));
+    }
+
+    return actors;
+  }, {
+    request: {
+      actorID: Arg(0, "domnode")
+    },
+    response: {
+      players: RetVal("array:animationplayer")
+    }
+  })
+});
+
+let AnimationsFront = exports.AnimationsFront = FrontClass(AnimationsActor, {
+  initialize: function(client, {animationsActor}) {
+    Front.prototype.initialize.call(this, client, {actor: animationsActor});
+    this.manage(this);
+  },
+
+  destroy: function() {
+    Front.prototype.destroy.call(this);
+  }
+});
--- a/toolkit/devtools/server/main.js
+++ b/toolkit/devtools/server/main.js
@@ -506,16 +506,21 @@ var DebuggerServer = {
     });
     if ("nsIProfiler" in Ci) {
       this.registerModule("devtools/server/actors/profiler", {
         prefix: "profiler",
         constructor: "ProfilerActor",
         type: { global: true, tab: true }
       });
     }
+    this.registerModule("devtools/server/actors/animation", {
+      prefix: "animations",
+      constructor: "AnimationsActor",
+      type: { global: true, tab: true }
+    });
   },
 
   /**
    * Passes a set of options to the BrowserAddonActors for the given ID.
    *
    * @param aId string
    *        The ID of the add-on to pass the options to
    * @param aOptions object
--- a/toolkit/devtools/server/moz.build
+++ b/toolkit/devtools/server/moz.build
@@ -29,16 +29,17 @@ EXTRA_JS_MODULES.devtools.server += [
     'child.js',
     'content-globals.js',
     'main.js',
     'protocol.js',
 ]
 
 EXTRA_JS_MODULES.devtools.server.actors += [
     'actors/actor-registry.js',
+    'actors/animation.js',
     'actors/call-watcher.js',
     'actors/canvas.js',
     'actors/child-process.js',
     'actors/childtab.js',
     'actors/common.js',
     'actors/csscoverage.js',
     'actors/device.js',
     'actors/eventlooplag.js',
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/browser/animation.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<style>
+  .simple-animation {
+    display: inline-block;
+
+    width: 150px;
+    height: 150px;
+    border-radius: 50%;
+    background: red;
+
+    animation: move 2s infinite;
+  }
+
+  .multiple-animations {
+    display: inline-block;
+
+    width: 150px;
+    height: 150px;
+    border-radius: 50%;
+    background: #eee;
+
+    animation: move 2s infinite, glow 1s 5;
+  }
+
+  .transition {
+    display: inline-block;
+
+    width: 150px;
+    height: 150px;
+    border-radius: 50%;
+    background: #f06;
+
+    transition: width 5s;
+  }
+  .transition.get-round {
+    width: 200px;
+  }
+
+  .short-animation {
+    display: inline-block;
+
+    width: 150px;
+    height: 150px;
+    border-radius: 50%;
+    background: purple;
+
+    animation: move 1s;
+  }
+
+  @keyframes move {
+    100% {
+      transform: translateY(100px);
+    }
+  }
+
+  @keyframes glow {
+    100% {
+      background: yellow;
+    }
+  }
+</style>
+<div class="not-animated"></div>
+<div class="simple-animation"></div>
+<div class="multiple-animations"></div>
+<div class="transition"></div>
+<div class="short-animation"></div>
+<script type="text/javascript">
+  // Get the transition started when the page loads
+  var players;
+  addEventListener("load", function() {
+    document.querySelector(".transition").classList.add("get-round");
+  });
+</script>
--- a/toolkit/devtools/server/tests/browser/browser.ini
+++ b/toolkit/devtools/server/tests/browser/browser.ini
@@ -1,23 +1,28 @@
 [DEFAULT]
 skip-if = e10s # Bug ?????? - devtools tests disabled with e10s
 subsuite = devtools
 support-files =
   head.js
+  animation.html
   navigate-first.html
   navigate-second.html
   storage-dynamic-windows.html
   storage-listings.html
   storage-unsecured-iframe.html
   storage-updates.html
   storage-secured-iframe.html
   timeline-iframe-child.html
   timeline-iframe-parent.html
 
+[browser_animation_actors_01.js]
+[browser_animation_actors_02.js]
+[browser_animation_actors_03.js]
+[browser_animation_actors_04.js]
 [browser_navigateEvents.js]
 [browser_storage_dynamic_windows.js]
 [browser_storage_listings.js]
 [browser_storage_updates.js]
 [browser_timeline.js]
 skip-if = buildapp == 'mulet'
 [browser_timeline_actors.js]
 skip-if = buildapp == 'mulet'
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/browser/browser_animation_actors_01.js
@@ -0,0 +1,40 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Simple checks for the AnimationsActor
+
+const {AnimationsFront} = require("devtools/server/actors/animation");
+const {InspectorFront} = require("devtools/server/actors/inspector");
+
+add_task(function*() {
+  let doc = yield addTab("data:text/html;charset=utf-8,<title>test</title><div></div>");
+
+  initDebuggerServer();
+  let client = new DebuggerClient(DebuggerServer.connectPipe());
+  let form = yield connectDebuggerClient(client);
+  let inspector = InspectorFront(client, form);
+  let walker = yield inspector.getWalker();
+  let front = AnimationsFront(client, form);
+
+  ok(front, "The AnimationsFront was created");
+  ok(front.getAnimationPlayersForNode, "The getAnimationPlayersForNode method exists");
+
+  let didThrow = false;
+  try {
+    yield front.getAnimationPlayersForNode(null);
+  } catch (e) {
+    didThrow = true;
+  }
+  ok(didThrow, "An exception was thrown for a missing NodeActor");
+
+  let invalidNode = yield walker.querySelector(walker.rootNode, "title");
+  let players = yield front.getAnimationPlayersForNode(invalidNode);
+  ok(Array.isArray(players), "An array of players was returned");
+  is(players.length, 0, "0 players have been returned for the invalid node");
+
+  yield closeDebuggerClient(client);
+  gBrowser.removeCurrentTab();
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/browser/browser_animation_actors_02.js
@@ -0,0 +1,62 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Check the output of getAnimationPlayersForNode
+
+const {AnimationsFront} = require("devtools/server/actors/animation");
+const {InspectorFront} = require("devtools/server/actors/inspector");
+
+add_task(function*() {
+  let doc = yield addTab(MAIN_DOMAIN + "animation.html");
+
+  initDebuggerServer();
+  let client = new DebuggerClient(DebuggerServer.connectPipe());
+  let form = yield connectDebuggerClient(client);
+  let inspector = InspectorFront(client, form);
+  let walker = yield inspector.getWalker();
+  let front = AnimationsFront(client, form);
+
+  yield theRightNumberOfPlayersIsReturned(walker, front);
+  yield playersCanBePausedAndResumed(walker, front);
+
+  yield closeDebuggerClient(client);
+  gBrowser.removeCurrentTab();
+});
+
+function* theRightNumberOfPlayersIsReturned(walker, front) {
+  let node = yield walker.querySelector(walker.rootNode, ".not-animated");
+  let players = yield front.getAnimationPlayersForNode(node);
+  is(players.length, 0, "0 players were returned for the unanimated node");
+
+  node = yield walker.querySelector(walker.rootNode, ".simple-animation");
+  players = yield front.getAnimationPlayersForNode(node);
+  is(players.length, 1, "One animation player was returned");
+
+  node = yield walker.querySelector(walker.rootNode, ".multiple-animations");
+  players = yield front.getAnimationPlayersForNode(node);
+  is(players.length, 2, "Two animation players were returned");
+
+  node = yield walker.querySelector(walker.rootNode, ".transition");
+  players = yield front.getAnimationPlayersForNode(node);
+  is(players.length, 1, "One animation player was returned for the transitioned node");
+}
+
+function* playersCanBePausedAndResumed(walker, front) {
+  let node = yield walker.querySelector(walker.rootNode, ".simple-animation");
+  let [player] = yield front.getAnimationPlayersForNode(node);
+
+  ok(player.initialState, "The player has an initialState");
+  ok(player.getCurrentState, "The player has the getCurrentState method");
+  is(player.initialState.playState, "running", "The animation is currently running");
+
+  yield player.pause();
+  let state = yield player.getCurrentState();
+  is(state.playState, "paused", "The animation is now paused");
+
+  yield player.play();
+  state = yield player.getCurrentState();
+  is(state.playState, "running", "The animation is now running again");
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/browser/browser_animation_actors_03.js
@@ -0,0 +1,79 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Check the animation player's initial state
+
+const {AnimationsFront} = require("devtools/server/actors/animation");
+const {InspectorFront} = require("devtools/server/actors/inspector");
+
+add_task(function*() {
+  let doc = yield addTab(MAIN_DOMAIN + "animation.html");
+
+  initDebuggerServer();
+  let client = new DebuggerClient(DebuggerServer.connectPipe());
+  let form = yield connectDebuggerClient(client);
+  let inspector = InspectorFront(client, form);
+  let walker = yield inspector.getWalker();
+  let front = AnimationsFront(client, form);
+
+  yield playerHasAnInitialState(walker, front);
+  yield playerStateIsCorrect(walker, front);
+
+  yield closeDebuggerClient(client);
+  gBrowser.removeCurrentTab();
+});
+
+function* playerHasAnInitialState(walker, front) {
+  let node = yield walker.querySelector(walker.rootNode, ".simple-animation");
+  let [player] = yield front.getAnimationPlayersForNode(node);
+
+  ok(player.initialState, "The player front has an initial state");
+  ok("startTime" in player.initialState, "Player's state has startTime");
+  ok("currentTime" in player.initialState, "Player's state has currentTime");
+  ok("playState" in player.initialState, "Player's state has playState");
+  ok("name" in player.initialState, "Player's state has name");
+  ok("duration" in player.initialState, "Player's state has duration");
+  ok("iterationCount" in player.initialState, "Player's state has iterationCount");
+  ok("isRunningOnCompositor" in player.initialState, "Player's state has isRunningOnCompositor");
+}
+
+function* playerStateIsCorrect(walker, front) {
+  info("Checking the state of the simple animation");
+
+  let node = yield walker.querySelector(walker.rootNode, ".simple-animation");
+  let [player] = yield front.getAnimationPlayersForNode(node);
+  let state = player.initialState;
+
+  is(state.name, "move", "Name is correct");
+  is(state.duration, 2000, "Duration is correct");
+  // null = infinite count
+  is(state.iterationCount, null, "Iteration count is correct");
+  is(state.playState, "running", "PlayState is correct");
+
+  info("Checking the state of the transition");
+
+  node = yield walker.querySelector(walker.rootNode, ".transition");
+  [player] = yield front.getAnimationPlayersForNode(node);
+  state = player.initialState;
+
+  is(state.name, "", "Transition has no name");
+  is(state.duration, 5000, "Transition duration is correct");
+  // transitions run only once
+  is(state.iterationCount, 1, "Transition iteration count is correct");
+  is(state.playState, "running", "Transition playState is correct");
+
+  info("Checking the state of one of multiple animations on a node");
+
+  node = yield walker.querySelector(walker.rootNode, ".multiple-animations");
+  // Checking the 2nd player
+  [, player] = yield front.getAnimationPlayersForNode(node);
+  state = player.initialState;
+
+  is(state.name, "glow", "The 2nd animation's name is correct");
+  is(state.duration, 1000, "The 2nd animation's duration is correct");
+  is(state.iterationCount, 5, "The 2nd animation's iteration count is correct");
+  is(state.playState, "running", "The 2nd animation's playState is correct");
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/browser/browser_animation_actors_04.js
@@ -0,0 +1,58 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Check the animation player's updated state
+
+const {AnimationsFront} = require("devtools/server/actors/animation");
+const {InspectorFront} = require("devtools/server/actors/inspector");
+
+add_task(function*() {
+  let doc = yield addTab(MAIN_DOMAIN + "animation.html");
+
+  initDebuggerServer();
+  let client = new DebuggerClient(DebuggerServer.connectPipe());
+  let form = yield connectDebuggerClient(client);
+  let inspector = InspectorFront(client, form);
+  let walker = yield inspector.getWalker();
+  let front = AnimationsFront(client, form);
+
+  yield playStateIsUpdatedDynamically(walker, front);
+
+  yield closeDebuggerClient(client);
+  gBrowser.removeCurrentTab();
+});
+
+function* playStateIsUpdatedDynamically(walker, front) {
+  let node = yield walker.querySelector(walker.rootNode, ".short-animation");
+
+  // Restart the animation to make sure we can get the player (it might already
+  // be finished by now). Do this by toggling the class and forcing a sync reflow
+  // using the CPOW.
+  let cpow = content.document.querySelector(".short-animation");
+  cpow.classList.remove("short-animation");
+  let reflow = cpow.offsetWidth;
+  cpow.classList.add("short-animation");
+
+  let [player] = yield front.getAnimationPlayersForNode(node);
+
+  is(player.initialState.playState, "running",
+    "The playState is running while the transition is running");
+
+  info("Wait until the animation stops (more than 1000ms)");
+  yield wait(1500); // Waiting 1.5sec for good measure
+
+  let state = yield player.getCurrentState();
+  is(state.playState, "finished",
+    "The animation has ended and the state has been updated");
+  ok(state.currentTime > player.initialState.currentTime,
+    "The currentTime has been updated");
+}
+
+function wait(ms) {
+  return new Promise(resolve => {
+    setTimeout(resolve, ms);
+  });
+}
--- a/toolkit/devtools/server/tests/browser/browser_timeline.js
+++ b/toolkit/devtools/server/tests/browser/browser_timeline.js
@@ -4,17 +4,17 @@
 
 "use strict";
 
 // Test that the timeline front's start/stop/isRecording methods work in a
 // simple use case, and that markers events are sent when operations occur.
 
 const {TimelineFront} = require("devtools/server/actors/timeline");
 
-let test = asyncTest(function*() {
+add_task(function*() {
   let doc = yield addTab("data:text/html;charset=utf-8,mop");
 
   initDebuggerServer();
   let client = new DebuggerClient(DebuggerServer.connectPipe());
 
   let form = yield connectDebuggerClient(client);
   let front = TimelineFront(client, form);
 
--- a/toolkit/devtools/server/tests/browser/browser_timeline_actors.js
+++ b/toolkit/devtools/server/tests/browser/browser_timeline_actors.js
@@ -4,17 +4,17 @@
 
 "use strict";
 
 // Test that the timeline can also record data from the memory and framerate
 // actors, emitted as events in tadem with the markers.
 
 const {TimelineFront} = require("devtools/server/actors/timeline");
 
-let test = asyncTest(function*() {
+add_task(function*() {
   let doc = yield addTab("data:text/html;charset=utf-8,mop");
 
   initDebuggerServer();
   let client = new DebuggerClient(DebuggerServer.connectPipe());
   let form = yield connectDebuggerClient(client);
   let front = TimelineFront(client, form);
 
   info("Start timeline marker recording");
--- a/toolkit/devtools/server/tests/browser/browser_timeline_iframes.js
+++ b/toolkit/devtools/server/tests/browser/browser_timeline_iframes.js
@@ -4,17 +4,17 @@
 
 "use strict";
 
 // Test the timeline front receives markers events for operations that occur in
 // iframes.
 
 const {TimelineFront} = require("devtools/server/actors/timeline");
 
-let test = asyncTest(function*() {
+add_task(function*() {
   let doc = yield addTab(MAIN_DOMAIN + "timeline-iframe-parent.html");
 
   initDebuggerServer();
   let client = new DebuggerClient(DebuggerServer.connectPipe());
   let form = yield connectDebuggerClient(client);
   let front = TimelineFront(client, form);
 
   info("Start timeline marker recording");
--- a/toolkit/devtools/server/tests/browser/head.js
+++ b/toolkit/devtools/server/tests/browser/head.js
@@ -16,23 +16,16 @@ const PATH = "browser/toolkit/devtools/s
 const MAIN_DOMAIN = "http://test1.example.org/" + PATH;
 const ALT_DOMAIN = "http://sectest1.example.org/" + PATH;
 const ALT_DOMAIN_SECURED = "https://sectest1.example.org:443/" + PATH;
 
 // All tests are asynchronous.
 waitForExplicitFinish();
 
 /**
- * Define an async test based on a generator function.
- */
-function asyncTest(generator) {
-  return () => Task.spawn(generator).then(null, ok.bind(null, false)).then(finish);
-}
-
-/**
  * Add a new test tab in the browser and load the given url.
  * @param {String} url The url to be loaded in the new tab
  * @return a promise that resolves to the document when the url is loaded
  */
 let addTab = Task.async(function* (url) {
   info("Adding a new tab with URL: '" + url + "'");
   let tab = gBrowser.selectedTab = gBrowser.addTab();
   let loaded = once(gBrowser.selectedBrowser, "load", true);