Bug 1112382 - Remove DataChannelTest wrapper. r=bwc
authorNils Ohlmeier [:drno] <drno@ohlmeier.org>
Fri, 26 Dec 2014 13:32:00 -0500
changeset 247966 0dfb8c9e7e64557f983c6930441c8fb1550c7bfc
parent 247965 8ef1de3364d77bf56d49e2c25171292703843750
child 247967 f09bfc8141719d9dc4bc9fc2bdac3d3853c8bcee
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbwc
bugs1112382
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 1112382 - Remove DataChannelTest wrapper. r=bwc
dom/media/tests/mochitest/dataChannel.js
dom/media/tests/mochitest/mochitest.ini
dom/media/tests/mochitest/pc.js
dom/media/tests/mochitest/templates.js
dom/media/tests/mochitest/test_dataChannel_basicAudio.html
dom/media/tests/mochitest/test_dataChannel_basicAudioVideo.html
dom/media/tests/mochitest/test_dataChannel_basicAudioVideoCombined.html
dom/media/tests/mochitest/test_dataChannel_basicAudioVideoNoBundle.html
dom/media/tests/mochitest/test_dataChannel_basicDataOnly.html
dom/media/tests/mochitest/test_dataChannel_basicVideo.html
dom/media/tests/mochitest/test_dataChannel_bug1013809.html
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/dataChannel.js
@@ -0,0 +1,230 @@
+/* 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/. */
+
+function addInitialDataChannel(chain) {
+  chain.insertBefore('PC_LOCAL_CREATE_OFFER', [
+    ['PC_LOCAL_CREATE_DATA_CHANNEL',
+      function (test) {
+        var channel = test.pcLocal.createDataChannel({});
+
+        is(channel.binaryType, "blob", channel + " is of binary type 'blob'");
+        is(channel.readyState, "connecting", channel + " is in state: 'connecting'");
+
+        is(test.pcLocal.signalingState, STABLE,
+           "Create datachannel does not change signaling state");
+
+        test.next();
+      }
+    ]
+  ]);
+  chain.insertAfter('PC_REMOTE_CREATE_ANSWER', [
+    [
+      'PC_LOCAL_SETUP_DATA_CHANNEL_CALLBACK',
+      function (test) {
+        test.waitForInitialDataChannel(test.pcLocal, function () {
+          ok(true, test.pcLocal + " dataChannels[0] switched to 'open'");
+        },
+        // At this point a timeout failure will be of no value
+        null);
+        test.next();
+      }
+    ],
+    [
+      'PC_REMOTE_SETUP_DATA_CHANNEL_CALLBACK',
+      function (test) {
+        test.waitForInitialDataChannel(test.pcRemote, function () {
+          ok(true, test.pcRemote + " dataChannels[0] switched to 'open'");
+        },
+        // At this point a timeout failure will be of no value
+        null);
+        test.next();
+      }
+    ]
+  ]);
+  chain.insertBefore('PC_LOCAL_CHECK_MEDIA_TRACKS', [
+    [
+      'PC_LOCAL_VERIFY_DATA_CHANNEL_STATE',
+      function (test) {
+        test.waitForInitialDataChannel(test.pcLocal, function() {
+          test.next();
+        }, function() {
+          ok(false, test.pcLocal + " initial dataChannels[0] failed to switch to 'open'");
+          //TODO: use stopAndExit() once bug 1019323 has landed
+          unexpectedEventAndFinish(this, 'timeout')
+          // to prevent test framework timeouts
+          test.next();
+        });
+      }
+    ],
+    [
+      'PC_REMOTE_VERIFY_DATA_CHANNEL_STATE',
+      function (test) {
+        test.waitForInitialDataChannel(test.pcRemote, function() {
+          test.next();
+        }, function() {
+          ok(false, test.pcRemote + " initial dataChannels[0] failed to switch to 'open'");
+          //TODO: use stopAndExit() once bug 1019323 has landed
+          unexpectedEventAndFinish(this, 'timeout');
+          // to prevent test framework timeouts
+          test.next();
+        });
+      }
+    ]
+  ]);
+  chain.removeAfter('PC_REMOTE_CHECK_ICE_CONNECTIONS');
+  chain.append([
+    [
+      'SEND_MESSAGE',
+      function (test) {
+        var message = "Lorem ipsum dolor sit amet";
+
+        test.send(message, function (channel, data) {
+          is(data, message, "Message correctly transmitted from pcLocal to pcRemote.");
+
+          test.next();
+        });
+      }
+    ],
+    [
+      'SEND_BLOB',
+      function (test) {
+        var contents = ["At vero eos et accusam et justo duo dolores et ea rebum."];
+        var blob = new Blob(contents, { "type" : "text/plain" });
+
+        test.send(blob, function (channel, data) {
+          ok(data instanceof Blob, "Received data is of instance Blob");
+          is(data.size, blob.size, "Received data has the correct size.");
+
+          getBlobContent(data, function (recv_contents) {
+            is(recv_contents, contents, "Received data has the correct content.");
+
+            test.next();
+          });
+        });
+      }
+    ],
+    [
+      'CREATE_SECOND_DATA_CHANNEL',
+      function (test) {
+        test.createDataChannel({ }, function (sourceChannel, targetChannel) {
+          is(sourceChannel.readyState, "open", sourceChannel + " is in state: 'open'");
+          is(targetChannel.readyState, "open", targetChannel + " is in state: 'open'");
+
+          is(targetChannel.binaryType, "blob", targetChannel + " is of binary type 'blob'");
+          is(targetChannel.readyState, "open", targetChannel + " is in state: 'open'");
+
+          test.next();
+        });
+      }
+    ],
+    [
+      'SEND_MESSAGE_THROUGH_LAST_OPENED_CHANNEL',
+      function (test) {
+        var channels = test.pcRemote.dataChannels;
+        var message = "Lorem ipsum dolor sit amet";
+
+        test.send(message, function (channel, data) {
+          is(channels.indexOf(channel), channels.length - 1, "Last channel used");
+          is(data, message, "Received message has the correct content.");
+
+          test.next();
+        });
+      }
+    ],
+    [
+      'SEND_MESSAGE_THROUGH_FIRST_CHANNEL',
+      function (test) {
+        var message = "Message through 1st channel";
+        var options = {
+          sourceChannel: test.pcLocal.dataChannels[0],
+          targetChannel: test.pcRemote.dataChannels[0]
+        };
+
+        test.send(message, function (channel, data) {
+          is(test.pcRemote.dataChannels.indexOf(channel), 0, "1st channel used");
+          is(data, message, "Received message has the correct content.");
+
+          test.next();
+        }, options);
+      }
+    ],
+    [
+      'SEND_MESSAGE_BACK_THROUGH_FIRST_CHANNEL',
+      function (test) {
+        var message = "Return a message also through 1st channel";
+        var options = {
+          sourceChannel: test.pcRemote.dataChannels[0],
+          targetChannel: test.pcLocal.dataChannels[0]
+        };
+
+        test.send(message, function (channel, data) {
+          is(test.pcLocal.dataChannels.indexOf(channel), 0, "1st channel used");
+          is(data, message, "Return message has the correct content.");
+
+          test.next();
+        }, options);
+      }
+    ],
+    [
+      'CREATE_NEGOTIATED_DATA_CHANNEL',
+      function (test) {
+        var options = {negotiated:true, id: 5, protocol:"foo/bar", ordered:false,
+          maxRetransmits:500};
+        test.createDataChannel(options, function (sourceChannel2, targetChannel2) {
+          is(sourceChannel2.readyState, "open", sourceChannel2 + " is in state: 'open'");
+          is(targetChannel2.readyState, "open", targetChannel2 + " is in state: 'open'");
+
+          is(targetChannel2.binaryType, "blob", targetChannel2 + " is of binary type 'blob'");
+          is(targetChannel2.readyState, "open", targetChannel2 + " is in state: 'open'");
+
+          if (options.id != undefined) {
+            is(sourceChannel2.id, options.id, sourceChannel2 + " id is:" + sourceChannel2.id);
+          }
+          else {
+            options.id = sourceChannel2.id;
+          }
+          var reliable = !options.ordered ? false : (options.maxRetransmits || options.maxRetransmitTime);
+          is(sourceChannel2.protocol, options.protocol, sourceChannel2 + " protocol is:" + sourceChannel2.protocol);
+          is(sourceChannel2.reliable, reliable, sourceChannel2 + " reliable is:" + sourceChannel2.reliable);
+  /*
+    These aren't exposed by IDL yet
+          is(sourceChannel2.ordered, options.ordered, sourceChannel2 + " ordered is:" + sourceChannel2.ordered);
+          is(sourceChannel2.maxRetransmits, options.maxRetransmits, sourceChannel2 + " maxRetransmits is:" +
+       sourceChannel2.maxRetransmits);
+          is(sourceChannel2.maxRetransmitTime, options.maxRetransmitTime, sourceChannel2 + " maxRetransmitTime is:" +
+       sourceChannel2.maxRetransmitTime);
+  */
+
+          is(targetChannel2.id, options.id, targetChannel2 + " id is:" + targetChannel2.id);
+          is(targetChannel2.protocol, options.protocol, targetChannel2 + " protocol is:" + targetChannel2.protocol);
+          is(targetChannel2.reliable, reliable, targetChannel2 + " reliable is:" + targetChannel2.reliable);
+  /*
+    These aren't exposed by IDL yet
+         is(targetChannel2.ordered, options.ordered, targetChannel2 + " ordered is:" + targetChannel2.ordered);
+          is(targetChannel2.maxRetransmits, options.maxRetransmits, targetChannel2 + " maxRetransmits is:" +
+       targetChannel2.maxRetransmits);
+          is(targetChannel2.maxRetransmitTime, options.maxRetransmitTime, targetChannel2 + " maxRetransmitTime is:" +
+       targetChannel2.maxRetransmitTime);
+  */
+
+          test.next();
+        });
+      }
+    ],
+    [
+      'SEND_MESSAGE_THROUGH_LAST_OPENED_CHANNEL2',
+      function (test) {
+        var channels = test.pcRemote.dataChannels;
+        var message = "Lorem ipsum dolor sit amet";
+
+        test.send(message, function (channel, data) {
+          is(channels.indexOf(channel), channels.length - 1, "Last channel used");
+          is(data, message, "Received message has the correct content.");
+
+          test.next();
+        });
+      }
+    ]
+  ]);
+}
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -1,14 +1,15 @@
 [DEFAULT]
 # strictContentSandbox - bug 1042735, Android 2.3 - bug 981881
 skip-if = (os == 'win' && strictContentSandbox) || android_version == '10'
 support-files =
   head.js
   constraints.js
