dom/media/tests/mochitest/pc.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 "use strict";
michael@0 6
michael@0 7
michael@0 8 /**
michael@0 9 * This class mimics a state machine and handles a list of commands by
michael@0 10 * executing them synchronously.
michael@0 11 *
michael@0 12 * @constructor
michael@0 13 * @param {object} framework
michael@0 14 * A back reference to the framework which makes use of the class. It's
michael@0 15 * getting passed in as parameter to each command callback.
michael@0 16 * @param {Array[]} [commandList=[]]
michael@0 17 * Default commands to set during initialization
michael@0 18 */
michael@0 19 function CommandChain(framework, commandList) {
michael@0 20 this._framework = framework;
michael@0 21
michael@0 22 this._commands = commandList || [ ];
michael@0 23 this._current = 0;
michael@0 24
michael@0 25 this.onFinished = null;
michael@0 26 }
michael@0 27
michael@0 28 CommandChain.prototype = {
michael@0 29
michael@0 30 /**
michael@0 31 * Returns the index of the current command of the chain
michael@0 32 *
michael@0 33 * @returns {number} Index of the current command
michael@0 34 */
michael@0 35 get current() {
michael@0 36 return this._current;
michael@0 37 },
michael@0 38
michael@0 39 /**
michael@0 40 * Checks if the chain has already processed all the commands
michael@0 41 *
michael@0 42 * @returns {boolean} True, if all commands have been processed
michael@0 43 */
michael@0 44 get finished() {
michael@0 45 return this._current === this._commands.length;
michael@0 46 },
michael@0 47
michael@0 48 /**
michael@0 49 * Returns the assigned commands of the chain.
michael@0 50 *
michael@0 51 * @returns {Array[]} Commands of the chain
michael@0 52 */
michael@0 53 get commands() {
michael@0 54 return this._commands;
michael@0 55 },
michael@0 56
michael@0 57 /**
michael@0 58 * Sets new commands for the chain. All existing commands will be replaced.
michael@0 59 *
michael@0 60 * @param {Array[]} commands
michael@0 61 * List of commands
michael@0 62 */
michael@0 63 set commands(commands) {
michael@0 64 this._commands = commands;
michael@0 65 },
michael@0 66
michael@0 67 /**
michael@0 68 * Execute the next command in the chain.
michael@0 69 */
michael@0 70 executeNext : function () {
michael@0 71 var self = this;
michael@0 72
michael@0 73 function _executeNext() {
michael@0 74 if (!self.finished) {
michael@0 75 var step = self._commands[self._current];
michael@0 76 self._current++;
michael@0 77
michael@0 78 self.currentStepLabel = step[0];
michael@0 79 info("Run step: " + self.currentStepLabel);
michael@0 80 step[1](self._framework); // Execute step
michael@0 81 }
michael@0 82 else if (typeof(self.onFinished) === 'function') {
michael@0 83 self.onFinished();
michael@0 84 }
michael@0 85 }
michael@0 86
michael@0 87 // To prevent building up the stack we have to execute the next
michael@0 88 // step asynchronously
michael@0 89 window.setTimeout(_executeNext, 0);
michael@0 90 },
michael@0 91
michael@0 92 /**
michael@0 93 * Add new commands to the end of the chain
michael@0 94 *
michael@0 95 * @param {Array[]} commands
michael@0 96 * List of commands
michael@0 97 */
michael@0 98 append: function (commands) {
michael@0 99 this._commands = this._commands.concat(commands);
michael@0 100 },
michael@0 101
michael@0 102 /**
michael@0 103 * Returns the index of the specified command in the chain.
michael@0 104 *
michael@0 105 * @param {string} id
michael@0 106 * Identifier of the command
michael@0 107 * @returns {number} Index of the command
michael@0 108 */
michael@0 109 indexOf: function (id) {
michael@0 110 for (var i = 0; i < this._commands.length; i++) {
michael@0 111 if (this._commands[i][0] === id) {
michael@0 112 return i;
michael@0 113 }
michael@0 114 }
michael@0 115
michael@0 116 return -1;
michael@0 117 },
michael@0 118
michael@0 119 /**
michael@0 120 * Inserts the new commands after the specified command.
michael@0 121 *
michael@0 122 * @param {string} id
michael@0 123 * Identifier of the command
michael@0 124 * @param {Array[]} commands
michael@0 125 * List of commands
michael@0 126 */
michael@0 127 insertAfter: function (id, commands) {
michael@0 128 var index = this.indexOf(id);
michael@0 129
michael@0 130 if (index > -1) {
michael@0 131 var tail = this.removeAfter(id);
michael@0 132
michael@0 133 this.append(commands);
michael@0 134 this.append(tail);
michael@0 135 }
michael@0 136 },
michael@0 137
michael@0 138 /**
michael@0 139 * Inserts the new commands before the specified command.
michael@0 140 *
michael@0 141 * @param {string} id
michael@0 142 * Identifier of the command
michael@0 143 * @param {Array[]} commands
michael@0 144 * List of commands
michael@0 145 */
michael@0 146 insertBefore: function (id, commands) {
michael@0 147 var index = this.indexOf(id);
michael@0 148
michael@0 149 if (index > -1) {
michael@0 150 var tail = this.removeAfter(id);
michael@0 151 var object = this.remove(id);
michael@0 152
michael@0 153 this.append(commands);
michael@0 154 this.append(object);
michael@0 155 this.append(tail);
michael@0 156 }
michael@0 157 },
michael@0 158
michael@0 159 /**
michael@0 160 * Removes the specified command
michael@0 161 *
michael@0 162 * @param {string} id
michael@0 163 * Identifier of the command
michael@0 164 * @returns {object[]} Removed command
michael@0 165 */
michael@0 166 remove : function (id) {
michael@0 167 return this._commands.splice(this.indexOf(id), 1);
michael@0 168 },
michael@0 169
michael@0 170 /**
michael@0 171 * Removes all commands after the specified one.
michael@0 172 *
michael@0 173 * @param {string} id
michael@0 174 * Identifier of the command
michael@0 175 * @returns {object[]} Removed commands
michael@0 176 */
michael@0 177 removeAfter : function (id) {
michael@0 178 var index = this.indexOf(id);
michael@0 179
michael@0 180 if (index > -1) {
michael@0 181 return this._commands.splice(index + 1);
michael@0 182 }
michael@0 183
michael@0 184 return null;
michael@0 185 },
michael@0 186
michael@0 187 /**
michael@0 188 * Removes all commands before the specified one.
michael@0 189 *
michael@0 190 * @param {string} id
michael@0 191 * Identifier of the command
michael@0 192 * @returns {object[]} Removed commands
michael@0 193 */
michael@0 194 removeBefore : function (id) {
michael@0 195 var index = this.indexOf(id);
michael@0 196
michael@0 197 if (index > -1) {
michael@0 198 return this._commands.splice(0, index);
michael@0 199 }
michael@0 200
michael@0 201 return null;
michael@0 202 },
michael@0 203
michael@0 204 /**
michael@0 205 * Replaces all commands after the specified one.
michael@0 206 *
michael@0 207 * @param {string} id
michael@0 208 * Identifier of the command
michael@0 209 * @returns {object[]} Removed commands
michael@0 210 */
michael@0 211 replaceAfter : function (id, commands) {
michael@0 212 var oldCommands = this.removeAfter(id);
michael@0 213 this.append(commands);
michael@0 214
michael@0 215 return oldCommands;
michael@0 216 },
michael@0 217
michael@0 218 /**
michael@0 219 * Replaces all commands before the specified one.
michael@0 220 *
michael@0 221 * @param {string} id
michael@0 222 * Identifier of the command
michael@0 223 * @returns {object[]} Removed commands
michael@0 224 */
michael@0 225 replaceBefore : function (id, commands) {
michael@0 226 var oldCommands = this.removeBefore(id);
michael@0 227 this.insertBefore(id, commands);
michael@0 228
michael@0 229 return oldCommands;
michael@0 230 },
michael@0 231
michael@0 232 /**
michael@0 233 * Remove all commands whose identifiers match the specified regex.
michael@0 234 *
michael@0 235 * @param {regex} id_match
michael@0 236 * Regular expression to match command identifiers.
michael@0 237 */
michael@0 238 filterOut : function (id_match) {
michael@0 239 for (var i = this._commands.length - 1; i >= 0; i--) {
michael@0 240 if (id_match.test(this._commands[i][0])) {
michael@0 241 this._commands.splice(i, 1);
michael@0 242 }
michael@0 243 }
michael@0 244 }
michael@0 245 };
michael@0 246
michael@0 247 /**
michael@0 248 * This class provides a state checker for media elements which store
michael@0 249 * a media stream to check for media attribute state and events fired.
michael@0 250 * When constructed by a caller, an object instance is created with
michael@0 251 * a media element, event state checkers for canplaythrough, timeupdate, and
michael@0 252 * time changing on the media element and stream.
michael@0 253 *
michael@0 254 * @param {HTMLMediaElement} element the media element being analyzed
michael@0 255 */
michael@0 256 function MediaElementChecker(element) {
michael@0 257 this.element = element;
michael@0 258 this.canPlayThroughFired = false;
michael@0 259 this.timeUpdateFired = false;
michael@0 260 this.timePassed = false;
michael@0 261
michael@0 262 var self = this;
michael@0 263 var elementId = self.element.getAttribute('id');
michael@0 264
michael@0 265 // When canplaythrough fires, we track that it's fired and remove the
michael@0 266 // event listener.
michael@0 267 var canPlayThroughCallback = function() {
michael@0 268 info('canplaythrough fired for media element ' + elementId);
michael@0 269 self.canPlayThroughFired = true;
michael@0 270 self.element.removeEventListener('canplaythrough', canPlayThroughCallback,
michael@0 271 false);
michael@0 272 };
michael@0 273
michael@0 274 // When timeupdate fires, we track that it's fired and check if time
michael@0 275 // has passed on the media stream and media element.
michael@0 276 var timeUpdateCallback = function() {
michael@0 277 self.timeUpdateFired = true;
michael@0 278 info('timeupdate fired for media element ' + elementId);
michael@0 279
michael@0 280 // If time has passed, then track that and remove the timeupdate event
michael@0 281 // listener.
michael@0 282 if(element.mozSrcObject && element.mozSrcObject.currentTime > 0 &&
michael@0 283 element.currentTime > 0) {
michael@0 284 info('time passed for media element ' + elementId);
michael@0 285 self.timePassed = true;
michael@0 286 self.element.removeEventListener('timeupdate', timeUpdateCallback,
michael@0 287 false);
michael@0 288 }
michael@0 289 };
michael@0 290
michael@0 291 element.addEventListener('canplaythrough', canPlayThroughCallback, false);
michael@0 292 element.addEventListener('timeupdate', timeUpdateCallback, false);
michael@0 293 }
michael@0 294
michael@0 295 MediaElementChecker.prototype = {
michael@0 296
michael@0 297 /**
michael@0 298 * Waits until the canplaythrough & timeupdate events to fire along with
michael@0 299 * ensuring time has passed on the stream and media element.
michael@0 300 *
michael@0 301 * @param {Function} onSuccess the success callback when media flow is
michael@0 302 * established
michael@0 303 */
michael@0 304 waitForMediaFlow : function MEC_WaitForMediaFlow(onSuccess) {
michael@0 305 var self = this;
michael@0 306 var elementId = self.element.getAttribute('id');
michael@0 307 info('Analyzing element: ' + elementId);
michael@0 308
michael@0 309 if(self.canPlayThroughFired && self.timeUpdateFired && self.timePassed) {
michael@0 310 ok(true, 'Media flowing for ' + elementId);
michael@0 311 onSuccess();
michael@0 312 } else {
michael@0 313 setTimeout(function() {
michael@0 314 self.waitForMediaFlow(onSuccess);
michael@0 315 }, 100);
michael@0 316 }
michael@0 317 },
michael@0 318
michael@0 319 /**
michael@0 320 * Checks if there is no media flow present by checking that the ready
michael@0 321 * state of the media element is HAVE_METADATA.
michael@0 322 */
michael@0 323 checkForNoMediaFlow : function MEC_CheckForNoMediaFlow() {
michael@0 324 ok(this.element.readyState === HTMLMediaElement.HAVE_METADATA,
michael@0 325 'Media element has a ready state of HAVE_METADATA');
michael@0 326 }
michael@0 327 };
michael@0 328
michael@0 329 /**
michael@0 330 * Query function for determining if any IP address is available for
michael@0 331 * generating SDP.
michael@0 332 *
michael@0 333 * @return false if required additional network setup.
michael@0 334 */
michael@0 335 function isNetworkReady() {
michael@0 336 // for gonk platform
michael@0 337 if ("nsINetworkInterfaceListService" in SpecialPowers.Ci) {
michael@0 338 var listService = SpecialPowers.Cc["@mozilla.org/network/interface-list-service;1"]
michael@0 339 .getService(SpecialPowers.Ci.nsINetworkInterfaceListService);
michael@0 340 var itfList = listService.getDataInterfaceList(
michael@0 341 SpecialPowers.Ci.nsINetworkInterfaceListService.LIST_NOT_INCLUDE_MMS_INTERFACES |
michael@0 342 SpecialPowers.Ci.nsINetworkInterfaceListService.LIST_NOT_INCLUDE_SUPL_INTERFACES |
michael@0 343 SpecialPowers.Ci.nsINetworkInterfaceListService.LIST_NOT_INCLUDE_IMS_INTERFACES |
michael@0 344 SpecialPowers.Ci.nsINetworkInterfaceListService.LIST_NOT_INCLUDE_DUN_INTERFACES);
michael@0 345 var num = itfList.getNumberOfInterface();
michael@0 346 for (var i = 0; i < num; i++) {
michael@0 347 var ips = {};
michael@0 348 var prefixLengths = {};
michael@0 349 var length = itfList.getInterface(i).getAddresses(ips, prefixLengths);
michael@0 350
michael@0 351 for (var j = 0; j < length; j++) {
michael@0 352 var ip = ips.value[j];
michael@0 353 // skip IPv6 address
michael@0 354 if (ip.indexOf(":") < 0) {
michael@0 355 info("Network interface is ready with address: " + ip);
michael@0 356 return true;
michael@0 357 }
michael@0 358 }
michael@0 359 }
michael@0 360 // ip address is not available
michael@0 361 info("Network interface is not ready, required additional network setup");
michael@0 362 return false;
michael@0 363 }
michael@0 364 info("Network setup is not required");
michael@0 365 return true;
michael@0 366 }
michael@0 367
michael@0 368 /**
michael@0 369 * Network setup utils for Gonk
michael@0 370 *
michael@0 371 * @return {object} providing functions for setup/teardown data connection
michael@0 372 */
michael@0 373 function getNetworkUtils() {
michael@0 374 var url = SimpleTest.getTestFileURL("NetworkPreparationChromeScript.js");
michael@0 375 var script = SpecialPowers.loadChromeScript(url);
michael@0 376
michael@0 377 var utils = {
michael@0 378 /**
michael@0 379 * Utility for setting up data connection.
michael@0 380 *
michael@0 381 * @param aCallback callback after data connection is ready.
michael@0 382 */
michael@0 383 prepareNetwork: function(aCallback) {
michael@0 384 script.addMessageListener('network-ready', function (message) {
michael@0 385 info("Network interface is ready");
michael@0 386 aCallback();
michael@0 387 });
michael@0 388 info("Setup network interface");
michael@0 389 script.sendAsyncMessage("prepare-network", true);
michael@0 390 },
michael@0 391 /**
michael@0 392 * Utility for tearing down data connection.
michael@0 393 *
michael@0 394 * @param aCallback callback after data connection is closed.
michael@0 395 */
michael@0 396 tearDownNetwork: function(aCallback) {
michael@0 397 script.addMessageListener('network-disabled', function (message) {
michael@0 398 ok(true, 'network-disabled');
michael@0 399 script.destroy();
michael@0 400 aCallback();
michael@0 401 });
michael@0 402 script.sendAsyncMessage("network-cleanup", true);
michael@0 403 }
michael@0 404 };
michael@0 405
michael@0 406 return utils;
michael@0 407 }
michael@0 408
michael@0 409 /**
michael@0 410 * This class handles tests for peer connections.
michael@0 411 *
michael@0 412 * @constructor
michael@0 413 * @param {object} [options={}]
michael@0 414 * Optional options for the peer connection test
michael@0 415 * @param {object} [options.commands=commandsPeerConnection]
michael@0 416 * Commands to run for the test
michael@0 417 * @param {bool} [options.is_local=true]
michael@0 418 * true if this test should run the tests for the "local" side.
michael@0 419 * @param {bool} [options.is_remote=true]
michael@0 420 * true if this test should run the tests for the "remote" side.
michael@0 421 * @param {object} [options.config_pc1=undefined]
michael@0 422 * Configuration for the local peer connection instance
michael@0 423 * @param {object} [options.config_pc2=undefined]
michael@0 424 * Configuration for the remote peer connection instance. If not defined
michael@0 425 * the configuration from the local instance will be used
michael@0 426 */
michael@0 427 function PeerConnectionTest(options) {
michael@0 428 // If no options are specified make it an empty object
michael@0 429 options = options || { };
michael@0 430 options.commands = options.commands || commandsPeerConnection;
michael@0 431 options.is_local = "is_local" in options ? options.is_local : true;
michael@0 432 options.is_remote = "is_remote" in options ? options.is_remote : true;
michael@0 433
michael@0 434 var netTeardownCommand = null;
michael@0 435 if (!isNetworkReady()) {
michael@0 436 var utils = getNetworkUtils();
michael@0 437 // Trigger network setup to obtain IP address before creating any PeerConnection.
michael@0 438 utils.prepareNetwork(function() {
michael@0 439 ok(isNetworkReady(),'setup network connection successfully');
michael@0 440 });
michael@0 441
michael@0 442 netTeardownCommand = [
michael@0 443 [
michael@0 444 'TEARDOWN_NETWORK',
michael@0 445 function(test) {
michael@0 446 utils.tearDownNetwork(function() {
michael@0 447 info('teardown network connection');
michael@0 448 test.next();
michael@0 449 });
michael@0 450 }
michael@0 451 ]
michael@0 452 ];
michael@0 453 }
michael@0 454
michael@0 455 if (options.is_local)
michael@0 456 this.pcLocal = new PeerConnectionWrapper('pcLocal', options.config_pc1);
michael@0 457 else
michael@0 458 this.pcLocal = null;
michael@0 459
michael@0 460 if (options.is_remote)
michael@0 461 this.pcRemote = new PeerConnectionWrapper('pcRemote', options.config_pc2 || options.config_pc1);
michael@0 462 else
michael@0 463 this.pcRemote = null;
michael@0 464
michael@0 465 this.connected = false;
michael@0 466
michael@0 467 // Create command chain instance and assign default commands
michael@0 468 this.chain = new CommandChain(this, options.commands);
michael@0 469 if (!options.is_local) {
michael@0 470 this.chain.filterOut(/^PC_LOCAL/);
michael@0 471 }
michael@0 472 if (!options.is_remote) {
michael@0 473 this.chain.filterOut(/^PC_REMOTE/);
michael@0 474 }
michael@0 475
michael@0 476 // Insert network teardown after testcase execution.
michael@0 477 if (netTeardownCommand) {
michael@0 478 this.chain.append(netTeardownCommand);
michael@0 479 }
michael@0 480
michael@0 481 var self = this;
michael@0 482 this.chain.onFinished = function () {
michael@0 483 self.teardown();
michael@0 484 };
michael@0 485 }
michael@0 486
michael@0 487 /**
michael@0 488 * Closes the peer connection if it is active
michael@0 489 *
michael@0 490 * @param {Function} onSuccess
michael@0 491 * Callback to execute when the peer connection has been closed successfully
michael@0 492 */
michael@0 493 PeerConnectionTest.prototype.close = function PCT_close(onSuccess) {
michael@0 494 info("Closing peer connections. Connection state=" + this.connected);
michael@0 495
michael@0 496 function signalingstatechangeClose(state) {
michael@0 497 info("'onsignalingstatechange' event '" + state + "' received");
michael@0 498 is(state, "closed", "onsignalingstatechange event is closed");
michael@0 499 }
michael@0 500
michael@0 501 // There is no onclose event for the remote peer existent yet. So close it
michael@0 502 // side-by-side with the local peer.
michael@0 503 if (this.pcLocal) {
michael@0 504 this.pcLocal.onsignalingstatechange = signalingstatechangeClose;
michael@0 505 this.pcLocal.close();
michael@0 506 }
michael@0 507 if (this.pcRemote) {
michael@0 508 this.pcRemote.onsignalingstatechange = signalingstatechangeClose;
michael@0 509 this.pcRemote.close();
michael@0 510 }
michael@0 511 this.connected = false;
michael@0 512
michael@0 513 onSuccess();
michael@0 514 };
michael@0 515
michael@0 516 /**
michael@0 517 * Executes the next command.
michael@0 518 */
michael@0 519 PeerConnectionTest.prototype.next = function PCT_next() {
michael@0 520 if (this._stepTimeout) {
michael@0 521 clearTimeout(this._stepTimeout);
michael@0 522 this._stepTimeout = null;
michael@0 523 }
michael@0 524 this.chain.executeNext();
michael@0 525 };
michael@0 526
michael@0 527 /**
michael@0 528 * Set a timeout for the current step.
michael@0 529 * @param {long] ms the number of milliseconds to allow for this step
michael@0 530 */
michael@0 531 PeerConnectionTest.prototype.setStepTimeout = function(ms) {
michael@0 532 this._stepTimeout = setTimeout(function() {
michael@0 533 ok(false, "Step timed out: " + this.chain.currentStepLabel);
michael@0 534 this.next();
michael@0 535 }.bind(this), ms);
michael@0 536 };
michael@0 537
michael@0 538 /**
michael@0 539 * Creates an answer for the specified peer connection instance
michael@0 540 * and automatically handles the failure case.
michael@0 541 *
michael@0 542 * @param {PeerConnectionWrapper} peer
michael@0 543 * The peer connection wrapper to run the command on
michael@0 544 * @param {function} onSuccess
michael@0 545 * Callback to execute if the offer was created successfully
michael@0 546 */
michael@0 547 PeerConnectionTest.prototype.createAnswer =
michael@0 548 function PCT_createAnswer(peer, onSuccess) {
michael@0 549 peer.createAnswer(function (answer) {
michael@0 550 onSuccess(answer);
michael@0 551 });
michael@0 552 };
michael@0 553
michael@0 554 /**
michael@0 555 * Creates an offer for the specified peer connection instance
michael@0 556 * and automatically handles the failure case.
michael@0 557 *
michael@0 558 * @param {PeerConnectionWrapper} peer
michael@0 559 * The peer connection wrapper to run the command on
michael@0 560 * @param {function} onSuccess
michael@0 561 * Callback to execute if the offer was created successfully
michael@0 562 */
michael@0 563 PeerConnectionTest.prototype.createOffer =
michael@0 564 function PCT_createOffer(peer, onSuccess) {
michael@0 565 peer.createOffer(function (offer) {
michael@0 566 onSuccess(offer);
michael@0 567 });
michael@0 568 };
michael@0 569
michael@0 570 PeerConnectionTest.prototype.setIdentityProvider =
michael@0 571 function(peer, provider, protocol, identity) {
michael@0 572 peer.setIdentityProvider(provider, protocol, identity);
michael@0 573 };
michael@0 574
michael@0 575 /**
michael@0 576 * Sets the local description for the specified peer connection instance
michael@0 577 * and automatically handles the failure case.
michael@0 578 *
michael@0 579 * @param {PeerConnectionWrapper} peer
michael@0 580 The peer connection wrapper to run the command on
michael@0 581 * @param {mozRTCSessionDescription} desc
michael@0 582 * Session description for the local description request
michael@0 583 * @param {function} onSuccess
michael@0 584 * Callback to execute if the local description was set successfully
michael@0 585 */
michael@0 586 PeerConnectionTest.prototype.setLocalDescription =
michael@0 587 function PCT_setLocalDescription(peer, desc, stateExpected, onSuccess) {
michael@0 588 var eventFired = false;
michael@0 589 var stateChanged = false;
michael@0 590
michael@0 591 function check_next_test() {
michael@0 592 if (eventFired && stateChanged) {
michael@0 593 onSuccess();
michael@0 594 }
michael@0 595 }
michael@0 596
michael@0 597 peer.onsignalingstatechange = function (state) {
michael@0 598 //info(peer + ": 'onsignalingstatechange' event registered, signalingState: " + peer.signalingState);
michael@0 599 info(peer + ": 'onsignalingstatechange' event '" + state + "' received");
michael@0 600 if(stateExpected === state && eventFired == false) {
michael@0 601 eventFired = true;
michael@0 602 check_next_test();
michael@0 603 } else {
michael@0 604 ok(false, "This event has either already fired or there has been a " +
michael@0 605 "mismatch between event received " + state +
michael@0 606 " and event expected " + stateExpected);
michael@0 607 }
michael@0 608 };
michael@0 609
michael@0 610 peer.setLocalDescription(desc, function () {
michael@0 611 stateChanged = true;
michael@0 612 check_next_test();
michael@0 613 });
michael@0 614 };
michael@0 615
michael@0 616 /**
michael@0 617 * Sets the media constraints for both peer connection instances.
michael@0 618 *
michael@0 619 * @param {object} constraintsLocal
michael@0 620 * Media constrains for the local peer connection instance
michael@0 621 * @param constraintsRemote
michael@0 622 */
michael@0 623 PeerConnectionTest.prototype.setMediaConstraints =
michael@0 624 function PCT_setMediaConstraints(constraintsLocal, constraintsRemote) {
michael@0 625 if (this.pcLocal)
michael@0 626 this.pcLocal.constraints = constraintsLocal;
michael@0 627 if (this.pcRemote)
michael@0 628 this.pcRemote.constraints = constraintsRemote;
michael@0 629 };
michael@0 630
michael@0 631 /**
michael@0 632 * Sets the media constraints used on a createOffer call in the test.
michael@0 633 *
michael@0 634 * @param {object} constraints the media constraints to use on createOffer
michael@0 635 */
michael@0 636 PeerConnectionTest.prototype.setOfferConstraints =
michael@0 637 function PCT_setOfferConstraints(constraints) {
michael@0 638 if (this.pcLocal)
michael@0 639 this.pcLocal.offerConstraints = constraints;
michael@0 640 };
michael@0 641
michael@0 642 /**
michael@0 643 * Sets the remote description for the specified peer connection instance
michael@0 644 * and automatically handles the failure case.
michael@0 645 *
michael@0 646 * @param {PeerConnectionWrapper} peer
michael@0 647 The peer connection wrapper to run the command on
michael@0 648 * @param {mozRTCSessionDescription} desc
michael@0 649 * Session description for the remote description request
michael@0 650 * @param {function} onSuccess
michael@0 651 * Callback to execute if the local description was set successfully
michael@0 652 */
michael@0 653 PeerConnectionTest.prototype.setRemoteDescription =
michael@0 654 function PCT_setRemoteDescription(peer, desc, stateExpected, onSuccess) {
michael@0 655 var eventFired = false;
michael@0 656 var stateChanged = false;
michael@0 657
michael@0 658 function check_next_test() {
michael@0 659 if (eventFired && stateChanged) {
michael@0 660 onSuccess();
michael@0 661 }
michael@0 662 }
michael@0 663
michael@0 664 peer.onsignalingstatechange = function (state) {
michael@0 665 info(peer + ": 'onsignalingstatechange' event '" + state + "' received");
michael@0 666 if(stateExpected === state && eventFired == false) {
michael@0 667 eventFired = true;
michael@0 668 check_next_test();
michael@0 669 } else {
michael@0 670 ok(false, "This event has either already fired or there has been a " +
michael@0 671 "mismatch between event received " + state +
michael@0 672 " and event expected " + stateExpected);
michael@0 673 }
michael@0 674 };
michael@0 675
michael@0 676 peer.setRemoteDescription(desc, function () {
michael@0 677 stateChanged = true;
michael@0 678 check_next_test();
michael@0 679 });
michael@0 680 };
michael@0 681
michael@0 682 /**
michael@0 683 * Start running the tests as assigned to the command chain.
michael@0 684 */
michael@0 685 PeerConnectionTest.prototype.run = function PCT_run() {
michael@0 686 this.next();
michael@0 687 };
michael@0 688
michael@0 689 /**
michael@0 690 * Clean up the objects used by the test
michael@0 691 */
michael@0 692 PeerConnectionTest.prototype.teardown = function PCT_teardown() {
michael@0 693 this.close(function () {
michael@0 694 info("Test finished");
michael@0 695 if (window.SimpleTest)
michael@0 696 SimpleTest.finish();
michael@0 697 else
michael@0 698 finish();
michael@0 699 });
michael@0 700 };
michael@0 701
michael@0 702 /**
michael@0 703 * This class handles tests for data channels.
michael@0 704 *
michael@0 705 * @constructor
michael@0 706 * @param {object} [options={}]
michael@0 707 * Optional options for the peer connection test
michael@0 708 * @param {object} [options.commands=commandsDataChannel]
michael@0 709 * Commands to run for the test
michael@0 710 * @param {object} [options.config_pc1=undefined]
michael@0 711 * Configuration for the local peer connection instance
michael@0 712 * @param {object} [options.config_pc2=undefined]
michael@0 713 * Configuration for the remote peer connection instance. If not defined
michael@0 714 * the configuration from the local instance will be used
michael@0 715 */
michael@0 716 function DataChannelTest(options) {
michael@0 717 options = options || { };
michael@0 718 options.commands = options.commands || commandsDataChannel;
michael@0 719
michael@0 720 PeerConnectionTest.call(this, options);
michael@0 721 }
michael@0 722
michael@0 723 DataChannelTest.prototype = Object.create(PeerConnectionTest.prototype, {
michael@0 724 close : {
michael@0 725 /**
michael@0 726 * Close the open data channels, followed by the underlying peer connection
michael@0 727 *
michael@0 728 * @param {Function} onSuccess
michael@0 729 * Callback to execute when the connection has been closed
michael@0 730 */
michael@0 731 value : function DCT_close(onSuccess) {
michael@0 732 var self = this;
michael@0 733
michael@0 734 function _closeChannels() {
michael@0 735 var length = self.pcLocal.dataChannels.length;
michael@0 736
michael@0 737 if (length > 0) {
michael@0 738 self.closeDataChannel(length - 1, function () {
michael@0 739 _closeChannels();
michael@0 740 });
michael@0 741 }
michael@0 742 else {
michael@0 743 PeerConnectionTest.prototype.close.call(self, onSuccess);
michael@0 744 }
michael@0 745 }
michael@0 746
michael@0 747 _closeChannels();
michael@0 748 }
michael@0 749 },
michael@0 750
michael@0 751 closeDataChannel : {
michael@0 752 /**
michael@0 753 * Close the specified data channel
michael@0 754 *
michael@0 755 * @param {Number} index
michael@0 756 * Index of the data channel to close on both sides
michael@0 757 * @param {Function} onSuccess
michael@0 758 * Callback to execute when the data channel has been closed
michael@0 759 */
michael@0 760 value : function DCT_closeDataChannel(index, onSuccess) {
michael@0 761 var localChannel = this.pcLocal.dataChannels[index];
michael@0 762 var remoteChannel = this.pcRemote.dataChannels[index];
michael@0 763
michael@0 764 var self = this;
michael@0 765
michael@0 766 // Register handler for remote channel, cause we have to wait until
michael@0 767 // the current close operation has been finished.
michael@0 768 remoteChannel.onclose = function () {
michael@0 769 self.pcRemote.dataChannels.splice(index, 1);
michael@0 770
michael@0 771 onSuccess(remoteChannel);
michael@0 772 };
michael@0 773
michael@0 774 localChannel.close();
michael@0 775 this.pcLocal.dataChannels.splice(index, 1);
michael@0 776 }
michael@0 777 },
michael@0 778
michael@0 779 createDataChannel : {
michael@0 780 /**
michael@0 781 * Create a data channel
michael@0 782 *
michael@0 783 * @param {Dict} options
michael@0 784 * Options for the data channel (see nsIPeerConnection)
michael@0 785 * @param {Function} onSuccess
michael@0 786 * Callback when the creation was successful
michael@0 787 */
michael@0 788 value : function DCT_createDataChannel(options, onSuccess) {
michael@0 789 var localChannel = null;
michael@0 790 var remoteChannel = null;
michael@0 791 var self = this;
michael@0 792
michael@0 793 // Method to synchronize all asynchronous events.
michael@0 794 function check_next_test() {
michael@0 795 if (self.connected && localChannel && remoteChannel) {
michael@0 796 onSuccess(localChannel, remoteChannel);
michael@0 797 }
michael@0 798 }
michael@0 799
michael@0 800 if (!options.negotiated) {
michael@0 801 // Register handlers for the remote peer
michael@0 802 this.pcRemote.registerDataChannelOpenEvents(function (channel) {
michael@0 803 remoteChannel = channel;
michael@0 804 check_next_test();
michael@0 805 });
michael@0 806 }
michael@0 807
michael@0 808 // Create the datachannel and handle the local 'onopen' event
michael@0 809 this.pcLocal.createDataChannel(options, function (channel) {
michael@0 810 localChannel = channel;
michael@0 811
michael@0 812 if (options.negotiated) {
michael@0 813 // externally negotiated - we need to open from both ends
michael@0 814 options.id = options.id || channel.id; // allow for no id to let the impl choose
michael@0 815 self.pcRemote.createDataChannel(options, function (channel) {
michael@0 816 remoteChannel = channel;
michael@0 817 check_next_test();
michael@0 818 });
michael@0 819 } else {
michael@0 820 check_next_test();
michael@0 821 }
michael@0 822 });
michael@0 823 }
michael@0 824 },
michael@0 825
michael@0 826 send : {
michael@0 827 /**
michael@0 828 * Send data (message or blob) to the other peer
michael@0 829 *
michael@0 830 * @param {String|Blob} data
michael@0 831 * Data to send to the other peer. For Blobs the MIME type will be lost.
michael@0 832 * @param {Function} onSuccess
michael@0 833 * Callback to execute when data has been sent
michael@0 834 * @param {Object} [options={ }]
michael@0 835 * Options to specify the data channels to be used
michael@0 836 * @param {DataChannelWrapper} [options.sourceChannel=pcLocal.dataChannels[length - 1]]
michael@0 837 * Data channel to use for sending the message
michael@0 838 * @param {DataChannelWrapper} [options.targetChannel=pcRemote.dataChannels[length - 1]]
michael@0 839 * Data channel to use for receiving the message
michael@0 840 */
michael@0 841 value : function DCT_send(data, onSuccess, options) {
michael@0 842 options = options || { };
michael@0 843 var source = options.sourceChannel ||
michael@0 844 this.pcLocal.dataChannels[this.pcLocal.dataChannels.length - 1];
michael@0 845 var target = options.targetChannel ||
michael@0 846 this.pcRemote.dataChannels[this.pcRemote.dataChannels.length - 1];
michael@0 847
michael@0 848 // Register event handler for the target channel
michael@0 849 target.onmessage = function (recv_data) {
michael@0 850 onSuccess(target, recv_data);
michael@0 851 };
michael@0 852
michael@0 853 source.send(data);
michael@0 854 }
michael@0 855 },
michael@0 856
michael@0 857 setLocalDescription : {
michael@0 858 /**
michael@0 859 * Sets the local description for the specified peer connection instance
michael@0 860 * and automatically handles the failure case. In case for the final call
michael@0 861 * it will setup the requested datachannel.
michael@0 862 *
michael@0 863 * @param {PeerConnectionWrapper} peer
michael@0 864 The peer connection wrapper to run the command on
michael@0 865 * @param {mozRTCSessionDescription} desc
michael@0 866 * Session description for the local description request
michael@0 867 * @param {function} onSuccess
michael@0 868 * Callback to execute if the local description was set successfully
michael@0 869 */
michael@0 870 value : function DCT_setLocalDescription(peer, desc, state, onSuccess) {
michael@0 871 // If the peer has a remote offer we are in the final call, and have
michael@0 872 // to wait for the datachannel connection to be open. It will also set
michael@0 873 // the local description internally.
michael@0 874 if (peer.signalingState === 'have-remote-offer') {
michael@0 875 this.waitForInitialDataChannel(peer, desc, state, onSuccess);
michael@0 876 }
michael@0 877 else {
michael@0 878 PeerConnectionTest.prototype.setLocalDescription.call(this, peer,
michael@0 879 desc, state, onSuccess);
michael@0 880 }
michael@0 881
michael@0 882 }
michael@0 883 },
michael@0 884
michael@0 885 waitForInitialDataChannel : {
michael@0 886 /**
michael@0 887 * Create an initial data channel before the peer connection has been connected
michael@0 888 *
michael@0 889 * @param {PeerConnectionWrapper} peer
michael@0 890 The peer connection wrapper to run the command on
michael@0 891 * @param {mozRTCSessionDescription} desc
michael@0 892 * Session description for the local description request
michael@0 893 * @param {Function} onSuccess
michael@0 894 * Callback when the creation was successful
michael@0 895 */
michael@0 896 value : function DCT_waitForInitialDataChannel(peer, desc, state, onSuccess) {
michael@0 897 var self = this;
michael@0 898
michael@0 899 var targetPeer = peer;
michael@0 900 var targetChannel = null;
michael@0 901
michael@0 902 var sourcePeer = (peer == this.pcLocal) ? this.pcRemote : this.pcLocal;
michael@0 903 var sourceChannel = null;
michael@0 904
michael@0 905 // Method to synchronize all asynchronous events which current happen
michael@0 906 // due to a non-predictable flow. With bug 875346 fixed we will be able
michael@0 907 // to simplify this code.
michael@0 908 function check_next_test() {
michael@0 909 if (self.connected && sourceChannel && targetChannel) {
michael@0 910 onSuccess(sourceChannel, targetChannel);
michael@0 911 }
michael@0 912 }
michael@0 913
michael@0 914 // Register 'onopen' handler for the first local data channel
michael@0 915 sourcePeer.dataChannels[0].onopen = function (channel) {
michael@0 916 sourceChannel = channel;
michael@0 917 check_next_test();
michael@0 918 };
michael@0 919
michael@0 920 // Register handlers for the target peer
michael@0 921 targetPeer.registerDataChannelOpenEvents(function (channel) {
michael@0 922 targetChannel = channel;
michael@0 923 check_next_test();
michael@0 924 });
michael@0 925
michael@0 926 PeerConnectionTest.prototype.setLocalDescription.call(this, targetPeer, desc,
michael@0 927 state,
michael@0 928 function () {
michael@0 929 self.connected = true;
michael@0 930 check_next_test();
michael@0 931 }
michael@0 932 );
michael@0 933 }
michael@0 934 }
michael@0 935 });
michael@0 936
michael@0 937 /**
michael@0 938 * This class acts as a wrapper around a DataChannel instance.
michael@0 939 *
michael@0 940 * @param dataChannel
michael@0 941 * @param peerConnectionWrapper
michael@0 942 * @constructor
michael@0 943 */
michael@0 944 function DataChannelWrapper(dataChannel, peerConnectionWrapper) {
michael@0 945 this._channel = dataChannel;
michael@0 946 this._pc = peerConnectionWrapper;
michael@0 947
michael@0 948 info("Creating " + this);
michael@0 949
michael@0 950 /**
michael@0 951 * Setup appropriate callbacks
michael@0 952 */
michael@0 953
michael@0 954 this.onclose = unexpectedEventAndFinish(this, 'onclose');
michael@0 955 this.onerror = unexpectedEventAndFinish(this, 'onerror');
michael@0 956 this.onmessage = unexpectedEventAndFinish(this, 'onmessage');
michael@0 957 this.onopen = unexpectedEventAndFinish(this, 'onopen');
michael@0 958
michael@0 959 var self = this;
michael@0 960
michael@0 961 /**
michael@0 962 * Callback for native data channel 'onclose' events. If no custom handler
michael@0 963 * has been specified via 'this.onclose', a failure will be raised if an
michael@0 964 * event of this type gets caught.
michael@0 965 */
michael@0 966 this._channel.onclose = function () {
michael@0 967 info(self + ": 'onclose' event fired");
michael@0 968
michael@0 969 self.onclose(self);
michael@0 970 self.onclose = unexpectedEventAndFinish(self, 'onclose');
michael@0 971 };
michael@0 972
michael@0 973 /**
michael@0 974 * Callback for native data channel 'onmessage' events. If no custom handler
michael@0 975 * has been specified via 'this.onmessage', a failure will be raised if an
michael@0 976 * event of this type gets caught.
michael@0 977 *
michael@0 978 * @param {Object} event
michael@0 979 * Event data which includes the sent message
michael@0 980 */
michael@0 981 this._channel.onmessage = function (event) {
michael@0 982 info(self + ": 'onmessage' event fired for '" + event.data + "'");
michael@0 983
michael@0 984 self.onmessage(event.data);
michael@0 985 self.onmessage = unexpectedEventAndFinish(self, 'onmessage');
michael@0 986 };
michael@0 987
michael@0 988 /**
michael@0 989 * Callback for native data channel 'onopen' events. If no custom handler
michael@0 990 * has been specified via 'this.onopen', a failure will be raised if an
michael@0 991 * event of this type gets caught.
michael@0 992 */
michael@0 993 this._channel.onopen = function () {
michael@0 994 info(self + ": 'onopen' event fired");
michael@0 995
michael@0 996 self.onopen(self);
michael@0 997 self.onopen = unexpectedEventAndFinish(self, 'onopen');
michael@0 998 };
michael@0 999 }
michael@0 1000
michael@0 1001 DataChannelWrapper.prototype = {
michael@0 1002 /**
michael@0 1003 * Returns the binary type of the channel
michael@0 1004 *
michael@0 1005 * @returns {String} The binary type
michael@0 1006 */
michael@0 1007 get binaryType() {
michael@0 1008 return this._channel.binaryType;
michael@0 1009 },
michael@0 1010
michael@0 1011 /**
michael@0 1012 * Sets the binary type of the channel
michael@0 1013 *
michael@0 1014 * @param {String} type
michael@0 1015 * The new binary type of the channel
michael@0 1016 */
michael@0 1017 set binaryType(type) {
michael@0 1018 this._channel.binaryType = type;
michael@0 1019 },
michael@0 1020
michael@0 1021 /**
michael@0 1022 * Returns the label of the underlying data channel
michael@0 1023 *
michael@0 1024 * @returns {String} The label
michael@0 1025 */
michael@0 1026 get label() {
michael@0 1027 return this._channel.label;
michael@0 1028 },
michael@0 1029
michael@0 1030 /**
michael@0 1031 * Returns the protocol of the underlying data channel
michael@0 1032 *
michael@0 1033 * @returns {String} The protocol
michael@0 1034 */
michael@0 1035 get protocol() {
michael@0 1036 return this._channel.protocol;
michael@0 1037 },
michael@0 1038
michael@0 1039 /**
michael@0 1040 * Returns the id of the underlying data channel
michael@0 1041 *
michael@0 1042 * @returns {number} The stream id
michael@0 1043 */
michael@0 1044 get id() {
michael@0 1045 return this._channel.id;
michael@0 1046 },
michael@0 1047
michael@0 1048 /**
michael@0 1049 * Returns the reliable state of the underlying data channel
michael@0 1050 *
michael@0 1051 * @returns {bool} The stream's reliable state
michael@0 1052 */
michael@0 1053 get reliable() {
michael@0 1054 return this._channel.reliable;
michael@0 1055 },
michael@0 1056
michael@0 1057 // ordered, maxRetransmits and maxRetransmitTime not exposed yet
michael@0 1058
michael@0 1059 /**
michael@0 1060 * Returns the readyState bit of the data channel
michael@0 1061 *
michael@0 1062 * @returns {String} The state of the channel
michael@0 1063 */
michael@0 1064 get readyState() {
michael@0 1065 return this._channel.readyState;
michael@0 1066 },
michael@0 1067
michael@0 1068 /**
michael@0 1069 * Close the data channel
michael@0 1070 */
michael@0 1071 close : function () {
michael@0 1072 info(this + ": Closing channel");
michael@0 1073 this._channel.close();
michael@0 1074 },
michael@0 1075
michael@0 1076 /**
michael@0 1077 * Send data through the data channel
michael@0 1078 *
michael@0 1079 * @param {String|Object} data
michael@0 1080 * Data which has to be sent through the data channel
michael@0 1081 */
michael@0 1082 send: function DCW_send(data) {
michael@0 1083 info(this + ": Sending data '" + data + "'");
michael@0 1084 this._channel.send(data);
michael@0 1085 },
michael@0 1086
michael@0 1087 /**
michael@0 1088 * Returns the string representation of the class
michael@0 1089 *
michael@0 1090 * @returns {String} The string representation
michael@0 1091 */
michael@0 1092 toString: function DCW_toString() {
michael@0 1093 return "DataChannelWrapper (" + this._pc.label + '_' + this._channel.label + ")";
michael@0 1094 }
michael@0 1095 };
michael@0 1096
michael@0 1097
michael@0 1098 /**
michael@0 1099 * This class acts as a wrapper around a PeerConnection instance.
michael@0 1100 *
michael@0 1101 * @constructor
michael@0 1102 * @param {string} label
michael@0 1103 * Description for the peer connection instance
michael@0 1104 * @param {object} configuration
michael@0 1105 * Configuration for the peer connection instance
michael@0 1106 */
michael@0 1107 function PeerConnectionWrapper(label, configuration) {
michael@0 1108 this.configuration = configuration;
michael@0 1109 this.label = label;
michael@0 1110 this.whenCreated = Date.now();
michael@0 1111
michael@0 1112 this.constraints = [ ];
michael@0 1113 this.offerConstraints = {};
michael@0 1114 this.streams = [ ];
michael@0 1115 this.mediaCheckers = [ ];
michael@0 1116
michael@0 1117 this.dataChannels = [ ];
michael@0 1118
michael@0 1119 info("Creating " + this);
michael@0 1120 this._pc = new mozRTCPeerConnection(this.configuration);
michael@0 1121 is(this._pc.iceConnectionState, "new", "iceConnectionState starts at 'new'");
michael@0 1122
michael@0 1123 /**
michael@0 1124 * Setup callback handlers
michael@0 1125 */
michael@0 1126 var self = this;
michael@0 1127 // This enables tests to validate that the next ice state is the one they expect to happen
michael@0 1128 this.next_ice_state = ""; // in most cases, the next state will be "checking", but in some tests "closed"
michael@0 1129 // This allows test to register their own callbacks for ICE connection state changes
michael@0 1130 this.ice_connection_callbacks = {};
michael@0 1131
michael@0 1132 this._pc.oniceconnectionstatechange = function() {
michael@0 1133 ok(self._pc.iceConnectionState !== undefined, "iceConnectionState should not be undefined");
michael@0 1134 info(self + ": oniceconnectionstatechange fired, new state is: " + self._pc.iceConnectionState);
michael@0 1135 Object.keys(self.ice_connection_callbacks).forEach(function(name) {
michael@0 1136 self.ice_connection_callbacks[name]();
michael@0 1137 });
michael@0 1138 if (self.next_ice_state !== "") {
michael@0 1139 is(self._pc.iceConnectionState, self.next_ice_state, "iceConnectionState changed to '" +
michael@0 1140 self.next_ice_state + "'");
michael@0 1141 self.next_ice_state = "";
michael@0 1142 }
michael@0 1143 };
michael@0 1144 this.ondatachannel = unexpectedEventAndFinish(this, 'ondatachannel');
michael@0 1145 this.onsignalingstatechange = unexpectedEventAndFinish(this, 'onsignalingstatechange');
michael@0 1146
michael@0 1147 /**
michael@0 1148 * Callback for native peer connection 'onaddstream' events.
michael@0 1149 *
michael@0 1150 * @param {Object} event
michael@0 1151 * Event data which includes the stream to be added
michael@0 1152 */
michael@0 1153 this._pc.onaddstream = function (event) {
michael@0 1154 info(self + ": 'onaddstream' event fired for " + JSON.stringify(event.stream));
michael@0 1155
michael@0 1156 var type = '';
michael@0 1157 if (event.stream.getAudioTracks().length > 0) {
michael@0 1158 type = 'audio';
michael@0 1159 }
michael@0 1160 if (event.stream.getVideoTracks().length > 0) {
michael@0 1161 type += 'video';
michael@0 1162 }
michael@0 1163 self.attachMedia(event.stream, type, 'remote');
michael@0 1164 };
michael@0 1165
michael@0 1166 /**
michael@0 1167 * Callback for native peer connection 'ondatachannel' events. If no custom handler
michael@0 1168 * has been specified via 'this.ondatachannel', a failure will be raised if an
michael@0 1169 * event of this type gets caught.
michael@0 1170 *
michael@0 1171 * @param {Object} event
michael@0 1172 * Event data which includes the newly created data channel
michael@0 1173 */
michael@0 1174 this._pc.ondatachannel = function (event) {
michael@0 1175 info(self + ": 'ondatachannel' event fired for " + event.channel.label);
michael@0 1176
michael@0 1177 self.ondatachannel(new DataChannelWrapper(event.channel, self));
michael@0 1178 self.ondatachannel = unexpectedEventAndFinish(self, 'ondatachannel');
michael@0 1179 };
michael@0 1180
michael@0 1181 /**
michael@0 1182 * Callback for native peer connection 'onsignalingstatechange' events. If no
michael@0 1183 * custom handler has been specified via 'this.onsignalingstatechange', a
michael@0 1184 * failure will be raised if an event of this type is caught.
michael@0 1185 *
michael@0 1186 * @param {Object} aEvent
michael@0 1187 * Event data which includes the newly created data channel
michael@0 1188 */
michael@0 1189 this._pc.onsignalingstatechange = function (aEvent) {
michael@0 1190 info(self + ": 'onsignalingstatechange' event fired");
michael@0 1191
michael@0 1192 // this calls the eventhandler only once and then overwrites it with the
michael@0 1193 // default unexpectedEvent handler
michael@0 1194 self.onsignalingstatechange(aEvent);
michael@0 1195 self.onsignalingstatechange = unexpectedEventAndFinish(self, 'onsignalingstatechange');
michael@0 1196 };
michael@0 1197 }
michael@0 1198
michael@0 1199 PeerConnectionWrapper.prototype = {
michael@0 1200
michael@0 1201 /**
michael@0 1202 * Returns the local description.
michael@0 1203 *
michael@0 1204 * @returns {object} The local description
michael@0 1205 */
michael@0 1206 get localDescription() {
michael@0 1207 return this._pc.localDescription;
michael@0 1208 },
michael@0 1209
michael@0 1210 /**
michael@0 1211 * Sets the local description.
michael@0 1212 *
michael@0 1213 * @param {object} desc
michael@0 1214 * The new local description
michael@0 1215 */
michael@0 1216 set localDescription(desc) {
michael@0 1217 this._pc.localDescription = desc;
michael@0 1218 },
michael@0 1219
michael@0 1220 /**
michael@0 1221 * Returns the readyState.
michael@0 1222 *
michael@0 1223 * @returns {string}
michael@0 1224 */
michael@0 1225 get readyState() {
michael@0 1226 return this._pc.readyState;
michael@0 1227 },
michael@0 1228
michael@0 1229 /**
michael@0 1230 * Returns the remote description.
michael@0 1231 *
michael@0 1232 * @returns {object} The remote description
michael@0 1233 */
michael@0 1234 get remoteDescription() {
michael@0 1235 return this._pc.remoteDescription;
michael@0 1236 },
michael@0 1237
michael@0 1238 /**
michael@0 1239 * Sets the remote description.
michael@0 1240 *
michael@0 1241 * @param {object} desc
michael@0 1242 * The new remote description
michael@0 1243 */
michael@0 1244 set remoteDescription(desc) {
michael@0 1245 this._pc.remoteDescription = desc;
michael@0 1246 },
michael@0 1247
michael@0 1248 /**
michael@0 1249 * Returns the signaling state.
michael@0 1250 *
michael@0 1251 * @returns {object} The local description
michael@0 1252 */
michael@0 1253 get signalingState() {
michael@0 1254 return this._pc.signalingState;
michael@0 1255 },
michael@0 1256 /**
michael@0 1257 * Returns the ICE connection state.
michael@0 1258 *
michael@0 1259 * @returns {object} The local description
michael@0 1260 */
michael@0 1261 get iceConnectionState() {
michael@0 1262 return this._pc.iceConnectionState;
michael@0 1263 },
michael@0 1264
michael@0 1265 setIdentityProvider: function(provider, protocol, identity) {
michael@0 1266 this._pc.setIdentityProvider(provider, protocol, identity);
michael@0 1267 },
michael@0 1268
michael@0 1269 /**
michael@0 1270 * Callback when we get media from either side. Also an appropriate
michael@0 1271 * HTML media element will be created.
michael@0 1272 *
michael@0 1273 * @param {MediaStream} stream
michael@0 1274 * Media stream to handle
michael@0 1275 * @param {string} type
michael@0 1276 * The type of media stream ('audio' or 'video')
michael@0 1277 * @param {string} side
michael@0 1278 * The location the stream is coming from ('local' or 'remote')
michael@0 1279 */
michael@0 1280 attachMedia : function PCW_attachMedia(stream, type, side) {
michael@0 1281 info("Got media stream: " + type + " (" + side + ")");
michael@0 1282 this.streams.push(stream);
michael@0 1283
michael@0 1284 if (side === 'local') {
michael@0 1285 this._pc.addStream(stream);
michael@0 1286 }
michael@0 1287
michael@0 1288 var element = createMediaElement(type, this.label + '_' + side);
michael@0 1289 this.mediaCheckers.push(new MediaElementChecker(element));
michael@0 1290 element.mozSrcObject = stream;
michael@0 1291 element.play();
michael@0 1292 },
michael@0 1293
michael@0 1294 /**
michael@0 1295 * Requests all the media streams as specified in the constrains property.
michael@0 1296 *
michael@0 1297 * @param {function} onSuccess
michael@0 1298 * Callback to execute if all media has been requested successfully
michael@0 1299 */
michael@0 1300 getAllUserMedia : function PCW_GetAllUserMedia(onSuccess) {
michael@0 1301 var self = this;
michael@0 1302
michael@0 1303 function _getAllUserMedia(constraintsList, index) {
michael@0 1304 if (index < constraintsList.length) {
michael@0 1305 var constraints = constraintsList[index];
michael@0 1306
michael@0 1307 getUserMedia(constraints, function (stream) {
michael@0 1308 var type = '';
michael@0 1309
michael@0 1310 if (constraints.audio) {
michael@0 1311 type = 'audio';
michael@0 1312 }
michael@0 1313
michael@0 1314 if (constraints.video) {
michael@0 1315 type += 'video';
michael@0 1316 }
michael@0 1317
michael@0 1318 self.attachMedia(stream, type, 'local');
michael@0 1319
michael@0 1320 _getAllUserMedia(constraintsList, index + 1);
michael@0 1321 }, generateErrorCallback());
michael@0 1322 } else {
michael@0 1323 onSuccess();
michael@0 1324 }
michael@0 1325 }
michael@0 1326
michael@0 1327 info("Get " + this.constraints.length + " local streams");
michael@0 1328 _getAllUserMedia(this.constraints, 0);
michael@0 1329 },
michael@0 1330
michael@0 1331 /**
michael@0 1332 * Create a new data channel instance
michael@0 1333 *
michael@0 1334 * @param {Object} options
michael@0 1335 * Options which get forwarded to nsIPeerConnection.createDataChannel
michael@0 1336 * @param {function} [onCreation=undefined]
michael@0 1337 * Callback to execute when the local data channel has been created
michael@0 1338 * @returns {DataChannelWrapper} The created data channel
michael@0 1339 */
michael@0 1340 createDataChannel : function PCW_createDataChannel(options, onCreation) {
michael@0 1341 var label = 'channel_' + this.dataChannels.length;
michael@0 1342 info(this + ": Create data channel '" + label);
michael@0 1343
michael@0 1344 var channel = this._pc.createDataChannel(label, options);
michael@0 1345 var wrapper = new DataChannelWrapper(channel, this);
michael@0 1346
michael@0 1347 if (onCreation) {
michael@0 1348 wrapper.onopen = function () {
michael@0 1349 onCreation(wrapper);
michael@0 1350 };
michael@0 1351 }
michael@0 1352
michael@0 1353 this.dataChannels.push(wrapper);
michael@0 1354 return wrapper;
michael@0 1355 },
michael@0 1356
michael@0 1357 /**
michael@0 1358 * Creates an offer and automatically handles the failure case.
michael@0 1359 *
michael@0 1360 * @param {function} onSuccess
michael@0 1361 * Callback to execute if the offer was created successfully
michael@0 1362 */
michael@0 1363 createOffer : function PCW_createOffer(onSuccess) {
michael@0 1364 var self = this;
michael@0 1365
michael@0 1366 this._pc.createOffer(function (offer) {
michael@0 1367 info("Got offer: " + JSON.stringify(offer));
michael@0 1368 self._last_offer = offer;
michael@0 1369 onSuccess(offer);
michael@0 1370 }, generateErrorCallback(), this.offerConstraints);
michael@0 1371 },
michael@0 1372
michael@0 1373 /**
michael@0 1374 * Creates an answer and automatically handles the failure case.
michael@0 1375 *
michael@0 1376 * @param {function} onSuccess
michael@0 1377 * Callback to execute if the answer was created successfully
michael@0 1378 */
michael@0 1379 createAnswer : function PCW_createAnswer(onSuccess) {
michael@0 1380 var self = this;
michael@0 1381
michael@0 1382 this._pc.createAnswer(function (answer) {
michael@0 1383 info(self + ": Got answer: " + JSON.stringify(answer));
michael@0 1384 self._last_answer = answer;
michael@0 1385 onSuccess(answer);
michael@0 1386 }, generateErrorCallback());
michael@0 1387 },
michael@0 1388
michael@0 1389 /**
michael@0 1390 * Sets the local description and automatically handles the failure case.
michael@0 1391 *
michael@0 1392 * @param {object} desc
michael@0 1393 * mozRTCSessionDescription for the local description request
michael@0 1394 * @param {function} onSuccess
michael@0 1395 * Callback to execute if the local description was set successfully
michael@0 1396 */
michael@0 1397 setLocalDescription : function PCW_setLocalDescription(desc, onSuccess) {
michael@0 1398 var self = this;
michael@0 1399 this._pc.setLocalDescription(desc, function () {
michael@0 1400 info(self + ": Successfully set the local description");
michael@0 1401 onSuccess();
michael@0 1402 }, generateErrorCallback());
michael@0 1403 },
michael@0 1404
michael@0 1405 /**
michael@0 1406 * Tries to set the local description and expect failure. Automatically
michael@0 1407 * causes the test case to fail if the call succeeds.
michael@0 1408 *
michael@0 1409 * @param {object} desc
michael@0 1410 * mozRTCSessionDescription for the local description request
michael@0 1411 * @param {function} onFailure
michael@0 1412 * Callback to execute if the call fails.
michael@0 1413 */
michael@0 1414 setLocalDescriptionAndFail : function PCW_setLocalDescriptionAndFail(desc, onFailure) {
michael@0 1415 var self = this;
michael@0 1416 this._pc.setLocalDescription(desc,
michael@0 1417 generateErrorCallback("setLocalDescription should have failed."),
michael@0 1418 function (err) {
michael@0 1419 info(self + ": As expected, failed to set the local description");
michael@0 1420 onFailure(err);
michael@0 1421 });
michael@0 1422 },
michael@0 1423
michael@0 1424 /**
michael@0 1425 * Sets the remote description and automatically handles the failure case.
michael@0 1426 *
michael@0 1427 * @param {object} desc
michael@0 1428 * mozRTCSessionDescription for the remote description request
michael@0 1429 * @param {function} onSuccess
michael@0 1430 * Callback to execute if the remote description was set successfully
michael@0 1431 */
michael@0 1432 setRemoteDescription : function PCW_setRemoteDescription(desc, onSuccess) {
michael@0 1433 var self = this;
michael@0 1434 this._pc.setRemoteDescription(desc, function () {
michael@0 1435 info(self + ": Successfully set remote description");
michael@0 1436 onSuccess();
michael@0 1437 }, generateErrorCallback());
michael@0 1438 },
michael@0 1439
michael@0 1440 /**
michael@0 1441 * Tries to set the remote description and expect failure. Automatically
michael@0 1442 * causes the test case to fail if the call succeeds.
michael@0 1443 *
michael@0 1444 * @param {object} desc
michael@0 1445 * mozRTCSessionDescription for the remote description request
michael@0 1446 * @param {function} onFailure
michael@0 1447 * Callback to execute if the call fails.
michael@0 1448 */
michael@0 1449 setRemoteDescriptionAndFail : function PCW_setRemoteDescriptionAndFail(desc, onFailure) {
michael@0 1450 var self = this;
michael@0 1451 this._pc.setRemoteDescription(desc,
michael@0 1452 generateErrorCallback("setRemoteDescription should have failed."),
michael@0 1453 function (err) {
michael@0 1454 info(self + ": As expected, failed to set the remote description");
michael@0 1455 onFailure(err);
michael@0 1456 });
michael@0 1457 },
michael@0 1458
michael@0 1459 /**
michael@0 1460 * Adds an ICE candidate and automatically handles the failure case.
michael@0 1461 *
michael@0 1462 * @param {object} candidate
michael@0 1463 * SDP candidate
michael@0 1464 * @param {function} onSuccess
michael@0 1465 * Callback to execute if the local description was set successfully
michael@0 1466 */
michael@0 1467 addIceCandidate : function PCW_addIceCandidate(candidate, onSuccess) {
michael@0 1468 var self = this;
michael@0 1469
michael@0 1470 this._pc.addIceCandidate(candidate, function () {
michael@0 1471 info(self + ": Successfully added an ICE candidate");
michael@0 1472 onSuccess();
michael@0 1473 }, generateErrorCallback());
michael@0 1474 },
michael@0 1475
michael@0 1476 /**
michael@0 1477 * Tries to add an ICE candidate and expects failure. Automatically
michael@0 1478 * causes the test case to fail if the call succeeds.
michael@0 1479 *
michael@0 1480 * @param {object} candidate
michael@0 1481 * SDP candidate
michael@0 1482 * @param {function} onFailure
michael@0 1483 * Callback to execute if the call fails.
michael@0 1484 */
michael@0 1485 addIceCandidateAndFail : function PCW_addIceCandidateAndFail(candidate, onFailure) {
michael@0 1486 var self = this;
michael@0 1487
michael@0 1488 this._pc.addIceCandidate(candidate,
michael@0 1489 generateErrorCallback("addIceCandidate should have failed."),
michael@0 1490 function (err) {
michael@0 1491 info(self + ": As expected, failed to add an ICE candidate");
michael@0 1492 onFailure(err);
michael@0 1493 }) ;
michael@0 1494 },
michael@0 1495
michael@0 1496 /**
michael@0 1497 * Returns if the ICE the connection state is "connected".
michael@0 1498 *
michael@0 1499 * @returns {boolean} True if the connection state is "connected", otherwise false.
michael@0 1500 */
michael@0 1501 isIceConnected : function PCW_isIceConnected() {
michael@0 1502 info("iceConnectionState: " + this.iceConnectionState);
michael@0 1503 return this.iceConnectionState === "connected";
michael@0 1504 },
michael@0 1505
michael@0 1506 /**
michael@0 1507 * Returns if the ICE the connection state is "checking".
michael@0 1508 *
michael@0 1509 * @returns {boolean} True if the connection state is "checking", otherwise false.
michael@0 1510 */
michael@0 1511 isIceChecking : function PCW_isIceChecking() {
michael@0 1512 return this.iceConnectionState === "checking";
michael@0 1513 },
michael@0 1514
michael@0 1515 /**
michael@0 1516 * Returns if the ICE the connection state is "new".
michael@0 1517 *
michael@0 1518 * @returns {boolean} True if the connection state is "new", otherwise false.
michael@0 1519 */
michael@0 1520 isIceNew : function PCW_isIceNew() {
michael@0 1521 return this.iceConnectionState === "new";
michael@0 1522 },
michael@0 1523
michael@0 1524 /**
michael@0 1525 * Checks if the ICE connection state still waits for a connection to get
michael@0 1526 * established.
michael@0 1527 *
michael@0 1528 * @returns {boolean} True if the connection state is "checking" or "new",
michael@0 1529 * otherwise false.
michael@0 1530 */
michael@0 1531 isIceConnectionPending : function PCW_isIceConnectionPending() {
michael@0 1532 return (this.isIceChecking() || this.isIceNew());
michael@0 1533 },
michael@0 1534
michael@0 1535 /**
michael@0 1536 * Registers a callback for the ICE connection state change and
michael@0 1537 * reports success (=connected) or failure via the callbacks.
michael@0 1538 * States "new" and "checking" are ignored.
michael@0 1539 *
michael@0 1540 * @param {function} onSuccess
michael@0 1541 * Callback if ICE connection status is "connected".
michael@0 1542 * @param {function} onFailure
michael@0 1543 * Callback if ICE connection reaches a different state than
michael@0 1544 * "new", "checking" or "connected".
michael@0 1545 */
michael@0 1546 waitForIceConnected : function PCW_waitForIceConnected(onSuccess, onFailure) {
michael@0 1547 var self = this;
michael@0 1548 var mySuccess = onSuccess;
michael@0 1549 var myFailure = onFailure;
michael@0 1550
michael@0 1551 function iceConnectedChanged () {
michael@0 1552 if (self.isIceConnected()) {
michael@0 1553 delete self.ice_connection_callbacks.waitForIceConnected;
michael@0 1554 mySuccess();
michael@0 1555 } else if (! self.isIceConnectionPending()) {
michael@0 1556 delete self.ice_connection_callbacks.waitForIceConnected;
michael@0 1557 myFailure();
michael@0 1558 }
michael@0 1559 }
michael@0 1560
michael@0 1561 self.ice_connection_callbacks.waitForIceConnected = iceConnectedChanged;
michael@0 1562 },
michael@0 1563
michael@0 1564 /**
michael@0 1565 * Checks that we are getting the media streams we expect.
michael@0 1566 *
michael@0 1567 * @param {object} constraintsRemote
michael@0 1568 * The media constraints of the remote peer connection object
michael@0 1569 */
michael@0 1570 checkMediaStreams : function PCW_checkMediaStreams(constraintsRemote) {
michael@0 1571 is(this._pc.getLocalStreams().length, this.constraints.length,
michael@0 1572 this + ' has ' + this.constraints.length + ' local streams');
michael@0 1573
michael@0 1574 // TODO: change this when multiple incoming streams are supported (bug 834835)
michael@0 1575 is(this._pc.getRemoteStreams().length, 1,
michael@0 1576 this + ' has ' + 1 + ' remote streams');
michael@0 1577 },
michael@0 1578
michael@0 1579 /**
michael@0 1580 * Check that media flow is present on all media elements involved in this
michael@0 1581 * test by waiting for confirmation that media flow is present.
michael@0 1582 *
michael@0 1583 * @param {Function} onSuccess the success callback when media flow
michael@0 1584 * is confirmed on all media elements
michael@0 1585 */
michael@0 1586 checkMediaFlowPresent : function PCW_checkMediaFlowPresent(onSuccess) {
michael@0 1587 var self = this;
michael@0 1588
michael@0 1589 function _checkMediaFlowPresent(index, onSuccess) {
michael@0 1590 if(index >= self.mediaCheckers.length) {
michael@0 1591 onSuccess();
michael@0 1592 } else {
michael@0 1593 var mediaChecker = self.mediaCheckers[index];
michael@0 1594 mediaChecker.waitForMediaFlow(function() {
michael@0 1595 _checkMediaFlowPresent(index + 1, onSuccess);
michael@0 1596 });
michael@0 1597 }
michael@0 1598 }
michael@0 1599
michael@0 1600 _checkMediaFlowPresent(0, onSuccess);
michael@0 1601 },
michael@0 1602
michael@0 1603 /**
michael@0 1604 * Check that stats are present by checking for known stats.
michael@0 1605 *
michael@0 1606 * @param {Function} onSuccess the success callback to return stats to
michael@0 1607 */
michael@0 1608 getStats : function PCW_getStats(selector, onSuccess) {
michael@0 1609 var self = this;
michael@0 1610
michael@0 1611 this._pc.getStats(selector, function(stats) {
michael@0 1612 info(self + ": Got stats: " + JSON.stringify(stats));
michael@0 1613 self._last_stats = stats;
michael@0 1614 onSuccess(stats);
michael@0 1615 }, generateErrorCallback());
michael@0 1616 },
michael@0 1617
michael@0 1618 /**
michael@0 1619 * Checks that we are getting the media streams we expect.
michael@0 1620 *
michael@0 1621 * @param {object} stats
michael@0 1622 * The stats to check from this PeerConnectionWrapper
michael@0 1623 */
michael@0 1624 checkStats : function PCW_checkStats(stats) {
michael@0 1625 function toNum(obj) {
michael@0 1626 return obj? obj : 0;
michael@0 1627 }
michael@0 1628 function numTracks(streams) {
michael@0 1629 var n = 0;
michael@0 1630 streams.forEach(function(stream) {
michael@0 1631 n += stream.getAudioTracks().length + stream.getVideoTracks().length;
michael@0 1632 });
michael@0 1633 return n;
michael@0 1634 }
michael@0 1635
michael@0 1636 const isWinXP = navigator.userAgent.indexOf("Windows NT 5.1") != -1;
michael@0 1637
michael@0 1638 // Use spec way of enumerating stats
michael@0 1639 var counters = {};
michael@0 1640 for (var key in stats) {
michael@0 1641 if (stats.hasOwnProperty(key)) {
michael@0 1642 var res = stats[key];
michael@0 1643 // validate stats
michael@0 1644 ok(res.id == key, "Coherent stats id");
michael@0 1645 var nowish = Date.now() + 1000; // TODO: clock drift observed
michael@0 1646 var minimum = this.whenCreated - 1000; // on Windows XP (Bug 979649)
michael@0 1647 if (isWinXP) {
michael@0 1648 todo(false, "Can't reliably test rtcp timestamps on WinXP (Bug 979649)");
michael@0 1649 } else {
michael@0 1650 ok(res.timestamp >= minimum,
michael@0 1651 "Valid " + (res.isRemote? "rtcp" : "rtp") + " timestamp " +
michael@0 1652 res.timestamp + " >= " + minimum + " (" +
michael@0 1653 (res.timestamp - minimum) + " ms)");
michael@0 1654 ok(res.timestamp <= nowish,
michael@0 1655 "Valid " + (res.isRemote? "rtcp" : "rtp") + " timestamp " +
michael@0 1656 res.timestamp + " <= " + nowish + " (" +
michael@0 1657 (res.timestamp - nowish) + " ms)");
michael@0 1658 }
michael@0 1659 if (!res.isRemote) {
michael@0 1660 counters[res.type] = toNum(counters[res.type]) + 1;
michael@0 1661
michael@0 1662 switch (res.type) {
michael@0 1663 case "inboundrtp":
michael@0 1664 case "outboundrtp": {
michael@0 1665 // ssrc is a 32 bit number returned as a string by spec
michael@0 1666 ok(res.ssrc.length > 0, "Ssrc has length");
michael@0 1667 ok(res.ssrc.length < 11, "Ssrc not lengthy");
michael@0 1668 ok(!/[^0-9]/.test(res.ssrc), "Ssrc numeric");
michael@0 1669 ok(parseInt(res.ssrc) < Math.pow(2,32), "Ssrc within limits");
michael@0 1670
michael@0 1671 if (res.type == "outboundrtp") {
michael@0 1672 ok(res.packetsSent !== undefined, "Rtp packetsSent");
michael@0 1673 // minimum fragment is 8 (from RFC 791)
michael@0 1674 ok(res.bytesSent >= res.packetsSent * 8, "Rtp bytesSent");
michael@0 1675 } else {
michael@0 1676 ok(res.packetsReceived !== undefined, "Rtp packetsReceived");
michael@0 1677 ok(res.bytesReceived >= res.packetsReceived * 8, "Rtp bytesReceived");
michael@0 1678 }
michael@0 1679 if (res.remoteId) {
michael@0 1680 var rem = stats[res.remoteId];
michael@0 1681 ok(rem.isRemote, "Remote is rtcp");
michael@0 1682 ok(rem.remoteId == res.id, "Remote backlink match");
michael@0 1683 if(res.type == "outboundrtp") {
michael@0 1684 ok(rem.type == "inboundrtp", "Rtcp is inbound");
michael@0 1685 ok(rem.packetsReceived !== undefined, "Rtcp packetsReceived");
michael@0 1686 ok(rem.packetsReceived <= res.packetsSent, "No more than sent");
michael@0 1687 ok(rem.packetsLost !== undefined, "Rtcp packetsLost");
michael@0 1688 ok(rem.bytesReceived >= rem.packetsReceived * 8, "Rtcp bytesReceived");
michael@0 1689 ok(rem.bytesReceived <= res.bytesSent, "No more than sent bytes");
michael@0 1690 ok(rem.jitter !== undefined, "Rtcp jitter");
michael@0 1691 ok(rem.mozRtt !== undefined, "Rtcp rtt");
michael@0 1692 ok(rem.mozRtt >= 0, "Rtcp rtt " + rem.mozRtt + " >= 0");
michael@0 1693 ok(rem.mozRtt < 60000, "Rtcp rtt " + rem.mozRtt + " < 1 min");
michael@0 1694 } else {
michael@0 1695 ok(rem.type == "outboundrtp", "Rtcp is outbound");
michael@0 1696 ok(rem.packetsSent !== undefined, "Rtcp packetsSent");
michael@0 1697 // We may have received more than outdated Rtcp packetsSent
michael@0 1698 ok(rem.bytesSent >= rem.packetsSent * 8, "Rtcp bytesSent");
michael@0 1699 }
michael@0 1700 ok(rem.ssrc == res.ssrc, "Remote ssrc match");
michael@0 1701 } else {
michael@0 1702 info("No rtcp info received yet");
michael@0 1703 }
michael@0 1704 }
michael@0 1705 break;
michael@0 1706 }
michael@0 1707 }
michael@0 1708 }
michael@0 1709 }
michael@0 1710
michael@0 1711 // Use MapClass way of enumerating stats
michael@0 1712 var counters2 = {};
michael@0 1713 stats.forEach(function(res) {
michael@0 1714 if (!res.isRemote) {
michael@0 1715 counters2[res.type] = toNum(counters2[res.type]) + 1;
michael@0 1716 }
michael@0 1717 });
michael@0 1718 is(JSON.stringify(counters), JSON.stringify(counters2),
michael@0 1719 "Spec and MapClass variant of RTCStatsReport enumeration agree");
michael@0 1720 var nin = numTracks(this._pc.getRemoteStreams());
michael@0 1721 var nout = numTracks(this._pc.getLocalStreams());
michael@0 1722
michael@0 1723 // TODO(Bug 957145): Restore stronger inboundrtp test once Bug 948249 is fixed
michael@0 1724 //is(toNum(counters["inboundrtp"]), nin, "Have " + nin + " inboundrtp stat(s)");
michael@0 1725 ok(toNum(counters.inboundrtp) >= nin, "Have at least " + nin + " inboundrtp stat(s) *");
michael@0 1726
michael@0 1727 is(toNum(counters.outboundrtp), nout, "Have " + nout + " outboundrtp stat(s)");
michael@0 1728
michael@0 1729 var numLocalCandidates = toNum(counters.localcandidate);
michael@0 1730 var numRemoteCandidates = toNum(counters.remotecandidate);
michael@0 1731 // If there are no tracks, there will be no stats either.
michael@0 1732 if (nin + nout > 0) {
michael@0 1733 ok(numLocalCandidates, "Have localcandidate stat(s)");
michael@0 1734 ok(numRemoteCandidates, "Have remotecandidate stat(s)");
michael@0 1735 } else {
michael@0 1736 is(numLocalCandidates, 0, "Have no localcandidate stats");
michael@0 1737 is(numRemoteCandidates, 0, "Have no remotecandidate stats");
michael@0 1738 }
michael@0 1739 },
michael@0 1740
michael@0 1741 /**
michael@0 1742 * Closes the connection
michael@0 1743 */
michael@0 1744 close : function PCW_close() {
michael@0 1745 // It might be that a test has already closed the pc. In those cases
michael@0 1746 // we should not fail.
michael@0 1747 try {
michael@0 1748 this._pc.close();
michael@0 1749 info(this + ": Closed connection.");
michael@0 1750 }
michael@0 1751 catch (e) {
michael@0 1752 info(this + ": Failure in closing connection - " + e.message);
michael@0 1753 }
michael@0 1754 },
michael@0 1755
michael@0 1756 /**
michael@0 1757 * Register all events during the setup of the data channel
michael@0 1758 *
michael@0 1759 * @param {Function} onDataChannelOpened
michael@0 1760 * Callback to execute when the data channel has been opened
michael@0 1761 */
michael@0 1762 registerDataChannelOpenEvents : function (onDataChannelOpened) {
michael@0 1763 info(this + ": Register callbacks for 'ondatachannel' and 'onopen'");
michael@0 1764
michael@0 1765 this.ondatachannel = function (targetChannel) {
michael@0 1766 targetChannel.onopen = function (targetChannel) {
michael@0 1767 onDataChannelOpened(targetChannel);
michael@0 1768 };
michael@0 1769
michael@0 1770 this.dataChannels.push(targetChannel);
michael@0 1771 };
michael@0 1772 },
michael@0 1773
michael@0 1774 /**
michael@0 1775 * Returns the string representation of the class
michael@0 1776 *
michael@0 1777 * @returns {String} The string representation
michael@0 1778 */
michael@0 1779 toString : function PCW_toString() {
michael@0 1780 return "PeerConnectionWrapper (" + this.label + ")";
michael@0 1781 }
michael@0 1782 };

mercurial