michael@0: /* jshint moz:true, browser:true */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; michael@0: michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "PeerConnectionIdp", michael@0: "resource://gre/modules/media/PeerConnectionIdp.jsm"); michael@0: michael@0: const PC_CONTRACT = "@mozilla.org/dom/peerconnection;1"; michael@0: const PC_OBS_CONTRACT = "@mozilla.org/dom/peerconnectionobserver;1"; michael@0: const PC_ICE_CONTRACT = "@mozilla.org/dom/rtcicecandidate;1"; michael@0: const PC_SESSION_CONTRACT = "@mozilla.org/dom/rtcsessiondescription;1"; michael@0: const PC_MANAGER_CONTRACT = "@mozilla.org/dom/peerconnectionmanager;1"; michael@0: const PC_STATS_CONTRACT = "@mozilla.org/dom/rtcstatsreport;1"; michael@0: const PC_IDENTITY_CONTRACT = "@mozilla.org/dom/rtcidentityassertion;1"; michael@0: michael@0: const PC_CID = Components.ID("{00e0e20d-1494-4776-8e0e-0f0acbea3c79}"); michael@0: const PC_OBS_CID = Components.ID("{d1748d4c-7f6a-4dc5-add6-d55b7678537e}"); michael@0: const PC_ICE_CID = Components.ID("{02b9970c-433d-4cc2-923d-f7028ac66073}"); michael@0: const PC_SESSION_CID = Components.ID("{1775081b-b62d-4954-8ffe-a067bbf508a7}"); michael@0: const PC_MANAGER_CID = Components.ID("{7293e901-2be3-4c02-b4bd-cbef6fc24f78}"); michael@0: const PC_STATS_CID = Components.ID("{7fe6e18b-0da3-4056-bf3b-440ef3809e06}"); michael@0: const PC_IDENTITY_CID = Components.ID("{1abc7499-3c54-43e0-bd60-686e2703f072}"); michael@0: michael@0: // Global list of PeerConnection objects, so they can be cleaned up when michael@0: // a page is torn down. (Maps inner window ID to an array of PC objects). michael@0: function GlobalPCList() { michael@0: this._list = {}; michael@0: this._networkdown = false; // XXX Need to query current state somehow michael@0: Services.obs.addObserver(this, "inner-window-destroyed", true); michael@0: Services.obs.addObserver(this, "profile-change-net-teardown", true); michael@0: Services.obs.addObserver(this, "network:offline-about-to-go-offline", true); michael@0: Services.obs.addObserver(this, "network:offline-status-changed", true); michael@0: } michael@0: GlobalPCList.prototype = { michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, michael@0: Ci.nsISupportsWeakReference, michael@0: Ci.IPeerConnectionManager]), michael@0: classID: PC_MANAGER_CID, michael@0: _xpcom_factory: { michael@0: createInstance: function(outer, iid) { michael@0: if (outer) { michael@0: throw Cr.NS_ERROR_NO_AGGREGATION; michael@0: } michael@0: return _globalPCList.QueryInterface(iid); michael@0: } michael@0: }, michael@0: michael@0: addPC: function(pc) { michael@0: let winID = pc._winID; michael@0: if (this._list[winID]) { michael@0: this._list[winID].push(Cu.getWeakReference(pc)); michael@0: } else { michael@0: this._list[winID] = [Cu.getWeakReference(pc)]; michael@0: } michael@0: this.removeNullRefs(winID); michael@0: }, michael@0: michael@0: removeNullRefs: function(winID) { michael@0: if (this._list[winID] === undefined) { michael@0: return; michael@0: } michael@0: this._list[winID] = this._list[winID].filter( michael@0: function (e,i,a) { return e.get() !== null; }); michael@0: michael@0: if (this._list[winID].length === 0) { michael@0: delete this._list[winID]; michael@0: } michael@0: }, michael@0: michael@0: hasActivePeerConnection: function(winID) { michael@0: this.removeNullRefs(winID); michael@0: return this._list[winID] ? true : false; michael@0: }, michael@0: michael@0: observe: function(subject, topic, data) { michael@0: let cleanupPcRef = function(pcref) { michael@0: let pc = pcref.get(); michael@0: if (pc) { michael@0: pc._pc.close(); michael@0: delete pc._observer; michael@0: pc._pc = null; michael@0: } michael@0: }; michael@0: michael@0: let cleanupWinId = function(list, winID) { michael@0: if (list.hasOwnProperty(winID)) { michael@0: list[winID].forEach(cleanupPcRef); michael@0: delete list[winID]; michael@0: } michael@0: }; michael@0: michael@0: if (topic == "inner-window-destroyed") { michael@0: cleanupWinId(this._list, subject.QueryInterface(Ci.nsISupportsPRUint64).data); michael@0: } else if (topic == "profile-change-net-teardown" || michael@0: topic == "network:offline-about-to-go-offline") { michael@0: // Delete all peerconnections on shutdown - mostly synchronously (we michael@0: // need them to be done deleting transports and streams before we michael@0: // return)! All socket operations must be queued to STS thread michael@0: // before we return to here. michael@0: // Also kill them if "Work Offline" is selected - more can be created michael@0: // while offline, but attempts to connect them should fail. michael@0: for (let winId in this._list) { michael@0: cleanupWinId(this._list, winId); michael@0: } michael@0: this._networkdown = true; michael@0: } michael@0: else if (topic == "network:offline-status-changed") { michael@0: if (data == "offline") { michael@0: // this._list shold be empty here michael@0: this._networkdown = true; michael@0: } else if (data == "online") { michael@0: this._networkdown = false; michael@0: } michael@0: } michael@0: }, michael@0: michael@0: }; michael@0: let _globalPCList = new GlobalPCList(); michael@0: michael@0: function RTCIceCandidate() { michael@0: this.candidate = this.sdpMid = this.sdpMLineIndex = null; michael@0: } michael@0: RTCIceCandidate.prototype = { michael@0: classDescription: "mozRTCIceCandidate", michael@0: classID: PC_ICE_CID, michael@0: contractID: PC_ICE_CONTRACT, michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, michael@0: Ci.nsIDOMGlobalPropertyInitializer]), michael@0: michael@0: init: function(win) { this._win = win; }, michael@0: michael@0: __init: function(dict) { michael@0: this.candidate = dict.candidate; michael@0: this.sdpMid = dict.sdpMid; michael@0: this.sdpMLineIndex = ("sdpMLineIndex" in dict)? dict.sdpMLineIndex : null; michael@0: } michael@0: }; michael@0: michael@0: function RTCSessionDescription() { michael@0: this.type = this.sdp = null; michael@0: } michael@0: RTCSessionDescription.prototype = { michael@0: classDescription: "mozRTCSessionDescription", michael@0: classID: PC_SESSION_CID, michael@0: contractID: PC_SESSION_CONTRACT, michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, michael@0: Ci.nsIDOMGlobalPropertyInitializer]), michael@0: michael@0: init: function(win) { this._win = win; }, michael@0: michael@0: __init: function(dict) { michael@0: this.type = dict.type; michael@0: this.sdp = dict.sdp; michael@0: } michael@0: }; michael@0: michael@0: function RTCStatsReport(win, dict) { michael@0: function appendStats(stats, report) { michael@0: stats.forEach(function(stat) { michael@0: report[stat.id] = stat; michael@0: }); michael@0: } michael@0: michael@0: this._win = win; michael@0: this._pcid = dict.pcid; michael@0: this._report = {}; michael@0: appendStats(dict.inboundRTPStreamStats, this._report); michael@0: appendStats(dict.outboundRTPStreamStats, this._report); michael@0: appendStats(dict.mediaStreamTrackStats, this._report); michael@0: appendStats(dict.mediaStreamStats, this._report); michael@0: appendStats(dict.transportStats, this._report); michael@0: appendStats(dict.iceComponentStats, this._report); michael@0: appendStats(dict.iceCandidatePairStats, this._report); michael@0: appendStats(dict.iceCandidateStats, this._report); michael@0: appendStats(dict.codecStats, this._report); michael@0: } michael@0: RTCStatsReport.prototype = { michael@0: classDescription: "RTCStatsReport", michael@0: classID: PC_STATS_CID, michael@0: contractID: PC_STATS_CONTRACT, michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]), michael@0: michael@0: // TODO: Change to use webidl getters once available (Bug 952122) michael@0: // michael@0: // Since webidl getters are not available, we make the stats available as michael@0: // enumerable read-only properties directly on our content-facing object. michael@0: // Must be called after our webidl sandwich is made. michael@0: michael@0: makeStatsPublic: function() { michael@0: let props = {}; michael@0: this.forEach(function(stat) { michael@0: props[stat.id] = { enumerable: true, configurable: false, michael@0: writable: false, value: stat }; michael@0: }); michael@0: Object.defineProperties(this.__DOM_IMPL__.wrappedJSObject, props); michael@0: }, michael@0: michael@0: forEach: function(cb, thisArg) { michael@0: for (var key in this._report) { michael@0: cb.call(thisArg || this._report, this.get(key), key, this._report); michael@0: } michael@0: }, michael@0: michael@0: get: function(key) { michael@0: function publifyReadonly(win, obj) { michael@0: let props = {}; michael@0: for (let k in obj) { michael@0: props[k] = {enumerable:true, configurable:false, writable:false, value:obj[k]}; michael@0: } michael@0: let pubobj = Cu.createObjectIn(win); michael@0: Object.defineProperties(pubobj, props); michael@0: return pubobj; michael@0: } michael@0: michael@0: // Return a content object rather than a wrapped chrome one. michael@0: return publifyReadonly(this._win, this._report[key]); michael@0: }, michael@0: michael@0: has: function(key) { michael@0: return this._report[key] !== undefined; michael@0: }, michael@0: michael@0: get mozPcid() { return this._pcid; } michael@0: }; michael@0: michael@0: function RTCIdentityAssertion() {} michael@0: RTCIdentityAssertion.prototype = { michael@0: classDescription: "RTCIdentityAssertion", michael@0: classID: PC_IDENTITY_CID, michael@0: contractID: PC_IDENTITY_CONTRACT, michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, michael@0: Ci.nsIDOMGlobalPropertyInitializer]), michael@0: michael@0: init: function(win) { this._win = win; }, michael@0: michael@0: __init: function(idp, name) { michael@0: this.idp = idp; michael@0: this.name = name; michael@0: } michael@0: }; michael@0: michael@0: function RTCPeerConnection() { michael@0: this._queue = []; michael@0: michael@0: this._pc = null; michael@0: this._observer = null; michael@0: this._closed = false; michael@0: michael@0: this._onCreateOfferSuccess = null; michael@0: this._onCreateOfferFailure = null; michael@0: this._onCreateAnswerSuccess = null; michael@0: this._onCreateAnswerFailure = null; michael@0: this._onGetStatsSuccess = null; michael@0: this._onGetStatsFailure = null; michael@0: michael@0: this._pendingType = null; michael@0: this._localType = null; michael@0: this._remoteType = null; michael@0: this._trickleIce = false; michael@0: this._peerIdentity = null; michael@0: michael@0: /** michael@0: * Everytime we get a request from content, we put it in the queue. If there michael@0: * are no pending operations though, we will execute it immediately. In michael@0: * PeerConnectionObserver, whenever we are notified that an operation has michael@0: * finished, we will check the queue for the next operation and execute if michael@0: * neccesary. The _pending flag indicates whether an operation is currently in michael@0: * progress. michael@0: */ michael@0: this._pending = false; michael@0: michael@0: // States michael@0: this._iceGatheringState = this._iceConnectionState = "new"; michael@0: } michael@0: RTCPeerConnection.prototype = { michael@0: classDescription: "mozRTCPeerConnection", michael@0: classID: PC_CID, michael@0: contractID: PC_CONTRACT, michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, michael@0: Ci.nsIDOMGlobalPropertyInitializer]), michael@0: init: function(win) { this._win = win; }, michael@0: michael@0: __init: function(rtcConfig) { michael@0: this._trickleIce = Services.prefs.getBoolPref("media.peerconnection.trickle_ice"); michael@0: if (!rtcConfig.iceServers || michael@0: !Services.prefs.getBoolPref("media.peerconnection.use_document_iceservers")) { michael@0: rtcConfig = {iceServers: michael@0: JSON.parse(Services.prefs.getCharPref("media.peerconnection.default_iceservers"))}; michael@0: } michael@0: this._mustValidateRTCConfiguration(rtcConfig, michael@0: "RTCPeerConnection constructor passed invalid RTCConfiguration"); michael@0: if (_globalPCList._networkdown) { michael@0: throw new this._win.DOMError("", michael@0: "Can't create RTCPeerConnections when the network is down"); michael@0: } michael@0: michael@0: this.makeGetterSetterEH("onaddstream"); michael@0: this.makeGetterSetterEH("onicecandidate"); michael@0: this.makeGetterSetterEH("onnegotiationneeded"); michael@0: this.makeGetterSetterEH("onsignalingstatechange"); michael@0: this.makeGetterSetterEH("onremovestream"); michael@0: this.makeGetterSetterEH("ondatachannel"); michael@0: this.makeGetterSetterEH("onconnection"); michael@0: this.makeGetterSetterEH("onclosedconnection"); michael@0: this.makeGetterSetterEH("oniceconnectionstatechange"); michael@0: this.makeGetterSetterEH("onidentityresult"); michael@0: this.makeGetterSetterEH("onpeeridentity"); michael@0: this.makeGetterSetterEH("onidpassertionerror"); michael@0: this.makeGetterSetterEH("onidpvalidationerror"); michael@0: michael@0: this._pc = new this._win.PeerConnectionImpl(); michael@0: michael@0: this.__DOM_IMPL__._innerObject = this; michael@0: this._observer = new this._win.PeerConnectionObserver(this.__DOM_IMPL__); michael@0: michael@0: // Add a reference to the PeerConnection to global list (before init). michael@0: this._winID = this._win.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID; michael@0: _globalPCList.addPC(this); michael@0: michael@0: this._queueOrRun({ michael@0: func: this._initialize, michael@0: args: [rtcConfig], michael@0: // If not trickling, suppress start. michael@0: wait: !this._trickleIce michael@0: }); michael@0: }, michael@0: michael@0: _initialize: function(rtcConfig) { michael@0: this._impl.initialize(this._observer, this._win, rtcConfig, michael@0: Services.tm.currentThread); michael@0: this._initIdp(); michael@0: }, michael@0: michael@0: get _impl() { michael@0: if (!this._pc) { michael@0: throw new this._win.DOMError("", michael@0: "RTCPeerConnection is gone (did you enter Offline mode?)"); michael@0: } michael@0: return this._pc; michael@0: }, michael@0: michael@0: _initIdp: function() { michael@0: let prefName = "media.peerconnection.identity.timeout"; michael@0: let idpTimeout = Services.prefs.getIntPref(prefName); michael@0: let warningFunc = this.logWarning.bind(this); michael@0: this._localIdp = new PeerConnectionIdp(this._win, idpTimeout, warningFunc, michael@0: this.dispatchEvent.bind(this)); michael@0: this._remoteIdp = new PeerConnectionIdp(this._win, idpTimeout, warningFunc, michael@0: this.dispatchEvent.bind(this)); michael@0: }, michael@0: michael@0: /** michael@0: * Add a function to the queue or run it immediately if the queue is empty. michael@0: * Argument is an object with the func, args and wait properties; wait should michael@0: * be set to true if the function has a success/error callback that will call michael@0: * _executeNext, false if it doesn't have a callback. michael@0: */ michael@0: _queueOrRun: function(obj) { michael@0: this._checkClosed(); michael@0: if (!this._pending) { michael@0: if (obj.type !== undefined) { michael@0: this._pendingType = obj.type; michael@0: } michael@0: obj.func.apply(this, obj.args); michael@0: if (obj.wait) { michael@0: this._pending = true; michael@0: } michael@0: } else { michael@0: this._queue.push(obj); michael@0: } michael@0: }, michael@0: michael@0: // Pick the next item from the queue and run it. michael@0: _executeNext: function() { michael@0: if (this._queue.length) { michael@0: let obj = this._queue.shift(); michael@0: if (obj.type !== undefined) { michael@0: this._pendingType = obj.type; michael@0: } michael@0: obj.func.apply(this, obj.args); michael@0: if (!obj.wait) { michael@0: this._executeNext(); michael@0: } michael@0: } else { michael@0: this._pending = false; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * An RTCConfiguration looks like this: michael@0: * michael@0: * { "iceServers": [ { url:"stun:stun.example.org" }, michael@0: * { url:"turn:turn.example.org", michael@0: * username:"jib", credential:"mypass"} ] } michael@0: * michael@0: * WebIDL normalizes structure for us, so we test well-formed stun/turn urls, michael@0: * but not validity of servers themselves, before passing along to C++. michael@0: * ErrorMsg is passed in to detail which array-entry failed, if any. michael@0: */ michael@0: _mustValidateRTCConfiguration: function(rtcConfig, errorMsg) { michael@0: var errorCtor = this._win.DOMError; michael@0: function nicerNewURI(uriStr, errorMsg) { michael@0: let ios = Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService); michael@0: try { michael@0: return ios.newURI(uriStr, null, null); michael@0: } catch (e if (e.result == Cr.NS_ERROR_MALFORMED_URI)) { michael@0: throw new errorCtor("", errorMsg + " - malformed URI: " + uriStr); michael@0: } michael@0: } michael@0: function mustValidateServer(server) { michael@0: if (!server.url) { michael@0: throw new errorCtor("", errorMsg + " - missing url"); michael@0: } michael@0: let url = nicerNewURI(server.url, errorMsg); michael@0: if (url.scheme in { turn:1, turns:1 }) { michael@0: if (!server.username) { michael@0: throw new errorCtor("", errorMsg + " - missing username: " + server.url); michael@0: } michael@0: if (!server.credential) { michael@0: throw new errorCtor("", errorMsg + " - missing credential: " + michael@0: server.url); michael@0: } michael@0: } michael@0: else if (!(url.scheme in { stun:1, stuns:1 })) { michael@0: throw new errorCtor("", errorMsg + " - improper scheme: " + url.scheme); michael@0: } michael@0: } michael@0: if (rtcConfig.iceServers) { michael@0: let len = rtcConfig.iceServers.length; michael@0: for (let i=0; i < len; i++) { michael@0: mustValidateServer (rtcConfig.iceServers[i], errorMsg); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * MediaConstraints look like this: michael@0: * michael@0: * { michael@0: * mandatory: {"OfferToReceiveAudio": true, "OfferToReceiveVideo": true }, michael@0: * optional: [{"VoiceActivityDetection": true}, {"FooBar": 10}] michael@0: * } michael@0: * michael@0: * WebIDL normalizes the top structure for us, but the mandatory constraints michael@0: * member comes in as a raw object so we can detect unknown constraints. We michael@0: * compare its members against ones we support, and fail if not found. michael@0: */ michael@0: _mustValidateConstraints: function(constraints, errorMsg) { michael@0: if (constraints.mandatory) { michael@0: let supported; michael@0: try { michael@0: // Passing the raw constraints.mandatory here validates its structure michael@0: supported = this._observer.getSupportedConstraints(constraints.mandatory); michael@0: } catch (e) { michael@0: throw new this._win.DOMError("", errorMsg + " - " + e.message); michael@0: } michael@0: michael@0: for (let constraint of Object.keys(constraints.mandatory)) { michael@0: if (!(constraint in supported)) { michael@0: throw new this._win.DOMError("", michael@0: errorMsg + " - unknown mandatory constraint: " + constraint); michael@0: } michael@0: } michael@0: } michael@0: if (constraints.optional) { michael@0: let len = constraints.optional.length; michael@0: for (let i = 0; i < len; i++) { michael@0: let constraints_per_entry = 0; michael@0: for (let constraint in Object.keys(constraints.optional[i])) { michael@0: if (constraints_per_entry) { michael@0: throw new this._win.DOMError("", errorMsg + michael@0: " - optional constraint must be single key/value pair"); michael@0: } michael@0: constraints_per_entry += 1; michael@0: } michael@0: } michael@0: } michael@0: }, michael@0: michael@0: // Ideally, this should be of the form _checkState(state), michael@0: // where the state is taken from an enumeration containing michael@0: // the valid peer connection states defined in the WebRTC michael@0: // spec. See Bug 831756. michael@0: _checkClosed: function() { michael@0: if (this._closed) { michael@0: throw new this._win.DOMError("", "Peer connection is closed"); michael@0: } michael@0: }, michael@0: michael@0: dispatchEvent: function(event) { michael@0: this.__DOM_IMPL__.dispatchEvent(event); michael@0: }, michael@0: michael@0: // Log error message to web console and window.onerror, if present. michael@0: logErrorAndCallOnError: function(msg, file, line) { michael@0: this.logMsg(msg, file, line, Ci.nsIScriptError.exceptionFlag); michael@0: michael@0: // Safely call onerror directly if present (necessary for testing) michael@0: try { michael@0: if (typeof this._win.onerror === "function") { michael@0: this._win.onerror(msg, file, line); michael@0: } michael@0: } catch(e) { michael@0: // If onerror itself throws, service it. michael@0: try { michael@0: this.logError(e.message, e.fileName, e.lineNumber); michael@0: } catch(e) {} michael@0: } michael@0: }, michael@0: michael@0: logError: function(msg, file, line) { michael@0: this.logMsg(msg, file, line, Ci.nsIScriptError.errorFlag); michael@0: }, michael@0: michael@0: logWarning: function(msg, file, line) { michael@0: this.logMsg(msg, file, line, Ci.nsIScriptError.warningFlag); michael@0: }, michael@0: michael@0: logMsg: function(msg, file, line, flag) { michael@0: let scriptErrorClass = Cc["@mozilla.org/scripterror;1"]; michael@0: let scriptError = scriptErrorClass.createInstance(Ci.nsIScriptError); michael@0: scriptError.initWithWindowID(msg, file, null, line, 0, flag, michael@0: "content javascript", this._winID); michael@0: let console = Cc["@mozilla.org/consoleservice;1"]. michael@0: getService(Ci.nsIConsoleService); michael@0: console.logMessage(scriptError); michael@0: }, michael@0: michael@0: getEH: function(type) { michael@0: return this.__DOM_IMPL__.getEventHandler(type); michael@0: }, michael@0: michael@0: setEH: function(type, handler) { michael@0: this.__DOM_IMPL__.setEventHandler(type, handler); michael@0: }, michael@0: michael@0: makeGetterSetterEH: function(name) { michael@0: Object.defineProperty(this, name, michael@0: { michael@0: get:function() { return this.getEH(name); }, michael@0: set:function(h) { return this.setEH(name, h); } michael@0: }); michael@0: }, michael@0: michael@0: createOffer: function(onSuccess, onError, constraints) { michael@0: if (!constraints) { michael@0: constraints = {}; michael@0: } michael@0: this._mustValidateConstraints(constraints, "createOffer passed invalid constraints"); michael@0: michael@0: this._queueOrRun({ michael@0: func: this._createOffer, michael@0: args: [onSuccess, onError, constraints], michael@0: wait: true michael@0: }); michael@0: }, michael@0: michael@0: _createOffer: function(onSuccess, onError, constraints) { michael@0: this._onCreateOfferSuccess = onSuccess; michael@0: this._onCreateOfferFailure = onError; michael@0: this._impl.createOffer(constraints); michael@0: }, michael@0: michael@0: _createAnswer: function(onSuccess, onError, constraints, provisional) { michael@0: this._onCreateAnswerSuccess = onSuccess; michael@0: this._onCreateAnswerFailure = onError; michael@0: michael@0: if (!this.remoteDescription) { michael@0: michael@0: this._observer.onCreateAnswerError(Ci.IPeerConnection.kInvalidState, michael@0: "setRemoteDescription not called"); michael@0: return; michael@0: } michael@0: michael@0: if (this.remoteDescription.type != "offer") { michael@0: michael@0: this._observer.onCreateAnswerError(Ci.IPeerConnection.kInvalidState, michael@0: "No outstanding offer"); michael@0: return; michael@0: } michael@0: michael@0: // TODO: Implement provisional answer. michael@0: michael@0: this._impl.createAnswer(constraints); michael@0: }, michael@0: michael@0: createAnswer: function(onSuccess, onError, constraints, provisional) { michael@0: if (!constraints) { michael@0: constraints = {}; michael@0: } michael@0: michael@0: this._mustValidateConstraints(constraints, "createAnswer passed invalid constraints"); michael@0: michael@0: if (!provisional) { michael@0: provisional = false; michael@0: } michael@0: michael@0: this._queueOrRun({ michael@0: func: this._createAnswer, michael@0: args: [onSuccess, onError, constraints, provisional], michael@0: wait: true michael@0: }); michael@0: }, michael@0: michael@0: setLocalDescription: function(desc, onSuccess, onError) { michael@0: let type; michael@0: switch (desc.type) { michael@0: case "offer": michael@0: type = Ci.IPeerConnection.kActionOffer; michael@0: break; michael@0: case "answer": michael@0: type = Ci.IPeerConnection.kActionAnswer; michael@0: break; michael@0: case "pranswer": michael@0: throw new this._win.DOMError("", "pranswer not yet implemented"); michael@0: default: michael@0: throw new this._win.DOMError("", michael@0: "Invalid type " + desc.type + " provided to setLocalDescription"); michael@0: } michael@0: michael@0: this._queueOrRun({ michael@0: func: this._setLocalDescription, michael@0: args: [type, desc.sdp, onSuccess, onError], michael@0: wait: true, michael@0: type: desc.type michael@0: }); michael@0: }, michael@0: michael@0: _setLocalDescription: function(type, sdp, onSuccess, onError) { michael@0: this._onSetLocalDescriptionSuccess = onSuccess; michael@0: this._onSetLocalDescriptionFailure = onError; michael@0: this._impl.setLocalDescription(type, sdp); michael@0: }, michael@0: michael@0: setRemoteDescription: function(desc, onSuccess, onError) { michael@0: let type; michael@0: switch (desc.type) { michael@0: case "offer": michael@0: type = Ci.IPeerConnection.kActionOffer; michael@0: break; michael@0: case "answer": michael@0: type = Ci.IPeerConnection.kActionAnswer; michael@0: break; michael@0: case "pranswer": michael@0: throw new this._win.DOMError("", "pranswer not yet implemented"); michael@0: default: michael@0: throw new this._win.DOMError("", michael@0: "Invalid type " + desc.type + " provided to setRemoteDescription"); michael@0: } michael@0: michael@0: try { michael@0: let processIdentity = this._processIdentity.bind(this); michael@0: this._remoteIdp.verifyIdentityFromSDP(desc.sdp, processIdentity); michael@0: } catch (e) { michael@0: this.logWarning(e.message, e.fileName, e.lineNumber); michael@0: // only happens if processing the SDP for identity doesn't work michael@0: // let _setRemoteDescription do the error reporting michael@0: } michael@0: michael@0: this._queueOrRun({ michael@0: func: this._setRemoteDescription, michael@0: args: [type, desc.sdp, onSuccess, onError], michael@0: wait: true, michael@0: type: desc.type michael@0: }); michael@0: }, michael@0: michael@0: _processIdentity: function(message) { michael@0: if (message) { michael@0: this._peerIdentity = new this._win.RTCIdentityAssertion( michael@0: this._remoteIdp.provider, message.identity); michael@0: michael@0: let args = { peerIdentity: this._peerIdentity }; michael@0: this.dispatchEvent(new this._win.Event("peeridentity")); michael@0: } michael@0: }, michael@0: michael@0: _setRemoteDescription: function(type, sdp, onSuccess, onError) { michael@0: this._onSetRemoteDescriptionSuccess = onSuccess; michael@0: this._onSetRemoteDescriptionFailure = onError; michael@0: this._impl.setRemoteDescription(type, sdp); michael@0: }, michael@0: michael@0: setIdentityProvider: function(provider, protocol, username) { michael@0: this._checkClosed(); michael@0: this._localIdp.setIdentityProvider(provider, protocol, username); michael@0: }, michael@0: michael@0: _gotIdentityAssertion: function(assertion){ michael@0: let args = { assertion: assertion }; michael@0: let ev = new this._win.RTCPeerConnectionIdentityEvent("identityresult", args); michael@0: this.dispatchEvent(ev); michael@0: }, michael@0: michael@0: getIdentityAssertion: function() { michael@0: this._checkClosed(); michael@0: michael@0: function gotAssertion(assertion) { michael@0: if (assertion) { michael@0: this._gotIdentityAssertion(assertion); michael@0: } michael@0: } michael@0: michael@0: this._localIdp.getIdentityAssertion(this._impl.fingerprint, michael@0: gotAssertion.bind(this)); michael@0: }, michael@0: michael@0: updateIce: function(config, constraints) { michael@0: throw new this._win.DOMError("", "updateIce not yet implemented"); michael@0: }, michael@0: michael@0: addIceCandidate: function(cand, onSuccess, onError) { michael@0: if (!cand.candidate && !cand.sdpMLineIndex) { michael@0: throw new this._win.DOMError("", michael@0: "Invalid candidate passed to addIceCandidate!"); michael@0: } michael@0: this._onAddIceCandidateSuccess = onSuccess || null; michael@0: this._onAddIceCandidateError = onError || null; michael@0: michael@0: this._queueOrRun({ func: this._addIceCandidate, args: [cand], wait: true }); michael@0: }, michael@0: michael@0: _addIceCandidate: function(cand) { michael@0: this._impl.addIceCandidate(cand.candidate, cand.sdpMid || "", michael@0: (cand.sdpMLineIndex === null) ? 0 : michael@0: cand.sdpMLineIndex + 1); michael@0: }, michael@0: michael@0: addStream: function(stream, constraints) { michael@0: if (!constraints) { michael@0: constraints = {}; michael@0: } michael@0: this._mustValidateConstraints(constraints, michael@0: "addStream passed invalid constraints"); michael@0: if (stream.currentTime === undefined) { michael@0: throw new this._win.DOMError("", "Invalid stream passed to addStream!"); michael@0: } michael@0: this._queueOrRun({ func: this._addStream, michael@0: args: [stream, constraints], michael@0: wait: false }); michael@0: }, michael@0: michael@0: _addStream: function(stream, constraints) { michael@0: this._impl.addStream(stream, constraints); michael@0: }, michael@0: michael@0: removeStream: function(stream) { michael@0: // Bug 844295: Not implementing this functionality. michael@0: throw new this._win.DOMError("", "removeStream not yet implemented"); michael@0: }, michael@0: michael@0: getStreamById: function(id) { michael@0: throw new this._win.DOMError("", "getStreamById not yet implemented"); michael@0: }, michael@0: michael@0: close: function() { michael@0: if (this._closed) { michael@0: return; michael@0: } michael@0: this._queueOrRun({ func: this._close, args: [false], wait: false }); michael@0: this._closed = true; michael@0: this.changeIceConnectionState("closed"); michael@0: }, michael@0: michael@0: _close: function() { michael@0: this._localIdp.close(); michael@0: this._remoteIdp.close(); michael@0: this._impl.close(); michael@0: }, michael@0: michael@0: getLocalStreams: function() { michael@0: this._checkClosed(); michael@0: return this._impl.getLocalStreams(); michael@0: }, michael@0: michael@0: getRemoteStreams: function() { michael@0: this._checkClosed(); michael@0: return this._impl.getRemoteStreams(); michael@0: }, michael@0: michael@0: get localDescription() { michael@0: this._checkClosed(); michael@0: let sdp = this._impl.localDescription; michael@0: if (sdp.length == 0) { michael@0: return null; michael@0: } michael@0: michael@0: sdp = this._localIdp.wrapSdp(sdp); michael@0: return new this._win.mozRTCSessionDescription({ type: this._localType, michael@0: sdp: sdp }); michael@0: }, michael@0: michael@0: get remoteDescription() { michael@0: this._checkClosed(); michael@0: let sdp = this._impl.remoteDescription; michael@0: if (sdp.length == 0) { michael@0: return null; michael@0: } michael@0: return new this._win.mozRTCSessionDescription({ type: this._remoteType, michael@0: sdp: sdp }); michael@0: }, michael@0: michael@0: get peerIdentity() { return this._peerIdentity; }, michael@0: get iceGatheringState() { return this._iceGatheringState; }, michael@0: get iceConnectionState() { return this._iceConnectionState; }, michael@0: michael@0: get signalingState() { michael@0: // checking for our local pc closed indication michael@0: // before invoking the pc methods. michael@0: if (this._closed) { michael@0: return "closed"; michael@0: } michael@0: return { michael@0: "SignalingInvalid": "", michael@0: "SignalingStable": "stable", michael@0: "SignalingHaveLocalOffer": "have-local-offer", michael@0: "SignalingHaveRemoteOffer": "have-remote-offer", michael@0: "SignalingHaveLocalPranswer": "have-local-pranswer", michael@0: "SignalingHaveRemotePranswer": "have-remote-pranswer", michael@0: "SignalingClosed": "closed" michael@0: }[this._impl.signalingState]; michael@0: }, michael@0: michael@0: changeIceGatheringState: function(state) { michael@0: this._iceGatheringState = state; michael@0: }, michael@0: michael@0: changeIceConnectionState: function(state) { michael@0: this._iceConnectionState = state; michael@0: this.dispatchEvent(new this._win.Event("iceconnectionstatechange")); michael@0: }, michael@0: michael@0: getStats: function(selector, onSuccess, onError) { michael@0: this._queueOrRun({ michael@0: func: this._getStats, michael@0: args: [selector, onSuccess, onError], michael@0: wait: true michael@0: }); michael@0: }, michael@0: michael@0: _getStats: function(selector, onSuccess, onError) { michael@0: this._onGetStatsSuccess = onSuccess; michael@0: this._onGetStatsFailure = onError; michael@0: michael@0: this._impl.getStats(selector); michael@0: }, michael@0: michael@0: createDataChannel: function(label, dict) { michael@0: this._checkClosed(); michael@0: if (dict == undefined) { michael@0: dict = {}; michael@0: } michael@0: if (dict.maxRetransmitNum != undefined) { michael@0: dict.maxRetransmits = dict.maxRetransmitNum; michael@0: this.logWarning("Deprecated RTCDataChannelInit dictionary entry maxRetransmitNum used!", null, 0); michael@0: } michael@0: if (dict.outOfOrderAllowed != undefined) { michael@0: dict.ordered = !dict.outOfOrderAllowed; // the meaning is swapped with michael@0: // the name change michael@0: this.logWarning("Deprecated RTCDataChannelInit dictionary entry outOfOrderAllowed used!", null, 0); michael@0: } michael@0: if (dict.preset != undefined) { michael@0: dict.negotiated = dict.preset; michael@0: this.logWarning("Deprecated RTCDataChannelInit dictionary entry preset used!", null, 0); michael@0: } michael@0: if (dict.stream != undefined) { michael@0: dict.id = dict.stream; michael@0: this.logWarning("Deprecated RTCDataChannelInit dictionary entry stream used!", null, 0); michael@0: } michael@0: michael@0: if (dict.maxRetransmitTime != undefined && michael@0: dict.maxRetransmits != undefined) { michael@0: throw new this._win.DOMError("", michael@0: "Both maxRetransmitTime and maxRetransmits cannot be provided"); michael@0: } michael@0: let protocol; michael@0: if (dict.protocol == undefined) { michael@0: protocol = ""; michael@0: } else { michael@0: protocol = dict.protocol; michael@0: } michael@0: michael@0: // Must determine the type where we still know if entries are undefined. michael@0: let type; michael@0: if (dict.maxRetransmitTime != undefined) { michael@0: type = Ci.IPeerConnection.kDataChannelPartialReliableTimed; michael@0: } else if (dict.maxRetransmits != undefined) { michael@0: type = Ci.IPeerConnection.kDataChannelPartialReliableRexmit; michael@0: } else { michael@0: type = Ci.IPeerConnection.kDataChannelReliable; michael@0: } michael@0: michael@0: // Synchronous since it doesn't block. michael@0: let channel = this._impl.createDataChannel( michael@0: label, protocol, type, !dict.ordered, dict.maxRetransmitTime, michael@0: dict.maxRetransmits, dict.negotiated ? true : false, michael@0: dict.id != undefined ? dict.id : 0xFFFF michael@0: ); michael@0: return channel; michael@0: }, michael@0: michael@0: connectDataConnection: function(localport, remoteport, numstreams) { michael@0: if (numstreams == undefined || numstreams <= 0) { michael@0: numstreams = 16; michael@0: } michael@0: this._queueOrRun({ michael@0: func: this._connectDataConnection, michael@0: args: [localport, remoteport, numstreams], michael@0: wait: false michael@0: }); michael@0: }, michael@0: michael@0: _connectDataConnection: function(localport, remoteport, numstreams) { michael@0: this._impl.connectDataConnection(localport, remoteport, numstreams); michael@0: } michael@0: }; michael@0: michael@0: function RTCError(code, message) { michael@0: this.name = this.reasonName[Math.min(code, this.reasonName.length - 1)]; michael@0: this.message = (typeof message === "string")? message : this.name; michael@0: this.__exposedProps__ = { name: "rw", message: "rw" }; michael@0: } michael@0: RTCError.prototype = { michael@0: // These strings must match those defined in the WebRTC spec. michael@0: reasonName: [ michael@0: "NO_ERROR", // Should never happen -- only used for testing michael@0: "INVALID_CONSTRAINTS_TYPE", michael@0: "INVALID_CANDIDATE_TYPE", michael@0: "INVALID_MEDIASTREAM_TRACK", michael@0: "INVALID_STATE", michael@0: "INVALID_SESSION_DESCRIPTION", michael@0: "INCOMPATIBLE_SESSION_DESCRIPTION", michael@0: "INCOMPATIBLE_CONSTRAINTS", michael@0: "INCOMPATIBLE_MEDIASTREAMTRACK", michael@0: "INTERNAL_ERROR" michael@0: ] michael@0: }; michael@0: michael@0: // This is a separate object because we don't want to expose it to DOM. michael@0: function PeerConnectionObserver() { michael@0: this._dompc = null; michael@0: } michael@0: PeerConnectionObserver.prototype = { michael@0: classDescription: "PeerConnectionObserver", michael@0: classID: PC_OBS_CID, michael@0: contractID: PC_OBS_CONTRACT, michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, michael@0: Ci.nsIDOMGlobalPropertyInitializer]), michael@0: init: function(win) { this._win = win; }, michael@0: michael@0: __init: function(dompc) { michael@0: this._dompc = dompc._innerObject; michael@0: }, michael@0: michael@0: dispatchEvent: function(event) { michael@0: this._dompc.dispatchEvent(event); michael@0: }, michael@0: michael@0: callCB: function(callback, arg) { michael@0: if (callback) { michael@0: try { michael@0: callback(arg); michael@0: } catch(e) { michael@0: // A content script (user-provided) callback threw an error. We don't michael@0: // want this to take down peerconnection, but we still want the user michael@0: // to see it, so we catch it, report it, and move on. michael@0: this._dompc.logErrorAndCallOnError(e.message, michael@0: e.fileName, michael@0: e.lineNumber); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: onCreateOfferSuccess: function(sdp) { michael@0: let pc = this._dompc; michael@0: let fp = pc._impl.fingerprint; michael@0: pc._localIdp.appendIdentityToSDP(sdp, fp, function(sdp, assertion) { michael@0: if (assertion) { michael@0: pc._gotIdentityAssertion(assertion); michael@0: } michael@0: this.callCB(pc._onCreateOfferSuccess, michael@0: new pc._win.mozRTCSessionDescription({ type: "offer", michael@0: sdp: sdp })); michael@0: pc._executeNext(); michael@0: }.bind(this)); michael@0: }, michael@0: michael@0: onCreateOfferError: function(code, message) { michael@0: this.callCB(this._dompc._onCreateOfferFailure, new RTCError(code, message)); michael@0: this._dompc._executeNext(); michael@0: }, michael@0: michael@0: onCreateAnswerSuccess: function(sdp) { michael@0: let pc = this._dompc; michael@0: let fp = pc._impl.fingerprint; michael@0: pc._localIdp.appendIdentityToSDP(sdp, fp, function(sdp, assertion) { michael@0: if (assertion) { michael@0: pc._gotIdentityAssertion(assertion); michael@0: } michael@0: this.callCB (pc._onCreateAnswerSuccess, michael@0: new pc._win.mozRTCSessionDescription({ type: "answer", michael@0: sdp: sdp })); michael@0: pc._executeNext(); michael@0: }.bind(this)); michael@0: }, michael@0: michael@0: onCreateAnswerError: function(code, message) { michael@0: this.callCB(this._dompc._onCreateAnswerFailure, new RTCError(code, message)); michael@0: this._dompc._executeNext(); michael@0: }, michael@0: michael@0: onSetLocalDescriptionSuccess: function() { michael@0: this._dompc._localType = this._dompc._pendingType; michael@0: this._dompc._pendingType = null; michael@0: this.callCB(this._dompc._onSetLocalDescriptionSuccess); michael@0: michael@0: if (this._dompc._iceGatheringState == "complete") { michael@0: // If we are not trickling or we completed gathering prior michael@0: // to setLocal, then trigger a call of onicecandidate here. michael@0: this.foundIceCandidate(null); michael@0: } michael@0: michael@0: this._dompc._executeNext(); michael@0: }, michael@0: michael@0: onSetRemoteDescriptionSuccess: function() { michael@0: this._dompc._remoteType = this._dompc._pendingType; michael@0: this._dompc._pendingType = null; michael@0: this.callCB(this._dompc._onSetRemoteDescriptionSuccess); michael@0: this._dompc._executeNext(); michael@0: }, michael@0: michael@0: onSetLocalDescriptionError: function(code, message) { michael@0: this._dompc._pendingType = null; michael@0: this.callCB(this._dompc._onSetLocalDescriptionFailure, michael@0: new RTCError(code, message)); michael@0: this._dompc._executeNext(); michael@0: }, michael@0: michael@0: onSetRemoteDescriptionError: function(code, message) { michael@0: this._dompc._pendingType = null; michael@0: this.callCB(this._dompc._onSetRemoteDescriptionFailure, michael@0: new RTCError(code, message)); michael@0: this._dompc._executeNext(); michael@0: }, michael@0: michael@0: onAddIceCandidateSuccess: function() { michael@0: this._dompc._pendingType = null; michael@0: this.callCB(this._dompc._onAddIceCandidateSuccess); michael@0: this._dompc._executeNext(); michael@0: }, michael@0: michael@0: onAddIceCandidateError: function(code, message) { michael@0: this._dompc._pendingType = null; michael@0: this.callCB(this._dompc._onAddIceCandidateError, new RTCError(code, message)); michael@0: this._dompc._executeNext(); michael@0: }, michael@0: michael@0: onIceCandidate: function(level, mid, candidate) { michael@0: this.foundIceCandidate(new this._dompc._win.mozRTCIceCandidate( michael@0: { michael@0: candidate: candidate, michael@0: sdpMid: mid, michael@0: sdpMLineIndex: level - 1 michael@0: } michael@0: )); michael@0: }, michael@0: michael@0: michael@0: // This method is primarily responsible for updating iceConnectionState. michael@0: // This state is defined in the WebRTC specification as follows: michael@0: // michael@0: // iceConnectionState: michael@0: // ------------------- michael@0: // new The ICE Agent is gathering addresses and/or waiting for michael@0: // remote candidates to be supplied. michael@0: // michael@0: // checking The ICE Agent has received remote candidates on at least michael@0: // one component, and is checking candidate pairs but has not michael@0: // yet found a connection. In addition to checking, it may michael@0: // also still be gathering. michael@0: // michael@0: // connected The ICE Agent has found a usable connection for all michael@0: // components but is still checking other candidate pairs to michael@0: // see if there is a better connection. It may also still be michael@0: // gathering. michael@0: // michael@0: // completed The ICE Agent has finished gathering and checking and found michael@0: // a connection for all components. Open issue: it is not michael@0: // clear how the non controlling ICE side knows it is in the michael@0: // state. michael@0: // michael@0: // failed The ICE Agent is finished checking all candidate pairs and michael@0: // failed to find a connection for at least one component. michael@0: // Connections may have been found for some components. michael@0: // michael@0: // disconnected Liveness checks have failed for one or more components. michael@0: // This is more aggressive than failed, and may trigger michael@0: // intermittently (and resolve itself without action) on a michael@0: // flaky network. michael@0: // michael@0: // closed The ICE Agent has shut down and is no longer responding to michael@0: // STUN requests. michael@0: michael@0: handleIceConnectionStateChange: function(iceConnectionState) { michael@0: var histogram = Services.telemetry.getHistogramById("WEBRTC_ICE_SUCCESS_RATE"); michael@0: michael@0: if (iceConnectionState === 'failed') { michael@0: histogram.add(false); michael@0: this._dompc.logError("ICE failed, see about:webrtc for more details", null, 0); michael@0: } michael@0: if (this._dompc.iceConnectionState === 'checking' && michael@0: (iceConnectionState === 'completed' || michael@0: iceConnectionState === 'connected')) { michael@0: histogram.add(true); michael@0: } michael@0: this._dompc.changeIceConnectionState(iceConnectionState); michael@0: }, michael@0: michael@0: // This method is responsible for updating iceGatheringState. This michael@0: // state is defined in the WebRTC specification as follows: michael@0: // michael@0: // iceGatheringState: michael@0: // ------------------ michael@0: // new The object was just created, and no networking has occurred michael@0: // yet. michael@0: // michael@0: // gathering The ICE engine is in the process of gathering candidates for michael@0: // this RTCPeerConnection. michael@0: // michael@0: // complete The ICE engine has completed gathering. Events such as adding michael@0: // a new interface or a new TURN server will cause the state to michael@0: // go back to gathering. michael@0: // michael@0: handleIceGatheringStateChange: function(gatheringState) { michael@0: this._dompc.changeIceGatheringState(gatheringState); michael@0: michael@0: if (gatheringState === "complete") { michael@0: if (!this._dompc._trickleIce) { michael@0: // If we are not trickling, then the queue is in a pending state michael@0: // waiting for ICE gathering and executeNext frees it michael@0: this._dompc._executeNext(); michael@0: } michael@0: else if (this._dompc.localDescription) { michael@0: // If we are trickling but we have already done setLocal, michael@0: // then we need to send a final foundIceCandidate(null) to indicate michael@0: // that we are done gathering. michael@0: this.foundIceCandidate(null); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: onStateChange: function(state) { michael@0: switch (state) { michael@0: case "SignalingState": michael@0: this.callCB(this._dompc.onsignalingstatechange, michael@0: this._dompc.signalingState); michael@0: break; michael@0: michael@0: case "IceConnectionState": michael@0: this.handleIceConnectionStateChange(this._dompc._pc.iceConnectionState); michael@0: break; michael@0: michael@0: case "IceGatheringState": michael@0: this.handleIceGatheringStateChange(this._dompc._pc.iceGatheringState); michael@0: break; michael@0: michael@0: case "SdpState": michael@0: // No-op michael@0: break; michael@0: michael@0: case "ReadyState": michael@0: // No-op michael@0: break; michael@0: michael@0: case "SipccState": michael@0: // No-op michael@0: break; michael@0: michael@0: default: michael@0: this._dompc.logWarning("Unhandled state type: " + state, null, 0); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: onGetStatsSuccess: function(dict) { michael@0: let chromeobj = new RTCStatsReport(this._dompc._win, dict); michael@0: let webidlobj = this._dompc._win.RTCStatsReport._create(this._dompc._win, michael@0: chromeobj); michael@0: chromeobj.makeStatsPublic(); michael@0: this.callCB(this._dompc._onGetStatsSuccess, webidlobj); michael@0: this._dompc._executeNext(); michael@0: }, michael@0: michael@0: onGetStatsError: function(code, message) { michael@0: this.callCB(this._dompc._onGetStatsFailure, new RTCError(code, message)); michael@0: this._dompc._executeNext(); michael@0: }, michael@0: michael@0: onAddStream: function(stream) { michael@0: this.dispatchEvent(new this._dompc._win.MediaStreamEvent("addstream", michael@0: { stream: stream })); michael@0: }, michael@0: michael@0: onRemoveStream: function(stream, type) { michael@0: this.dispatchEvent(new this._dompc._win.MediaStreamEvent("removestream", michael@0: { stream: stream })); michael@0: }, michael@0: michael@0: foundIceCandidate: function(cand) { michael@0: this.dispatchEvent(new this._dompc._win.RTCPeerConnectionIceEvent("icecandidate", michael@0: { candidate: cand } )); michael@0: }, michael@0: michael@0: notifyDataChannel: function(channel) { michael@0: this.dispatchEvent(new this._dompc._win.RTCDataChannelEvent("datachannel", michael@0: { channel: channel })); michael@0: }, michael@0: michael@0: notifyConnection: function() { michael@0: this.dispatchEvent(new this._dompc._win.Event("connection")); michael@0: }, michael@0: michael@0: notifyClosedConnection: function() { michael@0: this.dispatchEvent(new this._dompc._win.Event("closedconnection")); michael@0: }, michael@0: michael@0: getSupportedConstraints: function(dict) { michael@0: return dict; michael@0: }, michael@0: }; michael@0: michael@0: this.NSGetFactory = XPCOMUtils.generateNSGetFactory( michael@0: [GlobalPCList, michael@0: RTCIceCandidate, michael@0: RTCSessionDescription, michael@0: RTCPeerConnection, michael@0: RTCStatsReport, michael@0: RTCIdentityAssertion, michael@0: PeerConnectionObserver] michael@0: );