+  dataChannel.js
   mediaStreamPlayback.js
   nonTrickleIce.js
   pc.js
   templates.js
   NetworkPreparationChromeScript.js
   blacksilence.js
   turnConfig.js
 
--- a/dom/media/tests/mochitest/pc.js
+++ b/dom/media/tests/mochitest/pc.js
@@ -579,17 +579,17 @@ function PeerConnectionTest(options) {
 }
 
 /**
  * Closes the peer connection if it is active
  *
  * @param {Function} onSuccess
  *        Callback to execute when the peer connection has been closed successfully
  */
-PeerConnectionTest.prototype.close = function PCT_close(onSuccess) {
+PeerConnectionTest.prototype.closePC = function PCT_closePC(onSuccess) {
   info("Closing peer connections");
 
   var self = this;
   var closeTimeout = null;
   var waitingForLocal = false;
   var waitingForRemote = false;
   var everythingClosed = false;
 
@@ -651,16 +651,326 @@ PeerConnectionTest.prototype.close = fun
     // it is not a success, but the show must go on
     onSuccess();
   }, 60000);
 
   closeEverything();
 };
 
 /**
+ * Close the open data channels, followed by the underlying peer connection
+ *
+ * @param {Function} onSuccess
+ *        Callback to execute when all connections have been closed
+ */
+PeerConnectionTest.prototype.close = function PCT_close(onSuccess) {
+  var self = this;
+  var pendingDcClose = []
+  var closeTimeout = null;
+
+  info("PeerConnectionTest.close() called");
+
+  function _closePeerConnection() {
+    info("Now closing PeerConnection");
+    self.closePC.call(self, onSuccess);
+  }
+
+  function _closePeerConnectionCallback(index) {
+    info("_closePeerConnection called with index " + index);
+    var pos = pendingDcClose.indexOf(index);
+    if (pos != -1) {
+      pendingDcClose.splice(pos, 1);
+    }
+    else {
+      info("_closePeerConnection index " + index + " is missing from pendingDcClose: " + pendingDcClose);
+    }
+    if (pendingDcClose.length === 0) {
+      clearTimeout(closeTimeout);
+      _closePeerConnection();
+    }
+  }
+
+  var myDataChannels = null;
+  if (self.pcLocal) {
+    myDataChannels = self.pcLocal.dataChannels;
+  }
+  else if (self.pcRemote) {
+    myDataChannels = self.pcRemote.dataChannels;
+  }
+  var length = myDataChannels.length;
+  for (var i = 0; i < length; i++) {
+    var dataChannel = myDataChannels[i];
+    if (dataChannel.readyState !== "closed") {
+      pendingDcClose.push(i);
+      self.closeDataChannels(i, _closePeerConnectionCallback);
+    }
+  }
+  if (pendingDcClose.length === 0) {
+    _closePeerConnection();
+  }
+  else {
+    closeTimeout = setTimeout(function() {
+      ok(false, "Failed to properly close data channels: " +
+        pendingDcClose);
+      _closePeerConnection();
+    }, 60000);
+  }
+};
+
+/**
+ * Close the specified data channels
+ *
+ * @param {Number} index
+ *        Index of the data channels to close on both sides
+ * @param {Function} onSuccess
+ *        Callback to execute when the data channels has been closed
+ */
+PeerConnectionTest.prototype.closeDataChannels = function PCT_closeDataChannels(index, onSuccess) {
+  info("closeDataChannels called with index: " + index);
+  var localChannel = null;
+  if (this.pcLocal) {
+    localChannel = this.pcLocal.dataChannels[index];
+  }
+  var remoteChannel = null;
+  if (this.pcRemote) {
+    remoteChannel = this.pcRemote.dataChannels[index];
+  }
+
+  var self = this;
+  var wait = false;
+  var pollingMode = false;
+  var everythingClosed = false;
+  var verifyInterval = null;
+  var remoteCloseTimer = null;
+
+  function _allChannelsAreClosed() {
+    var ret = null;
+    if (localChannel) {
+      ret = (localChannel.readyState === "closed");
+    }
+    if (remoteChannel) {
+      if (ret !== null) {
+        ret = (ret && (remoteChannel.readyState === "closed"));
+      }
+      else {
+        ret = (remoteChannel.readyState === "closed");
+      }
+    }
+    return ret;
+  }
+
+  function verifyClosedChannels() {
+    if (everythingClosed) {
+      // safety protection against events firing late
+      return;
+    }
+    if (_allChannelsAreClosed()) {
+      ok(true, "DataChannel(s) have reached 'closed' state for data channel " + index);
+      if (remoteCloseTimer !== null) {
+        clearTimeout(remoteCloseTimer);
+      }
+      if (verifyInterval !== null) {
+        clearInterval(verifyInterval);
+      }
+      everythingClosed = true;
+      onSuccess(index);
+    }
+    else {
+      info("Still waiting for DataChannel closure");
+    }
+  }
+
+  if ((localChannel) && (localChannel.readyState !== "closed")) {
+    // in case of steeplechase there is no far end, so we can only poll
+    if (remoteChannel) {
+      remoteChannel.onclose = function () {
+        is(remoteChannel.readyState, "closed", "remoteChannel is in state 'closed'");
+        verifyClosedChannels();
+      };
+    }
+    else {
+      pollingMode = true;
+      verifyInterval = setInterval(verifyClosedChannels, 1000);
+    }
+
+    localChannel.close();
+    wait = true;
+  }
+  if ((remoteChannel) && (remoteChannel.readyState !== "closed")) {
+    if (localChannel) {
+      localChannel.onclose = function () {
+        is(localChannel.readyState, "closed", "localChannel is in state 'closed'");
+        verifyClosedChannels();
+      };
+
+      // Apparently we are running a local test which has both ends of the
+      // data channel locally available, so by default lets wait for the
+      // remoteChannel.onclose handler from above to confirm closure on both
+      // ends.
+      remoteCloseTimer = setTimeout(function() {
+        todo(false, "localChannel.close() did not resulted in close signal on remote side");
+        remoteChannel.close();
+        verifyClosedChannels();
+      }, 30000);
+    }
+    else {
+      pollingMode = true;
+      verifyTimer = setInterval(verifyClosedChannels, 1000);
+
+      remoteChannel.close();
+    }
+
+    wait = true;
+  }
+
+  if (!wait) {
+    onSuccess(index);
+  }
+};
+
+
+/**
+ * Wait for the initial data channel to get into the open state
+ *
+ * @param {PeerConnectionWrapper} peer
+ *        The peer connection wrapper to run the command on
+ * @param {Function} onSuccess
+ *        Callback when the creation was successful
+ */
+PeerConnectionTest.prototype.waitForInitialDataChannel =
+        function PCT_waitForInitialDataChannel(peer, onSuccess, onFailure) {
+  var dcConnectionTimeout = null;
+  var dcOpened = false;
+
+  function dataChannelConnected(channel) {
+    // in case the switch statement below had called onSuccess already we
+    // don't want to call it again
+    if (!dcOpened) {
+      clearTimeout(dcConnectionTimeout);
+      is(channel.readyState, "open", peer + " dataChannels[0] switched to state: 'open'");
+      dcOpened = true;
+      onSuccess();
+    } else {
+      info("dataChannelConnected() called, but data channel was open already");
+    }
+  }
+
+  // TODO: drno: convert dataChannels into an object and make
+  //             registerDataChannelOpenEvent a generic function
+  if (peer == this.pcLocal) {
+    peer.dataChannels[0].onopen = dataChannelConnected;
+  } else {
+    peer.registerDataChannelOpenEvents(dataChannelConnected);
+  }
+
+  if (peer.dataChannels.length >= 1) {
+    // snapshot of the live value as it might change during test execution
+    const readyState = peer.dataChannels[0].readyState;
+    switch (readyState) {
+      case "open": {
+        is(readyState, "open", peer + " dataChannels[0] is already in state: 'open'");
+        dcOpened = true;
+        onSuccess();
+        break;
+      }
+      case "connecting": {
+        is(readyState, "connecting", peer + " dataChannels[0] is in state: 'connecting'");
+        if (onFailure) {
+          dcConnectionTimeout = setTimeout(function () {
+            is(peer.dataChannels[0].readyState, "open", peer + " timed out while waiting for dataChannels[0] to open");
+            onFailure();
+          }, 60000);
+        }
+        break;
+      }
+      default: {
+        ok(false, "dataChannels[0] is in unexpected state " + readyState);
+        if (onFailure) {
+          onFailure()
+        }
+      }
+    }
+  }
+};
+
+/**
+ * Send data (message or blob) to the other peer
+ *
+ * @param {String|Blob} data
+ *        Data to send to the other peer. For Blobs the MIME type will be lost.
+ * @param {Function} onSuccess
+ *        Callback to execute when data has been sent
+ * @param {Object} [options={ }]
+ *        Options to specify the data channels to be used
+ * @param {DataChannelWrapper} [options.sourceChannel=pcLocal.dataChannels[length - 1]]
+ *        Data channel to use for sending the message
+ * @param {DataChannelWrapper} [options.targetChannel=pcRemote.dataChannels[length - 1]]
+ *        Data channel to use for receiving the message
+ */
+PeerConnectionTest.prototype.send = function PCT_send(data, onSuccess, options) {
+  options = options || { };
+  var source = options.sourceChannel ||
+           this.pcLocal.dataChannels[this.pcLocal.dataChannels.length - 1];
+  var target = options.targetChannel ||
+           this.pcRemote.dataChannels[this.pcRemote.dataChannels.length - 1];
+
+  // Register event handler for the target channel
+  target.onmessage = function (recv_data) {
+    onSuccess(target, recv_data);
+  };
+
+  source.send(data);
+};
+
+/**
+ * Create a data channel
+ *
+ * @param {Dict} options
+ *        Options for the data channel (see nsIPeerConnection)
+ * @param {Function} onSuccess
+ *        Callback when the creation was successful
+ */
+PeerConnectionTest.prototype.createDataChannel = function DCT_createDataChannel(options, onSuccess) {
+  var localChannel = null;
+  var remoteChannel = null;
+  var self = this;
+
+  // Method to synchronize all asynchronous events.
+  function check_next_test() {
+    if (localChannel && remoteChannel) {
+      onSuccess(localChannel, remoteChannel);
+    }
+  }
+
+  if (!options.negotiated) {
+    // Register handlers for the remote peer
+    this.pcRemote.registerDataChannelOpenEvents(function (channel) {
+      remoteChannel = channel;
+      check_next_test();
+    });
+  }
+
+  // Create the datachannel and handle the local 'onopen' event
+  this.pcLocal.createDataChannel(options, function (channel) {
+    localChannel = channel;
+
+    if (options.negotiated) {
+      // externally negotiated - we need to open from both ends
+      options.id = options.id || channel.id;  // allow for no id to let the impl choose
+      self.pcRemote.createDataChannel(options, function (channel) {
+        remoteChannel = channel;
+        check_next_test();
+      });
+    } else {
+      check_next_test();
+    }
+  });
+};
+
+/**
  * Executes the next command.
  */
 PeerConnectionTest.prototype.next = function PCT_next() {
   if (this._stepTimeout) {
     clearTimeout(this._stepTimeout);
     this._stepTimeout = null;
   }
   this.chain.executeNext();
@@ -990,382 +1300,16 @@ PCT_getSignalingMessage(messageType, onM
       info("invoking callback on message " + i + " from message queue, for message type:" + messageType);
       onMessage(this.signalingMessageQueue.splice(i, 1)[0]);
       return;
     }
   }
   this.registerSignalingCallback(messageType, onMessage);
 }
 
