1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/media/PeerConnection.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1249 @@ 1.4 +/* jshint moz:true, browser:true */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +"use strict"; 1.10 + 1.11 +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; 1.12 + 1.13 +Cu.import("resource://gre/modules/Services.jsm"); 1.14 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.15 +XPCOMUtils.defineLazyModuleGetter(this, "PeerConnectionIdp", 1.16 + "resource://gre/modules/media/PeerConnectionIdp.jsm"); 1.17 + 1.18 +const PC_CONTRACT = "@mozilla.org/dom/peerconnection;1"; 1.19 +const PC_OBS_CONTRACT = "@mozilla.org/dom/peerconnectionobserver;1"; 1.20 +const PC_ICE_CONTRACT = "@mozilla.org/dom/rtcicecandidate;1"; 1.21 +const PC_SESSION_CONTRACT = "@mozilla.org/dom/rtcsessiondescription;1"; 1.22 +const PC_MANAGER_CONTRACT = "@mozilla.org/dom/peerconnectionmanager;1"; 1.23 +const PC_STATS_CONTRACT = "@mozilla.org/dom/rtcstatsreport;1"; 1.24 +const PC_IDENTITY_CONTRACT = "@mozilla.org/dom/rtcidentityassertion;1"; 1.25 + 1.26 +const PC_CID = Components.ID("{00e0e20d-1494-4776-8e0e-0f0acbea3c79}"); 1.27 +const PC_OBS_CID = Components.ID("{d1748d4c-7f6a-4dc5-add6-d55b7678537e}"); 1.28 +const PC_ICE_CID = Components.ID("{02b9970c-433d-4cc2-923d-f7028ac66073}"); 1.29 +const PC_SESSION_CID = Components.ID("{1775081b-b62d-4954-8ffe-a067bbf508a7}"); 1.30 +const PC_MANAGER_CID = Components.ID("{7293e901-2be3-4c02-b4bd-cbef6fc24f78}"); 1.31 +const PC_STATS_CID = Components.ID("{7fe6e18b-0da3-4056-bf3b-440ef3809e06}"); 1.32 +const PC_IDENTITY_CID = Components.ID("{1abc7499-3c54-43e0-bd60-686e2703f072}"); 1.33 + 1.34 +// Global list of PeerConnection objects, so they can be cleaned up when 1.35 +// a page is torn down. (Maps inner window ID to an array of PC objects). 1.36 +function GlobalPCList() { 1.37 + this._list = {}; 1.38 + this._networkdown = false; // XXX Need to query current state somehow 1.39 + Services.obs.addObserver(this, "inner-window-destroyed", true); 1.40 + Services.obs.addObserver(this, "profile-change-net-teardown", true); 1.41 + Services.obs.addObserver(this, "network:offline-about-to-go-offline", true); 1.42 + Services.obs.addObserver(this, "network:offline-status-changed", true); 1.43 +} 1.44 +GlobalPCList.prototype = { 1.45 + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, 1.46 + Ci.nsISupportsWeakReference, 1.47 + Ci.IPeerConnectionManager]), 1.48 + classID: PC_MANAGER_CID, 1.49 + _xpcom_factory: { 1.50 + createInstance: function(outer, iid) { 1.51 + if (outer) { 1.52 + throw Cr.NS_ERROR_NO_AGGREGATION; 1.53 + } 1.54 + return _globalPCList.QueryInterface(iid); 1.55 + } 1.56 + }, 1.57 + 1.58 + addPC: function(pc) { 1.59 + let winID = pc._winID; 1.60 + if (this._list[winID]) { 1.61 + this._list[winID].push(Cu.getWeakReference(pc)); 1.62 + } else { 1.63 + this._list[winID] = [Cu.getWeakReference(pc)]; 1.64 + } 1.65 + this.removeNullRefs(winID); 1.66 + }, 1.67 + 1.68 + removeNullRefs: function(winID) { 1.69 + if (this._list[winID] === undefined) { 1.70 + return; 1.71 + } 1.72 + this._list[winID] = this._list[winID].filter( 1.73 + function (e,i,a) { return e.get() !== null; }); 1.74 + 1.75 + if (this._list[winID].length === 0) { 1.76 + delete this._list[winID]; 1.77 + } 1.78 + }, 1.79 + 1.80 + hasActivePeerConnection: function(winID) { 1.81 + this.removeNullRefs(winID); 1.82 + return this._list[winID] ? true : false; 1.83 + }, 1.84 + 1.85 + observe: function(subject, topic, data) { 1.86 + let cleanupPcRef = function(pcref) { 1.87 + let pc = pcref.get(); 1.88 + if (pc) { 1.89 + pc._pc.close(); 1.90 + delete pc._observer; 1.91 + pc._pc = null; 1.92 + } 1.93 + }; 1.94 + 1.95 + let cleanupWinId = function(list, winID) { 1.96 + if (list.hasOwnProperty(winID)) { 1.97 + list[winID].forEach(cleanupPcRef); 1.98 + delete list[winID]; 1.99 + } 1.100 + }; 1.101 + 1.102 + if (topic == "inner-window-destroyed") { 1.103 + cleanupWinId(this._list, subject.QueryInterface(Ci.nsISupportsPRUint64).data); 1.104 + } else if (topic == "profile-change-net-teardown" || 1.105 + topic == "network:offline-about-to-go-offline") { 1.106 + // Delete all peerconnections on shutdown - mostly synchronously (we 1.107 + // need them to be done deleting transports and streams before we 1.108 + // return)! All socket operations must be queued to STS thread 1.109 + // before we return to here. 1.110 + // Also kill them if "Work Offline" is selected - more can be created 1.111 + // while offline, but attempts to connect them should fail. 1.112 + for (let winId in this._list) { 1.113 + cleanupWinId(this._list, winId); 1.114 + } 1.115 + this._networkdown = true; 1.116 + } 1.117 + else if (topic == "network:offline-status-changed") { 1.118 + if (data == "offline") { 1.119 + // this._list shold be empty here 1.120 + this._networkdown = true; 1.121 + } else if (data == "online") { 1.122 + this._networkdown = false; 1.123 + } 1.124 + } 1.125 + }, 1.126 + 1.127 +}; 1.128 +let _globalPCList = new GlobalPCList(); 1.129 + 1.130 +function RTCIceCandidate() { 1.131 + this.candidate = this.sdpMid = this.sdpMLineIndex = null; 1.132 +} 1.133 +RTCIceCandidate.prototype = { 1.134 + classDescription: "mozRTCIceCandidate", 1.135 + classID: PC_ICE_CID, 1.136 + contractID: PC_ICE_CONTRACT, 1.137 + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, 1.138 + Ci.nsIDOMGlobalPropertyInitializer]), 1.139 + 1.140 + init: function(win) { this._win = win; }, 1.141 + 1.142 + __init: function(dict) { 1.143 + this.candidate = dict.candidate; 1.144 + this.sdpMid = dict.sdpMid; 1.145 + this.sdpMLineIndex = ("sdpMLineIndex" in dict)? dict.sdpMLineIndex : null; 1.146 + } 1.147 +}; 1.148 + 1.149 +function RTCSessionDescription() { 1.150 + this.type = this.sdp = null; 1.151 +} 1.152 +RTCSessionDescription.prototype = { 1.153 + classDescription: "mozRTCSessionDescription", 1.154 + classID: PC_SESSION_CID, 1.155 + contractID: PC_SESSION_CONTRACT, 1.156 + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, 1.157 + Ci.nsIDOMGlobalPropertyInitializer]), 1.158 + 1.159 + init: function(win) { this._win = win; }, 1.160 + 1.161 + __init: function(dict) { 1.162 + this.type = dict.type; 1.163 + this.sdp = dict.sdp; 1.164 + } 1.165 +}; 1.166 + 1.167 +function RTCStatsReport(win, dict) { 1.168 + function appendStats(stats, report) { 1.169 + stats.forEach(function(stat) { 1.170 + report[stat.id] = stat; 1.171 + }); 1.172 + } 1.173 + 1.174 + this._win = win; 1.175 + this._pcid = dict.pcid; 1.176 + this._report = {}; 1.177 + appendStats(dict.inboundRTPStreamStats, this._report); 1.178 + appendStats(dict.outboundRTPStreamStats, this._report); 1.179 + appendStats(dict.mediaStreamTrackStats, this._report); 1.180 + appendStats(dict.mediaStreamStats, this._report); 1.181 + appendStats(dict.transportStats, this._report); 1.182 + appendStats(dict.iceComponentStats, this._report); 1.183 + appendStats(dict.iceCandidatePairStats, this._report); 1.184 + appendStats(dict.iceCandidateStats, this._report); 1.185 + appendStats(dict.codecStats, this._report); 1.186 +} 1.187 +RTCStatsReport.prototype = { 1.188 + classDescription: "RTCStatsReport", 1.189 + classID: PC_STATS_CID, 1.190 + contractID: PC_STATS_CONTRACT, 1.191 + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]), 1.192 + 1.193 + // TODO: Change to use webidl getters once available (Bug 952122) 1.194 + // 1.195 + // Since webidl getters are not available, we make the stats available as 1.196 + // enumerable read-only properties directly on our content-facing object. 1.197 + // Must be called after our webidl sandwich is made. 1.198 + 1.199 + makeStatsPublic: function() { 1.200 + let props = {}; 1.201 + this.forEach(function(stat) { 1.202 + props[stat.id] = { enumerable: true, configurable: false, 1.203 + writable: false, value: stat }; 1.204 + }); 1.205 + Object.defineProperties(this.__DOM_IMPL__.wrappedJSObject, props); 1.206 + }, 1.207 + 1.208 + forEach: function(cb, thisArg) { 1.209 + for (var key in this._report) { 1.210 + cb.call(thisArg || this._report, this.get(key), key, this._report); 1.211 + } 1.212 + }, 1.213 + 1.214 + get: function(key) { 1.215 + function publifyReadonly(win, obj) { 1.216 + let props = {}; 1.217 + for (let k in obj) { 1.218 + props[k] = {enumerable:true, configurable:false, writable:false, value:obj[k]}; 1.219 + } 1.220 + let pubobj = Cu.createObjectIn(win); 1.221 + Object.defineProperties(pubobj, props); 1.222 + return pubobj; 1.223 + } 1.224 + 1.225 + // Return a content object rather than a wrapped chrome one. 1.226 + return publifyReadonly(this._win, this._report[key]); 1.227 + }, 1.228 + 1.229 + has: function(key) { 1.230 + return this._report[key] !== undefined; 1.231 + }, 1.232 + 1.233 + get mozPcid() { return this._pcid; } 1.234 +}; 1.235 + 1.236 +function RTCIdentityAssertion() {} 1.237 +RTCIdentityAssertion.prototype = { 1.238 + classDescription: "RTCIdentityAssertion", 1.239 + classID: PC_IDENTITY_CID, 1.240 + contractID: PC_IDENTITY_CONTRACT, 1.241 + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, 1.242 + Ci.nsIDOMGlobalPropertyInitializer]), 1.243 + 1.244 + init: function(win) { this._win = win; }, 1.245 + 1.246 + __init: function(idp, name) { 1.247 + this.idp = idp; 1.248 + this.name = name; 1.249 + } 1.250 +}; 1.251 + 1.252 +function RTCPeerConnection() { 1.253 + this._queue = []; 1.254 + 1.255 + this._pc = null; 1.256 + this._observer = null; 1.257 + this._closed = false; 1.258 + 1.259 + this._onCreateOfferSuccess = null; 1.260 + this._onCreateOfferFailure = null; 1.261 + this._onCreateAnswerSuccess = null; 1.262 + this._onCreateAnswerFailure = null; 1.263 + this._onGetStatsSuccess = null; 1.264 + this._onGetStatsFailure = null; 1.265 + 1.266 + this._pendingType = null; 1.267 + this._localType = null; 1.268 + this._remoteType = null; 1.269 + this._trickleIce = false; 1.270 + this._peerIdentity = null; 1.271 + 1.272 + /** 1.273 + * Everytime we get a request from content, we put it in the queue. If there 1.274 + * are no pending operations though, we will execute it immediately. In 1.275 + * PeerConnectionObserver, whenever we are notified that an operation has 1.276 + * finished, we will check the queue for the next operation and execute if 1.277 + * neccesary. The _pending flag indicates whether an operation is currently in 1.278 + * progress. 1.279 + */ 1.280 + this._pending = false; 1.281 + 1.282 + // States 1.283 + this._iceGatheringState = this._iceConnectionState = "new"; 1.284 +} 1.285 +RTCPeerConnection.prototype = { 1.286 + classDescription: "mozRTCPeerConnection", 1.287 + classID: PC_CID, 1.288 + contractID: PC_CONTRACT, 1.289 + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, 1.290 + Ci.nsIDOMGlobalPropertyInitializer]), 1.291 + init: function(win) { this._win = win; }, 1.292 + 1.293 + __init: function(rtcConfig) { 1.294 + this._trickleIce = Services.prefs.getBoolPref("media.peerconnection.trickle_ice"); 1.295 + if (!rtcConfig.iceServers || 1.296 + !Services.prefs.getBoolPref("media.peerconnection.use_document_iceservers")) { 1.297 + rtcConfig = {iceServers: 1.298 + JSON.parse(Services.prefs.getCharPref("media.peerconnection.default_iceservers"))}; 1.299 + } 1.300 + this._mustValidateRTCConfiguration(rtcConfig, 1.301 + "RTCPeerConnection constructor passed invalid RTCConfiguration"); 1.302 + if (_globalPCList._networkdown) { 1.303 + throw new this._win.DOMError("", 1.304 + "Can't create RTCPeerConnections when the network is down"); 1.305 + } 1.306 + 1.307 + this.makeGetterSetterEH("onaddstream"); 1.308 + this.makeGetterSetterEH("onicecandidate"); 1.309 + this.makeGetterSetterEH("onnegotiationneeded"); 1.310 + this.makeGetterSetterEH("onsignalingstatechange"); 1.311 + this.makeGetterSetterEH("onremovestream"); 1.312 + this.makeGetterSetterEH("ondatachannel"); 1.313 + this.makeGetterSetterEH("onconnection"); 1.314 + this.makeGetterSetterEH("onclosedconnection"); 1.315 + this.makeGetterSetterEH("oniceconnectionstatechange"); 1.316 + this.makeGetterSetterEH("onidentityresult"); 1.317 + this.makeGetterSetterEH("onpeeridentity"); 1.318 + this.makeGetterSetterEH("onidpassertionerror"); 1.319 + this.makeGetterSetterEH("onidpvalidationerror"); 1.320 + 1.321 + this._pc = new this._win.PeerConnectionImpl(); 1.322 + 1.323 + this.__DOM_IMPL__._innerObject = this; 1.324 + this._observer = new this._win.PeerConnectionObserver(this.__DOM_IMPL__); 1.325 + 1.326 + // Add a reference to the PeerConnection to global list (before init). 1.327 + this._winID = this._win.QueryInterface(Ci.nsIInterfaceRequestor) 1.328 + .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID; 1.329 + _globalPCList.addPC(this); 1.330 + 1.331 + this._queueOrRun({ 1.332 + func: this._initialize, 1.333 + args: [rtcConfig], 1.334 + // If not trickling, suppress start. 1.335 + wait: !this._trickleIce 1.336 + }); 1.337 + }, 1.338 + 1.339 + _initialize: function(rtcConfig) { 1.340 + this._impl.initialize(this._observer, this._win, rtcConfig, 1.341 + Services.tm.currentThread); 1.342 + this._initIdp(); 1.343 + }, 1.344 + 1.345 + get _impl() { 1.346 + if (!this._pc) { 1.347 + throw new this._win.DOMError("", 1.348 + "RTCPeerConnection is gone (did you enter Offline mode?)"); 1.349 + } 1.350 + return this._pc; 1.351 + }, 1.352 + 1.353 + _initIdp: function() { 1.354 + let prefName = "media.peerconnection.identity.timeout"; 1.355 + let idpTimeout = Services.prefs.getIntPref(prefName); 1.356 + let warningFunc = this.logWarning.bind(this); 1.357 + this._localIdp = new PeerConnectionIdp(this._win, idpTimeout, warningFunc, 1.358 + this.dispatchEvent.bind(this)); 1.359 + this._remoteIdp = new PeerConnectionIdp(this._win, idpTimeout, warningFunc, 1.360 + this.dispatchEvent.bind(this)); 1.361 + }, 1.362 + 1.363 + /** 1.364 + * Add a function to the queue or run it immediately if the queue is empty. 1.365 + * Argument is an object with the func, args and wait properties; wait should 1.366 + * be set to true if the function has a success/error callback that will call 1.367 + * _executeNext, false if it doesn't have a callback. 1.368 + */ 1.369 + _queueOrRun: function(obj) { 1.370 + this._checkClosed(); 1.371 + if (!this._pending) { 1.372 + if (obj.type !== undefined) { 1.373 + this._pendingType = obj.type; 1.374 + } 1.375 + obj.func.apply(this, obj.args); 1.376 + if (obj.wait) { 1.377 + this._pending = true; 1.378 + } 1.379 + } else { 1.380 + this._queue.push(obj); 1.381 + } 1.382 + }, 1.383 + 1.384 + // Pick the next item from the queue and run it. 1.385 + _executeNext: function() { 1.386 + if (this._queue.length) { 1.387 + let obj = this._queue.shift(); 1.388 + if (obj.type !== undefined) { 1.389 + this._pendingType = obj.type; 1.390 + } 1.391 + obj.func.apply(this, obj.args); 1.392 + if (!obj.wait) { 1.393 + this._executeNext(); 1.394 + } 1.395 + } else { 1.396 + this._pending = false; 1.397 + } 1.398 + }, 1.399 + 1.400 + /** 1.401 + * An RTCConfiguration looks like this: 1.402 + * 1.403 + * { "iceServers": [ { url:"stun:stun.example.org" }, 1.404 + * { url:"turn:turn.example.org", 1.405 + * username:"jib", credential:"mypass"} ] } 1.406 + * 1.407 + * WebIDL normalizes structure for us, so we test well-formed stun/turn urls, 1.408 + * but not validity of servers themselves, before passing along to C++. 1.409 + * ErrorMsg is passed in to detail which array-entry failed, if any. 1.410 + */ 1.411 + _mustValidateRTCConfiguration: function(rtcConfig, errorMsg) { 1.412 + var errorCtor = this._win.DOMError; 1.413 + function nicerNewURI(uriStr, errorMsg) { 1.414 + let ios = Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService); 1.415 + try { 1.416 + return ios.newURI(uriStr, null, null); 1.417 + } catch (e if (e.result == Cr.NS_ERROR_MALFORMED_URI)) { 1.418 + throw new errorCtor("", errorMsg + " - malformed URI: " + uriStr); 1.419 + } 1.420 + } 1.421 + function mustValidateServer(server) { 1.422 + if (!server.url) { 1.423 + throw new errorCtor("", errorMsg + " - missing url"); 1.424 + } 1.425 + let url = nicerNewURI(server.url, errorMsg); 1.426 + if (url.scheme in { turn:1, turns:1 }) { 1.427 + if (!server.username) { 1.428 + throw new errorCtor("", errorMsg + " - missing username: " + server.url); 1.429 + } 1.430 + if (!server.credential) { 1.431 + throw new errorCtor("", errorMsg + " - missing credential: " + 1.432 + server.url); 1.433 + } 1.434 + } 1.435 + else if (!(url.scheme in { stun:1, stuns:1 })) { 1.436 + throw new errorCtor("", errorMsg + " - improper scheme: " + url.scheme); 1.437 + } 1.438 + } 1.439 + if (rtcConfig.iceServers) { 1.440 + let len = rtcConfig.iceServers.length; 1.441 + for (let i=0; i < len; i++) { 1.442 + mustValidateServer (rtcConfig.iceServers[i], errorMsg); 1.443 + } 1.444 + } 1.445 + }, 1.446 + 1.447 + /** 1.448 + * MediaConstraints look like this: 1.449 + * 1.450 + * { 1.451 + * mandatory: {"OfferToReceiveAudio": true, "OfferToReceiveVideo": true }, 1.452 + * optional: [{"VoiceActivityDetection": true}, {"FooBar": 10}] 1.453 + * } 1.454 + * 1.455 + * WebIDL normalizes the top structure for us, but the mandatory constraints 1.456 + * member comes in as a raw object so we can detect unknown constraints. We 1.457 + * compare its members against ones we support, and fail if not found. 1.458 + */ 1.459 + _mustValidateConstraints: function(constraints, errorMsg) { 1.460 + if (constraints.mandatory) { 1.461 + let supported; 1.462 + try { 1.463 + // Passing the raw constraints.mandatory here validates its structure 1.464 + supported = this._observer.getSupportedConstraints(constraints.mandatory); 1.465 + } catch (e) { 1.466 + throw new this._win.DOMError("", errorMsg + " - " + e.message); 1.467 + } 1.468 + 1.469 + for (let constraint of Object.keys(constraints.mandatory)) { 1.470 + if (!(constraint in supported)) { 1.471 + throw new this._win.DOMError("", 1.472 + errorMsg + " - unknown mandatory constraint: " + constraint); 1.473 + } 1.474 + } 1.475 + } 1.476 + if (constraints.optional) { 1.477 + let len = constraints.optional.length; 1.478 + for (let i = 0; i < len; i++) { 1.479 + let constraints_per_entry = 0; 1.480 + for (let constraint in Object.keys(constraints.optional[i])) { 1.481 + if (constraints_per_entry) { 1.482 + throw new this._win.DOMError("", errorMsg + 1.483 + " - optional constraint must be single key/value pair"); 1.484 + } 1.485 + constraints_per_entry += 1; 1.486 + } 1.487 + } 1.488 + } 1.489 + }, 1.490 + 1.491 + // Ideally, this should be of the form _checkState(state), 1.492 + // where the state is taken from an enumeration containing 1.493 + // the valid peer connection states defined in the WebRTC 1.494 + // spec. See Bug 831756. 1.495 + _checkClosed: function() { 1.496 + if (this._closed) { 1.497 + throw new this._win.DOMError("", "Peer connection is closed"); 1.498 + } 1.499 + }, 1.500 + 1.501 + dispatchEvent: function(event) { 1.502 + this.__DOM_IMPL__.dispatchEvent(event); 1.503 + }, 1.504 + 1.505 + // Log error message to web console and window.onerror, if present. 1.506 + logErrorAndCallOnError: function(msg, file, line) { 1.507 + this.logMsg(msg, file, line, Ci.nsIScriptError.exceptionFlag); 1.508 + 1.509 + // Safely call onerror directly if present (necessary for testing) 1.510 + try { 1.511 + if (typeof this._win.onerror === "function") { 1.512 + this._win.onerror(msg, file, line); 1.513 + } 1.514 + } catch(e) { 1.515 + // If onerror itself throws, service it. 1.516 + try { 1.517 + this.logError(e.message, e.fileName, e.lineNumber); 1.518 + } catch(e) {} 1.519 + } 1.520 + }, 1.521 + 1.522 + logError: function(msg, file, line) { 1.523 + this.logMsg(msg, file, line, Ci.nsIScriptError.errorFlag); 1.524 + }, 1.525 + 1.526 + logWarning: function(msg, file, line) { 1.527 + this.logMsg(msg, file, line, Ci.nsIScriptError.warningFlag); 1.528 + }, 1.529 + 1.530 + logMsg: function(msg, file, line, flag) { 1.531 + let scriptErrorClass = Cc["@mozilla.org/scripterror;1"]; 1.532 + let scriptError = scriptErrorClass.createInstance(Ci.nsIScriptError); 1.533 + scriptError.initWithWindowID(msg, file, null, line, 0, flag, 1.534 + "content javascript", this._winID); 1.535 + let console = Cc["@mozilla.org/consoleservice;1"]. 1.536 + getService(Ci.nsIConsoleService); 1.537 + console.logMessage(scriptError); 1.538 + }, 1.539 + 1.540 + getEH: function(type) { 1.541 + return this.__DOM_IMPL__.getEventHandler(type); 1.542 + }, 1.543 + 1.544 + setEH: function(type, handler) { 1.545 + this.__DOM_IMPL__.setEventHandler(type, handler); 1.546 + }, 1.547 + 1.548 + makeGetterSetterEH: function(name) { 1.549 + Object.defineProperty(this, name, 1.550 + { 1.551 + get:function() { return this.getEH(name); }, 1.552 + set:function(h) { return this.setEH(name, h); } 1.553 + }); 1.554 + }, 1.555 + 1.556 + createOffer: function(onSuccess, onError, constraints) { 1.557 + if (!constraints) { 1.558 + constraints = {}; 1.559 + } 1.560 + this._mustValidateConstraints(constraints, "createOffer passed invalid constraints"); 1.561 + 1.562 + this._queueOrRun({ 1.563 + func: this._createOffer, 1.564 + args: [onSuccess, onError, constraints], 1.565 + wait: true 1.566 + }); 1.567 + }, 1.568 + 1.569 + _createOffer: function(onSuccess, onError, constraints) { 1.570 + this._onCreateOfferSuccess = onSuccess; 1.571 + this._onCreateOfferFailure = onError; 1.572 + this._impl.createOffer(constraints); 1.573 + }, 1.574 + 1.575 + _createAnswer: function(onSuccess, onError, constraints, provisional) { 1.576 + this._onCreateAnswerSuccess = onSuccess; 1.577 + this._onCreateAnswerFailure = onError; 1.578 + 1.579 + if (!this.remoteDescription) { 1.580 + 1.581 + this._observer.onCreateAnswerError(Ci.IPeerConnection.kInvalidState, 1.582 + "setRemoteDescription not called"); 1.583 + return; 1.584 + } 1.585 + 1.586 + if (this.remoteDescription.type != "offer") { 1.587 + 1.588 + this._observer.onCreateAnswerError(Ci.IPeerConnection.kInvalidState, 1.589 + "No outstanding offer"); 1.590 + return; 1.591 + } 1.592 + 1.593 + // TODO: Implement provisional answer. 1.594 + 1.595 + this._impl.createAnswer(constraints); 1.596 + }, 1.597 + 1.598 + createAnswer: function(onSuccess, onError, constraints, provisional) { 1.599 + if (!constraints) { 1.600 + constraints = {}; 1.601 + } 1.602 + 1.603 + this._mustValidateConstraints(constraints, "createAnswer passed invalid constraints"); 1.604 + 1.605 + if (!provisional) { 1.606 + provisional = false; 1.607 + } 1.608 + 1.609 + this._queueOrRun({ 1.610 + func: this._createAnswer, 1.611 + args: [onSuccess, onError, constraints, provisional], 1.612 + wait: true 1.613 + }); 1.614 + }, 1.615 + 1.616 + setLocalDescription: function(desc, onSuccess, onError) { 1.617 + let type; 1.618 + switch (desc.type) { 1.619 + case "offer": 1.620 + type = Ci.IPeerConnection.kActionOffer; 1.621 + break; 1.622 + case "answer": 1.623 + type = Ci.IPeerConnection.kActionAnswer; 1.624 + break; 1.625 + case "pranswer": 1.626 + throw new this._win.DOMError("", "pranswer not yet implemented"); 1.627 + default: 1.628 + throw new this._win.DOMError("", 1.629 + "Invalid type " + desc.type + " provided to setLocalDescription"); 1.630 + } 1.631 + 1.632 + this._queueOrRun({ 1.633 + func: this._setLocalDescription, 1.634 + args: [type, desc.sdp, onSuccess, onError], 1.635 + wait: true, 1.636 + type: desc.type 1.637 + }); 1.638 + }, 1.639 + 1.640 + _setLocalDescription: function(type, sdp, onSuccess, onError) { 1.641 + this._onSetLocalDescriptionSuccess = onSuccess; 1.642 + this._onSetLocalDescriptionFailure = onError; 1.643 + this._impl.setLocalDescription(type, sdp); 1.644 + }, 1.645 + 1.646 + setRemoteDescription: function(desc, onSuccess, onError) { 1.647 + let type; 1.648 + switch (desc.type) { 1.649 + case "offer": 1.650 + type = Ci.IPeerConnection.kActionOffer; 1.651 + break; 1.652 + case "answer": 1.653 + type = Ci.IPeerConnection.kActionAnswer; 1.654 + break; 1.655 + case "pranswer": 1.656 + throw new this._win.DOMError("", "pranswer not yet implemented"); 1.657 + default: 1.658 + throw new this._win.DOMError("", 1.659 + "Invalid type " + desc.type + " provided to setRemoteDescription"); 1.660 + } 1.661 + 1.662 + try { 1.663 + let processIdentity = this._processIdentity.bind(this); 1.664 + this._remoteIdp.verifyIdentityFromSDP(desc.sdp, processIdentity); 1.665 + } catch (e) { 1.666 + this.logWarning(e.message, e.fileName, e.lineNumber); 1.667 + // only happens if processing the SDP for identity doesn't work 1.668 + // let _setRemoteDescription do the error reporting 1.669 + } 1.670 + 1.671 + this._queueOrRun({ 1.672 + func: this._setRemoteDescription, 1.673 + args: [type, desc.sdp, onSuccess, onError], 1.674 + wait: true, 1.675 + type: desc.type 1.676 + }); 1.677 + }, 1.678 + 1.679 + _processIdentity: function(message) { 1.680 + if (message) { 1.681 + this._peerIdentity = new this._win.RTCIdentityAssertion( 1.682 + this._remoteIdp.provider, message.identity); 1.683 + 1.684 + let args = { peerIdentity: this._peerIdentity }; 1.685 + this.dispatchEvent(new this._win.Event("peeridentity")); 1.686 + } 1.687 + }, 1.688 + 1.689 + _setRemoteDescription: function(type, sdp, onSuccess, onError) { 1.690 + this._onSetRemoteDescriptionSuccess = onSuccess; 1.691 + this._onSetRemoteDescriptionFailure = onError; 1.692 + this._impl.setRemoteDescription(type, sdp); 1.693 + }, 1.694 + 1.695 + setIdentityProvider: function(provider, protocol, username) { 1.696 + this._checkClosed(); 1.697 + this._localIdp.setIdentityProvider(provider, protocol, username); 1.698 + }, 1.699 + 1.700 + _gotIdentityAssertion: function(assertion){ 1.701 + let args = { assertion: assertion }; 1.702 + let ev = new this._win.RTCPeerConnectionIdentityEvent("identityresult", args); 1.703 + this.dispatchEvent(ev); 1.704 + }, 1.705 + 1.706 + getIdentityAssertion: function() { 1.707 + this._checkClosed(); 1.708 + 1.709 + function gotAssertion(assertion) { 1.710 + if (assertion) { 1.711 + this._gotIdentityAssertion(assertion); 1.712 + } 1.713 + } 1.714 + 1.715 + this._localIdp.getIdentityAssertion(this._impl.fingerprint, 1.716 + gotAssertion.bind(this)); 1.717 + }, 1.718 + 1.719 + updateIce: function(config, constraints) { 1.720 + throw new this._win.DOMError("", "updateIce not yet implemented"); 1.721 + }, 1.722 + 1.723 + addIceCandidate: function(cand, onSuccess, onError) { 1.724 + if (!cand.candidate && !cand.sdpMLineIndex) { 1.725 + throw new this._win.DOMError("", 1.726 + "Invalid candidate passed to addIceCandidate!"); 1.727 + } 1.728 + this._onAddIceCandidateSuccess = onSuccess || null; 1.729 + this._onAddIceCandidateError = onError || null; 1.730 + 1.731 + this._queueOrRun({ func: this._addIceCandidate, args: [cand], wait: true }); 1.732 + }, 1.733 + 1.734 + _addIceCandidate: function(cand) { 1.735 + this._impl.addIceCandidate(cand.candidate, cand.sdpMid || "", 1.736 + (cand.sdpMLineIndex === null) ? 0 : 1.737 + cand.sdpMLineIndex + 1); 1.738 + }, 1.739 + 1.740 + addStream: function(stream, constraints) { 1.741 + if (!constraints) { 1.742 + constraints = {}; 1.743 + } 1.744 + this._mustValidateConstraints(constraints, 1.745 + "addStream passed invalid constraints"); 1.746 + if (stream.currentTime === undefined) { 1.747 + throw new this._win.DOMError("", "Invalid stream passed to addStream!"); 1.748 + } 1.749 + this._queueOrRun({ func: this._addStream, 1.750 + args: [stream, constraints], 1.751 + wait: false }); 1.752 + }, 1.753 + 1.754 + _addStream: function(stream, constraints) { 1.755 + this._impl.addStream(stream, constraints); 1.756 + }, 1.757 + 1.758 + removeStream: function(stream) { 1.759 + // Bug 844295: Not implementing this functionality. 1.760 + throw new this._win.DOMError("", "removeStream not yet implemented"); 1.761 + }, 1.762 + 1.763 + getStreamById: function(id) { 1.764 + throw new this._win.DOMError("", "getStreamById not yet implemented"); 1.765 + }, 1.766 + 1.767 + close: function() { 1.768 + if (this._closed) { 1.769 + return; 1.770 + } 1.771 + this._queueOrRun({ func: this._close, args: [false], wait: false }); 1.772 + this._closed = true; 1.773 + this.changeIceConnectionState("closed"); 1.774 + }, 1.775 + 1.776 + _close: function() { 1.777 + this._localIdp.close(); 1.778 + this._remoteIdp.close(); 1.779 + this._impl.close(); 1.780 + }, 1.781 + 1.782 + getLocalStreams: function() { 1.783 + this._checkClosed(); 1.784 + return this._impl.getLocalStreams(); 1.785 + }, 1.786 + 1.787 + getRemoteStreams: function() { 1.788 + this._checkClosed(); 1.789 + return this._impl.getRemoteStreams(); 1.790 + }, 1.791 + 1.792 + get localDescription() { 1.793 + this._checkClosed(); 1.794 + let sdp = this._impl.localDescription; 1.795 + if (sdp.length == 0) { 1.796 + return null; 1.797 + } 1.798 + 1.799 + sdp = this._localIdp.wrapSdp(sdp); 1.800 + return new this._win.mozRTCSessionDescription({ type: this._localType, 1.801 + sdp: sdp }); 1.802 + }, 1.803 + 1.804 + get remoteDescription() { 1.805 + this._checkClosed(); 1.806 + let sdp = this._impl.remoteDescription; 1.807 + if (sdp.length == 0) { 1.808 + return null; 1.809 + } 1.810 + return new this._win.mozRTCSessionDescription({ type: this._remoteType, 1.811 + sdp: sdp }); 1.812 + }, 1.813 + 1.814 + get peerIdentity() { return this._peerIdentity; }, 1.815 + get iceGatheringState() { return this._iceGatheringState; }, 1.816 + get iceConnectionState() { return this._iceConnectionState; }, 1.817 + 1.818 + get signalingState() { 1.819 + // checking for our local pc closed indication 1.820 + // before invoking the pc methods. 1.821 + if (this._closed) { 1.822 + return "closed"; 1.823 + } 1.824 + return { 1.825 + "SignalingInvalid": "", 1.826 + "SignalingStable": "stable", 1.827 + "SignalingHaveLocalOffer": "have-local-offer", 1.828 + "SignalingHaveRemoteOffer": "have-remote-offer", 1.829 + "SignalingHaveLocalPranswer": "have-local-pranswer", 1.830 + "SignalingHaveRemotePranswer": "have-remote-pranswer", 1.831 + "SignalingClosed": "closed" 1.832 + }[this._impl.signalingState]; 1.833 + }, 1.834 + 1.835 + changeIceGatheringState: function(state) { 1.836 + this._iceGatheringState = state; 1.837 + }, 1.838 + 1.839 + changeIceConnectionState: function(state) { 1.840 + this._iceConnectionState = state; 1.841 + this.dispatchEvent(new this._win.Event("iceconnectionstatechange")); 1.842 + }, 1.843 + 1.844 + getStats: function(selector, onSuccess, onError) { 1.845 + this._queueOrRun({ 1.846 + func: this._getStats, 1.847 + args: [selector, onSuccess, onError], 1.848 + wait: true 1.849 + }); 1.850 + }, 1.851 + 1.852 + _getStats: function(selector, onSuccess, onError) { 1.853 + this._onGetStatsSuccess = onSuccess; 1.854 + this._onGetStatsFailure = onError; 1.855 + 1.856 + this._impl.getStats(selector); 1.857 + }, 1.858 + 1.859 + createDataChannel: function(label, dict) { 1.860 + this._checkClosed(); 1.861 + if (dict == undefined) { 1.862 + dict = {}; 1.863 + } 1.864 + if (dict.maxRetransmitNum != undefined) { 1.865 + dict.maxRetransmits = dict.maxRetransmitNum; 1.866 + this.logWarning("Deprecated RTCDataChannelInit dictionary entry maxRetransmitNum used!", null, 0); 1.867 + } 1.868 + if (dict.outOfOrderAllowed != undefined) { 1.869 + dict.ordered = !dict.outOfOrderAllowed; // the meaning is swapped with 1.870 + // the name change 1.871 + this.logWarning("Deprecated RTCDataChannelInit dictionary entry outOfOrderAllowed used!", null, 0); 1.872 + } 1.873 + if (dict.preset != undefined) { 1.874 + dict.negotiated = dict.preset; 1.875 + this.logWarning("Deprecated RTCDataChannelInit dictionary entry preset used!", null, 0); 1.876 + } 1.877 + if (dict.stream != undefined) { 1.878 + dict.id = dict.stream; 1.879 + this.logWarning("Deprecated RTCDataChannelInit dictionary entry stream used!", null, 0); 1.880 + } 1.881 + 1.882 + if (dict.maxRetransmitTime != undefined && 1.883 + dict.maxRetransmits != undefined) { 1.884 + throw new this._win.DOMError("", 1.885 + "Both maxRetransmitTime and maxRetransmits cannot be provided"); 1.886 + } 1.887 + let protocol; 1.888 + if (dict.protocol == undefined) { 1.889 + protocol = ""; 1.890 + } else { 1.891 + protocol = dict.protocol; 1.892 + } 1.893 + 1.894 + // Must determine the type where we still know if entries are undefined. 1.895 + let type; 1.896 + if (dict.maxRetransmitTime != undefined) { 1.897 + type = Ci.IPeerConnection.kDataChannelPartialReliableTimed; 1.898 + } else if (dict.maxRetransmits != undefined) { 1.899 + type = Ci.IPeerConnection.kDataChannelPartialReliableRexmit; 1.900 + } else { 1.901 + type = Ci.IPeerConnection.kDataChannelReliable; 1.902 + } 1.903 + 1.904 + // Synchronous since it doesn't block. 1.905 + let channel = this._impl.createDataChannel( 1.906 + label, protocol, type, !dict.ordered, dict.maxRetransmitTime, 1.907 + dict.maxRetransmits, dict.negotiated ? true : false, 1.908 + dict.id != undefined ? dict.id : 0xFFFF 1.909 + ); 1.910 + return channel; 1.911 + }, 1.912 + 1.913 + connectDataConnection: function(localport, remoteport, numstreams) { 1.914 + if (numstreams == undefined || numstreams <= 0) { 1.915 + numstreams = 16; 1.916 + } 1.917 + this._queueOrRun({ 1.918 + func: this._connectDataConnection, 1.919 + args: [localport, remoteport, numstreams], 1.920 + wait: false 1.921 + }); 1.922 + }, 1.923 + 1.924 + _connectDataConnection: function(localport, remoteport, numstreams) { 1.925 + this._impl.connectDataConnection(localport, remoteport, numstreams); 1.926 + } 1.927 +}; 1.928 + 1.929 +function RTCError(code, message) { 1.930 + this.name = this.reasonName[Math.min(code, this.reasonName.length - 1)]; 1.931 + this.message = (typeof message === "string")? message : this.name; 1.932 + this.__exposedProps__ = { name: "rw", message: "rw" }; 1.933 +} 1.934 +RTCError.prototype = { 1.935 + // These strings must match those defined in the WebRTC spec. 1.936 + reasonName: [ 1.937 + "NO_ERROR", // Should never happen -- only used for testing 1.938 + "INVALID_CONSTRAINTS_TYPE", 1.939 + "INVALID_CANDIDATE_TYPE", 1.940 + "INVALID_MEDIASTREAM_TRACK", 1.941 + "INVALID_STATE", 1.942 + "INVALID_SESSION_DESCRIPTION", 1.943 + "INCOMPATIBLE_SESSION_DESCRIPTION", 1.944 + "INCOMPATIBLE_CONSTRAINTS", 1.945 + "INCOMPATIBLE_MEDIASTREAMTRACK", 1.946 + "INTERNAL_ERROR" 1.947 + ] 1.948 +}; 1.949 + 1.950 +// This is a separate object because we don't want to expose it to DOM. 1.951 +function PeerConnectionObserver() { 1.952 + this._dompc = null; 1.953 +} 1.954 +PeerConnectionObserver.prototype = { 1.955 + classDescription: "PeerConnectionObserver", 1.956 + classID: PC_OBS_CID, 1.957 + contractID: PC_OBS_CONTRACT, 1.958 + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, 1.959 + Ci.nsIDOMGlobalPropertyInitializer]), 1.960 + init: function(win) { this._win = win; }, 1.961 + 1.962 + __init: function(dompc) { 1.963 + this._dompc = dompc._innerObject; 1.964 + }, 1.965 + 1.966 + dispatchEvent: function(event) { 1.967 + this._dompc.dispatchEvent(event); 1.968 + }, 1.969 + 1.970 + callCB: function(callback, arg) { 1.971 + if (callback) { 1.972 + try { 1.973 + callback(arg); 1.974 + } catch(e) { 1.975 + // A content script (user-provided) callback threw an error. We don't 1.976 + // want this to take down peerconnection, but we still want the user 1.977 + // to see it, so we catch it, report it, and move on. 1.978 + this._dompc.logErrorAndCallOnError(e.message, 1.979 + e.fileName, 1.980 + e.lineNumber); 1.981 + } 1.982 + } 1.983 + }, 1.984 + 1.985 + onCreateOfferSuccess: function(sdp) { 1.986 + let pc = this._dompc; 1.987 + let fp = pc._impl.fingerprint; 1.988 + pc._localIdp.appendIdentityToSDP(sdp, fp, function(sdp, assertion) { 1.989 + if (assertion) { 1.990 + pc._gotIdentityAssertion(assertion); 1.991 + } 1.992 + this.callCB(pc._onCreateOfferSuccess, 1.993 + new pc._win.mozRTCSessionDescription({ type: "offer", 1.994 + sdp: sdp })); 1.995 + pc._executeNext(); 1.996 + }.bind(this)); 1.997 + }, 1.998 + 1.999 + onCreateOfferError: function(code, message) { 1.1000 + this.callCB(this._dompc._onCreateOfferFailure, new RTCError(code, message)); 1.1001 + this._dompc._executeNext(); 1.1002 + }, 1.1003 + 1.1004 + onCreateAnswerSuccess: function(sdp) { 1.1005 + let pc = this._dompc; 1.1006 + let fp = pc._impl.fingerprint; 1.1007 + pc._localIdp.appendIdentityToSDP(sdp, fp, function(sdp, assertion) { 1.1008 + if (assertion) { 1.1009 + pc._gotIdentityAssertion(assertion); 1.1010 + } 1.1011 + this.callCB (pc._onCreateAnswerSuccess, 1.1012 + new pc._win.mozRTCSessionDescription({ type: "answer", 1.1013 + sdp: sdp })); 1.1014 + pc._executeNext(); 1.1015 + }.bind(this)); 1.1016 + }, 1.1017 + 1.1018 + onCreateAnswerError: function(code, message) { 1.1019 + this.callCB(this._dompc._onCreateAnswerFailure, new RTCError(code, message)); 1.1020 + this._dompc._executeNext(); 1.1021 + }, 1.1022 + 1.1023 + onSetLocalDescriptionSuccess: function() { 1.1024 + this._dompc._localType = this._dompc._pendingType; 1.1025 + this._dompc._pendingType = null; 1.1026 + this.callCB(this._dompc._onSetLocalDescriptionSuccess); 1.1027 + 1.1028 + if (this._dompc._iceGatheringState == "complete") { 1.1029 + // If we are not trickling or we completed gathering prior 1.1030 + // to setLocal, then trigger a call of onicecandidate here. 1.1031 + this.foundIceCandidate(null); 1.1032 + } 1.1033 + 1.1034 + this._dompc._executeNext(); 1.1035 + }, 1.1036 + 1.1037 + onSetRemoteDescriptionSuccess: function() { 1.1038 + this._dompc._remoteType = this._dompc._pendingType; 1.1039 + this._dompc._pendingType = null; 1.1040 + this.callCB(this._dompc._onSetRemoteDescriptionSuccess); 1.1041 + this._dompc._executeNext(); 1.1042 + }, 1.1043 + 1.1044 + onSetLocalDescriptionError: function(code, message) { 1.1045 + this._dompc._pendingType = null; 1.1046 + this.callCB(this._dompc._onSetLocalDescriptionFailure, 1.1047 + new RTCError(code, message)); 1.1048 + this._dompc._executeNext(); 1.1049 + }, 1.1050 + 1.1051 + onSetRemoteDescriptionError: function(code, message) { 1.1052 + this._dompc._pendingType = null; 1.1053 + this.callCB(this._dompc._onSetRemoteDescriptionFailure, 1.1054 + new RTCError(code, message)); 1.1055 + this._dompc._executeNext(); 1.1056 + }, 1.1057 + 1.1058 + onAddIceCandidateSuccess: function() { 1.1059 + this._dompc._pendingType = null; 1.1060 + this.callCB(this._dompc._onAddIceCandidateSuccess); 1.1061 + this._dompc._executeNext(); 1.1062 + }, 1.1063 + 1.1064 + onAddIceCandidateError: function(code, message) { 1.1065 + this._dompc._pendingType = null; 1.1066 + this.callCB(this._dompc._onAddIceCandidateError, new RTCError(code, message)); 1.1067 + this._dompc._executeNext(); 1.1068 + }, 1.1069 + 1.1070 + onIceCandidate: function(level, mid, candidate) { 1.1071 + this.foundIceCandidate(new this._dompc._win.mozRTCIceCandidate( 1.1072 + { 1.1073 + candidate: candidate, 1.1074 + sdpMid: mid, 1.1075 + sdpMLineIndex: level - 1 1.1076 + } 1.1077 + )); 1.1078 + }, 1.1079 + 1.1080 + 1.1081 + // This method is primarily responsible for updating iceConnectionState. 1.1082 + // This state is defined in the WebRTC specification as follows: 1.1083 + // 1.1084 + // iceConnectionState: 1.1085 + // ------------------- 1.1086 + // new The ICE Agent is gathering addresses and/or waiting for 1.1087 + // remote candidates to be supplied. 1.1088 + // 1.1089 + // checking The ICE Agent has received remote candidates on at least 1.1090 + // one component, and is checking candidate pairs but has not 1.1091 + // yet found a connection. In addition to checking, it may 1.1092 + // also still be gathering. 1.1093 + // 1.1094 + // connected The ICE Agent has found a usable connection for all 1.1095 + // components but is still checking other candidate pairs to 1.1096 + // see if there is a better connection. It may also still be 1.1097 + // gathering. 1.1098 + // 1.1099 + // completed The ICE Agent has finished gathering and checking and found 1.1100 + // a connection for all components. Open issue: it is not 1.1101 + // clear how the non controlling ICE side knows it is in the 1.1102 + // state. 1.1103 + // 1.1104 + // failed The ICE Agent is finished checking all candidate pairs and 1.1105 + // failed to find a connection for at least one component. 1.1106 + // Connections may have been found for some components. 1.1107 + // 1.1108 + // disconnected Liveness checks have failed for one or more components. 1.1109 + // This is more aggressive than failed, and may trigger 1.1110 + // intermittently (and resolve itself without action) on a 1.1111 + // flaky network. 1.1112 + // 1.1113 + // closed The ICE Agent has shut down and is no longer responding to 1.1114 + // STUN requests. 1.1115 + 1.1116 + handleIceConnectionStateChange: function(iceConnectionState) { 1.1117 + var histogram = Services.telemetry.getHistogramById("WEBRTC_ICE_SUCCESS_RATE"); 1.1118 + 1.1119 + if (iceConnectionState === 'failed') { 1.1120 + histogram.add(false); 1.1121 + this._dompc.logError("ICE failed, see about:webrtc for more details", null, 0); 1.1122 + } 1.1123 + if (this._dompc.iceConnectionState === 'checking' && 1.1124 + (iceConnectionState === 'completed' || 1.1125 + iceConnectionState === 'connected')) { 1.1126 + histogram.add(true); 1.1127 + } 1.1128 + this._dompc.changeIceConnectionState(iceConnectionState); 1.1129 + }, 1.1130 + 1.1131 + // This method is responsible for updating iceGatheringState. This 1.1132 + // state is defined in the WebRTC specification as follows: 1.1133 + // 1.1134 + // iceGatheringState: 1.1135 + // ------------------ 1.1136 + // new The object was just created, and no networking has occurred 1.1137 + // yet. 1.1138 + // 1.1139 + // gathering The ICE engine is in the process of gathering candidates for 1.1140 + // this RTCPeerConnection. 1.1141 + // 1.1142 + // complete The ICE engine has completed gathering. Events such as adding 1.1143 + // a new interface or a new TURN server will cause the state to 1.1144 + // go back to gathering. 1.1145 + // 1.1146 + handleIceGatheringStateChange: function(gatheringState) { 1.1147 + this._dompc.changeIceGatheringState(gatheringState); 1.1148 + 1.1149 + if (gatheringState === "complete") { 1.1150 + if (!this._dompc._trickleIce) { 1.1151 + // If we are not trickling, then the queue is in a pending state 1.1152 + // waiting for ICE gathering and executeNext frees it 1.1153 + this._dompc._executeNext(); 1.1154 + } 1.1155 + else if (this._dompc.localDescription) { 1.1156 + // If we are trickling but we have already done setLocal, 1.1157 + // then we need to send a final foundIceCandidate(null) to indicate 1.1158 + // that we are done gathering. 1.1159 + this.foundIceCandidate(null); 1.1160 + } 1.1161 + } 1.1162 + }, 1.1163 + 1.1164 + onStateChange: function(state) { 1.1165 + switch (state) { 1.1166 + case "SignalingState": 1.1167 + this.callCB(this._dompc.onsignalingstatechange, 1.1168 + this._dompc.signalingState); 1.1169 + break; 1.1170 + 1.1171 + case "IceConnectionState": 1.1172 + this.handleIceConnectionStateChange(this._dompc._pc.iceConnectionState); 1.1173 + break; 1.1174 + 1.1175 + case "IceGatheringState": 1.1176 + this.handleIceGatheringStateChange(this._dompc._pc.iceGatheringState); 1.1177 + break; 1.1178 + 1.1179 + case "SdpState": 1.1180 + // No-op 1.1181 + break; 1.1182 + 1.1183 + case "ReadyState": 1.1184 + // No-op 1.1185 + break; 1.1186 + 1.1187 + case "SipccState": 1.1188 + // No-op 1.1189 + break; 1.1190 + 1.1191 + default: 1.1192 + this._dompc.logWarning("Unhandled state type: " + state, null, 0); 1.1193 + break; 1.1194 + } 1.1195 + }, 1.1196 + 1.1197 + onGetStatsSuccess: function(dict) { 1.1198 + let chromeobj = new RTCStatsReport(this._dompc._win, dict); 1.1199 + let webidlobj = this._dompc._win.RTCStatsReport._create(this._dompc._win, 1.1200 + chromeobj); 1.1201 + chromeobj.makeStatsPublic(); 1.1202 + this.callCB(this._dompc._onGetStatsSuccess, webidlobj); 1.1203 + this._dompc._executeNext(); 1.1204 + }, 1.1205 + 1.1206 + onGetStatsError: function(code, message) { 1.1207 + this.callCB(this._dompc._onGetStatsFailure, new RTCError(code, message)); 1.1208 + this._dompc._executeNext(); 1.1209 + }, 1.1210 + 1.1211 + onAddStream: function(stream) { 1.1212 + this.dispatchEvent(new this._dompc._win.MediaStreamEvent("addstream", 1.1213 + { stream: stream })); 1.1214 + }, 1.1215 + 1.1216 + onRemoveStream: function(stream, type) { 1.1217 + this.dispatchEvent(new this._dompc._win.MediaStreamEvent("removestream", 1.1218 + { stream: stream })); 1.1219 + }, 1.1220 + 1.1221 + foundIceCandidate: function(cand) { 1.1222 + this.dispatchEvent(new this._dompc._win.RTCPeerConnectionIceEvent("icecandidate", 1.1223 + { candidate: cand } )); 1.1224 + }, 1.1225 + 1.1226 + notifyDataChannel: function(channel) { 1.1227 + this.dispatchEvent(new this._dompc._win.RTCDataChannelEvent("datachannel", 1.1228 + { channel: channel })); 1.1229 + }, 1.1230 + 1.1231 + notifyConnection: function() { 1.1232 + this.dispatchEvent(new this._dompc._win.Event("connection")); 1.1233 + }, 1.1234 + 1.1235 + notifyClosedConnection: function() { 1.1236 + this.dispatchEvent(new this._dompc._win.Event("closedconnection")); 1.1237 + }, 1.1238 + 1.1239 + getSupportedConstraints: function(dict) { 1.1240 + return dict; 1.1241 + }, 1.1242 +}; 1.1243 + 1.1244 +this.NSGetFactory = XPCOMUtils.generateNSGetFactory( 1.1245 + [GlobalPCList, 1.1246 + RTCIceCandidate, 1.1247 + RTCSessionDescription, 1.1248 + RTCPeerConnection, 1.1249 + RTCStatsReport, 1.1250 + RTCIdentityAssertion, 1.1251 + PeerConnectionObserver] 1.1252 +);