dom/media/PeerConnection.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* jshint moz:true, browser:true */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 "use strict";
     8 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
    10 Cu.import("resource://gre/modules/Services.jsm");
    11 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    12 XPCOMUtils.defineLazyModuleGetter(this, "PeerConnectionIdp",
    13   "resource://gre/modules/media/PeerConnectionIdp.jsm");
    15 const PC_CONTRACT = "@mozilla.org/dom/peerconnection;1";
    16 const PC_OBS_CONTRACT = "@mozilla.org/dom/peerconnectionobserver;1";
    17 const PC_ICE_CONTRACT = "@mozilla.org/dom/rtcicecandidate;1";
    18 const PC_SESSION_CONTRACT = "@mozilla.org/dom/rtcsessiondescription;1";
    19 const PC_MANAGER_CONTRACT = "@mozilla.org/dom/peerconnectionmanager;1";
    20 const PC_STATS_CONTRACT = "@mozilla.org/dom/rtcstatsreport;1";
    21 const PC_IDENTITY_CONTRACT = "@mozilla.org/dom/rtcidentityassertion;1";
    23 const PC_CID = Components.ID("{00e0e20d-1494-4776-8e0e-0f0acbea3c79}");
    24 const PC_OBS_CID = Components.ID("{d1748d4c-7f6a-4dc5-add6-d55b7678537e}");
    25 const PC_ICE_CID = Components.ID("{02b9970c-433d-4cc2-923d-f7028ac66073}");
    26 const PC_SESSION_CID = Components.ID("{1775081b-b62d-4954-8ffe-a067bbf508a7}");
    27 const PC_MANAGER_CID = Components.ID("{7293e901-2be3-4c02-b4bd-cbef6fc24f78}");
    28 const PC_STATS_CID = Components.ID("{7fe6e18b-0da3-4056-bf3b-440ef3809e06}");
    29 const PC_IDENTITY_CID = Components.ID("{1abc7499-3c54-43e0-bd60-686e2703f072}");
    31 // Global list of PeerConnection objects, so they can be cleaned up when
    32 // a page is torn down. (Maps inner window ID to an array of PC objects).
    33 function GlobalPCList() {
    34   this._list = {};
    35   this._networkdown = false; // XXX Need to query current state somehow
    36   Services.obs.addObserver(this, "inner-window-destroyed", true);
    37   Services.obs.addObserver(this, "profile-change-net-teardown", true);
    38   Services.obs.addObserver(this, "network:offline-about-to-go-offline", true);
    39   Services.obs.addObserver(this, "network:offline-status-changed", true);
    40 }
    41 GlobalPCList.prototype = {
    42   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
    43                                          Ci.nsISupportsWeakReference,
    44                                          Ci.IPeerConnectionManager]),
    45   classID: PC_MANAGER_CID,
    46   _xpcom_factory: {
    47     createInstance: function(outer, iid) {
    48       if (outer) {
    49         throw Cr.NS_ERROR_NO_AGGREGATION;
    50       }
    51       return _globalPCList.QueryInterface(iid);
    52     }
    53   },
    55   addPC: function(pc) {
    56     let winID = pc._winID;
    57     if (this._list[winID]) {
    58       this._list[winID].push(Cu.getWeakReference(pc));
    59     } else {
    60       this._list[winID] = [Cu.getWeakReference(pc)];
    61     }
    62     this.removeNullRefs(winID);
    63   },
    65   removeNullRefs: function(winID) {
    66     if (this._list[winID] === undefined) {
    67       return;
    68     }
    69     this._list[winID] = this._list[winID].filter(
    70       function (e,i,a) { return e.get() !== null; });
    72     if (this._list[winID].length === 0) {
    73       delete this._list[winID];
    74     }
    75   },
    77   hasActivePeerConnection: function(winID) {
    78     this.removeNullRefs(winID);
    79     return this._list[winID] ? true : false;
    80   },
    82   observe: function(subject, topic, data) {
    83     let cleanupPcRef = function(pcref) {
    84       let pc = pcref.get();
    85       if (pc) {
    86         pc._pc.close();
    87         delete pc._observer;
    88         pc._pc = null;
    89       }
    90     };
    92     let cleanupWinId = function(list, winID) {
    93       if (list.hasOwnProperty(winID)) {
    94         list[winID].forEach(cleanupPcRef);
    95         delete list[winID];
    96       }
    97     };
    99     if (topic == "inner-window-destroyed") {
   100       cleanupWinId(this._list, subject.QueryInterface(Ci.nsISupportsPRUint64).data);
   101     } else if (topic == "profile-change-net-teardown" ||
   102                topic == "network:offline-about-to-go-offline") {
   103       // Delete all peerconnections on shutdown - mostly synchronously (we
   104       // need them to be done deleting transports and streams before we
   105       // return)! All socket operations must be queued to STS thread
   106       // before we return to here.
   107       // Also kill them if "Work Offline" is selected - more can be created
   108       // while offline, but attempts to connect them should fail.
   109       for (let winId in this._list) {
   110         cleanupWinId(this._list, winId);
   111       }
   112       this._networkdown = true;
   113     }
   114     else if (topic == "network:offline-status-changed") {
   115       if (data == "offline") {
   116         // this._list shold be empty here
   117         this._networkdown = true;
   118       } else if (data == "online") {
   119         this._networkdown = false;
   120       }
   121     }
   122   },
   124 };
   125 let _globalPCList = new GlobalPCList();
   127 function RTCIceCandidate() {
   128   this.candidate = this.sdpMid = this.sdpMLineIndex = null;
   129 }
   130 RTCIceCandidate.prototype = {
   131   classDescription: "mozRTCIceCandidate",
   132   classID: PC_ICE_CID,
   133   contractID: PC_ICE_CONTRACT,
   134   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
   135                                          Ci.nsIDOMGlobalPropertyInitializer]),
   137   init: function(win) { this._win = win; },
   139   __init: function(dict) {
   140     this.candidate = dict.candidate;
   141     this.sdpMid = dict.sdpMid;
   142     this.sdpMLineIndex = ("sdpMLineIndex" in dict)? dict.sdpMLineIndex : null;
   143   }
   144 };
   146 function RTCSessionDescription() {
   147   this.type = this.sdp = null;
   148 }
   149 RTCSessionDescription.prototype = {
   150   classDescription: "mozRTCSessionDescription",
   151   classID: PC_SESSION_CID,
   152   contractID: PC_SESSION_CONTRACT,
   153   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
   154                                          Ci.nsIDOMGlobalPropertyInitializer]),
   156   init: function(win) { this._win = win; },
   158   __init: function(dict) {
   159     this.type = dict.type;
   160     this.sdp  = dict.sdp;
   161   }
   162 };
   164 function RTCStatsReport(win, dict) {
   165   function appendStats(stats, report) {
   166     stats.forEach(function(stat) {
   167         report[stat.id] = stat;
   168       });
   169   }
   171   this._win = win;
   172   this._pcid = dict.pcid;
   173   this._report = {};
   174   appendStats(dict.inboundRTPStreamStats, this._report);
   175   appendStats(dict.outboundRTPStreamStats, this._report);
   176   appendStats(dict.mediaStreamTrackStats, this._report);
   177   appendStats(dict.mediaStreamStats, this._report);
   178   appendStats(dict.transportStats, this._report);
   179   appendStats(dict.iceComponentStats, this._report);
   180   appendStats(dict.iceCandidatePairStats, this._report);
   181   appendStats(dict.iceCandidateStats, this._report);
   182   appendStats(dict.codecStats, this._report);
   183 }
   184 RTCStatsReport.prototype = {
   185   classDescription: "RTCStatsReport",
   186   classID: PC_STATS_CID,
   187   contractID: PC_STATS_CONTRACT,
   188   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
   190   // TODO: Change to use webidl getters once available (Bug 952122)
   191   //
   192   // Since webidl getters are not available, we make the stats available as
   193   // enumerable read-only properties directly on our content-facing object.
   194   // Must be called after our webidl sandwich is made.
   196   makeStatsPublic: function() {
   197     let props = {};
   198     this.forEach(function(stat) {
   199         props[stat.id] = { enumerable: true, configurable: false,
   200                            writable: false, value: stat };
   201       });
   202     Object.defineProperties(this.__DOM_IMPL__.wrappedJSObject, props);
   203   },
   205   forEach: function(cb, thisArg) {
   206     for (var key in this._report) {
   207       cb.call(thisArg || this._report, this.get(key), key, this._report);
   208     }
   209   },
   211   get: function(key) {
   212     function publifyReadonly(win, obj) {
   213       let props = {};
   214       for (let k in obj) {
   215         props[k] = {enumerable:true, configurable:false, writable:false, value:obj[k]};
   216       }
   217       let pubobj = Cu.createObjectIn(win);
   218       Object.defineProperties(pubobj, props);
   219       return pubobj;
   220     }
   222     // Return a content object rather than a wrapped chrome one.
   223     return publifyReadonly(this._win, this._report[key]);
   224   },
   226   has: function(key) {
   227     return this._report[key] !== undefined;
   228   },
   230   get mozPcid() { return this._pcid; }
   231 };
   233 function RTCIdentityAssertion() {}
   234 RTCIdentityAssertion.prototype = {
   235   classDescription: "RTCIdentityAssertion",
   236   classID: PC_IDENTITY_CID,
   237   contractID: PC_IDENTITY_CONTRACT,
   238   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
   239                                          Ci.nsIDOMGlobalPropertyInitializer]),
   241   init: function(win) { this._win = win; },
   243   __init: function(idp, name) {
   244     this.idp = idp;
   245     this.name  = name;
   246   }
   247 };
   249 function RTCPeerConnection() {
   250   this._queue = [];
   252   this._pc = null;
   253   this._observer = null;
   254   this._closed = false;
   256   this._onCreateOfferSuccess = null;
   257   this._onCreateOfferFailure = null;
   258   this._onCreateAnswerSuccess = null;
   259   this._onCreateAnswerFailure = null;
   260   this._onGetStatsSuccess = null;
   261   this._onGetStatsFailure = null;
   263   this._pendingType = null;
   264   this._localType = null;
   265   this._remoteType = null;
   266   this._trickleIce = false;
   267   this._peerIdentity = null;
   269   /**
   270    * Everytime we get a request from content, we put it in the queue. If there
   271    * are no pending operations though, we will execute it immediately. In
   272    * PeerConnectionObserver, whenever we are notified that an operation has
   273    * finished, we will check the queue for the next operation and execute if
   274    * neccesary. The _pending flag indicates whether an operation is currently in
   275    * progress.
   276    */
   277   this._pending = false;
   279   // States
   280   this._iceGatheringState = this._iceConnectionState = "new";
   281 }
   282 RTCPeerConnection.prototype = {
   283   classDescription: "mozRTCPeerConnection",
   284   classID: PC_CID,
   285   contractID: PC_CONTRACT,
   286   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
   287                                          Ci.nsIDOMGlobalPropertyInitializer]),
   288   init: function(win) { this._win = win; },
   290   __init: function(rtcConfig) {
   291     this._trickleIce = Services.prefs.getBoolPref("media.peerconnection.trickle_ice");
   292     if (!rtcConfig.iceServers ||
   293         !Services.prefs.getBoolPref("media.peerconnection.use_document_iceservers")) {
   294       rtcConfig = {iceServers:
   295         JSON.parse(Services.prefs.getCharPref("media.peerconnection.default_iceservers"))};
   296     }
   297     this._mustValidateRTCConfiguration(rtcConfig,
   298         "RTCPeerConnection constructor passed invalid RTCConfiguration");
   299     if (_globalPCList._networkdown) {
   300       throw new this._win.DOMError("",
   301           "Can't create RTCPeerConnections when the network is down");
   302     }
   304     this.makeGetterSetterEH("onaddstream");
   305     this.makeGetterSetterEH("onicecandidate");
   306     this.makeGetterSetterEH("onnegotiationneeded");
   307     this.makeGetterSetterEH("onsignalingstatechange");
   308     this.makeGetterSetterEH("onremovestream");
   309     this.makeGetterSetterEH("ondatachannel");
   310     this.makeGetterSetterEH("onconnection");
   311     this.makeGetterSetterEH("onclosedconnection");
   312     this.makeGetterSetterEH("oniceconnectionstatechange");
   313     this.makeGetterSetterEH("onidentityresult");
   314     this.makeGetterSetterEH("onpeeridentity");
   315     this.makeGetterSetterEH("onidpassertionerror");
   316     this.makeGetterSetterEH("onidpvalidationerror");
   318     this._pc = new this._win.PeerConnectionImpl();
   320     this.__DOM_IMPL__._innerObject = this;
   321     this._observer = new this._win.PeerConnectionObserver(this.__DOM_IMPL__);
   323     // Add a reference to the PeerConnection to global list (before init).
   324     this._winID = this._win.QueryInterface(Ci.nsIInterfaceRequestor)
   325       .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
   326     _globalPCList.addPC(this);
   328     this._queueOrRun({
   329       func: this._initialize,
   330       args: [rtcConfig],
   331       // If not trickling, suppress start.
   332       wait: !this._trickleIce
   333     });
   334   },
   336   _initialize: function(rtcConfig) {
   337     this._impl.initialize(this._observer, this._win, rtcConfig,
   338                           Services.tm.currentThread);
   339     this._initIdp();
   340   },
   342   get _impl() {
   343     if (!this._pc) {
   344       throw new this._win.DOMError("",
   345           "RTCPeerConnection is gone (did you enter Offline mode?)");
   346     }
   347     return this._pc;
   348   },
   350   _initIdp: function() {
   351     let prefName = "media.peerconnection.identity.timeout";
   352     let idpTimeout = Services.prefs.getIntPref(prefName);
   353     let warningFunc = this.logWarning.bind(this);
   354     this._localIdp = new PeerConnectionIdp(this._win, idpTimeout, warningFunc,
   355                                            this.dispatchEvent.bind(this));
   356     this._remoteIdp = new PeerConnectionIdp(this._win, idpTimeout, warningFunc,
   357                                             this.dispatchEvent.bind(this));
   358   },
   360   /**
   361    * Add a function to the queue or run it immediately if the queue is empty.
   362    * Argument is an object with the func, args and wait properties; wait should
   363    * be set to true if the function has a success/error callback that will call
   364    * _executeNext, false if it doesn't have a callback.
   365    */
   366   _queueOrRun: function(obj) {
   367     this._checkClosed();
   368     if (!this._pending) {
   369       if (obj.type !== undefined) {
   370         this._pendingType = obj.type;
   371       }
   372       obj.func.apply(this, obj.args);
   373       if (obj.wait) {
   374         this._pending = true;
   375       }
   376     } else {
   377       this._queue.push(obj);
   378     }
   379   },
   381   // Pick the next item from the queue and run it.
   382   _executeNext: function() {
   383     if (this._queue.length) {
   384       let obj = this._queue.shift();
   385       if (obj.type !== undefined) {
   386         this._pendingType = obj.type;
   387       }
   388       obj.func.apply(this, obj.args);
   389       if (!obj.wait) {
   390         this._executeNext();
   391       }
   392     } else {
   393       this._pending = false;
   394     }
   395   },
   397   /**
   398    * An RTCConfiguration looks like this:
   399    *
   400    * { "iceServers": [ { url:"stun:stun.example.org" },
   401    *                   { url:"turn:turn.example.org",
   402    *                     username:"jib", credential:"mypass"} ] }
   403    *
   404    * WebIDL normalizes structure for us, so we test well-formed stun/turn urls,
   405    * but not validity of servers themselves, before passing along to C++.
   406    * ErrorMsg is passed in to detail which array-entry failed, if any.
   407    */
   408   _mustValidateRTCConfiguration: function(rtcConfig, errorMsg) {
   409     var errorCtor = this._win.DOMError;
   410     function nicerNewURI(uriStr, errorMsg) {
   411       let ios = Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService);
   412       try {
   413         return ios.newURI(uriStr, null, null);
   414       } catch (e if (e.result == Cr.NS_ERROR_MALFORMED_URI)) {
   415         throw new errorCtor("", errorMsg + " - malformed URI: " + uriStr);
   416       }
   417     }
   418     function mustValidateServer(server) {
   419       if (!server.url) {
   420         throw new errorCtor("", errorMsg + " - missing url");
   421       }
   422       let url = nicerNewURI(server.url, errorMsg);
   423       if (url.scheme in { turn:1, turns:1 }) {
   424         if (!server.username) {
   425           throw new errorCtor("", errorMsg + " - missing username: " + server.url);
   426         }
   427         if (!server.credential) {
   428           throw new errorCtor("", errorMsg + " - missing credential: " +
   429                               server.url);
   430         }
   431       }
   432       else if (!(url.scheme in { stun:1, stuns:1 })) {
   433         throw new errorCtor("", errorMsg + " - improper scheme: " + url.scheme);
   434       }
   435     }
   436     if (rtcConfig.iceServers) {
   437       let len = rtcConfig.iceServers.length;
   438       for (let i=0; i < len; i++) {
   439         mustValidateServer (rtcConfig.iceServers[i], errorMsg);
   440       }
   441     }
   442   },
   444   /**
   445    * MediaConstraints look like this:
   446    *
   447    * {
   448    *   mandatory: {"OfferToReceiveAudio": true, "OfferToReceiveVideo": true },
   449    *   optional: [{"VoiceActivityDetection": true}, {"FooBar": 10}]
   450    * }
   451    *
   452    * WebIDL normalizes the top structure for us, but the mandatory constraints
   453    * member comes in as a raw object so we can detect unknown constraints. We
   454    * compare its members against ones we support, and fail if not found.
   455    */
   456   _mustValidateConstraints: function(constraints, errorMsg) {
   457     if (constraints.mandatory) {
   458       let supported;
   459       try {
   460         // Passing the raw constraints.mandatory here validates its structure
   461         supported = this._observer.getSupportedConstraints(constraints.mandatory);
   462       } catch (e) {
   463         throw new this._win.DOMError("", errorMsg + " - " + e.message);
   464       }
   466       for (let constraint of Object.keys(constraints.mandatory)) {
   467         if (!(constraint in supported)) {
   468           throw new this._win.DOMError("",
   469               errorMsg + " - unknown mandatory constraint: " + constraint);
   470         }
   471       }
   472     }
   473     if (constraints.optional) {
   474       let len = constraints.optional.length;
   475       for (let i = 0; i < len; i++) {
   476         let constraints_per_entry = 0;
   477         for (let constraint in Object.keys(constraints.optional[i])) {
   478           if (constraints_per_entry) {
   479             throw new this._win.DOMError("", errorMsg +
   480                 " - optional constraint must be single key/value pair");
   481           }
   482           constraints_per_entry += 1;
   483         }
   484       }
   485     }
   486   },
   488   // Ideally, this should be of the form _checkState(state),
   489   // where the state is taken from an enumeration containing
   490   // the valid peer connection states defined in the WebRTC
   491   // spec. See Bug 831756.
   492   _checkClosed: function() {
   493     if (this._closed) {
   494       throw new this._win.DOMError("", "Peer connection is closed");
   495     }
   496   },
   498   dispatchEvent: function(event) {
   499     this.__DOM_IMPL__.dispatchEvent(event);
   500   },
   502   // Log error message to web console and window.onerror, if present.
   503   logErrorAndCallOnError: function(msg, file, line) {
   504     this.logMsg(msg, file, line, Ci.nsIScriptError.exceptionFlag);
   506     // Safely call onerror directly if present (necessary for testing)
   507     try {
   508       if (typeof this._win.onerror === "function") {
   509         this._win.onerror(msg, file, line);
   510       }
   511     } catch(e) {
   512       // If onerror itself throws, service it.
   513       try {
   514         this.logError(e.message, e.fileName, e.lineNumber);
   515       } catch(e) {}
   516     }
   517   },
   519   logError: function(msg, file, line) {
   520     this.logMsg(msg, file, line, Ci.nsIScriptError.errorFlag);
   521   },
   523   logWarning: function(msg, file, line) {
   524     this.logMsg(msg, file, line, Ci.nsIScriptError.warningFlag);
   525   },
   527   logMsg: function(msg, file, line, flag) {
   528     let scriptErrorClass = Cc["@mozilla.org/scripterror;1"];
   529     let scriptError = scriptErrorClass.createInstance(Ci.nsIScriptError);
   530     scriptError.initWithWindowID(msg, file, null, line, 0, flag,
   531                                  "content javascript", this._winID);
   532     let console = Cc["@mozilla.org/consoleservice;1"].
   533       getService(Ci.nsIConsoleService);
   534     console.logMessage(scriptError);
   535   },
   537   getEH: function(type) {
   538     return this.__DOM_IMPL__.getEventHandler(type);
   539   },
   541   setEH: function(type, handler) {
   542     this.__DOM_IMPL__.setEventHandler(type, handler);
   543   },
   545   makeGetterSetterEH: function(name) {
   546     Object.defineProperty(this, name,
   547                           {
   548                             get:function()  { return this.getEH(name); },
   549                             set:function(h) { return this.setEH(name, h); }
   550                           });
   551   },
   553   createOffer: function(onSuccess, onError, constraints) {
   554     if (!constraints) {
   555       constraints = {};
   556     }
   557     this._mustValidateConstraints(constraints, "createOffer passed invalid constraints");
   559     this._queueOrRun({
   560       func: this._createOffer,
   561       args: [onSuccess, onError, constraints],
   562       wait: true
   563     });
   564   },
   566   _createOffer: function(onSuccess, onError, constraints) {
   567     this._onCreateOfferSuccess = onSuccess;
   568     this._onCreateOfferFailure = onError;
   569     this._impl.createOffer(constraints);
   570   },
   572   _createAnswer: function(onSuccess, onError, constraints, provisional) {
   573     this._onCreateAnswerSuccess = onSuccess;
   574     this._onCreateAnswerFailure = onError;
   576     if (!this.remoteDescription) {
   578       this._observer.onCreateAnswerError(Ci.IPeerConnection.kInvalidState,
   579                                          "setRemoteDescription not called");
   580       return;
   581     }
   583     if (this.remoteDescription.type != "offer") {
   585       this._observer.onCreateAnswerError(Ci.IPeerConnection.kInvalidState,
   586                                          "No outstanding offer");
   587       return;
   588     }
   590     // TODO: Implement provisional answer.
   592     this._impl.createAnswer(constraints);
   593   },
   595   createAnswer: function(onSuccess, onError, constraints, provisional) {
   596     if (!constraints) {
   597       constraints = {};
   598     }
   600     this._mustValidateConstraints(constraints, "createAnswer passed invalid constraints");
   602     if (!provisional) {
   603       provisional = false;
   604     }
   606     this._queueOrRun({
   607       func: this._createAnswer,
   608       args: [onSuccess, onError, constraints, provisional],
   609       wait: true
   610     });
   611   },
   613   setLocalDescription: function(desc, onSuccess, onError) {
   614     let type;
   615     switch (desc.type) {
   616       case "offer":
   617         type = Ci.IPeerConnection.kActionOffer;
   618         break;
   619       case "answer":
   620         type = Ci.IPeerConnection.kActionAnswer;
   621         break;
   622       case "pranswer":
   623         throw new this._win.DOMError("", "pranswer not yet implemented");
   624       default:
   625         throw new this._win.DOMError("",
   626             "Invalid type " + desc.type + " provided to setLocalDescription");
   627     }
   629     this._queueOrRun({
   630       func: this._setLocalDescription,
   631       args: [type, desc.sdp, onSuccess, onError],
   632       wait: true,
   633       type: desc.type
   634     });
   635   },
   637   _setLocalDescription: function(type, sdp, onSuccess, onError) {
   638     this._onSetLocalDescriptionSuccess = onSuccess;
   639     this._onSetLocalDescriptionFailure = onError;
   640     this._impl.setLocalDescription(type, sdp);
   641   },
   643   setRemoteDescription: function(desc, onSuccess, onError) {
   644     let type;
   645     switch (desc.type) {
   646       case "offer":
   647         type = Ci.IPeerConnection.kActionOffer;
   648         break;
   649       case "answer":
   650         type = Ci.IPeerConnection.kActionAnswer;
   651         break;
   652       case "pranswer":
   653         throw new this._win.DOMError("", "pranswer not yet implemented");
   654       default:
   655         throw new this._win.DOMError("",
   656             "Invalid type " + desc.type + " provided to setRemoteDescription");
   657     }
   659     try {
   660       let processIdentity = this._processIdentity.bind(this);
   661       this._remoteIdp.verifyIdentityFromSDP(desc.sdp, processIdentity);
   662     } catch (e) {
   663       this.logWarning(e.message, e.fileName, e.lineNumber);
   664       // only happens if processing the SDP for identity doesn't work
   665       // let _setRemoteDescription do the error reporting
   666     }
   668     this._queueOrRun({
   669       func: this._setRemoteDescription,
   670       args: [type, desc.sdp, onSuccess, onError],
   671       wait: true,
   672       type: desc.type
   673     });
   674   },
   676   _processIdentity: function(message) {
   677     if (message) {
   678       this._peerIdentity = new this._win.RTCIdentityAssertion(
   679           this._remoteIdp.provider, message.identity);
   681       let args = { peerIdentity: this._peerIdentity };
   682       this.dispatchEvent(new this._win.Event("peeridentity"));
   683     }
   684   },
   686   _setRemoteDescription: function(type, sdp, onSuccess, onError) {
   687     this._onSetRemoteDescriptionSuccess = onSuccess;
   688     this._onSetRemoteDescriptionFailure = onError;
   689     this._impl.setRemoteDescription(type, sdp);
   690   },
   692   setIdentityProvider: function(provider, protocol, username) {
   693     this._checkClosed();
   694     this._localIdp.setIdentityProvider(provider, protocol, username);
   695   },
   697   _gotIdentityAssertion: function(assertion){
   698     let args = { assertion: assertion };
   699     let ev = new this._win.RTCPeerConnectionIdentityEvent("identityresult", args);
   700     this.dispatchEvent(ev);
   701   },
   703   getIdentityAssertion: function() {
   704     this._checkClosed();
   706     function gotAssertion(assertion) {
   707       if (assertion) {
   708         this._gotIdentityAssertion(assertion);
   709       }
   710     }
   712     this._localIdp.getIdentityAssertion(this._impl.fingerprint,
   713                                         gotAssertion.bind(this));
   714   },
   716   updateIce: function(config, constraints) {
   717     throw new this._win.DOMError("", "updateIce not yet implemented");
   718   },
   720   addIceCandidate: function(cand, onSuccess, onError) {
   721     if (!cand.candidate && !cand.sdpMLineIndex) {
   722       throw new this._win.DOMError("",
   723           "Invalid candidate passed to addIceCandidate!");
   724     }
   725     this._onAddIceCandidateSuccess = onSuccess || null;
   726     this._onAddIceCandidateError = onError || null;
   728     this._queueOrRun({ func: this._addIceCandidate, args: [cand], wait: true });
   729   },
   731   _addIceCandidate: function(cand) {
   732     this._impl.addIceCandidate(cand.candidate, cand.sdpMid || "",
   733                                (cand.sdpMLineIndex === null) ? 0 :
   734                                  cand.sdpMLineIndex + 1);
   735   },
   737   addStream: function(stream, constraints) {
   738     if (!constraints) {
   739       constraints = {};
   740     }
   741     this._mustValidateConstraints(constraints,
   742                                   "addStream passed invalid constraints");
   743     if (stream.currentTime === undefined) {
   744       throw new this._win.DOMError("", "Invalid stream passed to addStream!");
   745     }
   746     this._queueOrRun({ func: this._addStream,
   747                        args: [stream, constraints],
   748                        wait: false });
   749   },
   751   _addStream: function(stream, constraints) {
   752     this._impl.addStream(stream, constraints);
   753   },
   755   removeStream: function(stream) {
   756      // Bug 844295: Not implementing this functionality.
   757      throw new this._win.DOMError("", "removeStream not yet implemented");
   758   },
   760   getStreamById: function(id) {
   761     throw new this._win.DOMError("", "getStreamById not yet implemented");
   762   },
   764   close: function() {
   765     if (this._closed) {
   766       return;
   767     }
   768     this._queueOrRun({ func: this._close, args: [false], wait: false });
   769     this._closed = true;
   770     this.changeIceConnectionState("closed");
   771   },
   773   _close: function() {
   774     this._localIdp.close();
   775     this._remoteIdp.close();
   776     this._impl.close();
   777   },
   779   getLocalStreams: function() {
   780     this._checkClosed();
   781     return this._impl.getLocalStreams();
   782   },
   784   getRemoteStreams: function() {
   785     this._checkClosed();
   786     return this._impl.getRemoteStreams();
   787   },
   789   get localDescription() {
   790     this._checkClosed();
   791     let sdp = this._impl.localDescription;
   792     if (sdp.length == 0) {
   793       return null;
   794     }
   796     sdp = this._localIdp.wrapSdp(sdp);
   797     return new this._win.mozRTCSessionDescription({ type: this._localType,
   798                                                     sdp: sdp });
   799   },
   801   get remoteDescription() {
   802     this._checkClosed();
   803     let sdp = this._impl.remoteDescription;
   804     if (sdp.length == 0) {
   805       return null;
   806     }
   807     return new this._win.mozRTCSessionDescription({ type: this._remoteType,
   808                                                     sdp: sdp });
   809   },
   811   get peerIdentity() { return this._peerIdentity; },
   812   get iceGatheringState()  { return this._iceGatheringState; },
   813   get iceConnectionState() { return this._iceConnectionState; },
   815   get signalingState() {
   816     // checking for our local pc closed indication
   817     // before invoking the pc methods.
   818     if (this._closed) {
   819       return "closed";
   820     }
   821     return {
   822       "SignalingInvalid":            "",
   823       "SignalingStable":             "stable",
   824       "SignalingHaveLocalOffer":     "have-local-offer",
   825       "SignalingHaveRemoteOffer":    "have-remote-offer",
   826       "SignalingHaveLocalPranswer":  "have-local-pranswer",
   827       "SignalingHaveRemotePranswer": "have-remote-pranswer",
   828       "SignalingClosed":             "closed"
   829     }[this._impl.signalingState];
   830   },
   832   changeIceGatheringState: function(state) {
   833     this._iceGatheringState = state;
   834   },
   836   changeIceConnectionState: function(state) {
   837     this._iceConnectionState = state;
   838     this.dispatchEvent(new this._win.Event("iceconnectionstatechange"));
   839   },
   841   getStats: function(selector, onSuccess, onError) {
   842     this._queueOrRun({
   843       func: this._getStats,
   844       args: [selector, onSuccess, onError],
   845       wait: true
   846     });
   847   },
   849   _getStats: function(selector, onSuccess, onError) {
   850     this._onGetStatsSuccess = onSuccess;
   851     this._onGetStatsFailure = onError;
   853     this._impl.getStats(selector);
   854   },
   856   createDataChannel: function(label, dict) {
   857     this._checkClosed();
   858     if (dict == undefined) {
   859       dict = {};
   860     }
   861     if (dict.maxRetransmitNum != undefined) {
   862       dict.maxRetransmits = dict.maxRetransmitNum;
   863       this.logWarning("Deprecated RTCDataChannelInit dictionary entry maxRetransmitNum used!", null, 0);
   864     }
   865     if (dict.outOfOrderAllowed != undefined) {
   866       dict.ordered = !dict.outOfOrderAllowed; // the meaning is swapped with
   867                                               // the name change
   868       this.logWarning("Deprecated RTCDataChannelInit dictionary entry outOfOrderAllowed used!", null, 0);
   869     }
   870     if (dict.preset != undefined) {
   871       dict.negotiated = dict.preset;
   872       this.logWarning("Deprecated RTCDataChannelInit dictionary entry preset used!", null, 0);
   873     }
   874     if (dict.stream != undefined) {
   875       dict.id = dict.stream;
   876       this.logWarning("Deprecated RTCDataChannelInit dictionary entry stream used!", null, 0);
   877     }
   879     if (dict.maxRetransmitTime != undefined &&
   880         dict.maxRetransmits != undefined) {
   881       throw new this._win.DOMError("",
   882           "Both maxRetransmitTime and maxRetransmits cannot be provided");
   883     }
   884     let protocol;
   885     if (dict.protocol == undefined) {
   886       protocol = "";
   887     } else {
   888       protocol = dict.protocol;
   889     }
   891     // Must determine the type where we still know if entries are undefined.
   892     let type;
   893     if (dict.maxRetransmitTime != undefined) {
   894       type = Ci.IPeerConnection.kDataChannelPartialReliableTimed;
   895     } else if (dict.maxRetransmits != undefined) {
   896       type = Ci.IPeerConnection.kDataChannelPartialReliableRexmit;
   897     } else {
   898       type = Ci.IPeerConnection.kDataChannelReliable;
   899     }
   901     // Synchronous since it doesn't block.
   902     let channel = this._impl.createDataChannel(
   903       label, protocol, type, !dict.ordered, dict.maxRetransmitTime,
   904       dict.maxRetransmits, dict.negotiated ? true : false,
   905       dict.id != undefined ? dict.id : 0xFFFF
   906     );
   907     return channel;
   908   },
   910   connectDataConnection: function(localport, remoteport, numstreams) {
   911     if (numstreams == undefined || numstreams <= 0) {
   912       numstreams = 16;
   913     }
   914     this._queueOrRun({
   915       func: this._connectDataConnection,
   916       args: [localport, remoteport, numstreams],
   917       wait: false
   918     });
   919   },
   921   _connectDataConnection: function(localport, remoteport, numstreams) {
   922     this._impl.connectDataConnection(localport, remoteport, numstreams);
   923   }
   924 };
   926 function RTCError(code, message) {
   927   this.name = this.reasonName[Math.min(code, this.reasonName.length - 1)];
   928   this.message = (typeof message === "string")? message : this.name;
   929   this.__exposedProps__ = { name: "rw", message: "rw" };
   930 }
   931 RTCError.prototype = {
   932   // These strings must match those defined in the WebRTC spec.
   933   reasonName: [
   934     "NO_ERROR", // Should never happen -- only used for testing
   935     "INVALID_CONSTRAINTS_TYPE",
   936     "INVALID_CANDIDATE_TYPE",
   937     "INVALID_MEDIASTREAM_TRACK",
   938     "INVALID_STATE",
   939     "INVALID_SESSION_DESCRIPTION",
   940     "INCOMPATIBLE_SESSION_DESCRIPTION",
   941     "INCOMPATIBLE_CONSTRAINTS",
   942     "INCOMPATIBLE_MEDIASTREAMTRACK",
   943     "INTERNAL_ERROR"
   944   ]
   945 };
   947 // This is a separate object because we don't want to expose it to DOM.
   948 function PeerConnectionObserver() {
   949   this._dompc = null;
   950 }
   951 PeerConnectionObserver.prototype = {
   952   classDescription: "PeerConnectionObserver",
   953   classID: PC_OBS_CID,
   954   contractID: PC_OBS_CONTRACT,
   955   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
   956                                          Ci.nsIDOMGlobalPropertyInitializer]),
   957   init: function(win) { this._win = win; },
   959   __init: function(dompc) {
   960     this._dompc = dompc._innerObject;
   961   },
   963   dispatchEvent: function(event) {
   964     this._dompc.dispatchEvent(event);
   965   },
   967   callCB: function(callback, arg) {
   968     if (callback) {
   969       try {
   970         callback(arg);
   971       } catch(e) {
   972         // A content script (user-provided) callback threw an error. We don't
   973         // want this to take down peerconnection, but we still want the user
   974         // to see it, so we catch it, report it, and move on.
   975         this._dompc.logErrorAndCallOnError(e.message,
   976                                            e.fileName,
   977                                            e.lineNumber);
   978       }
   979     }
   980   },
   982   onCreateOfferSuccess: function(sdp) {
   983     let pc = this._dompc;
   984     let fp = pc._impl.fingerprint;
   985     pc._localIdp.appendIdentityToSDP(sdp, fp, function(sdp, assertion) {
   986       if (assertion) {
   987         pc._gotIdentityAssertion(assertion);
   988       }
   989       this.callCB(pc._onCreateOfferSuccess,
   990                   new pc._win.mozRTCSessionDescription({ type: "offer",
   991                                                          sdp: sdp }));
   992       pc._executeNext();
   993     }.bind(this));
   994   },
   996   onCreateOfferError: function(code, message) {
   997     this.callCB(this._dompc._onCreateOfferFailure, new RTCError(code, message));
   998     this._dompc._executeNext();
   999   },
  1001   onCreateAnswerSuccess: function(sdp) {
  1002     let pc = this._dompc;
  1003     let fp = pc._impl.fingerprint;
  1004     pc._localIdp.appendIdentityToSDP(sdp, fp, function(sdp, assertion) {
  1005       if (assertion) {
  1006         pc._gotIdentityAssertion(assertion);
  1008       this.callCB (pc._onCreateAnswerSuccess,
  1009                    new pc._win.mozRTCSessionDescription({ type: "answer",
  1010                                                           sdp: sdp }));
  1011       pc._executeNext();
  1012     }.bind(this));
  1013   },
  1015   onCreateAnswerError: function(code, message) {
  1016     this.callCB(this._dompc._onCreateAnswerFailure, new RTCError(code, message));
  1017     this._dompc._executeNext();
  1018   },
  1020   onSetLocalDescriptionSuccess: function() {
  1021     this._dompc._localType = this._dompc._pendingType;
  1022     this._dompc._pendingType = null;
  1023     this.callCB(this._dompc._onSetLocalDescriptionSuccess);
  1025     if (this._dompc._iceGatheringState == "complete") {
  1026         // If we are not trickling or we completed gathering prior
  1027         // to setLocal, then trigger a call of onicecandidate here.
  1028         this.foundIceCandidate(null);
  1031     this._dompc._executeNext();
  1032   },
  1034   onSetRemoteDescriptionSuccess: function() {
  1035     this._dompc._remoteType = this._dompc._pendingType;
  1036     this._dompc._pendingType = null;
  1037     this.callCB(this._dompc._onSetRemoteDescriptionSuccess);
  1038     this._dompc._executeNext();
  1039   },
  1041   onSetLocalDescriptionError: function(code, message) {
  1042     this._dompc._pendingType = null;
  1043     this.callCB(this._dompc._onSetLocalDescriptionFailure,
  1044                 new RTCError(code, message));
  1045     this._dompc._executeNext();
  1046   },
  1048   onSetRemoteDescriptionError: function(code, message) {
  1049     this._dompc._pendingType = null;
  1050     this.callCB(this._dompc._onSetRemoteDescriptionFailure,
  1051                 new RTCError(code, message));
  1052     this._dompc._executeNext();
  1053   },
  1055   onAddIceCandidateSuccess: function() {
  1056     this._dompc._pendingType = null;
  1057     this.callCB(this._dompc._onAddIceCandidateSuccess);
  1058     this._dompc._executeNext();
  1059   },
  1061   onAddIceCandidateError: function(code, message) {
  1062     this._dompc._pendingType = null;
  1063     this.callCB(this._dompc._onAddIceCandidateError, new RTCError(code, message));
  1064     this._dompc._executeNext();
  1065   },
  1067   onIceCandidate: function(level, mid, candidate) {
  1068     this.foundIceCandidate(new this._dompc._win.mozRTCIceCandidate(
  1070             candidate: candidate,
  1071             sdpMid: mid,
  1072             sdpMLineIndex: level - 1
  1074     ));
  1075   },
  1078   // This method is primarily responsible for updating iceConnectionState.
  1079   // This state is defined in the WebRTC specification as follows:
  1080   //
  1081   // iceConnectionState:
  1082   // -------------------
  1083   //   new           The ICE Agent is gathering addresses and/or waiting for
  1084   //                 remote candidates to be supplied.
  1085   //
  1086   //   checking      The ICE Agent has received remote candidates on at least
  1087   //                 one component, and is checking candidate pairs but has not
  1088   //                 yet found a connection. In addition to checking, it may
  1089   //                 also still be gathering.
  1090   //
  1091   //   connected     The ICE Agent has found a usable connection for all
  1092   //                 components but is still checking other candidate pairs to
  1093   //                 see if there is a better connection. It may also still be
  1094   //                 gathering.
  1095   //
  1096   //   completed     The ICE Agent has finished gathering and checking and found
  1097   //                 a connection for all components. Open issue: it is not
  1098   //                 clear how the non controlling ICE side knows it is in the
  1099   //                 state.
  1100   //
  1101   //   failed        The ICE Agent is finished checking all candidate pairs and
  1102   //                 failed to find a connection for at least one component.
  1103   //                 Connections may have been found for some components.
  1104   //
  1105   //   disconnected  Liveness checks have failed for one or more components.
  1106   //                 This is more aggressive than failed, and may trigger
  1107   //                 intermittently (and resolve itself without action) on a
  1108   //                 flaky network.
  1109   //
  1110   //   closed        The ICE Agent has shut down and is no longer responding to
  1111   //                 STUN requests.
  1113   handleIceConnectionStateChange: function(iceConnectionState) {
  1114     var histogram = Services.telemetry.getHistogramById("WEBRTC_ICE_SUCCESS_RATE");
  1116     if (iceConnectionState === 'failed') {
  1117       histogram.add(false);
  1118       this._dompc.logError("ICE failed, see about:webrtc for more details", null, 0);
  1120     if (this._dompc.iceConnectionState === 'checking' &&
  1121         (iceConnectionState === 'completed' ||
  1122          iceConnectionState === 'connected')) {
  1123           histogram.add(true);
  1125     this._dompc.changeIceConnectionState(iceConnectionState);
  1126   },
  1128   // This method is responsible for updating iceGatheringState. This
  1129   // state is defined in the WebRTC specification as follows:
  1130   //
  1131   // iceGatheringState:
  1132   // ------------------
  1133   //   new        The object was just created, and no networking has occurred
  1134   //              yet.
  1135   //
  1136   //   gathering  The ICE engine is in the process of gathering candidates for
  1137   //              this RTCPeerConnection.
  1138   //
  1139   //   complete   The ICE engine has completed gathering. Events such as adding
  1140   //              a new interface or a new TURN server will cause the state to
  1141   //              go back to gathering.
  1142   //
  1143   handleIceGatheringStateChange: function(gatheringState) {
  1144     this._dompc.changeIceGatheringState(gatheringState);
  1146     if (gatheringState === "complete") {
  1147       if (!this._dompc._trickleIce) {
  1148         // If we are not trickling, then the queue is in a pending state
  1149         // waiting for ICE gathering and executeNext frees it
  1150         this._dompc._executeNext();
  1152       else if (this._dompc.localDescription) {
  1153         // If we are trickling but we have already done setLocal,
  1154         // then we need to send a final foundIceCandidate(null) to indicate
  1155         // that we are done gathering.
  1156         this.foundIceCandidate(null);
  1159   },
  1161   onStateChange: function(state) {
  1162     switch (state) {
  1163       case "SignalingState":
  1164         this.callCB(this._dompc.onsignalingstatechange,
  1165                     this._dompc.signalingState);
  1166         break;
  1168       case "IceConnectionState":
  1169         this.handleIceConnectionStateChange(this._dompc._pc.iceConnectionState);
  1170         break;
  1172       case "IceGatheringState":
  1173         this.handleIceGatheringStateChange(this._dompc._pc.iceGatheringState);
  1174         break;
  1176       case "SdpState":
  1177         // No-op
  1178         break;
  1180       case "ReadyState":
  1181         // No-op
  1182         break;
  1184       case "SipccState":
  1185         // No-op
  1186         break;
  1188       default:
  1189         this._dompc.logWarning("Unhandled state type: " + state, null, 0);
  1190         break;
  1192   },
  1194   onGetStatsSuccess: function(dict) {
  1195     let chromeobj = new RTCStatsReport(this._dompc._win, dict);
  1196     let webidlobj = this._dompc._win.RTCStatsReport._create(this._dompc._win,
  1197                                                             chromeobj);
  1198     chromeobj.makeStatsPublic();
  1199     this.callCB(this._dompc._onGetStatsSuccess, webidlobj);
  1200     this._dompc._executeNext();
  1201   },
  1203   onGetStatsError: function(code, message) {
  1204     this.callCB(this._dompc._onGetStatsFailure, new RTCError(code, message));
  1205     this._dompc._executeNext();
  1206   },
  1208   onAddStream: function(stream) {
  1209     this.dispatchEvent(new this._dompc._win.MediaStreamEvent("addstream",
  1210                                                              { stream: stream }));
  1211   },
  1213   onRemoveStream: function(stream, type) {
  1214     this.dispatchEvent(new this._dompc._win.MediaStreamEvent("removestream",
  1215                                                              { stream: stream }));
  1216   },
  1218   foundIceCandidate: function(cand) {
  1219     this.dispatchEvent(new this._dompc._win.RTCPeerConnectionIceEvent("icecandidate",
  1220                                                                       { candidate: cand } ));
  1221   },
  1223   notifyDataChannel: function(channel) {
  1224     this.dispatchEvent(new this._dompc._win.RTCDataChannelEvent("datachannel",
  1225                                                                 { channel: channel }));
  1226   },
  1228   notifyConnection: function() {
  1229     this.dispatchEvent(new this._dompc._win.Event("connection"));
  1230   },
  1232   notifyClosedConnection: function() {
  1233     this.dispatchEvent(new this._dompc._win.Event("closedconnection"));
  1234   },
  1236   getSupportedConstraints: function(dict) {
  1237     return dict;
  1238   },
  1239 };
  1241 this.NSGetFactory = XPCOMUtils.generateNSGetFactory(
  1242   [GlobalPCList,
  1243    RTCIceCandidate,
  1244    RTCSessionDescription,
  1245    RTCPeerConnection,
  1246    RTCStatsReport,
  1247    RTCIdentityAssertion,
  1248    PeerConnectionObserver]
  1249 );

mercurial