-/**
- * This class handles tests for data channels.
- *
- * @constructor
- * @param {object} [options={}]
- *        Optional options for the peer connection test
- * @param {object} [options.commands=commandsDataChannel]
- *        Commands to run for the test
- * @param {object} [options.config_local=undefined]
- *        Configuration for the local peer connection instance
- * @param {object} [options.config_remote=undefined]
- *        Configuration for the remote peer connection instance. If not defined
- *        the configuration from the local instance will be used
- */
-function DataChannelTest(options) {
-  options = options || { };
-  options.commands = options.commands || commandsDataChannel;
-
-  PeerConnectionTest.call(this, options);
-}
-
-DataChannelTest.prototype = Object.create(PeerConnectionTest.prototype, {
-  close : {
-    /**
-     * Close the open data channels, followed by the underlying peer connection
-     *
-     * @param {Function} onSuccess
-     *        Callback to execute when all connections have been closed
-     */
-    value : function DCT_close(onSuccess) {
-      var self = this;
-      var pendingDcClose = []
-      var closeTimeout = null;
-
-      info("DataChannelTest.close() called");
-
-      function _closePeerConnection() {
-        info("DataChannelTest closing PeerConnection");
-        PeerConnectionTest.prototype.close.call(self, onSuccess);
-      }
-
-      function _closePeerConnectionCallback(index) {
-        info("_closePeerConnection called with index " + index);
-        var pos = pendingDcClose.indexOf(index);
-        if (pos != -1) {
-          pendingDcClose.splice(pos, 1);
-        }
-        else {
-          info("_closePeerConnection index " + index + " is missing from pendingDcClose: " + pendingDcClose);
-        }
-        if (pendingDcClose.length === 0) {
-          clearTimeout(closeTimeout);
-          _closePeerConnection();
-        }
-      }
-
-      var myDataChannels = null;
-      if (self.pcLocal) {
-        myDataChannels = self.pcLocal.dataChannels;
-      }
-      else if (self.pcRemote) {
-        myDataChannels = self.pcRemote.dataChannels;
-      }
-      var length = myDataChannels.length;
-      for (var i = 0; i < length; i++) {
-        var dataChannel = myDataChannels[i];
-        if (dataChannel.readyState !== "closed") {
-          pendingDcClose.push(i);
-          self.closeDataChannels(i, _closePeerConnectionCallback);
-        }
-      }
-      if (pendingDcClose.length === 0) {
-        _closePeerConnection();
-      }
-      else {
-        closeTimeout = setTimeout(function() {
-          ok(false, "Failed to properly close data channels: " +
-            pendingDcClose);
-          _closePeerConnection();
-        }, 60000);
-      }
-    }
-  },
-
-  closeDataChannels : {
-    /**
-     * Close the specified data channels
-     *
-     * @param {Number} index
-     *        Index of the data channels to close on both sides
-     * @param {Function} onSuccess
-     *        Callback to execute when the data channels has been closed
-     */
-    value : function DCT_closeDataChannels(index, onSuccess) {
-      info("_closeDataChannels called with index: " + index);
-      var localChannel = null;
-      if (this.pcLocal) {
-        localChannel = this.pcLocal.dataChannels[index];
-      }
-      var remoteChannel = null;
-      if (this.pcRemote) {
-        remoteChannel = this.pcRemote.dataChannels[index];
-      }
-
-      var self = this;
-      var wait = false;
-      var pollingMode = false;
-      var everythingClosed = false;
-      var verifyInterval = null;
-      var remoteCloseTimer = null;
-
-      function _allChannelsAreClosed() {
-        var ret = null;
-        if (localChannel) {
-          ret = (localChannel.readyState === "closed");
-        }
-        if (remoteChannel) {
-          if (ret !== null) {
-            ret = (ret && (remoteChannel.readyState === "closed"));
-          }
-          else {
-            ret = (remoteChannel.readyState === "closed");
-          }
-        }
-        return ret;
-      }
-
-      function verifyClosedChannels() {
-        if (everythingClosed) {
-          // safety protection against events firing late
-          return;
-        }
-        if (_allChannelsAreClosed) {
-          ok(true, "DataChannel(s) have reached 'closed' state for data channel " + index);
-          if (remoteCloseTimer !== null) {
-            clearTimeout(remoteCloseTimer);
-          }
-          if (verifyInterval !== null) {
-            clearInterval(verifyInterval);
-          }
-          everythingClosed = true;
-          onSuccess(index);
-        }
-        else {
-          info("Still waiting for DataChannel closure");
-        }
-      }
-
-      if ((localChannel) && (localChannel.readyState !== "closed")) {
-        // in case of steeplechase there is no far end, so we can only poll
-        if (remoteChannel) {
-          remoteChannel.onclose = function () {
-            is(remoteChannel.readyState, "closed", "remoteChannel is in state 'closed'");
-            verifyClosedChannels();
-          };
-        }
-        else {
-          pollingMode = true;
-          verifyInterval = setInterval(verifyClosedChannels, 1000);
-        }
-
-        localChannel.close();
-        wait = true;
-      }
-      if ((remoteChannel) && (remoteChannel.readyState !== "closed")) {
-        if (localChannel) {
-          localChannel.onclose = function () {
-            is(localChannel.readyState, "closed", "localChannel is in state 'closed'");
-            verifyClosedChannels();
-          };
-
-          // Apparently we are running a local test which has both ends of the
-          // data channel locally available, so by default lets wait for the
-          // remoteChannel.onclose handler from above to confirm closure on both
-          // ends.
-          remoteCloseTimer = setTimeout(function() {
-            todo(false, "localChannel.close() did not resulted in close signal on remote side");
-            remoteChannel.close();
-            verifyClosedChannels();
-          }, 30000);
-        }
-        else {
-          pollingMode = true;
-          verifyTimer = setInterval(verifyClosedChannels, 1000);
-
-          remoteChannel.close();
-        }
-
-        wait = true;
-      }
-
-      if (!wait) {
-        onSuccess(index);
-      }
-    }
-  },
-
-  createDataChannel : {
-    /**
-     * Create a data channel
-     *
-     * @param {Dict} options
-     *        Options for the data channel (see nsIPeerConnection)
-     * @param {Function} onSuccess
-     *        Callback when the creation was successful
-     */
-    value : function DCT_createDataChannel(options, onSuccess) {
-      var localChannel = null;
-      var remoteChannel = null;
-      var self = this;
-
-      // Method to synchronize all asynchronous events.
-      function check_next_test() {
-        if (localChannel && remoteChannel) {
-          onSuccess(localChannel, remoteChannel);
-        }
-      }
-
-      if (!options.negotiated) {
-        // Register handlers for the remote peer
-        this.pcRemote.registerDataChannelOpenEvents(function (channel) {
-          remoteChannel = channel;
-          check_next_test();
-        });
-      }
-
-      // Create the datachannel and handle the local 'onopen' event
-      this.pcLocal.createDataChannel(options, function (channel) {
-        localChannel = channel;
-
-        if (options.negotiated) {
-          // externally negotiated - we need to open from both ends
-          options.id = options.id || channel.id;  // allow for no id to let the impl choose
-          self.pcRemote.createDataChannel(options, function (channel) {
-            remoteChannel = channel;
-            check_next_test();
-          });
-        } else {
-          check_next_test();
-        }
-      });
-    }
-  },
-
-  send : {
-    /**
-     * Send data (message or blob) to the other peer
-     *
-     * @param {String|Blob} data
-     *        Data to send to the other peer. For Blobs the MIME type will be lost.
-     * @param {Function} onSuccess
-     *        Callback to execute when data has been sent
-     * @param {Object} [options={ }]
-     *        Options to specify the data channels to be used
-     * @param {DataChannelWrapper} [options.sourceChannel=pcLocal.dataChannels[length - 1]]
-     *        Data channel to use for sending the message
-     * @param {DataChannelWrapper} [options.targetChannel=pcRemote.dataChannels[length - 1]]
-     *        Data channel to use for receiving the message
-     */
-    value : function DCT_send(data, onSuccess, options) {
-      options = options || { };
-      var source = options.sourceChannel ||
-               this.pcLocal.dataChannels[this.pcLocal.dataChannels.length - 1];
-      var target = options.targetChannel ||
-               this.pcRemote.dataChannels[this.pcRemote.dataChannels.length - 1];
-
-      // Register event handler for the target channel
-      target.onmessage = function (recv_data) {
-        onSuccess(target, recv_data);
-      };
-
-      source.send(data);
-    }
-  },
-
-  createOffer : {
-    value : function DCT_createOffer(peer, onSuccess) {
-      PeerConnectionTest.prototype.createOffer.call(this, peer, onSuccess);
-    }
-  },
-
-  setLocalDescription : {
-    /**
-     * Sets the local description for the specified peer connection instance
-     * and automatically handles the failure case.
-     *
-     * @param {PeerConnectionWrapper} peer
-              The peer connection wrapper to run the command on
-     * @param {mozRTCSessionDescription} desc
-     *        Session description for the local description request
-     * @param {function} onSuccess
-     *        Callback to execute if the local description was set successfully
-     */
-    value : function DCT_setLocalDescription(peer, desc, state, onSuccess) {
-      PeerConnectionTest.prototype.setLocalDescription.call(this, peer,
-                                                              desc, state, onSuccess);
-
-    }
-  },
-
-  waitForInitialDataChannel : {
-    /**
-     * Wait for the initial data channel to get into the open state
-     *
-     * @param {PeerConnectionWrapper} peer
-     *        The peer connection wrapper to run the command on
-     * @param {Function} onSuccess
-     *        Callback when the creation was successful
-     */
-    value : function DCT_waitForInitialDataChannel(peer, onSuccess, onFailure) {
-      var dcConnectionTimeout = null;
-      var dcOpened = false;
-
-      function dataChannelConnected(channel) {
-        // in case the switch statement below had called onSuccess already we
-        // don't want to call it again
-        if (!dcOpened) {
-          clearTimeout(dcConnectionTimeout);
-          is(channel.readyState, "open", peer + " dataChannels[0] switched to state: 'open'");
-          dcOpened = true;
-          onSuccess();
-        } else {
-          info("dataChannelConnected() called, but data channel was open already");
-        }
-      }
-
-      // TODO: drno: convert dataChannels into an object and make
-      //             registerDataChannelOpenEvent a generic function
-      if (peer == this.pcLocal) {
-        peer.dataChannels[0].onopen = dataChannelConnected;
-      } else {
-        peer.registerDataChannelOpenEvents(dataChannelConnected);
-      }
-
-      if (peer.dataChannels.length >= 1) {
-        // snapshot of the live value as it might change during test execution
-        const readyState = peer.dataChannels[0].readyState;
-        switch (readyState) {
-          case "open": {
-            is(readyState, "open", peer + " dataChannels[0] is already in state: 'open'");
-            dcOpened = true;
-            onSuccess();
-            break;
-          }
-          case "connecting": {
-            is(readyState, "connecting", peer + " dataChannels[0] is in state: 'connecting'");
-            if (onFailure) {
-              dcConnectionTimeout = setTimeout(function () {
-                is(peer.dataChannels[0].readyState, "open", peer + " timed out while waiting for dataChannels[0] to open");
-                onFailure();
-              }, 60000);
-            }
-            break;
-          }
-          default: {
-            ok(false, "dataChannels[0] is in unexpected state " + readyState);
-            if (onFailure) {
-              onFailure()
-            }
-          }
-        }
-      }
-    }
-  }
-
-});
 
 /**
  * This class acts as a wrapper around a DataChannel instance.
  *
  * @param dataChannel
  * @param peerConnectionWrapper
  * @constructor
  */
