dom/media/tests/mochitest/pc.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/dom/media/tests/mochitest/pc.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1782 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +"use strict";
     1.9 +
    1.10 +
    1.11 +/**
    1.12 + * This class mimics a state machine and handles a list of commands by
    1.13 + * executing them synchronously.
    1.14 + *
    1.15 + * @constructor
    1.16 + * @param {object} framework
    1.17 + *        A back reference to the framework which makes use of the class. It's
    1.18 + *        getting passed in as parameter to each command callback.
    1.19 + * @param {Array[]} [commandList=[]]
    1.20 + *        Default commands to set during initialization
    1.21 + */
    1.22 +function CommandChain(framework, commandList) {
    1.23 +  this._framework = framework;
    1.24 +
    1.25 +  this._commands = commandList || [ ];
    1.26 +  this._current = 0;
    1.27 +
    1.28 +  this.onFinished = null;
    1.29 +}
    1.30 +
    1.31 +CommandChain.prototype = {
    1.32 +
    1.33 +  /**
    1.34 +   * Returns the index of the current command of the chain
    1.35 +   *
    1.36 +   * @returns {number} Index of the current command
    1.37 +   */
    1.38 +  get current() {
    1.39 +    return this._current;
    1.40 +  },
    1.41 +
    1.42 +  /**
    1.43 +   * Checks if the chain has already processed all the commands
    1.44 +   *
    1.45 +   * @returns {boolean} True, if all commands have been processed
    1.46 +   */
    1.47 +  get finished() {
    1.48 +    return this._current === this._commands.length;
    1.49 +  },
    1.50 +
    1.51 +  /**
    1.52 +   * Returns the assigned commands of the chain.
    1.53 +   *
    1.54 +   * @returns {Array[]} Commands of the chain
    1.55 +   */
    1.56 +  get commands() {
    1.57 +    return this._commands;
    1.58 +  },
    1.59 +
    1.60 +  /**
    1.61 +   * Sets new commands for the chain. All existing commands will be replaced.
    1.62 +   *
    1.63 +   * @param {Array[]} commands
    1.64 +   *        List of commands
    1.65 +   */
    1.66 +  set commands(commands) {
    1.67 +    this._commands = commands;
    1.68 +  },
    1.69 +
    1.70 +  /**
    1.71 +   * Execute the next command in the chain.
    1.72 +   */
    1.73 +  executeNext : function () {
    1.74 +    var self = this;
    1.75 +
    1.76 +    function _executeNext() {
    1.77 +      if (!self.finished) {
    1.78 +        var step = self._commands[self._current];
    1.79 +        self._current++;
    1.80 +
    1.81 +        self.currentStepLabel = step[0];
    1.82 +        info("Run step: " + self.currentStepLabel);
    1.83 +        step[1](self._framework);      // Execute step
    1.84 +      }
    1.85 +      else if (typeof(self.onFinished) === 'function') {
    1.86 +        self.onFinished();
    1.87 +      }
    1.88 +    }
    1.89 +
    1.90 +    // To prevent building up the stack we have to execute the next
    1.91 +    // step asynchronously
    1.92 +    window.setTimeout(_executeNext, 0);
    1.93 +  },
    1.94 +
    1.95 +  /**
    1.96 +   * Add new commands to the end of the chain
    1.97 +   *
    1.98 +   * @param {Array[]} commands
    1.99 +   *        List of commands
   1.100 +   */
   1.101 +  append: function (commands) {
   1.102 +    this._commands = this._commands.concat(commands);
   1.103 +  },
   1.104 +
   1.105 +  /**
   1.106 +   * Returns the index of the specified command in the chain.
   1.107 +   *
   1.108 +   * @param {string} id
   1.109 +   *        Identifier of the command
   1.110 +   * @returns {number} Index of the command
   1.111 +   */
   1.112 +  indexOf: function (id) {
   1.113 +    for (var i = 0; i < this._commands.length; i++) {
   1.114 +      if (this._commands[i][0] === id) {
   1.115 +        return i;
   1.116 +      }
   1.117 +    }
   1.118 +
   1.119 +    return -1;
   1.120 +  },
   1.121 +
   1.122 +  /**
   1.123 +   * Inserts the new commands after the specified command.
   1.124 +   *
   1.125 +   * @param {string} id
   1.126 +   *        Identifier of the command
   1.127 +   * @param {Array[]} commands
   1.128 +   *        List of commands
   1.129 +   */
   1.130 +  insertAfter: function (id, commands) {
   1.131 +    var index = this.indexOf(id);
   1.132 +
   1.133 +    if (index > -1) {
   1.134 +      var tail = this.removeAfter(id);
   1.135 +
   1.136 +      this.append(commands);
   1.137 +      this.append(tail);
   1.138 +    }
   1.139 +  },
   1.140 +
   1.141 +  /**
   1.142 +   * Inserts the new commands before the specified command.
   1.143 +   *
   1.144 +   * @param {string} id
   1.145 +   *        Identifier of the command
   1.146 +   * @param {Array[]} commands
   1.147 +   *        List of commands
   1.148 +   */
   1.149 +  insertBefore: function (id, commands) {
   1.150 +    var index = this.indexOf(id);
   1.151 +
   1.152 +    if (index > -1) {
   1.153 +      var tail = this.removeAfter(id);
   1.154 +      var object = this.remove(id);
   1.155 +
   1.156 +      this.append(commands);
   1.157 +      this.append(object);
   1.158 +      this.append(tail);
   1.159 +    }
   1.160 +  },
   1.161 +
   1.162 +  /**
   1.163 +   * Removes the specified command
   1.164 +   *
   1.165 +   * @param {string} id
   1.166 +   *        Identifier of the command
   1.167 +   * @returns {object[]} Removed command
   1.168 +   */
   1.169 +  remove : function (id) {
   1.170 +    return this._commands.splice(this.indexOf(id), 1);
   1.171 +  },
   1.172 +
   1.173 +  /**
   1.174 +   * Removes all commands after the specified one.
   1.175 +   *
   1.176 +   * @param {string} id
   1.177 +   *        Identifier of the command
   1.178 +   * @returns {object[]} Removed commands
   1.179 +   */
   1.180 +  removeAfter : function (id) {
   1.181 +    var index = this.indexOf(id);
   1.182 +
   1.183 +    if (index > -1) {
   1.184 +      return this._commands.splice(index + 1);
   1.185 +    }
   1.186 +
   1.187 +    return null;
   1.188 +  },
   1.189 +
   1.190 +  /**
   1.191 +   * Removes all commands before the specified one.
   1.192 +   *
   1.193 +   * @param {string} id
   1.194 +   *        Identifier of the command
   1.195 +   * @returns {object[]} Removed commands
   1.196 +   */
   1.197 +  removeBefore : function (id) {
   1.198 +    var index = this.indexOf(id);
   1.199 +
   1.200 +    if (index > -1) {
   1.201 +      return this._commands.splice(0, index);
   1.202 +    }
   1.203 +
   1.204 +    return null;
   1.205 +  },
   1.206 +
   1.207 +  /**
   1.208 +   * Replaces all commands after the specified one.
   1.209 +   *
   1.210 +   * @param {string} id
   1.211 +   *        Identifier of the command
   1.212 +   * @returns {object[]} Removed commands
   1.213 +   */
   1.214 +  replaceAfter : function (id, commands) {
   1.215 +    var oldCommands = this.removeAfter(id);
   1.216 +    this.append(commands);
   1.217 +
   1.218 +    return oldCommands;
   1.219 +  },
   1.220 +
   1.221 +  /**
   1.222 +   * Replaces all commands before the specified one.
   1.223 +   *
   1.224 +   * @param {string} id
   1.225 +   *        Identifier of the command
   1.226 +   * @returns {object[]} Removed commands
   1.227 +   */
   1.228 +  replaceBefore : function (id, commands) {
   1.229 +    var oldCommands = this.removeBefore(id);
   1.230 +    this.insertBefore(id, commands);
   1.231 +
   1.232 +    return oldCommands;
   1.233 +  },
   1.234 +
   1.235 +  /**
   1.236 +   * Remove all commands whose identifiers match the specified regex.
   1.237 +   *
   1.238 +   * @param {regex} id_match
   1.239 +   *        Regular expression to match command identifiers.
   1.240 +   */
   1.241 +  filterOut : function (id_match) {
   1.242 +    for (var i = this._commands.length - 1; i >= 0; i--) {
   1.243 +      if (id_match.test(this._commands[i][0])) {
   1.244 +        this._commands.splice(i, 1);
   1.245 +      }
   1.246 +    }
   1.247 +  }
   1.248 +};
   1.249 +
   1.250 +/**
   1.251 + * This class provides a state checker for media elements which store
   1.252 + * a media stream to check for media attribute state and events fired.
   1.253 + * When constructed by a caller, an object instance is created with
   1.254 + * a media element, event state checkers for canplaythrough, timeupdate, and
   1.255 + * time changing on the media element and stream.
   1.256 + *
   1.257 + * @param {HTMLMediaElement} element the media element being analyzed
   1.258 + */
   1.259 +function MediaElementChecker(element) {
   1.260 +  this.element = element;
   1.261 +  this.canPlayThroughFired = false;
   1.262 +  this.timeUpdateFired = false;
   1.263 +  this.timePassed = false;
   1.264 +
   1.265 +  var self = this;
   1.266 +  var elementId = self.element.getAttribute('id');
   1.267 +
   1.268 +  // When canplaythrough fires, we track that it's fired and remove the
   1.269 +  // event listener.
   1.270 +  var canPlayThroughCallback = function() {
   1.271 +    info('canplaythrough fired for media element ' + elementId);
   1.272 +    self.canPlayThroughFired = true;
   1.273 +    self.element.removeEventListener('canplaythrough', canPlayThroughCallback,
   1.274 +                                     false);
   1.275 +  };
   1.276 +
   1.277 +  // When timeupdate fires, we track that it's fired and check if time
   1.278 +  // has passed on the media stream and media element.
   1.279 +  var timeUpdateCallback = function() {
   1.280 +    self.timeUpdateFired = true;
   1.281 +    info('timeupdate fired for media element ' + elementId);
   1.282 +
   1.283 +    // If time has passed, then track that and remove the timeupdate event
   1.284 +    // listener.
   1.285 +    if(element.mozSrcObject && element.mozSrcObject.currentTime > 0 &&
   1.286 +       element.currentTime > 0) {
   1.287 +      info('time passed for media element ' + elementId);
   1.288 +      self.timePassed = true;
   1.289 +      self.element.removeEventListener('timeupdate', timeUpdateCallback,
   1.290 +                                       false);
   1.291 +    }
   1.292 +  };
   1.293 +
   1.294 +  element.addEventListener('canplaythrough', canPlayThroughCallback, false);
   1.295 +  element.addEventListener('timeupdate', timeUpdateCallback, false);
   1.296 +}
   1.297 +
   1.298 +MediaElementChecker.prototype = {
   1.299 +
   1.300 +  /**
   1.301 +   * Waits until the canplaythrough & timeupdate events to fire along with
   1.302 +   * ensuring time has passed on the stream and media element.
   1.303 +   *
   1.304 +   * @param {Function} onSuccess the success callback when media flow is
   1.305 +   *                             established
   1.306 +   */
   1.307 +  waitForMediaFlow : function MEC_WaitForMediaFlow(onSuccess) {
   1.308 +    var self = this;
   1.309 +    var elementId = self.element.getAttribute('id');
   1.310 +    info('Analyzing element: ' + elementId);
   1.311 +
   1.312 +    if(self.canPlayThroughFired && self.timeUpdateFired && self.timePassed) {
   1.313 +      ok(true, 'Media flowing for ' + elementId);
   1.314 +      onSuccess();
   1.315 +    } else {
   1.316 +      setTimeout(function() {
   1.317 +        self.waitForMediaFlow(onSuccess);
   1.318 +      }, 100);
   1.319 +    }
   1.320 +  },
   1.321 +
   1.322 +  /**
   1.323 +   * Checks if there is no media flow present by checking that the ready
   1.324 +   * state of the media element is HAVE_METADATA.
   1.325 +   */
   1.326 +  checkForNoMediaFlow : function MEC_CheckForNoMediaFlow() {
   1.327 +    ok(this.element.readyState === HTMLMediaElement.HAVE_METADATA,
   1.328 +       'Media element has a ready state of HAVE_METADATA');
   1.329 +  }
   1.330 +};
   1.331 +
   1.332 +/**
   1.333 + * Query function for determining if any IP address is available for
   1.334 + * generating SDP.
   1.335 + *
   1.336 + * @return false if required additional network setup.
   1.337 + */
   1.338 +function isNetworkReady() {
   1.339 +  // for gonk platform
   1.340 +  if ("nsINetworkInterfaceListService" in SpecialPowers.Ci) {
   1.341 +    var listService = SpecialPowers.Cc["@mozilla.org/network/interface-list-service;1"]
   1.342 +                        .getService(SpecialPowers.Ci.nsINetworkInterfaceListService);
   1.343 +    var itfList = listService.getDataInterfaceList(
   1.344 +          SpecialPowers.Ci.nsINetworkInterfaceListService.LIST_NOT_INCLUDE_MMS_INTERFACES |
   1.345 +          SpecialPowers.Ci.nsINetworkInterfaceListService.LIST_NOT_INCLUDE_SUPL_INTERFACES |
   1.346 +          SpecialPowers.Ci.nsINetworkInterfaceListService.LIST_NOT_INCLUDE_IMS_INTERFACES |
   1.347 +          SpecialPowers.Ci.nsINetworkInterfaceListService.LIST_NOT_INCLUDE_DUN_INTERFACES);
   1.348 +    var num = itfList.getNumberOfInterface();
   1.349 +    for (var i = 0; i < num; i++) {
   1.350 +      var ips = {};
   1.351 +      var prefixLengths = {};
   1.352 +      var length = itfList.getInterface(i).getAddresses(ips, prefixLengths);
   1.353 +
   1.354 +      for (var j = 0; j < length; j++) {
   1.355 +        var ip = ips.value[j];
   1.356 +        // skip IPv6 address
   1.357 +        if (ip.indexOf(":") < 0) {
   1.358 +          info("Network interface is ready with address: " + ip);
   1.359 +          return true;
   1.360 +        }
   1.361 +      }
   1.362 +    }
   1.363 +    // ip address is not available
   1.364 +    info("Network interface is not ready, required additional network setup");
   1.365 +    return false;
   1.366 +  }
   1.367 +  info("Network setup is not required");
   1.368 +  return true;
   1.369 +}
   1.370 +
   1.371 +/**
   1.372 + * Network setup utils for Gonk
   1.373 + *
   1.374 + * @return {object} providing functions for setup/teardown data connection
   1.375 + */
   1.376 +function getNetworkUtils() {
   1.377 +  var url = SimpleTest.getTestFileURL("NetworkPreparationChromeScript.js");
   1.378 +  var script = SpecialPowers.loadChromeScript(url);
   1.379 +
   1.380 +  var utils = {
   1.381 +    /**
   1.382 +     * Utility for setting up data connection.
   1.383 +     *
   1.384 +     * @param aCallback callback after data connection is ready.
   1.385 +     */
   1.386 +    prepareNetwork: function(aCallback) {
   1.387 +      script.addMessageListener('network-ready', function (message) {
   1.388 +        info("Network interface is ready");
   1.389 +        aCallback();
   1.390 +      });
   1.391 +      info("Setup network interface");
   1.392 +      script.sendAsyncMessage("prepare-network", true);
   1.393 +    },
   1.394 +    /**
   1.395 +     * Utility for tearing down data connection.
   1.396 +     *
   1.397 +     * @param aCallback callback after data connection is closed.
   1.398 +     */
   1.399 +    tearDownNetwork: function(aCallback) {
   1.400 +      script.addMessageListener('network-disabled', function (message) {
   1.401 +        ok(true, 'network-disabled');
   1.402 +        script.destroy();
   1.403 +        aCallback();
   1.404 +      });
   1.405 +      script.sendAsyncMessage("network-cleanup", true);
   1.406 +    }
   1.407 +  };
   1.408 +
   1.409 +  return utils;
   1.410 +}
   1.411 +
   1.412 +/**
   1.413 + * This class handles tests for peer connections.
   1.414 + *
   1.415 + * @constructor
   1.416 + * @param {object} [options={}]
   1.417 + *        Optional options for the peer connection test
   1.418 + * @param {object} [options.commands=commandsPeerConnection]
   1.419 + *        Commands to run for the test
   1.420 + * @param {bool}   [options.is_local=true]
   1.421 + *        true if this test should run the tests for the "local" side.
   1.422 + * @param {bool}   [options.is_remote=true]
   1.423 + *        true if this test should run the tests for the "remote" side.
   1.424 + * @param {object} [options.config_pc1=undefined]
   1.425 + *        Configuration for the local peer connection instance
   1.426 + * @param {object} [options.config_pc2=undefined]
   1.427 + *        Configuration for the remote peer connection instance. If not defined
   1.428 + *        the configuration from the local instance will be used
   1.429 + */
   1.430 +function PeerConnectionTest(options) {
   1.431 +  // If no options are specified make it an empty object
   1.432 +  options = options || { };
   1.433 +  options.commands = options.commands || commandsPeerConnection;
   1.434 +  options.is_local = "is_local" in options ? options.is_local : true;
   1.435 +  options.is_remote = "is_remote" in options ? options.is_remote : true;
   1.436 +
   1.437 +  var netTeardownCommand = null;
   1.438 +  if (!isNetworkReady()) {
   1.439 +    var utils = getNetworkUtils();
   1.440 +    // Trigger network setup to obtain IP address before creating any PeerConnection.
   1.441 +    utils.prepareNetwork(function() {
   1.442 +      ok(isNetworkReady(),'setup network connection successfully');
   1.443 +    });
   1.444 +
   1.445 +    netTeardownCommand = [
   1.446 +      [
   1.447 +        'TEARDOWN_NETWORK',
   1.448 +        function(test) {
   1.449 +          utils.tearDownNetwork(function() {
   1.450 +            info('teardown network connection');
   1.451 +            test.next();
   1.452 +          });
   1.453 +        }
   1.454 +      ]
   1.455 +    ];
   1.456 +  }
   1.457 +
   1.458 +  if (options.is_local)
   1.459 +    this.pcLocal = new PeerConnectionWrapper('pcLocal', options.config_pc1);
   1.460 +  else
   1.461 +    this.pcLocal = null;
   1.462 +
   1.463 +  if (options.is_remote)
   1.464 +    this.pcRemote = new PeerConnectionWrapper('pcRemote', options.config_pc2 || options.config_pc1);
   1.465 +  else
   1.466 +    this.pcRemote = null;
   1.467 +
   1.468 +  this.connected = false;
   1.469 +
   1.470 +  // Create command chain instance and assign default commands
   1.471 +  this.chain = new CommandChain(this, options.commands);
   1.472 +  if (!options.is_local) {
   1.473 +    this.chain.filterOut(/^PC_LOCAL/);
   1.474 +  }
   1.475 +  if (!options.is_remote) {
   1.476 +    this.chain.filterOut(/^PC_REMOTE/);
   1.477 +  }
   1.478 +
   1.479 +  // Insert network teardown after testcase execution.
   1.480 +  if (netTeardownCommand) {
   1.481 +    this.chain.append(netTeardownCommand);
   1.482 +  }
   1.483 +
   1.484 +  var self = this;
   1.485 +  this.chain.onFinished = function () {
   1.486 +    self.teardown();
   1.487 +  };
   1.488 +}
   1.489 +
   1.490 +/**
   1.491 + * Closes the peer connection if it is active
   1.492 + *
   1.493 + * @param {Function} onSuccess
   1.494 + *        Callback to execute when the peer connection has been closed successfully
   1.495 + */
   1.496 +PeerConnectionTest.prototype.close = function PCT_close(onSuccess) {
   1.497 +  info("Closing peer connections. Connection state=" + this.connected);
   1.498 +
   1.499 +  function signalingstatechangeClose(state) {
   1.500 +    info("'onsignalingstatechange' event '" + state + "' received");
   1.501 +    is(state, "closed", "onsignalingstatechange event is closed");
   1.502 +  }
   1.503 +
   1.504 +  // There is no onclose event for the remote peer existent yet. So close it
   1.505 +  // side-by-side with the local peer.
   1.506 +  if (this.pcLocal) {
   1.507 +    this.pcLocal.onsignalingstatechange = signalingstatechangeClose;
   1.508 +    this.pcLocal.close();
   1.509 +  }
   1.510 +  if (this.pcRemote) {
   1.511 +    this.pcRemote.onsignalingstatechange = signalingstatechangeClose;
   1.512 +    this.pcRemote.close();
   1.513 +  }
   1.514 +  this.connected = false;
   1.515 +
   1.516 +  onSuccess();
   1.517 +};
   1.518 +
   1.519 +/**
   1.520 + * Executes the next command.
   1.521 + */
   1.522 +PeerConnectionTest.prototype.next = function PCT_next() {
   1.523 +  if (this._stepTimeout) {
   1.524 +    clearTimeout(this._stepTimeout);
   1.525 +    this._stepTimeout = null;
   1.526 +  }
   1.527 +  this.chain.executeNext();
   1.528 +};
   1.529 +
   1.530 +/**
   1.531 + * Set a timeout for the current step.
   1.532 + * @param {long] ms the number of milliseconds to allow for this step
   1.533 + */
   1.534 +PeerConnectionTest.prototype.setStepTimeout = function(ms) {
   1.535 +  this._stepTimeout = setTimeout(function() {
   1.536 +    ok(false, "Step timed out: " + this.chain.currentStepLabel);
   1.537 +    this.next();
   1.538 +  }.bind(this), ms);
   1.539 +};
   1.540 +
   1.541 +/**
   1.542 + * Creates an answer for the specified peer connection instance
   1.543 + * and automatically handles the failure case.
   1.544 + *
   1.545 + * @param {PeerConnectionWrapper} peer
   1.546 + *        The peer connection wrapper to run the command on
   1.547 + * @param {function} onSuccess
   1.548 + *        Callback to execute if the offer was created successfully
   1.549 + */
   1.550 +PeerConnectionTest.prototype.createAnswer =
   1.551 +function PCT_createAnswer(peer, onSuccess) {
   1.552 +  peer.createAnswer(function (answer) {
   1.553 +    onSuccess(answer);
   1.554 +  });
   1.555 +};
   1.556 +
   1.557 +/**
   1.558 + * Creates an offer for the specified peer connection instance
   1.559 + * and automatically handles the failure case.
   1.560 + *
   1.561 + * @param {PeerConnectionWrapper} peer
   1.562 + *        The peer connection wrapper to run the command on
   1.563 + * @param {function} onSuccess
   1.564 + *        Callback to execute if the offer was created successfully
   1.565 + */
   1.566 +PeerConnectionTest.prototype.createOffer =
   1.567 +function PCT_createOffer(peer, onSuccess) {
   1.568 +  peer.createOffer(function (offer) {
   1.569 +    onSuccess(offer);
   1.570 +  });
   1.571 +};
   1.572 +
   1.573 +PeerConnectionTest.prototype.setIdentityProvider =
   1.574 +function(peer, provider, protocol, identity) {
   1.575 +  peer.setIdentityProvider(provider, protocol, identity);
   1.576 +};
   1.577 +
   1.578 +/**
   1.579 + * Sets the local description for the specified peer connection instance
   1.580 + * and automatically handles the failure case.
   1.581 + *
   1.582 + * @param {PeerConnectionWrapper} peer
   1.583 +          The peer connection wrapper to run the command on
   1.584 + * @param {mozRTCSessionDescription} desc
   1.585 + *        Session description for the local description request
   1.586 + * @param {function} onSuccess
   1.587 + *        Callback to execute if the local description was set successfully
   1.588 + */
   1.589 +PeerConnectionTest.prototype.setLocalDescription =
   1.590 +function PCT_setLocalDescription(peer, desc, stateExpected, onSuccess) {
   1.591 +  var eventFired = false;
   1.592 +  var stateChanged = false;
   1.593 +
   1.594 +  function check_next_test() {
   1.595 +    if (eventFired && stateChanged) {
   1.596 +      onSuccess();
   1.597 +    }
   1.598 +  }
   1.599 +
   1.600 +  peer.onsignalingstatechange = function (state) {
   1.601 +    //info(peer + ": 'onsignalingstatechange' event registered, signalingState: " + peer.signalingState);
   1.602 +    info(peer + ": 'onsignalingstatechange' event '" + state + "' received");
   1.603 +    if(stateExpected === state && eventFired == false) {
   1.604 +      eventFired = true;
   1.605 +      check_next_test();
   1.606 +    } else {
   1.607 +      ok(false, "This event has either already fired or there has been a " +
   1.608 +                "mismatch between event received " + state +
   1.609 +                " and event expected " + stateExpected);
   1.610 +    }
   1.611 +  };
   1.612 +
   1.613 +  peer.setLocalDescription(desc, function () {
   1.614 +    stateChanged = true;
   1.615 +    check_next_test();
   1.616 +  });
   1.617 +};
   1.618 +
   1.619 +/**
   1.620 + * Sets the media constraints for both peer connection instances.
   1.621 + *
   1.622 + * @param {object} constraintsLocal
   1.623 + *        Media constrains for the local peer connection instance
   1.624 + * @param constraintsRemote
   1.625 + */
   1.626 +PeerConnectionTest.prototype.setMediaConstraints =
   1.627 +function PCT_setMediaConstraints(constraintsLocal, constraintsRemote) {
   1.628 +  if (this.pcLocal)
   1.629 +    this.pcLocal.constraints = constraintsLocal;
   1.630 +  if (this.pcRemote)
   1.631 +    this.pcRemote.constraints = constraintsRemote;
   1.632 +};
   1.633 +
   1.634 +/**
   1.635 + * Sets the media constraints used on a createOffer call in the test.
   1.636 + *
   1.637 + * @param {object} constraints the media constraints to use on createOffer
   1.638 + */
   1.639 +PeerConnectionTest.prototype.setOfferConstraints =
   1.640 +function PCT_setOfferConstraints(constraints) {
   1.641 +  if (this.pcLocal)
   1.642 +    this.pcLocal.offerConstraints = constraints;
   1.643 +};
   1.644 +
   1.645 +/**
   1.646 + * Sets the remote description for the specified peer connection instance
   1.647 + * and automatically handles the failure case.
   1.648 + *
   1.649 + * @param {PeerConnectionWrapper} peer
   1.650 +          The peer connection wrapper to run the command on
   1.651 + * @param {mozRTCSessionDescription} desc
   1.652 + *        Session description for the remote description request
   1.653 + * @param {function} onSuccess
   1.654 + *        Callback to execute if the local description was set successfully
   1.655 + */
   1.656 +PeerConnectionTest.prototype.setRemoteDescription =
   1.657 +function PCT_setRemoteDescription(peer, desc, stateExpected, onSuccess) {
   1.658 +  var eventFired = false;
   1.659 +  var stateChanged = false;
   1.660 +
   1.661 +  function check_next_test() {
   1.662 +    if (eventFired && stateChanged) {
   1.663 +      onSuccess();
   1.664 +    }
   1.665 +  }
   1.666 +
   1.667 +  peer.onsignalingstatechange = function (state) {
   1.668 +    info(peer + ": 'onsignalingstatechange' event '" + state + "' received");
   1.669 +    if(stateExpected === state && eventFired == false) {
   1.670 +      eventFired = true;
   1.671 +      check_next_test();
   1.672 +    } else {
   1.673 +      ok(false, "This event has either already fired or there has been a " +
   1.674 +                "mismatch between event received " + state +
   1.675 +                " and event expected " + stateExpected);
   1.676 +    }
   1.677 +  };
   1.678 +
   1.679 +  peer.setRemoteDescription(desc, function () {
   1.680 +    stateChanged = true;
   1.681 +    check_next_test();
   1.682 +  });
   1.683 +};
   1.684 +
   1.685 +/**
   1.686 + * Start running the tests as assigned to the command chain.
   1.687 + */
   1.688 +PeerConnectionTest.prototype.run = function PCT_run() {
   1.689 +  this.next();
   1.690 +};
   1.691 +
   1.692 +/**
   1.693 + * Clean up the objects used by the test
   1.694 + */
   1.695 +PeerConnectionTest.prototype.teardown = function PCT_teardown() {
   1.696 +  this.close(function () {
   1.697 +    info("Test finished");
   1.698 +    if (window.SimpleTest)
   1.699 +      SimpleTest.finish();
   1.700 +    else
   1.701 +      finish();
   1.702 +  });
   1.703 +};
   1.704 +
   1.705 +/**
   1.706 + * This class handles tests for data channels.
   1.707 + *
   1.708 + * @constructor
   1.709 + * @param {object} [options={}]
   1.710 + *        Optional options for the peer connection test
   1.711 + * @param {object} [options.commands=commandsDataChannel]
   1.712 + *        Commands to run for the test
   1.713 + * @param {object} [options.config_pc1=undefined]
   1.714 + *        Configuration for the local peer connection instance
   1.715 + * @param {object} [options.config_pc2=undefined]
   1.716 + *        Configuration for the remote peer connection instance. If not defined
   1.717 + *        the configuration from the local instance will be used
   1.718 + */
   1.719 +function DataChannelTest(options) {
   1.720 +  options = options || { };
   1.721 +  options.commands = options.commands || commandsDataChannel;
   1.722 +
   1.723 +  PeerConnectionTest.call(this, options);
   1.724 +}
   1.725 +
   1.726 +DataChannelTest.prototype = Object.create(PeerConnectionTest.prototype, {
   1.727 +  close : {
   1.728 +    /**
   1.729 +     * Close the open data channels, followed by the underlying peer connection
   1.730 +     *
   1.731 +     * @param {Function} onSuccess
   1.732 +     *        Callback to execute when the connection has been closed
   1.733 +     */
   1.734 +    value : function DCT_close(onSuccess) {
   1.735 +      var self = this;
   1.736 +
   1.737 +      function _closeChannels() {
   1.738 +        var length = self.pcLocal.dataChannels.length;
   1.739 +
   1.740 +        if (length > 0) {
   1.741 +          self.closeDataChannel(length - 1, function () {
   1.742 +            _closeChannels();
   1.743 +          });
   1.744 +        }
   1.745 +        else {
   1.746 +          PeerConnectionTest.prototype.close.call(self, onSuccess);
   1.747 +        }
   1.748 +      }
   1.749 +
   1.750 +      _closeChannels();
   1.751 +    }
   1.752 +  },
   1.753 +
   1.754 +  closeDataChannel : {
   1.755 +    /**
   1.756 +     * Close the specified data channel
   1.757 +     *
   1.758 +     * @param {Number} index
   1.759 +     *        Index of the data channel to close on both sides
   1.760 +     * @param {Function} onSuccess
   1.761 +     *        Callback to execute when the data channel has been closed
   1.762 +     */
   1.763 +    value : function DCT_closeDataChannel(index, onSuccess) {
   1.764 +      var localChannel = this.pcLocal.dataChannels[index];
   1.765 +      var remoteChannel = this.pcRemote.dataChannels[index];
   1.766 +
   1.767 +      var self = this;
   1.768 +
   1.769 +      // Register handler for remote channel, cause we have to wait until
   1.770 +      // the current close operation has been finished.
   1.771 +      remoteChannel.onclose = function () {
   1.772 +        self.pcRemote.dataChannels.splice(index, 1);
   1.773 +
   1.774 +        onSuccess(remoteChannel);
   1.775 +      };
   1.776 +
   1.777 +      localChannel.close();
   1.778 +      this.pcLocal.dataChannels.splice(index, 1);
   1.779 +    }
   1.780 +  },
   1.781 +
   1.782 +  createDataChannel : {
   1.783 +    /**
   1.784 +     * Create a data channel
   1.785 +     *
   1.786 +     * @param {Dict} options
   1.787 +     *        Options for the data channel (see nsIPeerConnection)
   1.788 +     * @param {Function} onSuccess
   1.789 +     *        Callback when the creation was successful
   1.790 +     */
   1.791 +    value : function DCT_createDataChannel(options, onSuccess) {
   1.792 +      var localChannel = null;
   1.793 +      var remoteChannel = null;
   1.794 +      var self = this;
   1.795 +
   1.796 +      // Method to synchronize all asynchronous events.
   1.797 +      function check_next_test() {
   1.798 +        if (self.connected && localChannel && remoteChannel) {
   1.799 +          onSuccess(localChannel, remoteChannel);
   1.800 +        }
   1.801 +      }
   1.802 +
   1.803 +      if (!options.negotiated) {
   1.804 +        // Register handlers for the remote peer
   1.805 +        this.pcRemote.registerDataChannelOpenEvents(function (channel) {
   1.806 +          remoteChannel = channel;
   1.807 +          check_next_test();
   1.808 +        });
   1.809 +      }
   1.810 +
   1.811 +      // Create the datachannel and handle the local 'onopen' event
   1.812 +      this.pcLocal.createDataChannel(options, function (channel) {
   1.813 +        localChannel = channel;
   1.814 +
   1.815 +        if (options.negotiated) {
   1.816 +          // externally negotiated - we need to open from both ends
   1.817 +          options.id = options.id || channel.id;  // allow for no id to let the impl choose
   1.818 +          self.pcRemote.createDataChannel(options, function (channel) {
   1.819 +            remoteChannel = channel;
   1.820 +            check_next_test();
   1.821 +          });
   1.822 +        } else {
   1.823 +          check_next_test();
   1.824 +        }
   1.825 +      });
   1.826 +    }
   1.827 +  },
   1.828 +
   1.829 +  send : {
   1.830 +    /**
   1.831 +     * Send data (message or blob) to the other peer
   1.832 +     *
   1.833 +     * @param {String|Blob} data
   1.834 +     *        Data to send to the other peer. For Blobs the MIME type will be lost.
   1.835 +     * @param {Function} onSuccess
   1.836 +     *        Callback to execute when data has been sent
   1.837 +     * @param {Object} [options={ }]
   1.838 +     *        Options to specify the data channels to be used
   1.839 +     * @param {DataChannelWrapper} [options.sourceChannel=pcLocal.dataChannels[length - 1]]
   1.840 +     *        Data channel to use for sending the message
   1.841 +     * @param {DataChannelWrapper} [options.targetChannel=pcRemote.dataChannels[length - 1]]
   1.842 +     *        Data channel to use for receiving the message
   1.843 +     */
   1.844 +    value : function DCT_send(data, onSuccess, options) {
   1.845 +      options = options || { };
   1.846 +      var source = options.sourceChannel ||
   1.847 +               this.pcLocal.dataChannels[this.pcLocal.dataChannels.length - 1];
   1.848 +      var target = options.targetChannel ||
   1.849 +               this.pcRemote.dataChannels[this.pcRemote.dataChannels.length - 1];
   1.850 +
   1.851 +      // Register event handler for the target channel
   1.852 +      target.onmessage = function (recv_data) {
   1.853 +        onSuccess(target, recv_data);
   1.854 +      };
   1.855 +
   1.856 +      source.send(data);
   1.857 +    }
   1.858 +  },
   1.859 +
   1.860 +  setLocalDescription : {
   1.861 +    /**
   1.862 +     * Sets the local description for the specified peer connection instance
   1.863 +     * and automatically handles the failure case. In case for the final call
   1.864 +     * it will setup the requested datachannel.
   1.865 +     *
   1.866 +     * @param {PeerConnectionWrapper} peer
   1.867 +              The peer connection wrapper to run the command on
   1.868 +     * @param {mozRTCSessionDescription} desc
   1.869 +     *        Session description for the local description request
   1.870 +     * @param {function} onSuccess
   1.871 +     *        Callback to execute if the local description was set successfully
   1.872 +     */
   1.873 +    value : function DCT_setLocalDescription(peer, desc, state, onSuccess) {
   1.874 +      // If the peer has a remote offer we are in the final call, and have
   1.875 +      // to wait for the datachannel connection to be open. It will also set
   1.876 +      // the local description internally.
   1.877 +      if (peer.signalingState === 'have-remote-offer') {
   1.878 +        this.waitForInitialDataChannel(peer, desc, state, onSuccess);
   1.879 +      }
   1.880 +      else {
   1.881 +        PeerConnectionTest.prototype.setLocalDescription.call(this, peer,
   1.882 +                                                              desc, state, onSuccess);
   1.883 +      }
   1.884 +
   1.885 +    }
   1.886 +  },
   1.887 +
   1.888 +  waitForInitialDataChannel : {
   1.889 +    /**
   1.890 +     * Create an initial data channel before the peer connection has been connected
   1.891 +     *
   1.892 +     * @param {PeerConnectionWrapper} peer
   1.893 +              The peer connection wrapper to run the command on
   1.894 +     * @param {mozRTCSessionDescription} desc
   1.895 +     *        Session description for the local description request
   1.896 +     * @param {Function} onSuccess
   1.897 +     *        Callback when the creation was successful
   1.898 +     */
   1.899 +    value : function DCT_waitForInitialDataChannel(peer, desc, state, onSuccess) {
   1.900 +      var self = this;
   1.901 +
   1.902 +      var targetPeer = peer;
   1.903 +      var targetChannel = null;
   1.904 +
   1.905 +      var sourcePeer = (peer == this.pcLocal) ? this.pcRemote : this.pcLocal;
   1.906 +      var sourceChannel = null;
   1.907 +
   1.908 +      // Method to synchronize all asynchronous events which current happen
   1.909 +      // due to a non-predictable flow. With bug 875346 fixed we will be able
   1.910 +      // to simplify this code.
   1.911 +      function check_next_test() {
   1.912 +        if (self.connected && sourceChannel && targetChannel) {
   1.913 +          onSuccess(sourceChannel, targetChannel);
   1.914 +        }
   1.915 +      }
   1.916 +
   1.917 +      // Register 'onopen' handler for the first local data channel
   1.918 +      sourcePeer.dataChannels[0].onopen = function (channel) {
   1.919 +        sourceChannel = channel;
   1.920 +        check_next_test();
   1.921 +      };
   1.922 +
   1.923 +      // Register handlers for the target peer
   1.924 +      targetPeer.registerDataChannelOpenEvents(function (channel) {
   1.925 +        targetChannel = channel;
   1.926 +        check_next_test();
   1.927 +      });
   1.928 +
   1.929 +      PeerConnectionTest.prototype.setLocalDescription.call(this, targetPeer, desc,
   1.930 +        state,
   1.931 +        function () {
   1.932 +          self.connected = true;
   1.933 +          check_next_test();
   1.934 +        }
   1.935 +      );
   1.936 +    }
   1.937 +  }
   1.938 +});
   1.939 +
   1.940 +/**
   1.941 + * This class acts as a wrapper around a DataChannel instance.
   1.942 + *
   1.943 + * @param dataChannel
   1.944 + * @param peerConnectionWrapper
   1.945 + * @constructor
   1.946 + */
   1.947 +function DataChannelWrapper(dataChannel, peerConnectionWrapper) {
   1.948 +  this._channel = dataChannel;
   1.949 +  this._pc = peerConnectionWrapper;
   1.950 +
   1.951 +  info("Creating " + this);
   1.952 +
   1.953 +  /**
   1.954 +   * Setup appropriate callbacks
   1.955 +   */
   1.956 +
   1.957 +  this.onclose = unexpectedEventAndFinish(this, 'onclose');
   1.958 +  this.onerror = unexpectedEventAndFinish(this, 'onerror');
   1.959 +  this.onmessage = unexpectedEventAndFinish(this, 'onmessage');
   1.960 +  this.onopen = unexpectedEventAndFinish(this, 'onopen');
   1.961 +
   1.962 +  var self = this;
   1.963 +
   1.964 +  /**
   1.965 +   * Callback for native data channel 'onclose' events. If no custom handler
   1.966 +   * has been specified via 'this.onclose', a failure will be raised if an
   1.967 +   * event of this type gets caught.
   1.968 +   */
   1.969 +  this._channel.onclose = function () {
   1.970 +    info(self + ": 'onclose' event fired");
   1.971 +
   1.972 +    self.onclose(self);
   1.973 +    self.onclose = unexpectedEventAndFinish(self, 'onclose');
   1.974 +  };
   1.975 +
   1.976 +  /**
   1.977 +   * Callback for native data channel 'onmessage' events. If no custom handler
   1.978 +   * has been specified via 'this.onmessage', a failure will be raised if an
   1.979 +   * event of this type gets caught.
   1.980 +   *
   1.981 +   * @param {Object} event
   1.982 +   *        Event data which includes the sent message
   1.983 +   */
   1.984 +  this._channel.onmessage = function (event) {
   1.985 +    info(self + ": 'onmessage' event fired for '" + event.data + "'");
   1.986 +
   1.987 +    self.onmessage(event.data);
   1.988 +    self.onmessage = unexpectedEventAndFinish(self, 'onmessage');
   1.989 +  };
   1.990 +
   1.991 +  /**
   1.992 +   * Callback for native data channel 'onopen' events. If no custom handler
   1.993 +   * has been specified via 'this.onopen', a failure will be raised if an
   1.994 +   * event of this type gets caught.
   1.995 +   */
   1.996 +  this._channel.onopen = function () {
   1.997 +    info(self + ": 'onopen' event fired");
   1.998 +
   1.999 +    self.onopen(self);
  1.1000 +    self.onopen = unexpectedEventAndFinish(self, 'onopen');
  1.1001 +  };
  1.1002 +}
  1.1003 +
  1.1004 +DataChannelWrapper.prototype = {
  1.1005 +  /**
  1.1006 +   * Returns the binary type of the channel
  1.1007 +   *
  1.1008 +   * @returns {String} The binary type
  1.1009 +   */
  1.1010 +  get binaryType() {
  1.1011 +    return this._channel.binaryType;
  1.1012 +  },
  1.1013 +
  1.1014 +  /**
  1.1015 +   * Sets the binary type of the channel
  1.1016 +   *
  1.1017 +   * @param {String} type
  1.1018 +   *        The new binary type of the channel
  1.1019 +   */
  1.1020 +  set binaryType(type) {
  1.1021 +    this._channel.binaryType = type;
  1.1022 +  },
  1.1023 +
  1.1024 +  /**
  1.1025 +   * Returns the label of the underlying data channel
  1.1026 +   *
  1.1027 +   * @returns {String} The label
  1.1028 +   */
  1.1029 +  get label() {
  1.1030 +    return this._channel.label;
  1.1031 +  },
  1.1032 +
  1.1033 +  /**
  1.1034 +   * Returns the protocol of the underlying data channel
  1.1035 +   *
  1.1036 +   * @returns {String} The protocol
  1.1037 +   */
  1.1038 +  get protocol() {
  1.1039 +    return this._channel.protocol;
  1.1040 +  },
  1.1041 +
  1.1042 +  /**
  1.1043 +   * Returns the id of the underlying data channel
  1.1044 +   *
  1.1045 +   * @returns {number} The stream id
  1.1046 +   */
  1.1047 +  get id() {
  1.1048 +    return this._channel.id;
  1.1049 +  },
  1.1050 +
  1.1051 +  /**
  1.1052 +   * Returns the reliable state of the underlying data channel
  1.1053 +   *
  1.1054 +   * @returns {bool} The stream's reliable state
  1.1055 +   */
  1.1056 +  get reliable() {
  1.1057 +    return this._channel.reliable;
  1.1058 +  },
  1.1059 +
  1.1060 +  // ordered, maxRetransmits and maxRetransmitTime not exposed yet
  1.1061 +
  1.1062 +  /**
  1.1063 +   * Returns the readyState bit of the data channel
  1.1064 +   *
  1.1065 +   * @returns {String} The state of the channel
  1.1066 +   */
  1.1067 +  get readyState() {
  1.1068 +    return this._channel.readyState;
  1.1069 +  },
  1.1070 +
  1.1071 +  /**
  1.1072 +   * Close the data channel
  1.1073 +   */
  1.1074 +  close : function () {
  1.1075 +    info(this + ": Closing channel");
  1.1076 +    this._channel.close();
  1.1077 +  },
  1.1078 +
  1.1079 +  /**
  1.1080 +   * Send data through the data channel
  1.1081 +   *
  1.1082 +   * @param {String|Object} data
  1.1083 +   *        Data which has to be sent through the data channel
  1.1084 +   */
  1.1085 +  send: function DCW_send(data) {
  1.1086 +    info(this + ": Sending data '" + data + "'");
  1.1087 +    this._channel.send(data);
  1.1088 +  },
  1.1089 +
  1.1090 +  /**
  1.1091 +   * Returns the string representation of the class
  1.1092 +   *
  1.1093 +   * @returns {String} The string representation
  1.1094 +   */
  1.1095 +  toString: function DCW_toString() {
  1.1096 +    return "DataChannelWrapper (" + this._pc.label + '_' + this._channel.label + ")";
  1.1097 +  }
  1.1098 +};
  1.1099 +
  1.1100 +
  1.1101 +/**
  1.1102 + * This class acts as a wrapper around a PeerConnection instance.
  1.1103 + *
  1.1104 + * @constructor
  1.1105 + * @param {string} label
  1.1106 + *        Description for the peer connection instance
  1.1107 + * @param {object} configuration
  1.1108 + *        Configuration for the peer connection instance
  1.1109 + */
  1.1110 +function PeerConnectionWrapper(label, configuration) {
  1.1111 +  this.configuration = configuration;
  1.1112 +  this.label = label;
  1.1113 +  this.whenCreated = Date.now();
  1.1114 +
  1.1115 +  this.constraints = [ ];
  1.1116 +  this.offerConstraints = {};
  1.1117 +  this.streams = [ ];
  1.1118 +  this.mediaCheckers = [ ];
  1.1119 +
  1.1120 +  this.dataChannels = [ ];
  1.1121 +
  1.1122 +  info("Creating " + this);
  1.1123 +  this._pc = new mozRTCPeerConnection(this.configuration);
  1.1124 +  is(this._pc.iceConnectionState, "new", "iceConnectionState starts at 'new'");
  1.1125 +
  1.1126 +  /**
  1.1127 +   * Setup callback handlers
  1.1128 +   */
  1.1129 +  var self = this;
  1.1130 +  // This enables tests to validate that the next ice state is the one they expect to happen
  1.1131 +  this.next_ice_state = ""; // in most cases, the next state will be "checking", but in some tests "closed"
  1.1132 +  // This allows test to register their own callbacks for ICE connection state changes
  1.1133 +  this.ice_connection_callbacks = {};
  1.1134 +
  1.1135 +  this._pc.oniceconnectionstatechange = function() {
  1.1136 +    ok(self._pc.iceConnectionState !== undefined, "iceConnectionState should not be undefined");
  1.1137 +    info(self + ": oniceconnectionstatechange fired, new state is: " + self._pc.iceConnectionState);
  1.1138 +    Object.keys(self.ice_connection_callbacks).forEach(function(name) {
  1.1139 +      self.ice_connection_callbacks[name]();
  1.1140 +    });
  1.1141 +    if (self.next_ice_state !== "") {
  1.1142 +      is(self._pc.iceConnectionState, self.next_ice_state, "iceConnectionState changed to '" +
  1.1143 +         self.next_ice_state + "'");
  1.1144 +      self.next_ice_state = "";
  1.1145 +    }
  1.1146 +  };
  1.1147 +  this.ondatachannel = unexpectedEventAndFinish(this, 'ondatachannel');
  1.1148 +  this.onsignalingstatechange = unexpectedEventAndFinish(this, 'onsignalingstatechange');
  1.1149 +
  1.1150 +  /**
  1.1151 +   * Callback for native peer connection 'onaddstream' events.
  1.1152 +   *
  1.1153 +   * @param {Object} event
  1.1154 +   *        Event data which includes the stream to be added
  1.1155 +   */
  1.1156 +  this._pc.onaddstream = function (event) {
  1.1157 +    info(self + ": 'onaddstream' event fired for " + JSON.stringify(event.stream));
  1.1158 +
  1.1159 +    var type = '';
  1.1160 +    if (event.stream.getAudioTracks().length > 0) {
  1.1161 +      type = 'audio';
  1.1162 +    }
  1.1163 +    if (event.stream.getVideoTracks().length > 0) {
  1.1164 +      type += 'video';
  1.1165 +    }
  1.1166 +    self.attachMedia(event.stream, type, 'remote');
  1.1167 +   };
  1.1168 +
  1.1169 +  /**
  1.1170 +   * Callback for native peer connection 'ondatachannel' events. If no custom handler
  1.1171 +   * has been specified via 'this.ondatachannel', a failure will be raised if an
  1.1172 +   * event of this type gets caught.
  1.1173 +   *
  1.1174 +   * @param {Object} event
  1.1175 +   *        Event data which includes the newly created data channel
  1.1176 +   */
  1.1177 +  this._pc.ondatachannel = function (event) {
  1.1178 +    info(self + ": 'ondatachannel' event fired for " + event.channel.label);
  1.1179 +
  1.1180 +    self.ondatachannel(new DataChannelWrapper(event.channel, self));
  1.1181 +    self.ondatachannel = unexpectedEventAndFinish(self, 'ondatachannel');
  1.1182 +  };
  1.1183 +
  1.1184 +  /**
  1.1185 +   * Callback for native peer connection 'onsignalingstatechange' events. If no
  1.1186 +   * custom handler has been specified via 'this.onsignalingstatechange', a
  1.1187 +   * failure will be raised if an event of this type is caught.
  1.1188 +   *
  1.1189 +   * @param {Object} aEvent
  1.1190 +   *        Event data which includes the newly created data channel
  1.1191 +   */
  1.1192 +  this._pc.onsignalingstatechange = function (aEvent) {
  1.1193 +    info(self + ": 'onsignalingstatechange' event fired");
  1.1194 +
  1.1195 +    // this calls the eventhandler only once and then overwrites it with the
  1.1196 +    // default unexpectedEvent handler
  1.1197 +    self.onsignalingstatechange(aEvent);
  1.1198 +    self.onsignalingstatechange = unexpectedEventAndFinish(self, 'onsignalingstatechange');
  1.1199 +  };
  1.1200 +}
  1.1201 +
  1.1202 +PeerConnectionWrapper.prototype = {
  1.1203 +
  1.1204 +  /**
  1.1205 +   * Returns the local description.
  1.1206 +   *
  1.1207 +   * @returns {object} The local description
  1.1208 +   */
  1.1209 +  get localDescription() {
  1.1210 +    return this._pc.localDescription;
  1.1211 +  },
  1.1212 +
  1.1213 +  /**
  1.1214 +   * Sets the local description.
  1.1215 +   *
  1.1216 +   * @param {object} desc
  1.1217 +   *        The new local description
  1.1218 +   */
  1.1219 +  set localDescription(desc) {
  1.1220 +    this._pc.localDescription = desc;
  1.1221 +  },
  1.1222 +
  1.1223 +  /**
  1.1224 +   * Returns the readyState.
  1.1225 +   *
  1.1226 +   * @returns {string}
  1.1227 +   */
  1.1228 +  get readyState() {
  1.1229 +    return this._pc.readyState;
  1.1230 +  },
  1.1231 +
  1.1232 +  /**
  1.1233 +   * Returns the remote description.
  1.1234 +   *
  1.1235 +   * @returns {object} The remote description
  1.1236 +   */
  1.1237 +  get remoteDescription() {
  1.1238 +    return this._pc.remoteDescription;
  1.1239 +  },
  1.1240 +
  1.1241 +  /**
  1.1242 +   * Sets the remote description.
  1.1243 +   *
  1.1244 +   * @param {object} desc
  1.1245 +   *        The new remote description
  1.1246 +   */
  1.1247 +  set remoteDescription(desc) {
  1.1248 +    this._pc.remoteDescription = desc;
  1.1249 +  },
  1.1250 +
  1.1251 +  /**
  1.1252 +   * Returns the signaling state.
  1.1253 +   *
  1.1254 +   * @returns {object} The local description
  1.1255 +   */
  1.1256 +  get signalingState() {
  1.1257 +    return this._pc.signalingState;
  1.1258 +  },
  1.1259 +  /**
  1.1260 +   * Returns the ICE connection state.
  1.1261 +   *
  1.1262 +   * @returns {object} The local description
  1.1263 +   */
  1.1264 +  get iceConnectionState() {
  1.1265 +    return this._pc.iceConnectionState;
  1.1266 +  },
  1.1267 +
  1.1268 +  setIdentityProvider: function(provider, protocol, identity) {
  1.1269 +      this._pc.setIdentityProvider(provider, protocol, identity);
  1.1270 +  },
  1.1271 +
  1.1272 +  /**
  1.1273 +   * Callback when we get media from either side. Also an appropriate
  1.1274 +   * HTML media element will be created.
  1.1275 +   *
  1.1276 +   * @param {MediaStream} stream
  1.1277 +   *        Media stream to handle
  1.1278 +   * @param {string} type
  1.1279 +   *        The type of media stream ('audio' or 'video')
  1.1280 +   * @param {string} side
  1.1281 +   *        The location the stream is coming from ('local' or 'remote')
  1.1282 +   */
  1.1283 +  attachMedia : function PCW_attachMedia(stream, type, side) {
  1.1284 +    info("Got media stream: " + type + " (" + side + ")");
  1.1285 +    this.streams.push(stream);
  1.1286 +
  1.1287 +    if (side === 'local') {
  1.1288 +      this._pc.addStream(stream);
  1.1289 +    }
  1.1290 +
  1.1291 +    var element = createMediaElement(type, this.label + '_' + side);
  1.1292 +    this.mediaCheckers.push(new MediaElementChecker(element));
  1.1293 +    element.mozSrcObject = stream;
  1.1294 +    element.play();
  1.1295 +  },
  1.1296 +
  1.1297 +  /**
  1.1298 +   * Requests all the media streams as specified in the constrains property.
  1.1299 +   *
  1.1300 +   * @param {function} onSuccess
  1.1301 +   *        Callback to execute if all media has been requested successfully
  1.1302 +   */
  1.1303 +  getAllUserMedia : function PCW_GetAllUserMedia(onSuccess) {
  1.1304 +    var self = this;
  1.1305 +
  1.1306 +    function _getAllUserMedia(constraintsList, index) {
  1.1307 +      if (index < constraintsList.length) {
  1.1308 +        var constraints = constraintsList[index];
  1.1309 +
  1.1310 +        getUserMedia(constraints, function (stream) {
  1.1311 +          var type = '';
  1.1312 +
  1.1313 +          if (constraints.audio) {
  1.1314 +            type = 'audio';
  1.1315 +          }
  1.1316 +
  1.1317 +          if (constraints.video) {
  1.1318 +            type += 'video';
  1.1319 +          }
  1.1320 +
  1.1321 +          self.attachMedia(stream, type, 'local');
  1.1322 +
  1.1323 +          _getAllUserMedia(constraintsList, index + 1);
  1.1324 +        }, generateErrorCallback());
  1.1325 +      } else {
  1.1326 +        onSuccess();
  1.1327 +      }
  1.1328 +    }
  1.1329 +
  1.1330 +    info("Get " + this.constraints.length + " local streams");
  1.1331 +    _getAllUserMedia(this.constraints, 0);
  1.1332 +  },
  1.1333 +
  1.1334 +  /**
  1.1335 +   * Create a new data channel instance
  1.1336 +   *
  1.1337 +   * @param {Object} options
  1.1338 +   *        Options which get forwarded to nsIPeerConnection.createDataChannel
  1.1339 +   * @param {function} [onCreation=undefined]
  1.1340 +   *        Callback to execute when the local data channel has been created
  1.1341 +   * @returns {DataChannelWrapper} The created data channel
  1.1342 +   */
  1.1343 +  createDataChannel : function PCW_createDataChannel(options, onCreation) {
  1.1344 +    var label = 'channel_' + this.dataChannels.length;
  1.1345 +    info(this + ": Create data channel '" + label);
  1.1346 +
  1.1347 +    var channel = this._pc.createDataChannel(label, options);
  1.1348 +    var wrapper = new DataChannelWrapper(channel, this);
  1.1349 +
  1.1350 +    if (onCreation) {
  1.1351 +      wrapper.onopen = function () {
  1.1352 +        onCreation(wrapper);
  1.1353 +      };
  1.1354 +    }
  1.1355 +
  1.1356 +    this.dataChannels.push(wrapper);
  1.1357 +    return wrapper;
  1.1358 +  },
  1.1359 +
  1.1360 +  /**
  1.1361 +   * Creates an offer and automatically handles the failure case.
  1.1362 +   *
  1.1363 +   * @param {function} onSuccess
  1.1364 +   *        Callback to execute if the offer was created successfully
  1.1365 +   */
  1.1366 +  createOffer : function PCW_createOffer(onSuccess) {
  1.1367 +    var self = this;
  1.1368 +
  1.1369 +    this._pc.createOffer(function (offer) {
  1.1370 +      info("Got offer: " + JSON.stringify(offer));
  1.1371 +      self._last_offer = offer;
  1.1372 +      onSuccess(offer);
  1.1373 +    }, generateErrorCallback(), this.offerConstraints);
  1.1374 +  },
  1.1375 +
  1.1376 +  /**
  1.1377 +   * Creates an answer and automatically handles the failure case.
  1.1378 +   *
  1.1379 +   * @param {function} onSuccess
  1.1380 +   *        Callback to execute if the answer was created successfully
  1.1381 +   */
  1.1382 +  createAnswer : function PCW_createAnswer(onSuccess) {
  1.1383 +    var self = this;
  1.1384 +
  1.1385 +    this._pc.createAnswer(function (answer) {
  1.1386 +      info(self + ": Got answer: " + JSON.stringify(answer));
  1.1387 +      self._last_answer = answer;
  1.1388 +      onSuccess(answer);
  1.1389 +    }, generateErrorCallback());
  1.1390 +  },
  1.1391 +
  1.1392 +  /**
  1.1393 +   * Sets the local description and automatically handles the failure case.
  1.1394 +   *
  1.1395 +   * @param {object} desc
  1.1396 +   *        mozRTCSessionDescription for the local description request
  1.1397 +   * @param {function} onSuccess
  1.1398 +   *        Callback to execute if the local description was set successfully
  1.1399 +   */
  1.1400 +  setLocalDescription : function PCW_setLocalDescription(desc, onSuccess) {
  1.1401 +    var self = this;
  1.1402 +    this._pc.setLocalDescription(desc, function () {
  1.1403 +      info(self + ": Successfully set the local description");
  1.1404 +      onSuccess();
  1.1405 +    }, generateErrorCallback());
  1.1406 +  },
  1.1407 +
  1.1408 +  /**
  1.1409 +   * Tries to set the local description and expect failure. Automatically
  1.1410 +   * causes the test case to fail if the call succeeds.
  1.1411 +   *
  1.1412 +   * @param {object} desc
  1.1413 +   *        mozRTCSessionDescription for the local description request
  1.1414 +   * @param {function} onFailure
  1.1415 +   *        Callback to execute if the call fails.
  1.1416 +   */
  1.1417 +  setLocalDescriptionAndFail : function PCW_setLocalDescriptionAndFail(desc, onFailure) {
  1.1418 +    var self = this;
  1.1419 +    this._pc.setLocalDescription(desc,
  1.1420 +      generateErrorCallback("setLocalDescription should have failed."),
  1.1421 +      function (err) {
  1.1422 +        info(self + ": As expected, failed to set the local description");
  1.1423 +        onFailure(err);
  1.1424 +    });
  1.1425 +  },
  1.1426 +
  1.1427 +  /**
  1.1428 +   * Sets the remote description and automatically handles the failure case.
  1.1429 +   *
  1.1430 +   * @param {object} desc
  1.1431 +   *        mozRTCSessionDescription for the remote description request
  1.1432 +   * @param {function} onSuccess
  1.1433 +   *        Callback to execute if the remote description was set successfully
  1.1434 +   */
  1.1435 +  setRemoteDescription : function PCW_setRemoteDescription(desc, onSuccess) {
  1.1436 +    var self = this;
  1.1437 +    this._pc.setRemoteDescription(desc, function () {
  1.1438 +      info(self + ": Successfully set remote description");
  1.1439 +      onSuccess();
  1.1440 +    }, generateErrorCallback());
  1.1441 +  },
  1.1442 +
  1.1443 +  /**
  1.1444 +   * Tries to set the remote description and expect failure. Automatically
  1.1445 +   * causes the test case to fail if the call succeeds.
  1.1446 +   *
  1.1447 +   * @param {object} desc
  1.1448 +   *        mozRTCSessionDescription for the remote description request
  1.1449 +   * @param {function} onFailure
  1.1450 +   *        Callback to execute if the call fails.
  1.1451 +   */
  1.1452 +  setRemoteDescriptionAndFail : function PCW_setRemoteDescriptionAndFail(desc, onFailure) {
  1.1453 +    var self = this;
  1.1454 +    this._pc.setRemoteDescription(desc,
  1.1455 +      generateErrorCallback("setRemoteDescription should have failed."),
  1.1456 +      function (err) {
  1.1457 +        info(self + ": As expected, failed to set the remote description");
  1.1458 +        onFailure(err);
  1.1459 +    });
  1.1460 +  },
  1.1461 +
  1.1462 +  /**
  1.1463 +   * Adds an ICE candidate and automatically handles the failure case.
  1.1464 +   *
  1.1465 +   * @param {object} candidate
  1.1466 +   *        SDP candidate
  1.1467 +   * @param {function} onSuccess
  1.1468 +   *        Callback to execute if the local description was set successfully
  1.1469 +   */
  1.1470 +  addIceCandidate : function PCW_addIceCandidate(candidate, onSuccess) {
  1.1471 +    var self = this;
  1.1472 +
  1.1473 +    this._pc.addIceCandidate(candidate, function () {
  1.1474 +      info(self + ": Successfully added an ICE candidate");
  1.1475 +      onSuccess();
  1.1476 +    }, generateErrorCallback());
  1.1477 +  },
  1.1478 +
  1.1479 +  /**
  1.1480 +   * Tries to add an ICE candidate and expects failure. Automatically
  1.1481 +   * causes the test case to fail if the call succeeds.
  1.1482 +   *
  1.1483 +   * @param {object} candidate
  1.1484 +   *        SDP candidate
  1.1485 +   * @param {function} onFailure
  1.1486 +   *        Callback to execute if the call fails.
  1.1487 +   */
  1.1488 +  addIceCandidateAndFail : function PCW_addIceCandidateAndFail(candidate, onFailure) {
  1.1489 +    var self = this;
  1.1490 +
  1.1491 +    this._pc.addIceCandidate(candidate,
  1.1492 +      generateErrorCallback("addIceCandidate should have failed."),
  1.1493 +      function (err) {
  1.1494 +        info(self + ": As expected, failed to add an ICE candidate");
  1.1495 +        onFailure(err);
  1.1496 +    }) ;
  1.1497 +  },
  1.1498 +
  1.1499 +  /**
  1.1500 +   * Returns if the ICE the connection state is "connected".
  1.1501 +   *
  1.1502 +   * @returns {boolean} True if the connection state is "connected", otherwise false.
  1.1503 +   */
  1.1504 +  isIceConnected : function PCW_isIceConnected() {
  1.1505 +    info("iceConnectionState: " + this.iceConnectionState);
  1.1506 +    return this.iceConnectionState === "connected";
  1.1507 +  },
  1.1508 +
  1.1509 +  /**
  1.1510 +   * Returns if the ICE the connection state is "checking".
  1.1511 +   *
  1.1512 +   * @returns {boolean} True if the connection state is "checking", otherwise false.
  1.1513 +   */
  1.1514 +  isIceChecking : function PCW_isIceChecking() {
  1.1515 +    return this.iceConnectionState === "checking";
  1.1516 +  },
  1.1517 +
  1.1518 +  /**
  1.1519 +   * Returns if the ICE the connection state is "new".
  1.1520 +   *
  1.1521 +   * @returns {boolean} True if the connection state is "new", otherwise false.
  1.1522 +   */
  1.1523 +  isIceNew : function PCW_isIceNew() {
  1.1524 +    return this.iceConnectionState === "new";
  1.1525 +  },
  1.1526 +
  1.1527 +  /**
  1.1528 +   * Checks if the ICE connection state still waits for a connection to get
  1.1529 +   * established.
  1.1530 +   *
  1.1531 +   * @returns {boolean} True if the connection state is "checking" or "new",
  1.1532 +   *  otherwise false.
  1.1533 +   */
  1.1534 +  isIceConnectionPending : function PCW_isIceConnectionPending() {
  1.1535 +    return (this.isIceChecking() || this.isIceNew());
  1.1536 +  },
  1.1537 +
  1.1538 +  /**
  1.1539 +   * Registers a callback for the ICE connection state change and
  1.1540 +   * reports success (=connected) or failure via the callbacks.
  1.1541 +   * States "new" and "checking" are ignored.
  1.1542 +   *
  1.1543 +   * @param {function} onSuccess
  1.1544 +   *        Callback if ICE connection status is "connected".
  1.1545 +   * @param {function} onFailure
  1.1546 +   *        Callback if ICE connection reaches a different state than
  1.1547 +   *        "new", "checking" or "connected".
  1.1548 +   */
  1.1549 +  waitForIceConnected : function PCW_waitForIceConnected(onSuccess, onFailure) {
  1.1550 +    var self = this;
  1.1551 +    var mySuccess = onSuccess;
  1.1552 +    var myFailure = onFailure;
  1.1553 +
  1.1554 +    function iceConnectedChanged () {
  1.1555 +      if (self.isIceConnected()) {
  1.1556 +        delete self.ice_connection_callbacks.waitForIceConnected;
  1.1557 +        mySuccess();
  1.1558 +      } else if (! self.isIceConnectionPending()) {
  1.1559 +        delete self.ice_connection_callbacks.waitForIceConnected;
  1.1560 +        myFailure();
  1.1561 +      }
  1.1562 +    }
  1.1563 +
  1.1564 +    self.ice_connection_callbacks.waitForIceConnected = iceConnectedChanged;
  1.1565 +  },
  1.1566 +
  1.1567 +  /**
  1.1568 +   * Checks that we are getting the media streams we expect.
  1.1569 +   *
  1.1570 +   * @param {object} constraintsRemote
  1.1571 +   *        The media constraints of the remote peer connection object
  1.1572 +   */
  1.1573 +  checkMediaStreams : function PCW_checkMediaStreams(constraintsRemote) {
  1.1574 +    is(this._pc.getLocalStreams().length, this.constraints.length,
  1.1575 +       this + ' has ' + this.constraints.length + ' local streams');
  1.1576 +
  1.1577 +    // TODO: change this when multiple incoming streams are supported (bug 834835)
  1.1578 +    is(this._pc.getRemoteStreams().length, 1,
  1.1579 +       this + ' has ' + 1 + ' remote streams');
  1.1580 +  },
  1.1581 +
  1.1582 +  /**
  1.1583 +   * Check that media flow is present on all media elements involved in this
  1.1584 +   * test by waiting for confirmation that media flow is present.
  1.1585 +   *
  1.1586 +   * @param {Function} onSuccess the success callback when media flow
  1.1587 +   *                             is confirmed on all media elements
  1.1588 +   */
  1.1589 +  checkMediaFlowPresent : function PCW_checkMediaFlowPresent(onSuccess) {
  1.1590 +    var self = this;
  1.1591 +
  1.1592 +    function _checkMediaFlowPresent(index, onSuccess) {
  1.1593 +      if(index >= self.mediaCheckers.length) {
  1.1594 +        onSuccess();
  1.1595 +      } else {
  1.1596 +        var mediaChecker = self.mediaCheckers[index];
  1.1597 +        mediaChecker.waitForMediaFlow(function() {
  1.1598 +          _checkMediaFlowPresent(index + 1, onSuccess);
  1.1599 +        });
  1.1600 +      }
  1.1601 +    }
  1.1602 +
  1.1603 +    _checkMediaFlowPresent(0, onSuccess);
  1.1604 +  },
  1.1605 +
  1.1606 +  /**
  1.1607 +   * Check that stats are present by checking for known stats.
  1.1608 +   *
  1.1609 +   * @param {Function} onSuccess the success callback to return stats to
  1.1610 +   */
  1.1611 +  getStats : function PCW_getStats(selector, onSuccess) {
  1.1612 +    var self = this;
  1.1613 +
  1.1614 +    this._pc.getStats(selector, function(stats) {
  1.1615 +      info(self + ": Got stats: " + JSON.stringify(stats));
  1.1616 +      self._last_stats = stats;
  1.1617 +      onSuccess(stats);
  1.1618 +    }, generateErrorCallback());
  1.1619 +  },
  1.1620 +
  1.1621 +  /**
  1.1622 +   * Checks that we are getting the media streams we expect.
  1.1623 +   *
  1.1624 +   * @param {object} stats
  1.1625 +   *        The stats to check from this PeerConnectionWrapper
  1.1626 +   */
  1.1627 +  checkStats : function PCW_checkStats(stats) {
  1.1628 +    function toNum(obj) {
  1.1629 +      return obj? obj : 0;
  1.1630 +    }
  1.1631 +    function numTracks(streams) {
  1.1632 +      var n = 0;
  1.1633 +      streams.forEach(function(stream) {
  1.1634 +          n += stream.getAudioTracks().length + stream.getVideoTracks().length;
  1.1635 +        });
  1.1636 +      return n;
  1.1637 +    }
  1.1638 +
  1.1639 +    const isWinXP = navigator.userAgent.indexOf("Windows NT 5.1") != -1;
  1.1640 +
  1.1641 +    // Use spec way of enumerating stats
  1.1642 +    var counters = {};
  1.1643 +    for (var key in stats) {
  1.1644 +      if (stats.hasOwnProperty(key)) {
  1.1645 +        var res = stats[key];
  1.1646 +        // validate stats
  1.1647 +        ok(res.id == key, "Coherent stats id");
  1.1648 +        var nowish = Date.now() + 1000;        // TODO: clock drift observed
  1.1649 +        var minimum = this.whenCreated - 1000; // on Windows XP (Bug 979649)
  1.1650 +        if (isWinXP) {
  1.1651 +          todo(false, "Can't reliably test rtcp timestamps on WinXP (Bug 979649)");
  1.1652 +        } else {
  1.1653 +          ok(res.timestamp >= minimum,
  1.1654 +             "Valid " + (res.isRemote? "rtcp" : "rtp") + " timestamp " +
  1.1655 +                 res.timestamp + " >= " + minimum + " (" +
  1.1656 +                 (res.timestamp - minimum) + " ms)");
  1.1657 +          ok(res.timestamp <= nowish,
  1.1658 +             "Valid " + (res.isRemote? "rtcp" : "rtp") + " timestamp " +
  1.1659 +                 res.timestamp + " <= " + nowish + " (" +
  1.1660 +                 (res.timestamp - nowish) + " ms)");
  1.1661 +        }
  1.1662 +        if (!res.isRemote) {
  1.1663 +          counters[res.type] = toNum(counters[res.type]) + 1;
  1.1664 +
  1.1665 +          switch (res.type) {
  1.1666 +            case "inboundrtp":
  1.1667 +            case "outboundrtp": {
  1.1668 +              // ssrc is a 32 bit number returned as a string by spec
  1.1669 +              ok(res.ssrc.length > 0, "Ssrc has length");
  1.1670 +              ok(res.ssrc.length < 11, "Ssrc not lengthy");
  1.1671 +              ok(!/[^0-9]/.test(res.ssrc), "Ssrc numeric");
  1.1672 +              ok(parseInt(res.ssrc) < Math.pow(2,32), "Ssrc within limits");
  1.1673 +
  1.1674 +              if (res.type == "outboundrtp") {
  1.1675 +                ok(res.packetsSent !== undefined, "Rtp packetsSent");
  1.1676 +                // minimum fragment is 8 (from RFC 791)
  1.1677 +                ok(res.bytesSent >= res.packetsSent * 8, "Rtp bytesSent");
  1.1678 +              } else {
  1.1679 +                ok(res.packetsReceived !== undefined, "Rtp packetsReceived");
  1.1680 +                ok(res.bytesReceived >= res.packetsReceived * 8, "Rtp bytesReceived");
  1.1681 +              }
  1.1682 +              if (res.remoteId) {
  1.1683 +                var rem = stats[res.remoteId];
  1.1684 +                ok(rem.isRemote, "Remote is rtcp");
  1.1685 +                ok(rem.remoteId == res.id, "Remote backlink match");
  1.1686 +                if(res.type == "outboundrtp") {
  1.1687 +                  ok(rem.type == "inboundrtp", "Rtcp is inbound");
  1.1688 +                  ok(rem.packetsReceived !== undefined, "Rtcp packetsReceived");
  1.1689 +                  ok(rem.packetsReceived <= res.packetsSent, "No more than sent");
  1.1690 +                  ok(rem.packetsLost !== undefined, "Rtcp packetsLost");
  1.1691 +                  ok(rem.bytesReceived >= rem.packetsReceived * 8, "Rtcp bytesReceived");
  1.1692 +                  ok(rem.bytesReceived <= res.bytesSent, "No more than sent bytes");
  1.1693 +                  ok(rem.jitter !== undefined, "Rtcp jitter");
  1.1694 +                  ok(rem.mozRtt !== undefined, "Rtcp rtt");
  1.1695 +                  ok(rem.mozRtt >= 0, "Rtcp rtt " + rem.mozRtt + " >= 0");
  1.1696 +                  ok(rem.mozRtt < 60000, "Rtcp rtt " + rem.mozRtt + " < 1 min");
  1.1697 +                } else {
  1.1698 +                  ok(rem.type == "outboundrtp", "Rtcp is outbound");
  1.1699 +                  ok(rem.packetsSent !== undefined, "Rtcp packetsSent");
  1.1700 +                  // We may have received more than outdated Rtcp packetsSent
  1.1701 +                  ok(rem.bytesSent >= rem.packetsSent * 8, "Rtcp bytesSent");
  1.1702 +                }
  1.1703 +                ok(rem.ssrc == res.ssrc, "Remote ssrc match");
  1.1704 +              } else {
  1.1705 +                info("No rtcp info received yet");
  1.1706 +              }
  1.1707 +            }
  1.1708 +            break;
  1.1709 +          }
  1.1710 +        }
  1.1711 +      }
  1.1712 +    }
  1.1713 +
  1.1714 +    // Use MapClass way of enumerating stats
  1.1715 +    var counters2 = {};
  1.1716 +    stats.forEach(function(res) {
  1.1717 +        if (!res.isRemote) {
  1.1718 +          counters2[res.type] = toNum(counters2[res.type]) + 1;
  1.1719 +        }
  1.1720 +      });
  1.1721 +    is(JSON.stringify(counters), JSON.stringify(counters2),
  1.1722 +       "Spec and MapClass variant of RTCStatsReport enumeration agree");
  1.1723 +    var nin = numTracks(this._pc.getRemoteStreams());
  1.1724 +    var nout = numTracks(this._pc.getLocalStreams());
  1.1725 +
  1.1726 +    // TODO(Bug 957145): Restore stronger inboundrtp test once Bug 948249 is fixed
  1.1727 +    //is(toNum(counters["inboundrtp"]), nin, "Have " + nin + " inboundrtp stat(s)");
  1.1728 +    ok(toNum(counters.inboundrtp) >= nin, "Have at least " + nin + " inboundrtp stat(s) *");
  1.1729 +
  1.1730 +    is(toNum(counters.outboundrtp), nout, "Have " + nout + " outboundrtp stat(s)");
  1.1731 +
  1.1732 +    var numLocalCandidates  = toNum(counters.localcandidate);
  1.1733 +    var numRemoteCandidates = toNum(counters.remotecandidate);
  1.1734 +    // If there are no tracks, there will be no stats either.
  1.1735 +    if (nin + nout > 0) {
  1.1736 +      ok(numLocalCandidates, "Have localcandidate stat(s)");
  1.1737 +      ok(numRemoteCandidates, "Have remotecandidate stat(s)");
  1.1738 +    } else {
  1.1739 +      is(numLocalCandidates, 0, "Have no localcandidate stats");
  1.1740 +      is(numRemoteCandidates, 0, "Have no remotecandidate stats");
  1.1741 +    }
  1.1742 +  },
  1.1743 +
  1.1744 +  /**
  1.1745 +   * Closes the connection
  1.1746 +   */
  1.1747 +  close : function PCW_close() {
  1.1748 +    // It might be that a test has already closed the pc. In those cases
  1.1749 +    // we should not fail.
  1.1750 +    try {
  1.1751 +      this._pc.close();
  1.1752 +      info(this + ": Closed connection.");
  1.1753 +    }
  1.1754 +    catch (e) {
  1.1755 +      info(this + ": Failure in closing connection - " + e.message);
  1.1756 +    }
  1.1757 +  },
  1.1758 +
  1.1759 +  /**
  1.1760 +   * Register all events during the setup of the data channel
  1.1761 +   *
  1.1762 +   * @param {Function} onDataChannelOpened
  1.1763 +   *        Callback to execute when the data channel has been opened
  1.1764 +   */
  1.1765 +  registerDataChannelOpenEvents : function (onDataChannelOpened) {
  1.1766 +    info(this + ": Register callbacks for 'ondatachannel' and 'onopen'");
  1.1767 +
  1.1768 +    this.ondatachannel = function (targetChannel) {
  1.1769 +      targetChannel.onopen = function (targetChannel) {
  1.1770 +        onDataChannelOpened(targetChannel);
  1.1771 +      };
  1.1772 +
  1.1773 +      this.dataChannels.push(targetChannel);
  1.1774 +    };
  1.1775 +  },
  1.1776 +
  1.1777 +  /**
  1.1778 +   * Returns the string representation of the class
  1.1779 +   *
  1.1780 +   * @returns {String} The string representation
  1.1781 +   */
  1.1782 +  toString : function PCW_toString() {
  1.1783 +    return "PeerConnectionWrapper (" + this.label + ")";
  1.1784 +  }
  1.1785 +};

mercurial