@@ -2569,27 +2513,28 @@ PeerConnectionWrapper.prototype = {
         if (!res.isRemote) {
           counters2[res.type] = toNum(counters2[res.type]) + 1;
         }
       });
     is(JSON.stringify(counters), JSON.stringify(counters2),
        "Spec and MapClass variant of RTCStatsReport enumeration agree");
     var nin = numTracks(this._pc.getRemoteStreams());
     var nout = numTracks(this._pc.getLocalStreams());
+    var ndata = this.dataChannels.length;
 
     // TODO(Bug 957145): Restore stronger inboundrtp test once Bug 948249 is fixed
     //is(toNum(counters["inboundrtp"]), nin, "Have " + nin + " inboundrtp stat(s)");
     ok(toNum(counters.inboundrtp) >= nin, "Have at least " + nin + " inboundrtp stat(s) *");
 
     is(toNum(counters.outboundrtp), nout, "Have " + nout + " outboundrtp stat(s)");
 
     var numLocalCandidates  = toNum(counters.localcandidate);
     var numRemoteCandidates = toNum(counters.remotecandidate);
     // If there are no tracks, there will be no stats either.
-    if (nin + nout > 0) {
+    if (nin + nout + ndata > 0) {
       ok(numLocalCandidates, "Have localcandidate stat(s)");
       ok(numRemoteCandidates, "Have remotecandidate stat(s)");
     } else {
       is(numLocalCandidates, 0, "Have no localcandidate stats");
       is(numRemoteCandidates, 0, "Have no remotecandidate stats");
     }
   },
 
@@ -2643,17 +2588,17 @@ PeerConnectionWrapper.prototype = {
    * @param {object} stats
    *        The stats to check for ICE candidate pairs
    * @param {object} counters
    *        The counters for media and data tracks based on constraints
    * @param {object} answer
    *        The SDP answer to check for SDP bundle support
    */
   checkStatsIceConnections : function PCW_checkStatsIceConnections(stats,
-      offerConstraintsList, offerOptions, numDataTracks, answer) {
+      offerConstraintsList, offerOptions, answer) {
     var numIceConnections = 0;
     Object.keys(stats).forEach(function(key) {
       if ((stats[key].type === "candidatepair") && stats[key].selected) {
         numIceConnections += 1;
       }
     });
     info("ICE connections according to stats: " + numIceConnections);
     if (answer.sdp.contains('a=group:BUNDLE')) {
@@ -2664,16 +2609,18 @@ PeerConnectionWrapper.prototype = {
       var numAudioTracks =
         this.countAudioTracksInMediaConstraint(offerConstraintsList) ||
         this.audioInOfferOptions(offerOptions);
 
       var numVideoTracks =
         this.countVideoTracksInMediaConstraint(offerConstraintsList) ||
         this.videoInOfferOptions(offerOptions);
 
+      var numDataTracks = this.dataChannels.length;
+
       var numAudioVideoDataTracks = numAudioTracks + numVideoTracks + numDataTracks;
       info("expected audio + video + data tracks: " + numAudioVideoDataTracks);
       is(numAudioVideoDataTracks, numIceConnections, "stats ICE connections matches expected A/V tracks");
     }
   },
 
   /**
    * Property-matching function for finding a certain stat in passed-in stats
--- a/dom/media/tests/mochitest/templates.js
+++ b/dom/media/tests/mochitest/templates.js
@@ -530,30 +530,28 @@ var commandsPeerConnection = [
   ],
   [
     'PC_LOCAL_CHECK_ICE_CONNECTIONS',
     function (test) {
       test.pcLocal.getStats(null, function(stats) {
         test.pcLocal.checkStatsIceConnections(stats,
                                               test._offer_constraints,
                                               test._offer_options,
-                                              0,
                                               test.originalAnswer);
         test.next();
       });
     }
   ],
   [
     'PC_REMOTE_CHECK_ICE_CONNECTIONS',
     function (test) {
       test.pcRemote.getStats(null, function(stats) {
         test.pcRemote.checkStatsIceConnections(stats,
                                                test._offer_constraints,
                                                test._offer_options,
-                                               0,
                                                test.originalAnswer);
         test.next();
       });
     }
   ],
   [
     'PC_LOCAL_CHECK_GETSTATS_AUDIOTRACK_OUTBOUND',
     function (test) {
@@ -719,631 +717,8 @@ var commandsPeerConnection = [
         });
       } else {
         test.next();
       }
     }
   ]
 ];
 
-
-/**
- * Default list of commands to execute for a Datachannel test.
- */
-var commandsDataChannel = [
-  [
-    'PC_LOCAL_SETUP_ICE_LOGGER',
-    function (test) {
-      test.pcLocal.logIceConnectionState();
-      test.next();
-    }
-  ],
-  [
-    'PC_REMOTE_SETUP_ICE_LOGGER',
-    function (test) {
-      test.pcRemote.logIceConnectionState();
-      test.next();
-    }
-  ],
-  [
-    'PC_LOCAL_SETUP_SIGNALING_LOGGER',
-    function (test) {
-      test.pcLocal.logSignalingState();
-      test.next();
-    }
-  ],
-  [
-    'PC_REMOTE_SETUP_SIGNALING_LOGGER',
-    function (test) {
-      test.pcRemote.logSignalingState();
-      test.next();
-    }
-  ],
-  [
-    'PC_LOCAL_GUM',
-    function (test) {
-      test.pcLocal.getAllUserMedia(test.pcLocal.constraints, function () {
-        test.next();
-      });
-    }
-  ],
-  [
-    'PC_LOCAL_INITIAL_SIGNALINGSTATE',
-    function (test) {
-      is(test.pcLocal.signalingState, STABLE,
-         "Initial local signalingState is stable");
-      test.next();
-    }
-  ],
-  [
-    'PC_LOCAL_CHECK_INITIAL_ICE_STATE',
-    function (test) {
-      is(test.pcLocal.iceConnectionState, ICE_NEW,
-        "Initial local ICE connection state is 'new'");
-      test.next();
-    }
-  ],
-  [
-    'PC_REMOTE_GUM',
-    function (test) {
-      test.pcRemote.getAllUserMedia(test.pcRemote.constraints, function () {
-      test.next();
-      });
-    }
-  ],
-  [
-    'PC_REMOTE_INITIAL_SIGNALINGSTATE',
-    function (test) {
-      is(test.pcRemote.signalingState, STABLE,
-         "Initial remote signalingState is stable");
-      test.next();
-    }
-  ],
-  [
-    'PC_REMOTE_CHECK_INITIAL_ICE_STATE',
-    function (test) {
-      is(test.pcRemote.iceConnectionState, ICE_NEW,
-        "Initial remote ICE connection state is 'new'");
-      test.next();
-    }
-  ],
-  [
-    'PC_LOCAL_SETUP_ICE_HANDLER',
-    function (test) {
-      test.pcLocal.setupIceCandidateHandler(test);
-      test.next();
-    }
-  ],
-  [
-    'PC_REMOTE_SETUP_ICE_HANDLER',
-    function (test) {
-      test.pcRemote.setupIceCandidateHandler(test);
-      test.next();
-    }
-  ],
-  [
-    'PC_LOCAL_CREATE_DATA_CHANNEL',
-    function (test) {
-      var channel = test.pcLocal.createDataChannel({});
-
-      is(channel.binaryType, "blob", channel + " is of binary type 'blob'");
-      is(channel.readyState, "connecting", channel + " is in state: 'connecting'");
-
-      is(test.pcLocal.signalingState, STABLE,
-         "Create datachannel does not change signaling state");
-
-      test.next();
-    }
-  ],
-  [
-    'PC_LOCAL_CREATE_OFFER',
-    function (test) {
-      test.createOffer(test.pcLocal, function (offer) {
-        is(test.pcLocal.signalingState, STABLE,
-           "Local create offer does not change signaling state");
-        ok(offer.sdp.contains("m=application"),
-           "m=application is contained in the SDP");
-        test.next();
-      });
-    }
-  ],
-  [
-    'PC_LOCAL_STEEPLECHASE_SIGNAL_OFFER',
-    function (test) {
-      if (test.steeplechase) {
-        send_message({"type": "offer",
-          "offer": test.originalOffer,
-          "offer_constraints": test.pcLocal.constraints,
-          "offer_options": test.pcLocal.offerOptions});
-        test._local_offer = test.originalOffer;
-        test._offer_constraints = test.pcLocal.constraints;
-        test._offer_options = test.pcLocal.offerOptions;
-      }
-      test.next();
-    }
-  ],
-  [
-    'PC_LOCAL_SET_LOCAL_DESCRIPTION',
-    function (test) {
-      test.setLocalDescription(test.pcLocal, test.originalOffer, HAVE_LOCAL_OFFER,
-        function () {
-        is(test.pcLocal.signalingState, HAVE_LOCAL_OFFER,
-           "signalingState after local setLocalDescription is 'have-local-offer'");
-        test.next();
-      });
-    }
-  ],
-  [
-    'PC_REMOTE_GET_OFFER',
-    function (test) {
-      if (!test.steeplechase) {
-        test._local_offer = test.originalOffer;
-        test._offer_constraints = test.pcLocal.constraints;
-        test._offer_options = test.pcLocal.offerOptions;
-        test.next();
-      } else {
-        test.getSignalingMessage("offer", function (message) {
-          ok("offer" in message, "Got an offer message");
-          test._local_offer = new mozRTCSessionDescription(message.offer);
-          test._offer_constraints = message.offer_constraints;
-          test._offer_options = message.offer_options;
-          test.next();
-        });
-      }
-    }
-  ],
-  [
-    'PC_REMOTE_SET_REMOTE_DESCRIPTION',
-    function (test) {
-      test.setRemoteDescription(test.pcRemote, test._local_offer, HAVE_REMOTE_OFFER,
-        function () {
-        is(test.pcRemote.signalingState, HAVE_REMOTE_OFFER,
-           "signalingState after remote setRemoteDescription is 'have-remote-offer'");
-        test.next();
-      });
-    }
-  ],
-  [
-    'PC_LOCAL_SANE_LOCAL_SDP',
-    function (test) {
-      test.pcLocal.verifySdp(test._local_offer, "offer",
-        test._offer_constraints, test._offer_options,
-        function(trickle) {
-          test.pcLocal.localRequiresTrickleIce = trickle;
-        });
-      test.next();
-    }
-  ],
-  [
-    'PC_REMOTE_SANE_REMOTE_SDP',
-    function (test) {
-      test.pcRemote.verifySdp(test._local_offer, "offer",
-        test._offer_constraints, test._offer_options,
-        function (trickle) {
-          test.pcRemote.remoteRequiresTrickleIce = trickle;
-        });
-      test.next();
-    }
-  ],
-  [
-    'PC_REMOTE_CREATE_ANSWER',
-    function (test) {
-      test.createAnswer(test.pcRemote, function (answer) {
-        is(test.pcRemote.signalingState, HAVE_REMOTE_OFFER,
-           "Remote createAnswer does not change signaling state");
-        ok(answer.sdp.contains("m=application"),
-           "m=application is contained in the SDP");
-        if (test.steeplechase) {
-          send_message({"type":"answer",
-                        "answer": test.originalAnswer,
-                        "answer_constraints": test.pcRemote.constraints});
-          test._remote_answer = test.pcRemote._last_answer;
-          test._answer_constraints = test.pcRemote.constraints;
-        }
-        test.next();
-      });
-    }
-  ],
-  [
-    'PC_LOCAL_SETUP_DATA_CHANNEL_CALLBACK',
-    function (test) {
-      test.waitForInitialDataChannel(test.pcLocal, function () {
-        ok(true, test.pcLocal + " dataChannels[0] switched to 'open'");
-      },
-      // At this point a timeout failure will be of no value
-      null);
-      test.next();
-    }
-  ],
-  [
-    'PC_REMOTE_SETUP_DATA_CHANNEL_CALLBACK',
-    function (test) {
-      test.waitForInitialDataChannel(test.pcRemote, function () {
-        ok(true, test.pcRemote + " dataChannels[0] switched to 'open'");
-      },
-      // At this point a timeout failure will be of no value
-      null);
-      test.next();
-    }
-  ],
-  [
-    'PC_REMOTE_SET_LOCAL_DESCRIPTION',
-    function (test) {
-      test.setLocalDescription(test.pcRemote, test.originalAnswer, STABLE,
-        function () {
-          is(test.pcRemote.signalingState, STABLE,
-            "signalingState after remote setLocalDescription is 'stable'");
-          test.next();
-        }
-      );
-    }
-  ],
-  [
-    'PC_LOCAL_GET_ANSWER',
-    function (test) {
-      if (!test.steeplechase) {
-        test._remote_answer = test.originalAnswer;
-        test._answer_constraints = test.pcRemote.constraints;
-        test.next();
-      } else {
-        test.getSignalingMessage("answer", function (message) {
-          ok("answer" in message, "Got an answer message");
-          test._remote_answer = new mozRTCSessionDescription(message.answer);
-          test._answer_constraints = message.answer_constraints;
-          test.next();
-        });
-      }
-    }
-  ],
-  [
-    'PC_LOCAL_SET_REMOTE_DESCRIPTION',
-    function (test) {
-      test.setRemoteDescription(test.pcLocal, test._remote_answer, STABLE,
-        function () {
-          is(test.pcLocal.signalingState, STABLE,
-            "signalingState after local setRemoteDescription is 'stable'");
-          test.next();
-        }
-      );
-    }
-  ],
-  [
-    'PC_REMOTE_SANE_LOCAL_SDP',
-    function (test) {
-      test.pcRemote.verifySdp(test._remote_answer, "answer",
-        test._offer_constraints, test._offer_options,
-        function (trickle) {
-          test.pcRemote.localRequiresTrickleIce = trickle;
-        });
-      test.next();
-    }
-  ],
-  [
-    'PC_LOCAL_SANE_REMOTE_SDP',
-    function (test) {
-      test.pcLocal.verifySdp(test._remote_answer, "answer",
-        test._offer_constraints, test._offer_options,
-        function (trickle) {
-          test.pcLocal.remoteRequiresTrickleIce = trickle;
-        });
-      test.next();
-    }
-  ],
-  [
-    'PC_LOCAL_WAIT_FOR_ICE_CONNECTED',
-    function (test) {
-      var myTest = test;
-      var myPc = myTest.pcLocal;
-
-      function onIceConnectedSuccess () {
-        info("pcLocal ICE connection state log: " + test.pcLocal.iceConnectionLog);
-        ok(true, "pc_local: ICE switched to 'connected' state");
-        myTest.next();
-      };
-      function onIceConnectedFailed () {
-        dumpSdp(myTest);
-        ok(false, "pc_local: ICE failed to switch to 'connected' state: " + myPc.iceConnectionState);
-        myTest.next();
-      };
-
-      if (myPc.isIceConnected()) {
-        info("pcLocal ICE connection state log: " + test.pcLocal.iceConnectionLog);
-        ok(true, "pc_local: ICE is in connected state");
-        myTest.next();
-      } else if (myPc.isIceConnectionPending()) {
-        myPc.waitForIceConnected(onIceConnectedSuccess, onIceConnectedFailed);
-      } else {
-        dumpSdp(myTest);
-        ok(false, "pc_local: ICE is already in bad state: " + myPc.iceConnectionState);
-        myTest.next();
-      }
-    }
-  ],
-  [
-    'PC_LOCAL_VERIFY_ICE_GATHERING',
-    function (test) {
-      if (test.pcLocal.localRequiresTrickleIce) {
-        ok(test.pcLocal._local_ice_candidates.length > 0, "Received local trickle ICE candidates");
-      }
-      isnot(test.pcLocal._pc.iceGatheringState, GATH_NEW, "ICE gathering state is not 'new'");
-      test.next();
-    }
-  ],
-  [
-    'PC_REMOTE_WAIT_FOR_ICE_CONNECTED',
-    function (test) {
-      var myTest = test;
-      var myPc = myTest.pcRemote;
-
-      function onIceConnectedSuccess () {
-        info("pcRemote ICE connection state log: " + test.pcRemote.iceConnectionLog);
-        ok(true, "pc_remote: ICE switched to 'connected' state");
-        myTest.next();
-      };
-      function onIceConnectedFailed () {
-        dumpSdp(myTest);
-        ok(false, "pc_remote: ICE failed to switch to 'connected' state: " + myPc.iceConnectionState);
-        myTest.next();
-      };
-
-      if (myPc.isIceConnected()) {
-        info("pcRemote ICE connection state log: " + test.pcRemote.iceConnectionLog);
-        ok(true, "pc_remote: ICE is in connected state");
-        myTest.next();
-      } else if (myPc.isIceConnectionPending()) {
-        myPc.waitForIceConnected(onIceConnectedSuccess, onIceConnectedFailed);
-      } else {
-        dumpSdp(myTest);
-        ok(false, "pc_remote: ICE is already in bad state: " + myPc.iceConnectionState);
-        myTest.next();
-      }
-    }
-  ],
-  [
-    'PC_REMOTE_VERIFY_ICE_GATHERING',
-    function (test) {
-      if (test.pcRemote.localRequiresTrickleIce) {
-        ok(test.pcRemote._local_ice_candidates.length > 0, "Received local trickle ICE candidates");
-      }
-      isnot(test.pcRemote._pc.iceGatheringState, GATH_NEW, "ICE gathering state is not 'new'");
-      test.next();
-    }
-  ],
-  [
-    'PC_LOCAL_VERIFY_DATA_CHANNEL_STATE',
-    function (test) {
-      test.waitForInitialDataChannel(test.pcLocal, function() {
-        test.next();
-      }, function() {
-        ok(false, test.pcLocal + " initial dataChannels[0] failed to switch to 'open'");
-        //TODO: use stopAndExit() once bug 1019323 has landed
-        unexpectedEventAndFinish(this, 'timeout')
-        // to prevent test framework timeouts
-        test.next();
-      });
-    }
-  ],
-  [
-    'PC_REMOTE_VERIFY_DATA_CHANNEL_STATE',
-    function (test) {
-      test.waitForInitialDataChannel(test.pcRemote, function() {
-        test.next();
-      }, function() {
-        ok(false, test.pcRemote + " initial dataChannels[0] failed to switch to 'open'");
-        //TODO: use stopAndExit() once bug 1019323 has landed
-        unexpectedEventAndFinish(this, 'timeout');
-        // to prevent test framework timeouts
-        test.next();
-      });
-    }
-  ],
-  [
-    'PC_LOCAL_CHECK_MEDIA_TRACKS',
-    function (test) {
-      test.pcLocal.checkMediaTracks(test._answer_constraints, function () {
-        test.next();
-      });
-    }
-  ],
-  [
-    'PC_REMOTE_CHECK_MEDIA_TRACKS',
-    function (test) {
-      test.pcRemote.checkMediaTracks(test._offer_constraints, function () {
-        test.next();
-      });
-    }
-  ],
-  [
-    'PC_LOCAL_CHECK_MEDIA_FLOW_PRESENT',
-    function (test) {
-      test.pcLocal.checkMediaFlowPresent(function () {
-        test.next();
-      });
-    }
-  ],
-  [
-    'PC_REMOTE_CHECK_MEDIA_FLOW_PRESENT',
-    function (test) {
-      test.pcRemote.checkMediaFlowPresent(function () {
-        test.next();
-      });
-    }
-  ],
-  [
-    'PC_LOCAL_CHECK_ICE_CONNECTIONS',
-    function (test) {
-      test.pcLocal.getStats(null, function(stats) {
-        test.pcLocal.checkStatsIceConnections(stats,
-                                              test._offer_constraints,
-                                              test._offer_options,
-                                              1,
-                                              test.originalAnswer);
-        test.next();
-      });
-    }
-  ],
-  [
-    'PC_REMOTE_CHECK_ICE_CONNECTIONS',
-    function (test) {
-      test.pcRemote.getStats(null, function(stats) {
-        test.pcRemote.checkStatsIceConnections(stats,
-                                               test._offer_constraints,
-                                               test._offer_options,
-                                               1,
-                                               test.originalAnswer);
-        test.next();
-      });
-    }
-  ],
-  [
-    'SEND_MESSAGE',
-    function (test) {
-      var message = "Lorem ipsum dolor sit amet";
-
-      test.send(message, function (channel, data) {
-        is(data, message, "Message correctly transmitted from pcLocal to pcRemote.");
-
-        test.next();
-      });
-    }
-  ],
-  [
-    'SEND_BLOB',
-    function (test) {
-      var contents = ["At vero eos et accusam et justo duo dolores et ea rebum."];
-      var blob = new Blob(contents, { "type" : "text/plain" });
-
-      test.send(blob, function (channel, data) {
-        ok(data instanceof Blob, "Received data is of instance Blob");
-        is(data.size, blob.size, "Received data has the correct size.");
-
-        getBlobContent(data, function (recv_contents) {
-          is(recv_contents, contents, "Received data has the correct content.");
-
-          test.next();
-        });
-      });
-    }
-  ],
-  [
-    'CREATE_SECOND_DATA_CHANNEL',
-    function (test) {
-      test.createDataChannel({ }, function (sourceChannel, targetChannel) {
-        is(sourceChannel.readyState, "open", sourceChannel + " is in state: 'open'");
-        is(targetChannel.readyState, "open", targetChannel + " is in state: 'open'");
-
-        is(targetChannel.binaryType, "blob", targetChannel + " is of binary type 'blob'");
-        is(targetChannel.readyState, "open", targetChannel + " is in state: 'open'");
-
-        test.next();
-      });
-    }
-  ],
-  [
-    'SEND_MESSAGE_THROUGH_LAST_OPENED_CHANNEL',
-    function (test) {
-      var channels = test.pcRemote.dataChannels;
-      var message = "Lorem ipsum dolor sit amet";
-
-      test.send(message, function (channel, data) {
-        is(channels.indexOf(channel), channels.length - 1, "Last channel used");
-        is(data, message, "Received message has the correct content.");
-
-        test.next();
-      });
-    }
-  ],
-  [
-    'SEND_MESSAGE_THROUGH_FIRST_CHANNEL',
-    function (test) {
-      var message = "Message through 1st channel";
-      var options = {
-        sourceChannel: test.pcLocal.dataChannels[0],
-        targetChannel: test.pcRemote.dataChannels[0]
-      };
-
-      test.send(message, function (channel, data) {
-        is(test.pcRemote.dataChannels.indexOf(channel), 0, "1st channel used");
-        is(data, message, "Received message has the correct content.");
-
-        test.next();
-      }, options);
-    }
-  ],
-  [
-    'SEND_MESSAGE_BACK_THROUGH_FIRST_CHANNEL',
-    function (test) {
-      var message = "Return a message also through 1st channel";
-      var options = {
-        sourceChannel: test.pcRemote.dataChannels[0],
-        targetChannel: test.pcLocal.dataChannels[0]
-      };
-
-      test.send(message, function (channel, data) {
-        is(test.pcLocal.dataChannels.indexOf(channel), 0, "1st channel used");
-        is(data, message, "Return message has the correct content.");
-
-        test.next();
-      }, options);
-    }
-  ],
-  [
-    'CREATE_NEGOTIATED_DATA_CHANNEL',
-    function (test) {
-      var options = {negotiated:true, id: 5, protocol:"foo/bar", ordered:false,
-        maxRetransmits:500};
-      test.createDataChannel(options, function (sourceChannel2, targetChannel2) {
-        is(sourceChannel2.readyState, "open", sourceChannel2 + " is in state: 'open'");
-        is(targetChannel2.readyState, "open", targetChannel2 + " is in state: 'open'");
-
-        is(targetChannel2.binaryType, "blob", targetChannel2 + " is of binary type 'blob'");
-        is(targetChannel2.readyState, "open", targetChannel2 + " is in state: 'open'");
-
-        if (options.id != undefined) {
-          is(sourceChannel2.id, options.id, sourceChannel2 + " id is:" + sourceChannel2.id);
-        }
-        else {
-          options.id = sourceChannel2.id;
-        }
-        var reliable = !options.ordered ? false : (options.maxRetransmits || options.maxRetransmitTime);
-        is(sourceChannel2.protocol, options.protocol, sourceChannel2 + " protocol is:" + sourceChannel2.protocol);
-        is(sourceChannel2.reliable, reliable, sourceChannel2 + " reliable is:" + sourceChannel2.reliable);
-/*
-  These aren't exposed by IDL yet
-        is(sourceChannel2.ordered, options.ordered, sourceChannel2 + " ordered is:" + sourceChannel2.ordered);
-        is(sourceChannel2.maxRetransmits, options.maxRetransmits, sourceChannel2 + " maxRetransmits is:" +
-	   sourceChannel2.maxRetransmits);
-        is(sourceChannel2.maxRetransmitTime, options.maxRetransmitTime, sourceChannel2 + " maxRetransmitTime is:" +
-	   sourceChannel2.maxRetransmitTime);
-*/
-
-        is(targetChannel2.id, options.id, targetChannel2 + " id is:" + targetChannel2.id);
-        is(targetChannel2.protocol, options.protocol, targetChannel2 + " protocol is:" + targetChannel2.protocol);
-        is(targetChannel2.reliable, reliable, targetChannel2 + " reliable is:" + targetChannel2.reliable);
-/*
-  These aren't exposed by IDL yet
-       is(targetChannel2.ordered, options.ordered, targetChannel2 + " ordered is:" + targetChannel2.ordered);
-        is(targetChannel2.maxRetransmits, options.maxRetransmits, targetChannel2 + " maxRetransmits is:" +
-	   targetChannel2.maxRetransmits);
-        is(targetChannel2.maxRetransmitTime, options.maxRetransmitTime, targetChannel2 + " maxRetransmitTime is:" +
-	   targetChannel2.maxRetransmitTime);
-*/
-
-        test.next();
-      });
-    }
-  ],
-  [
-    'SEND_MESSAGE_THROUGH_LAST_OPENED_CHANNEL2',
-    function (test) {
-      var channels = test.pcRemote.dataChannels;
-      var message = "Lorem ipsum dolor sit amet";
-
-      test.send(message, function (channel, data) {
-        is(channels.indexOf(channel), channels.length - 1, "Last channel used");
-        is(data, message, "Received message has the correct content.");
-
-        test.next();
-      });
-    }
-  ]
-];
-
--- a/dom/media/tests/mochitest/test_dataChannel_basicAudio.html
+++ b/dom/media/tests/mochitest/test_dataChannel_basicAudio.html
@@ -1,29 +1,31 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="dataChannel.js"></script>
   <script type="application/javascript" src="head.js"></script>
   <script type="application/javascript" src="pc.js"></script>
   <script type="application/javascript" src="templates.js"></script>
   <script type="application/javascript" src="turnConfig.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "796895",
     title: "Basic data channel audio connection"
   });
 
   var test;
-  runNetworkTest(function () {
-    test = new DataChannelTest();
+  runNetworkTest(function (options) {
+    test = new PeerConnectionTest(options);
+    addInitialDataChannel(test.chain);
     test.setMediaConstraints([{audio: true}], [{audio: true}]);
     test.run();
   });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_dataChannel_basicAudioVideo.html
+++ b/dom/media/tests/mochitest/test_dataChannel_basicAudioVideo.html
@@ -1,29 +1,31 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="dataChannel.js"></script>
   <script type="application/javascript" src="head.js"></script>
   <script type="application/javascript" src="pc.js"></script>
   <script type="application/javascript" src="templates.js"></script>
   <script type="application/javascript" src="turnConfig.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "796891",
     title: "Basic data channel audio/video connection"
   });
 
   var test;
-  runNetworkTest(function () {
-    test = new DataChannelTest();
+  runNetworkTest(function (options) {
+    test = new PeerConnectionTest(options);
+    addInitialDataChannel(test.chain);
     test.setMediaConstraints([{audio: true}, {video: true}],
                              [{audio: true}, {video: true}]);
     test.run();
   });
 
 </script>
 </pre>
 </body>
--- a/dom/media/tests/mochitest/test_dataChannel_basicAudioVideoCombined.html
+++ b/dom/media/tests/mochitest/test_dataChannel_basicAudioVideoCombined.html
@@ -1,29 +1,31 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="dataChannel.js"></script>
   <script type="application/javascript" src="head.js"></script>
   <script type="application/javascript" src="pc.js"></script>
   <script type="application/javascript" src="templates.js"></script>
   <script type="application/javascript" src="turnConfig.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "796891",
     title: "Basic data channel audio/video connection"
   });
 
   var test;
-  runNetworkTest(function () {
-    test = new DataChannelTest();
+  runNetworkTest(function (options) {
+    test = new PeerConnectionTest(options);
+    addInitialDataChannel(test.chain);
     test.setMediaConstraints([{audio: true, video: true}],
                              [{audio: true, video: true}]);
     test.run();
   });
 
 </script>
 </pre>
 </body>
--- a/dom/media/tests/mochitest/test_dataChannel_basicAudioVideoNoBundle.html
+++ b/dom/media/tests/mochitest/test_dataChannel_basicAudioVideoNoBundle.html
@@ -1,44 +1,44 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="dataChannel.js"></script>
   <script type="application/javascript" src="head.js"></script>
   <script type="application/javascript" src="pc.js"></script>
   <script type="application/javascript" src="templates.js"></script>
   <script type="application/javascript" src="turnConfig.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
-  SimpleTest.requestFlakyTimeout("untriaged");
-
   createHTML({
     bug: "1016476",
     title: "Basic data channel audio/video connection without bundle"
   });
 
   var test;
   runNetworkTest(function () {
-    test = new DataChannelTest();
-    test.setMediaConstraints([{audio: true}, {video: true}],
-                             [{audio: true}, {video: true}]);
+    test = new PeerConnectionTest();
+    addInitialDataChannel(test.chain);
     test.chain.insertAfter("PC_LOCAL_CREATE_OFFER",
       [[
         'PC_LOCAL_REMOVE_BUNDLE_FROM_OFFER',
         function (test) {
           // Just replace a=group:BUNDLE with something that will be ignored.
           test.originalOffer.sdp = test.originalOffer.sdp.replace(
             "a=group:BUNDLE",
             "a=foo:");
           test.next();
         }
       ]]
       );
+    test.setMediaConstraints([{audio: true}, {video: true}],
+                             [{audio: true}, {video: true}]);
     test.run();
   });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_dataChannel_basicDataOnly.html
+++ b/dom/media/tests/mochitest/test_dataChannel_basicDataOnly.html
@@ -1,28 +1,30 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="dataChannel.js"></script>
   <script type="application/javascript" src="head.js"></script>
   <script type="application/javascript" src="pc.js"></script>
   <script type="application/javascript" src="templates.js"></script>
   <script type="application/javascript" src="turnConfig.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "796894",
     title: "Basic datachannel only connection"
   });
 
   var test;
-  runNetworkTest(function () {
-    test = new DataChannelTest();
+  runNetworkTest(function (options) {
+    test = new PeerConnectionTest(options);
+    addInitialDataChannel(test.chain);
     test.run();
   });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_dataChannel_basicVideo.html
+++ b/dom/media/tests/mochitest/test_dataChannel_basicVideo.html
@@ -1,29 +1,31 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="dataChannel.js"></script>
   <script type="application/javascript" src="head.js"></script>
   <script type="application/javascript" src="pc.js"></script>
   <script type="application/javascript" src="templates.js"></script>
   <script type="application/javascript" src="turnConfig.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "796889",
     title: "Basic data channel video connection"
   });
 
   var test;
-  runNetworkTest(function () {
-    test = new DataChannelTest();
+  runNetworkTest(function (options) {
+    test = new PeerConnectionTest(options);
+    addInitialDataChannel(test.chain);
     test.setMediaConstraints([{video: true}], [{video: true}]);
     test.run();
   });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_dataChannel_bug1013809.html
+++ b/dom/media/tests/mochitest/test_dataChannel_bug1013809.html
@@ -1,29 +1,31 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="dataChannel.js"></script>
   <script type="application/javascript" src="head.js"></script>
   <script type="application/javascript" src="pc.js"></script>
   <script type="application/javascript" src="templates.js"></script>
   <script type="application/javascript" src="turnConfig.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "796895",
     title: "Basic data channel audio connection"
   });
 
   var test;
-  runNetworkTest(function () {
-    test = new DataChannelTest();
+  runNetworkTest(function (options) {
+    test = new PeerConnectionTest(options);
+    addInitialDataChannel(test.chain);
     var sld = test.chain.remove("PC_REMOTE_SET_LOCAL_DESCRIPTION");
     test.chain.insertAfter("PC_LOCAL_SET_REMOTE_DESCRIPTION", sld);
     test.setMediaConstraints([{audio: true}], [{audio: true}]);
     test.run();
   });
 
 </script>
 </pre>