PeerConnection.js 58 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693
  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/. */
  5. "use strict";
  6. const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
  7. Cu.import("resource://gre/modules/Services.jsm");
  8. Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  9. XPCOMUtils.defineLazyModuleGetter(this, "PeerConnectionIdp",
  10. "resource://gre/modules/media/PeerConnectionIdp.jsm");
  11. XPCOMUtils.defineLazyModuleGetter(this, "convertToRTCStatsReport",
  12. "resource://gre/modules/media/RTCStatsReport.jsm");
  13. const PC_CONTRACT = "@mozilla.org/dom/peerconnection;1";
  14. const PC_OBS_CONTRACT = "@mozilla.org/dom/peerconnectionobserver;1";
  15. const PC_ICE_CONTRACT = "@mozilla.org/dom/rtcicecandidate;1";
  16. const PC_SESSION_CONTRACT = "@mozilla.org/dom/rtcsessiondescription;1";
  17. const PC_MANAGER_CONTRACT = "@mozilla.org/dom/peerconnectionmanager;1";
  18. const PC_STATS_CONTRACT = "@mozilla.org/dom/rtcstatsreport;1";
  19. const PC_STATIC_CONTRACT = "@mozilla.org/dom/peerconnectionstatic;1";
  20. const PC_SENDER_CONTRACT = "@mozilla.org/dom/rtpsender;1";
  21. const PC_RECEIVER_CONTRACT = "@mozilla.org/dom/rtpreceiver;1";
  22. const PC_COREQUEST_CONTRACT = "@mozilla.org/dom/createofferrequest;1";
  23. const PC_DTMF_SENDER_CONTRACT = "@mozilla.org/dom/rtcdtmfsender;1";
  24. const PC_CID = Components.ID("{bdc2e533-b308-4708-ac8e-a8bfade6d851}");
  25. const PC_OBS_CID = Components.ID("{d1748d4c-7f6a-4dc5-add6-d55b7678537e}");
  26. const PC_ICE_CID = Components.ID("{02b9970c-433d-4cc2-923d-f7028ac66073}");
  27. const PC_SESSION_CID = Components.ID("{1775081b-b62d-4954-8ffe-a067bbf508a7}");
  28. const PC_MANAGER_CID = Components.ID("{7293e901-2be3-4c02-b4bd-cbef6fc24f78}");
  29. const PC_STATS_CID = Components.ID("{7fe6e18b-0da3-4056-bf3b-440ef3809e06}");
  30. const PC_STATIC_CID = Components.ID("{0fb47c47-a205-4583-a9fc-cbadf8c95880}");
  31. const PC_SENDER_CID = Components.ID("{4fff5d46-d827-4cd4-a970-8fd53977440e}");
  32. const PC_RECEIVER_CID = Components.ID("{d974b814-8fde-411c-8c45-b86791b81030}");
  33. const PC_COREQUEST_CID = Components.ID("{74b2122d-65a8-4824-aa9e-3d664cb75dc2}");
  34. const PC_DTMF_SENDER_CID = Components.ID("{3610C242-654E-11E6-8EC0-6D1BE389A607}");
  35. // Global list of PeerConnection objects, so they can be cleaned up when
  36. // a page is torn down. (Maps inner window ID to an array of PC objects).
  37. function GlobalPCList() {
  38. this._list = {};
  39. this._networkdown = false; // XXX Need to query current state somehow
  40. this._lifecycleobservers = {};
  41. this._nextId = 1;
  42. Services.obs.addObserver(this, "inner-window-destroyed", true);
  43. Services.obs.addObserver(this, "profile-change-net-teardown", true);
  44. Services.obs.addObserver(this, "network:offline-about-to-go-offline", true);
  45. Services.obs.addObserver(this, "network:offline-status-changed", true);
  46. Services.obs.addObserver(this, "gmp-plugin-crash", true);
  47. Services.obs.addObserver(this, "PeerConnection:response:allow", true);
  48. Services.obs.addObserver(this, "PeerConnection:response:deny", true);
  49. if (Cc["@mozilla.org/childprocessmessagemanager;1"]) {
  50. let mm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
  51. mm.addMessageListener("gmp-plugin-crash", this);
  52. }
  53. }
  54. GlobalPCList.prototype = {
  55. QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
  56. Ci.nsIMessageListener,
  57. Ci.nsISupportsWeakReference,
  58. Ci.IPeerConnectionManager]),
  59. classID: PC_MANAGER_CID,
  60. _xpcom_factory: {
  61. createInstance: function(outer, iid) {
  62. if (outer) {
  63. throw Cr.NS_ERROR_NO_AGGREGATION;
  64. }
  65. return _globalPCList.QueryInterface(iid);
  66. }
  67. },
  68. notifyLifecycleObservers: function(pc, type) {
  69. for (var key of Object.keys(this._lifecycleobservers)) {
  70. this._lifecycleobservers[key](pc, pc._winID, type);
  71. }
  72. },
  73. addPC: function(pc) {
  74. let winID = pc._winID;
  75. if (this._list[winID]) {
  76. this._list[winID].push(Cu.getWeakReference(pc));
  77. } else {
  78. this._list[winID] = [Cu.getWeakReference(pc)];
  79. }
  80. pc._globalPCListId = this._nextId++;
  81. this.removeNullRefs(winID);
  82. },
  83. findPC: function(globalPCListId) {
  84. for (let winId in this._list) {
  85. if (this._list.hasOwnProperty(winId)) {
  86. for (let pcref of this._list[winId]) {
  87. let pc = pcref.get();
  88. if (pc && pc._globalPCListId == globalPCListId) {
  89. return pc;
  90. }
  91. }
  92. }
  93. }
  94. },
  95. removeNullRefs: function(winID) {
  96. if (this._list[winID] === undefined) {
  97. return;
  98. }
  99. this._list[winID] = this._list[winID].filter(
  100. function (e,i,a) { return e.get() !== null; });
  101. if (this._list[winID].length === 0) {
  102. delete this._list[winID];
  103. }
  104. },
  105. hasActivePeerConnection: function(winID) {
  106. this.removeNullRefs(winID);
  107. return this._list[winID] ? true : false;
  108. },
  109. handleGMPCrash: function(data) {
  110. let broadcastPluginCrash = function(list, winID, pluginID, pluginName) {
  111. if (list.hasOwnProperty(winID)) {
  112. list[winID].forEach(function(pcref) {
  113. let pc = pcref.get();
  114. if (pc) {
  115. pc._pc.pluginCrash(pluginID, pluginName);
  116. }
  117. });
  118. }
  119. };
  120. // a plugin crashed; if it's associated with any of our PCs, fire an
  121. // event to the DOM window
  122. for (let winId in this._list) {
  123. broadcastPluginCrash(this._list, winId, data.pluginID, data.pluginName);
  124. }
  125. },
  126. receiveMessage: function(message) {
  127. if (message.name == "gmp-plugin-crash") {
  128. this.handleGMPCrash(message.data);
  129. }
  130. },
  131. observe: function(subject, topic, data) {
  132. let cleanupPcRef = function(pcref) {
  133. let pc = pcref.get();
  134. if (pc) {
  135. pc._pc.close();
  136. delete pc._observer;
  137. pc._pc = null;
  138. }
  139. };
  140. let cleanupWinId = function(list, winID) {
  141. if (list.hasOwnProperty(winID)) {
  142. list[winID].forEach(cleanupPcRef);
  143. delete list[winID];
  144. }
  145. };
  146. if (topic == "inner-window-destroyed") {
  147. let winID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
  148. cleanupWinId(this._list, winID);
  149. if (this._lifecycleobservers.hasOwnProperty(winID)) {
  150. delete this._lifecycleobservers[winID];
  151. }
  152. } else if (topic == "profile-change-net-teardown" ||
  153. topic == "network:offline-about-to-go-offline") {
  154. // Delete all peerconnections on shutdown - mostly synchronously (we
  155. // need them to be done deleting transports and streams before we
  156. // return)! All socket operations must be queued to STS thread
  157. // before we return to here.
  158. // Also kill them if "Work Offline" is selected - more can be created
  159. // while offline, but attempts to connect them should fail.
  160. for (let winId in this._list) {
  161. cleanupWinId(this._list, winId);
  162. }
  163. this._networkdown = true;
  164. }
  165. else if (topic == "network:offline-status-changed") {
  166. if (data == "offline") {
  167. // this._list shold be empty here
  168. this._networkdown = true;
  169. } else if (data == "online") {
  170. this._networkdown = false;
  171. }
  172. } else if (topic == "gmp-plugin-crash") {
  173. if (subject instanceof Ci.nsIWritablePropertyBag2) {
  174. let pluginID = subject.getPropertyAsUint32("pluginID");
  175. let pluginName = subject.getPropertyAsAString("pluginName");
  176. let data = { pluginID, pluginName };
  177. this.handleGMPCrash(data);
  178. }
  179. } else if (topic == "PeerConnection:response:allow" ||
  180. topic == "PeerConnection:response:deny") {
  181. var pc = this.findPC(data);
  182. if (pc) {
  183. if (topic == "PeerConnection:response:allow") {
  184. pc._settlePermission.allow();
  185. } else {
  186. let err = new pc._win.DOMException("The request is not allowed by " +
  187. "the user agent or the platform in the current context.",
  188. "NotAllowedError");
  189. pc._settlePermission.deny(err);
  190. }
  191. }
  192. }
  193. },
  194. _registerPeerConnectionLifecycleCallback: function(winID, cb) {
  195. this._lifecycleobservers[winID] = cb;
  196. },
  197. };
  198. var _globalPCList = new GlobalPCList();
  199. function RTCIceCandidate() {
  200. this.candidate = this.sdpMid = this.sdpMLineIndex = null;
  201. }
  202. RTCIceCandidate.prototype = {
  203. classDescription: "RTCIceCandidate",
  204. classID: PC_ICE_CID,
  205. contractID: PC_ICE_CONTRACT,
  206. QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
  207. Ci.nsIDOMGlobalPropertyInitializer]),
  208. init: function(win) { this._win = win; },
  209. __init: function(dict) {
  210. this.candidate = dict.candidate;
  211. this.sdpMid = dict.sdpMid;
  212. this.sdpMLineIndex = ("sdpMLineIndex" in dict)? dict.sdpMLineIndex : null;
  213. }
  214. };
  215. function RTCSessionDescription() {
  216. this.type = this.sdp = null;
  217. }
  218. RTCSessionDescription.prototype = {
  219. classDescription: "RTCSessionDescription",
  220. classID: PC_SESSION_CID,
  221. contractID: PC_SESSION_CONTRACT,
  222. QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
  223. Ci.nsIDOMGlobalPropertyInitializer]),
  224. init: function(win) { this._win = win; },
  225. __init: function(dict) {
  226. this.type = dict.type;
  227. this.sdp = dict.sdp;
  228. }
  229. };
  230. function RTCStatsReport(win, dict) {
  231. this._win = win;
  232. this._pcid = dict.pcid;
  233. this._report = convertToRTCStatsReport(dict);
  234. }
  235. RTCStatsReport.prototype = {
  236. classDescription: "RTCStatsReport",
  237. classID: PC_STATS_CID,
  238. contractID: PC_STATS_CONTRACT,
  239. QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
  240. setInternal: function(aKey, aObj) {
  241. return this.__DOM_IMPL__.__set(aKey, aObj);
  242. },
  243. // TODO: Remove legacy API eventually
  244. //
  245. // Since maplike is recent, we still also make the stats available as legacy
  246. // enumerable read-only properties directly on our content-facing object.
  247. // Must be called after our webidl sandwich is made.
  248. makeStatsPublic: function(warnNullable) {
  249. let legacyProps = {};
  250. for (let key in this._report) {
  251. let value = Cu.cloneInto(this._report[key], this._win);
  252. this.setInternal(key, value);
  253. legacyProps[key] = {
  254. enumerable: true, configurable: false,
  255. get: Cu.exportFunction(function() {
  256. if (warnNullable.warn) {
  257. warnNullable.warn();
  258. warnNullable.warn = null;
  259. }
  260. return value;
  261. }, this.__DOM_IMPL__.wrappedJSObject)
  262. };
  263. }
  264. Object.defineProperties(this.__DOM_IMPL__.wrappedJSObject, legacyProps);
  265. },
  266. get mozPcid() { return this._pcid; }
  267. };
  268. function RTCPeerConnection() {
  269. this._senders = [];
  270. this._receivers = [];
  271. this._pc = null;
  272. this._observer = null;
  273. this._closed = false;
  274. this._onCreateOfferSuccess = null;
  275. this._onCreateOfferFailure = null;
  276. this._onCreateAnswerSuccess = null;
  277. this._onCreateAnswerFailure = null;
  278. this._onGetStatsSuccess = null;
  279. this._onGetStatsFailure = null;
  280. this._onReplaceTrackSender = null;
  281. this._onReplaceTrackWithTrack = null;
  282. this._onReplaceTrackSuccess = null;
  283. this._onReplaceTrackFailure = null;
  284. this._localType = null;
  285. this._remoteType = null;
  286. // http://rtcweb-wg.github.io/jsep/#rfc.section.4.1.9
  287. // canTrickle == null means unknown; when a remote description is received it
  288. // is set to true or false based on the presence of the "trickle" ice-option
  289. this._canTrickle = null;
  290. // States
  291. this._iceGatheringState = this._iceConnectionState = "new";
  292. }
  293. RTCPeerConnection.prototype = {
  294. classDescription: "RTCPeerConnection",
  295. classID: PC_CID,
  296. contractID: PC_CONTRACT,
  297. QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
  298. Ci.nsIDOMGlobalPropertyInitializer]),
  299. init: function(win) { this._win = win; },
  300. __init: function(rtcConfig) {
  301. this._winID = this._win.QueryInterface(Ci.nsIInterfaceRequestor)
  302. .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
  303. // TODO: Update this code once we support pc.setConfiguration, to track
  304. // setting from content independently from pref (Bug 1181768).
  305. if (rtcConfig.iceTransportPolicy == "all" &&
  306. Services.prefs.getBoolPref("media.peerconnection.ice.relay_only")) {
  307. rtcConfig.iceTransportPolicy = "relay";
  308. }
  309. this._config = Object.assign({}, rtcConfig);
  310. if (!rtcConfig.iceServers ||
  311. !Services.prefs.getBoolPref("media.peerconnection.use_document_iceservers")) {
  312. try {
  313. rtcConfig.iceServers =
  314. JSON.parse(Services.prefs.getCharPref("media.peerconnection.default_iceservers") || "[]");
  315. } catch (e) {
  316. this.logWarning(
  317. "Ignoring invalid media.peerconnection.default_iceservers in about:config");
  318. rtcConfig.iceServers = [];
  319. }
  320. try {
  321. this._mustValidateRTCConfiguration(rtcConfig,
  322. "Ignoring invalid media.peerconnection.default_iceservers in about:config");
  323. } catch (e) {
  324. this.logWarning(e.message);
  325. rtcConfig.iceServers = [];
  326. }
  327. } else {
  328. // This gets executed in the typical case when iceServers
  329. // are passed in through the web page.
  330. this._mustValidateRTCConfiguration(rtcConfig,
  331. "RTCPeerConnection constructor passed invalid RTCConfiguration");
  332. }
  333. var principal = Cu.getWebIDLCallerPrincipal();
  334. this._isChrome = Services.scriptSecurityManager.isSystemPrincipal(principal);
  335. if (_globalPCList._networkdown) {
  336. throw new this._win.DOMException(
  337. "Can't create RTCPeerConnections when the network is down",
  338. "InvalidStateError");
  339. }
  340. this.makeGetterSetterEH("ontrack");
  341. this.makeLegacyGetterSetterEH("onaddstream", "Use peerConnection.ontrack instead.");
  342. this.makeLegacyGetterSetterEH("onaddtrack", "Use peerConnection.ontrack instead.");
  343. this.makeGetterSetterEH("onicecandidate");
  344. this.makeGetterSetterEH("onnegotiationneeded");
  345. this.makeGetterSetterEH("onsignalingstatechange");
  346. this.makeGetterSetterEH("onremovestream");
  347. this.makeGetterSetterEH("ondatachannel");
  348. this.makeGetterSetterEH("oniceconnectionstatechange");
  349. this.makeGetterSetterEH("onidentityresult");
  350. this.makeGetterSetterEH("onpeeridentity");
  351. this.makeGetterSetterEH("onidpassertionerror");
  352. this.makeGetterSetterEH("onidpvalidationerror");
  353. this._pc = new this._win.PeerConnectionImpl();
  354. this._operationsChain = this._win.Promise.resolve();
  355. this.__DOM_IMPL__._innerObject = this;
  356. this._observer = new this._win.PeerConnectionObserver(this.__DOM_IMPL__);
  357. var location = "" + this._win.location;
  358. // Warn just once per PeerConnection about deprecated getStats usage.
  359. this._warnDeprecatedStatsAccessNullable = { warn: () =>
  360. this.logWarning("non-maplike pc.getStats access is deprecated! " +
  361. "See http://w3c.github.io/webrtc-pc/#example for usage.") };
  362. // Add a reference to the PeerConnection to global list (before init).
  363. _globalPCList.addPC(this);
  364. this._impl.initialize(this._observer, this._win, rtcConfig,
  365. Services.tm.currentThread);
  366. this._initCertificate(rtcConfig.certificates);
  367. this._initIdp();
  368. _globalPCList.notifyLifecycleObservers(this, "initialized");
  369. },
  370. get _impl() {
  371. if (!this._pc) {
  372. throw new this._win.DOMException(
  373. "RTCPeerConnection is gone (did you enter Offline mode?)",
  374. "InvalidStateError");
  375. }
  376. return this._pc;
  377. },
  378. getConfiguration: function() {
  379. return this._config;
  380. },
  381. _initCertificate: function(certificates) {
  382. let certPromise;
  383. if (certificates && certificates.length > 0) {
  384. if (certificates.length > 1) {
  385. throw new this._win.DOMException(
  386. "RTCPeerConnection does not currently support multiple certificates",
  387. "NotSupportedError");
  388. }
  389. let cert = certificates.find(c => c.expires > Date.now());
  390. if (!cert) {
  391. throw new this._win.DOMException(
  392. "Unable to create RTCPeerConnection with an expired certificate",
  393. "InvalidParameterError");
  394. }
  395. certPromise = Promise.resolve(cert);
  396. } else {
  397. certPromise = this._win.RTCPeerConnection.generateCertificate({
  398. name: "ECDSA", namedCurve: "P-256"
  399. });
  400. }
  401. this._certificateReady = certPromise
  402. .then(cert => this._impl.certificate = cert);
  403. },
  404. _initIdp: function() {
  405. this._peerIdentity = new this._win.Promise((resolve, reject) => {
  406. this._resolvePeerIdentity = resolve;
  407. this._rejectPeerIdentity = reject;
  408. });
  409. this._lastIdentityValidation = this._win.Promise.resolve();
  410. let prefName = "media.peerconnection.identity.timeout";
  411. let idpTimeout = Services.prefs.getIntPref(prefName);
  412. this._localIdp = new PeerConnectionIdp(this._win, idpTimeout);
  413. this._remoteIdp = new PeerConnectionIdp(this._win, idpTimeout);
  414. },
  415. // Add a function to the internal operations chain.
  416. _chain: function(func) {
  417. this._checkClosed(); // out here DOMException line-numbers work.
  418. let p = this._operationsChain.then(() => {
  419. // Don't _checkClosed() inside the chain, because it throws, and spec
  420. // behavior as of this writing is to NOT reject outstanding promises on
  421. // close. This is what happens most of the time anyways, as the c++ code
  422. // stops calling us once closed, hanging the chain. However, c++ may
  423. // already have queued tasks on us, so if we're one of those then sit back.
  424. if (!this._closed) {
  425. return func();
  426. }
  427. });
  428. // don't propagate errors in the operations chain (this is a fork of p).
  429. this._operationsChain = p.catch(() => {});
  430. return p;
  431. },
  432. // This wrapper helps implement legacy callbacks in a manner that produces
  433. // correct line-numbers in errors, provided that methods validate their inputs
  434. // before putting themselves on the pc's operations chain.
  435. //
  436. // It also serves as guard against settling promises past close().
  437. _legacyCatchAndCloseGuard: function(onSuccess, onError, func) {
  438. if (!onSuccess) {
  439. return func().then(v => (this._closed ? new Promise(() => {}) : v),
  440. e => (this._closed ? new Promise(() => {}) : Promise.reject(e)));
  441. }
  442. try {
  443. return func().then(this._wrapLegacyCallback(onSuccess),
  444. this._wrapLegacyCallback(onError));
  445. } catch (e) {
  446. this._wrapLegacyCallback(onError)(e);
  447. return this._win.Promise.resolve(); // avoid webidl TypeError
  448. }
  449. },
  450. _wrapLegacyCallback: function(func) {
  451. return result => {
  452. try {
  453. func && func(result);
  454. } catch (e) {
  455. this.logErrorAndCallOnError(e);
  456. }
  457. };
  458. },
  459. // This implements the fairly common "Queue a task" logic
  460. async _queueTaskWithClosedCheck(func) {
  461. return new Promise(resolve => {
  462. Services.tm.mainThread.dispatch({ run() {
  463. if (!this._closed) {
  464. func();
  465. resolve();
  466. }
  467. }}, Ci.nsIThread.DISPATCH_NORMAL);
  468. });
  469. },
  470. /**
  471. * An RTCConfiguration may look like this:
  472. *
  473. * { "iceServers": [ { urls: "stun:stun.example.org", },
  474. * { url: "stun:stun.example.org", }, // deprecated version
  475. * { urls: ["turn:turn1.x.org", "turn:turn2.x.org"],
  476. * username:"jib", credential:"mypass"} ] }
  477. *
  478. * This function normalizes the structure of the input for rtcConfig.iceServers for us,
  479. * so we test well-formed stun/turn urls before passing along to C++.
  480. * msg - Error message to detail which array-entry failed, if any.
  481. */
  482. _mustValidateRTCConfiguration: function(rtcConfig, msg) {
  483. // Normalize iceServers input
  484. rtcConfig.iceServers.forEach(server => {
  485. if (typeof server.urls === "string") {
  486. server.urls = [server.urls];
  487. } else if (!server.urls && server.url) {
  488. // TODO: Remove support for legacy iceServer.url eventually (Bug 1116766)
  489. server.urls = [server.url];
  490. this.logWarning("RTCIceServer.url is deprecated! Use urls instead.");
  491. }
  492. });
  493. let ios = Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService);
  494. let nicerNewURI = uriStr => {
  495. try {
  496. return ios.newURI(uriStr, null, null);
  497. } catch (e if (e.result == Cr.NS_ERROR_MALFORMED_URI)) {
  498. throw new this._win.DOMException(msg + " - malformed URI: " + uriStr,
  499. "SyntaxError");
  500. }
  501. };
  502. rtcConfig.iceServers.forEach(server => {
  503. if (!server.urls) {
  504. throw new this._win.DOMException(msg + " - missing urls", "InvalidAccessError");
  505. }
  506. server.urls.forEach(urlStr => {
  507. let url = nicerNewURI(urlStr);
  508. if (url.scheme in { turn:1, turns:1 }) {
  509. if (server.username == undefined) {
  510. throw new this._win.DOMException(msg + " - missing username: " + urlStr,
  511. "InvalidAccessError");
  512. }
  513. if (server.credential == undefined) {
  514. throw new this._win.DOMException(msg + " - missing credential: " + urlStr,
  515. "InvalidAccessError");
  516. }
  517. if (server.credentialType != "password") {
  518. this.logWarning("RTCConfiguration TURN credentialType \""+
  519. server.credentialType +
  520. "\" is not yet implemented. Treating as password."+
  521. " https://bugzil.la/1247616");
  522. }
  523. }
  524. else if (!(url.scheme in { stun:1, stuns:1 })) {
  525. throw new this._win.DOMException(msg + " - improper scheme: " + url.scheme,
  526. "SyntaxError");
  527. }
  528. if (url.scheme in { stuns:1, turns:1 }) {
  529. this.logWarning(url.scheme.toUpperCase() + " is not yet supported.");
  530. }
  531. });
  532. });
  533. },
  534. // Ideally, this should be of the form _checkState(state),
  535. // where the state is taken from an enumeration containing
  536. // the valid peer connection states defined in the WebRTC
  537. // spec. See Bug 831756.
  538. _checkClosed: function() {
  539. if (this._closed) {
  540. throw new this._win.DOMException("Peer connection is closed",
  541. "InvalidStateError");
  542. }
  543. },
  544. dispatchEvent: function(event) {
  545. // PC can close while events are firing if there is an async dispatch
  546. // in c++ land. But let through "closed" signaling and ice connection events.
  547. if (!this._closed || this._inClose) {
  548. this.__DOM_IMPL__.dispatchEvent(event);
  549. }
  550. },
  551. // Log error message to web console and window.onerror, if present.
  552. logErrorAndCallOnError: function(e) {
  553. this.logMsg(e.message, e.fileName, e.lineNumber, Ci.nsIScriptError.exceptionFlag);
  554. // Safely call onerror directly if present (necessary for testing)
  555. try {
  556. if (typeof this._win.onerror === "function") {
  557. this._win.onerror(e.message, e.fileName, e.lineNumber);
  558. }
  559. } catch(e) {
  560. // If onerror itself throws, service it.
  561. try {
  562. this.logMsg(e.message, e.fileName, e.lineNumber, Ci.nsIScriptError.errorFlag);
  563. } catch(e) {}
  564. }
  565. },
  566. logError: function(msg) {
  567. this.logStackMsg(msg, Ci.nsIScriptError.errorFlag);
  568. },
  569. logWarning: function(msg) {
  570. this.logStackMsg(msg, Ci.nsIScriptError.warningFlag);
  571. },
  572. logStackMsg: function(msg, flag) {
  573. let err = this._win.Error();
  574. this.logMsg(msg, err.fileName, err.lineNumber, flag);
  575. },
  576. logMsg: function(msg, file, line, flag) {
  577. let scriptErrorClass = Cc["@mozilla.org/scripterror;1"];
  578. let scriptError = scriptErrorClass.createInstance(Ci.nsIScriptError);
  579. scriptError.initWithWindowID(msg, file, null, line, 0, flag,
  580. "content javascript", this._winID);
  581. let console = Cc["@mozilla.org/consoleservice;1"].
  582. getService(Ci.nsIConsoleService);
  583. console.logMessage(scriptError);
  584. },
  585. getEH: function(type) {
  586. return this.__DOM_IMPL__.getEventHandler(type);
  587. },
  588. setEH: function(type, handler) {
  589. this.__DOM_IMPL__.setEventHandler(type, handler);
  590. },
  591. makeGetterSetterEH: function(name) {
  592. Object.defineProperty(this, name,
  593. {
  594. get:function() { return this.getEH(name); },
  595. set:function(h) { return this.setEH(name, h); }
  596. });
  597. },
  598. makeLegacyGetterSetterEH: function(name, msg) {
  599. Object.defineProperty(this, name,
  600. {
  601. get:function() { return this.getEH(name); },
  602. set:function(h) {
  603. this.logWarning(name + " is deprecated! " + msg);
  604. return this.setEH(name, h);
  605. }
  606. });
  607. },
  608. _addIdentityAssertion: function(sdpPromise, origin) {
  609. if (!this._localIdp.enabled) {
  610. return sdpPromise;
  611. }
  612. return Promise.all([
  613. this._certificateReady
  614. .then(() => this._localIdp.getIdentityAssertion(this._impl.fingerprint,
  615. origin)),
  616. sdpPromise
  617. ]).then(([,sdp]) => this._localIdp.addIdentityAttribute(sdp));
  618. },
  619. createOffer: function(optionsOrOnSuccess, onError, options) {
  620. // This entry-point handles both new and legacy call sig. Decipher which one
  621. let onSuccess;
  622. if (typeof optionsOrOnSuccess == "function") {
  623. onSuccess = optionsOrOnSuccess;
  624. } else {
  625. options = optionsOrOnSuccess;
  626. }
  627. return this._legacyCatchAndCloseGuard(onSuccess, onError, () => {
  628. // TODO: Remove error on constraint-like RTCOptions next cycle (1197021).
  629. // Note that webidl bindings make o.mandatory implicit but not o.optional.
  630. function convertLegacyOptions(o) {
  631. // Detect (mandatory OR optional) AND no other top-level members.
  632. let lcy = ((o.mandatory && Object.keys(o.mandatory).length) || o.optional) &&
  633. Object.keys(o).length == (o.mandatory? 1 : 0) + (o.optional? 1 : 0);
  634. if (!lcy) {
  635. return false;
  636. }
  637. let old = o.mandatory || {};
  638. if (o.mandatory) {
  639. delete o.mandatory;
  640. }
  641. if (o.optional) {
  642. o.optional.forEach(one => {
  643. // The old spec had optional as an array of objects w/1 attribute each.
  644. // Assumes our JS-webidl bindings only populate passed-in properties.
  645. let key = Object.keys(one)[0];
  646. if (key && old[key] === undefined) {
  647. old[key] = one[key];
  648. }
  649. });
  650. delete o.optional;
  651. }
  652. o.offerToReceiveAudio = old.OfferToReceiveAudio;
  653. o.offerToReceiveVideo = old.OfferToReceiveVideo;
  654. o.mozDontOfferDataChannel = old.MozDontOfferDataChannel;
  655. o.mozBundleOnly = old.MozBundleOnly;
  656. Object.keys(o).forEach(k => {
  657. if (o[k] === undefined) {
  658. delete o[k];
  659. }
  660. });
  661. return true;
  662. }
  663. if (options && convertLegacyOptions(options)) {
  664. this.logError(
  665. "Mandatory/optional in createOffer options no longer works! Use " +
  666. JSON.stringify(options) + " instead (note the case difference)!");
  667. options = {};
  668. }
  669. let origin = Cu.getWebIDLCallerPrincipal().origin;
  670. return this._chain(() => {
  671. let p = Promise.all([this.getPermission(), this._certificateReady])
  672. .then(() => new this._win.Promise((resolve, reject) => {
  673. this._onCreateOfferSuccess = resolve;
  674. this._onCreateOfferFailure = reject;
  675. this._impl.createOffer(options);
  676. }));
  677. p = this._addIdentityAssertion(p, origin);
  678. return p.then(
  679. sdp => new this._win.RTCSessionDescription({ type: "offer", sdp: sdp }));
  680. });
  681. });
  682. },
  683. createAnswer: function(optionsOrOnSuccess, onError) {
  684. // This entry-point handles both new and legacy call sig. Decipher which one
  685. let onSuccess, options;
  686. if (typeof optionsOrOnSuccess == "function") {
  687. onSuccess = optionsOrOnSuccess;
  688. } else {
  689. options = optionsOrOnSuccess;
  690. }
  691. return this._legacyCatchAndCloseGuard(onSuccess, onError, () => {
  692. let origin = Cu.getWebIDLCallerPrincipal().origin;
  693. return this._chain(() => {
  694. let p = Promise.all([this.getPermission(), this._certificateReady])
  695. .then(() => new this._win.Promise((resolve, reject) => {
  696. // We give up line-numbers in errors by doing this here, but do all
  697. // state-checks inside the chain, to support the legacy feature that
  698. // callers don't have to wait for setRemoteDescription to finish.
  699. if (!this.remoteDescription) {
  700. throw new this._win.DOMException("setRemoteDescription not called",
  701. "InvalidStateError");
  702. }
  703. if (this.remoteDescription.type != "offer") {
  704. throw new this._win.DOMException("No outstanding offer",
  705. "InvalidStateError");
  706. }
  707. this._onCreateAnswerSuccess = resolve;
  708. this._onCreateAnswerFailure = reject;
  709. this._impl.createAnswer();
  710. }));
  711. p = this._addIdentityAssertion(p, origin);
  712. return p.then(sdp => {
  713. return new this._win.RTCSessionDescription({ type: "answer", sdp: sdp });
  714. });
  715. });
  716. });
  717. },
  718. getPermission: function() {
  719. if (this._havePermission) {
  720. return this._havePermission;
  721. }
  722. if (this._isChrome ||
  723. Services.prefs.getBoolPref("media.navigator.permission.disabled")) {
  724. return this._havePermission = Promise.resolve();
  725. }
  726. return this._havePermission = new Promise((resolve, reject) => {
  727. this._settlePermission = { allow: resolve, deny: reject };
  728. let outerId = this._win.QueryInterface(Ci.nsIInterfaceRequestor).
  729. getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
  730. let chrome = new CreateOfferRequest(outerId, this._winID,
  731. this._globalPCListId, false);
  732. let request = this._win.CreateOfferRequest._create(this._win, chrome);
  733. Services.obs.notifyObservers(request, "PeerConnection:request", null);
  734. });
  735. },
  736. setLocalDescription: function(desc, onSuccess, onError) {
  737. return this._legacyCatchAndCloseGuard(onSuccess, onError, () => {
  738. this._localType = desc.type;
  739. let type;
  740. switch (desc.type) {
  741. case "offer":
  742. type = Ci.IPeerConnection.kActionOffer;
  743. break;
  744. case "answer":
  745. type = Ci.IPeerConnection.kActionAnswer;
  746. break;
  747. case "pranswer":
  748. throw new this._win.DOMException("pranswer not yet implemented",
  749. "NotSupportedError");
  750. case "rollback":
  751. type = Ci.IPeerConnection.kActionRollback;
  752. break;
  753. default:
  754. throw new this._win.DOMException(
  755. "Invalid type " + desc.type + " provided to setLocalDescription",
  756. "InvalidParameterError");
  757. }
  758. if (desc.type !== "rollback" && !desc.sdp) {
  759. throw new this._win.DOMException(
  760. "Empty or null SDP provided to setLocalDescription",
  761. "InvalidParameterError");
  762. }
  763. return this._chain(() => this.getPermission()
  764. .then(() => new this._win.Promise((resolve, reject) => {
  765. this._onSetLocalDescriptionSuccess = resolve;
  766. this._onSetLocalDescriptionFailure = reject;
  767. this._impl.setLocalDescription(type, desc.sdp);
  768. })));
  769. });
  770. },
  771. _validateIdentity: function(sdp, origin) {
  772. let expectedIdentity;
  773. // Only run a single identity verification at a time. We have to do this to
  774. // avoid problems with the fact that identity validation doesn't block the
  775. // resolution of setRemoteDescription().
  776. let validation = this._lastIdentityValidation
  777. .then(() => this._remoteIdp.verifyIdentityFromSDP(sdp, origin))
  778. .then(msg => {
  779. expectedIdentity = this._impl.peerIdentity;
  780. // If this pc has an identity already, then the identity in sdp must match
  781. if (expectedIdentity && (!msg || msg.identity !== expectedIdentity)) {
  782. this.close();
  783. throw new this._win.DOMException(
  784. "Peer Identity mismatch, expected: " + expectedIdentity,
  785. "IncompatibleSessionDescriptionError");
  786. }
  787. if (msg) {
  788. // Set new identity and generate an event.
  789. this._impl.peerIdentity = msg.identity;
  790. this._resolvePeerIdentity(Cu.cloneInto({
  791. idp: this._remoteIdp.provider,
  792. name: msg.identity
  793. }, this._win));
  794. }
  795. })
  796. .catch(e => {
  797. this._rejectPeerIdentity(e);
  798. // If we don't expect a specific peer identity, failure to get a valid
  799. // peer identity is not a terminal state, so replace the promise to
  800. // allow another attempt.
  801. if (!this._impl.peerIdentity) {
  802. this._peerIdentity = new this._win.Promise((resolve, reject) => {
  803. this._resolvePeerIdentity = resolve;
  804. this._rejectPeerIdentity = reject;
  805. });
  806. }
  807. throw e;
  808. });
  809. this._lastIdentityValidation = validation.catch(() => {});
  810. // Only wait for IdP validation if we need identity matching
  811. return expectedIdentity ? validation : this._win.Promise.resolve();
  812. },
  813. setRemoteDescription: function(desc, onSuccess, onError) {
  814. return this._legacyCatchAndCloseGuard(onSuccess, onError, () => {
  815. this._remoteType = desc.type;
  816. let type;
  817. switch (desc.type) {
  818. case "offer":
  819. type = Ci.IPeerConnection.kActionOffer;
  820. break;
  821. case "answer":
  822. type = Ci.IPeerConnection.kActionAnswer;
  823. break;
  824. case "pranswer":
  825. throw new this._win.DOMException("pranswer not yet implemented",
  826. "NotSupportedError");
  827. case "rollback":
  828. type = Ci.IPeerConnection.kActionRollback;
  829. break;
  830. default:
  831. throw new this._win.DOMException(
  832. "Invalid type " + desc.type + " provided to setRemoteDescription",
  833. "InvalidParameterError");
  834. }
  835. if (!desc.sdp && desc.type !== "rollback") {
  836. throw new this._win.DOMException(
  837. "Empty or null SDP provided to setRemoteDescription",
  838. "InvalidParameterError");
  839. }
  840. // Get caller's origin before hitting the promise chain
  841. let origin = Cu.getWebIDLCallerPrincipal().origin;
  842. return this._chain(() => {
  843. let setRem = this.getPermission()
  844. .then(() => new this._win.Promise((resolve, reject) => {
  845. this._onSetRemoteDescriptionSuccess = resolve;
  846. this._onSetRemoteDescriptionFailure = reject;
  847. this._impl.setRemoteDescription(type, desc.sdp);
  848. })).then(() => { this._updateCanTrickle(); });
  849. if (desc.type === "rollback") {
  850. return setRem;
  851. }
  852. // Do setRemoteDescription and identity validation in parallel
  853. let validId = this._validateIdentity(desc.sdp, origin);
  854. return this._win.Promise.all([setRem, validId])
  855. .then(() => {}); // must return undefined
  856. });
  857. });
  858. },
  859. setIdentityProvider: function(provider, protocol, username) {
  860. this._checkClosed();
  861. this._localIdp.setIdentityProvider(provider, protocol, username);
  862. },
  863. getIdentityAssertion: function() {
  864. let origin = Cu.getWebIDLCallerPrincipal().origin;
  865. return this._chain(
  866. () => this._certificateReady.then(
  867. () => this._localIdp.getIdentityAssertion(this._impl.fingerprint, origin)
  868. )
  869. );
  870. },
  871. get canTrickleIceCandidates() {
  872. return this._canTrickle;
  873. },
  874. _updateCanTrickle: function() {
  875. let containsTrickle = section => {
  876. let lines = section.toLowerCase().split(/(?:\r\n?|\n)/);
  877. return lines.some(line => {
  878. let prefix = "a=ice-options:";
  879. if (line.substring(0, prefix.length) !== prefix) {
  880. return false;
  881. }
  882. let tokens = line.substring(prefix.length).split(" ");
  883. return tokens.some(x => x === "trickle");
  884. });
  885. };
  886. let desc = null;
  887. try {
  888. // The getter for remoteDescription can throw if the pc is closed.
  889. desc = this.remoteDescription;
  890. } catch (e) {}
  891. if (!desc) {
  892. this._canTrickle = null;
  893. return;
  894. }
  895. let sections = desc.sdp.split(/(?:\r\n?|\n)m=/);
  896. let topSection = sections.shift();
  897. this._canTrickle =
  898. containsTrickle(topSection) || sections.every(containsTrickle);
  899. },
  900. addIceCandidate: function(c, onSuccess, onError) {
  901. return this._legacyCatchAndCloseGuard(onSuccess, onError, () => {
  902. if (!c.candidate && !c.sdpMLineIndex) {
  903. throw new this._win.DOMException("Invalid candidate passed to addIceCandidate!",
  904. "InvalidParameterError");
  905. }
  906. return this._chain(() => new this._win.Promise((resolve, reject) => {
  907. this._onAddIceCandidateSuccess = resolve;
  908. this._onAddIceCandidateError = reject;
  909. this._impl.addIceCandidate(c.candidate, c.sdpMid || "", c.sdpMLineIndex);
  910. }));
  911. });
  912. },
  913. addStream: function(stream) {
  914. stream.getTracks().forEach(track => this.addTrack(track, stream));
  915. },
  916. getStreamById: function(id) {
  917. throw new this._win.DOMException("getStreamById not yet implemented",
  918. "NotSupportedError");
  919. },
  920. addTrack: function(track, stream) {
  921. if (stream.currentTime === undefined) {
  922. throw new this._win.DOMException("invalid stream.", "InvalidParameterError");
  923. }
  924. this._checkClosed();
  925. this._senders.forEach(sender => {
  926. if (sender.track == track) {
  927. throw new this._win.DOMException("already added.",
  928. "InvalidParameterError");
  929. }
  930. });
  931. this._impl.addTrack(track, stream);
  932. let sender = this._win.RTCRtpSender._create(this._win,
  933. new RTCRtpSender(this, track,
  934. stream));
  935. this._senders.push(sender);
  936. return sender;
  937. },
  938. removeTrack: function(sender) {
  939. this._checkClosed();
  940. var i = this._senders.indexOf(sender);
  941. if (i >= 0) {
  942. this._senders.splice(i, 1);
  943. this._impl.removeTrack(sender.track); // fires negotiation needed
  944. }
  945. },
  946. _insertDTMF: function(sender, tones, duration, interToneGap) {
  947. return this._impl.insertDTMF(sender.__DOM_IMPL__, tones, duration, interToneGap);
  948. },
  949. _getDTMFToneBuffer: function(sender) {
  950. return this._impl.getDTMFToneBuffer(sender.__DOM_IMPL__);
  951. },
  952. _replaceTrack: function(sender, withTrack) {
  953. // TODO: Do a (sender._stream.getTracks().indexOf(track) < 0) check
  954. // on both track args someday.
  955. //
  956. // The proposed API will be that both tracks must already be in the same
  957. // stream. However, since our MediaStreams currently are limited to one
  958. // track per type, we allow replacement with an outside track not already
  959. // in the same stream.
  960. //
  961. // Since a track may be replaced more than once, the track being replaced
  962. // may not be in the stream either, so we check neither arg right now.
  963. return new this._win.Promise((resolve, reject) => {
  964. this._onReplaceTrackSender = sender;
  965. this._onReplaceTrackWithTrack = withTrack;
  966. this._onReplaceTrackSuccess = resolve;
  967. this._onReplaceTrackFailure = reject;
  968. this._impl.replaceTrack(sender.track, withTrack);
  969. });
  970. },
  971. _setParameters: function(sender, parameters) {
  972. if (!Services.prefs.getBoolPref("media.peerconnection.simulcast")) {
  973. return;
  974. }
  975. // validate parameters input
  976. var encodings = parameters.encodings || [];
  977. encodings.reduce((uniqueRids, encoding) => {
  978. if (encoding.scaleResolutionDownBy < 1.0) {
  979. throw new this._win.RangeError("scaleResolutionDownBy must be >= 1.0");
  980. }
  981. if (!encoding.rid && encodings.length > 1) {
  982. throw new this._win.DOMException("Missing rid", "TypeError");
  983. }
  984. if (uniqueRids[encoding.rid]) {
  985. throw new this._win.DOMException("Duplicate rid", "TypeError");
  986. }
  987. uniqueRids[encoding.rid] = true;
  988. return uniqueRids;
  989. }, {});
  990. this._impl.setParameters(sender.track, parameters);
  991. },
  992. _getParameters: function(sender) {
  993. if (!Services.prefs.getBoolPref("media.peerconnection.simulcast")) {
  994. return;
  995. }
  996. return this._impl.getParameters(sender.track);
  997. },
  998. close: function() {
  999. if (this._closed) {
  1000. return;
  1001. }
  1002. this._closed = true;
  1003. this._inClose = true;
  1004. this.changeIceConnectionState("closed");
  1005. this._localIdp.close();
  1006. this._remoteIdp.close();
  1007. this._impl.close();
  1008. this._inClose = false;
  1009. },
  1010. getLocalStreams: function() {
  1011. this._checkClosed();
  1012. return this._impl.getLocalStreams();
  1013. },
  1014. getRemoteStreams: function() {
  1015. this._checkClosed();
  1016. return this._impl.getRemoteStreams();
  1017. },
  1018. getSenders: function() {
  1019. return this._senders;
  1020. },
  1021. getReceivers: function() {
  1022. return this._receivers;
  1023. },
  1024. mozSelectSsrc: function(receiver, ssrcIndex) {
  1025. this._impl.selectSsrc(receiver.track, ssrcIndex);
  1026. },
  1027. get localDescription() {
  1028. this._checkClosed();
  1029. let sdp = this._impl.localDescription;
  1030. if (sdp.length == 0) {
  1031. return null;
  1032. }
  1033. return new this._win.RTCSessionDescription({ type: this._localType,
  1034. sdp: sdp });
  1035. },
  1036. get remoteDescription() {
  1037. this._checkClosed();
  1038. let sdp = this._impl.remoteDescription;
  1039. if (sdp.length == 0) {
  1040. return null;
  1041. }
  1042. return new this._win.RTCSessionDescription({ type: this._remoteType,
  1043. sdp: sdp });
  1044. },
  1045. get peerIdentity() { return this._peerIdentity; },
  1046. get idpLoginUrl() { return this._localIdp.idpLoginUrl; },
  1047. get id() { return this._impl.id; },
  1048. set id(s) { this._impl.id = s; },
  1049. get iceGatheringState() { return this._iceGatheringState; },
  1050. get iceConnectionState() { return this._iceConnectionState; },
  1051. get signalingState() {
  1052. // checking for our local pc closed indication
  1053. // before invoking the pc methods.
  1054. if (this._closed) {
  1055. return "closed";
  1056. }
  1057. return {
  1058. "SignalingInvalid": "",
  1059. "SignalingStable": "stable",
  1060. "SignalingHaveLocalOffer": "have-local-offer",
  1061. "SignalingHaveRemoteOffer": "have-remote-offer",
  1062. "SignalingHaveLocalPranswer": "have-local-pranswer",
  1063. "SignalingHaveRemotePranswer": "have-remote-pranswer",
  1064. "SignalingClosed": "closed"
  1065. }[this._impl.signalingState];
  1066. },
  1067. changeIceGatheringState: function(state) {
  1068. this._iceGatheringState = state;
  1069. _globalPCList.notifyLifecycleObservers(this, "icegatheringstatechange");
  1070. },
  1071. changeIceConnectionState: function(state) {
  1072. this._iceConnectionState = state;
  1073. _globalPCList.notifyLifecycleObservers(this, "iceconnectionstatechange");
  1074. this.dispatchEvent(new this._win.Event("iceconnectionstatechange"));
  1075. },
  1076. getStats: function(selector, onSuccess, onError) {
  1077. return this._legacyCatchAndCloseGuard(onSuccess, onError, () => {
  1078. return this._chain(() => new this._win.Promise((resolve, reject) => {
  1079. this._onGetStatsSuccess = resolve;
  1080. this._onGetStatsFailure = reject;
  1081. this._impl.getStats(selector);
  1082. }));
  1083. });
  1084. },
  1085. createDataChannel: function(label, dict) {
  1086. this._checkClosed();
  1087. if (dict == undefined) {
  1088. dict = {};
  1089. }
  1090. if (dict.maxRetransmitNum != undefined) {
  1091. dict.maxRetransmits = dict.maxRetransmitNum;
  1092. this.logWarning("Deprecated RTCDataChannelInit dictionary entry maxRetransmitNum used!");
  1093. }
  1094. if (dict.outOfOrderAllowed != undefined) {
  1095. dict.ordered = !dict.outOfOrderAllowed; // the meaning is swapped with
  1096. // the name change
  1097. this.logWarning("Deprecated RTCDataChannelInit dictionary entry outOfOrderAllowed used!");
  1098. }
  1099. if (dict.preset != undefined) {
  1100. dict.negotiated = dict.preset;
  1101. this.logWarning("Deprecated RTCDataChannelInit dictionary entry preset used!");
  1102. }
  1103. if (dict.stream != undefined) {
  1104. dict.id = dict.stream;
  1105. this.logWarning("Deprecated RTCDataChannelInit dictionary entry stream used!");
  1106. }
  1107. if (dict.maxRetransmitTime !== null && dict.maxRetransmits !== null) {
  1108. throw new this._win.DOMException(
  1109. "Both maxRetransmitTime and maxRetransmits cannot be provided",
  1110. "InvalidParameterError");
  1111. }
  1112. let protocol;
  1113. if (dict.protocol == undefined) {
  1114. protocol = "";
  1115. } else {
  1116. protocol = dict.protocol;
  1117. }
  1118. // Must determine the type where we still know if entries are undefined.
  1119. let type;
  1120. if (dict.maxRetransmitTime != undefined) {
  1121. type = Ci.IPeerConnection.kDataChannelPartialReliableTimed;
  1122. } else if (dict.maxRetransmits != undefined) {
  1123. type = Ci.IPeerConnection.kDataChannelPartialReliableRexmit;
  1124. } else {
  1125. type = Ci.IPeerConnection.kDataChannelReliable;
  1126. }
  1127. // Synchronous since it doesn't block.
  1128. let channel = this._impl.createDataChannel(
  1129. label, protocol, type, !dict.ordered, dict.maxRetransmitTime,
  1130. dict.maxRetransmits, dict.negotiated ? true : false,
  1131. dict.id != undefined ? dict.id : 0xFFFF
  1132. );
  1133. return channel;
  1134. }
  1135. };
  1136. // This is a separate object because we don't want to expose it to DOM.
  1137. function PeerConnectionObserver() {
  1138. this._dompc = null;
  1139. }
  1140. PeerConnectionObserver.prototype = {
  1141. classDescription: "PeerConnectionObserver",
  1142. classID: PC_OBS_CID,
  1143. contractID: PC_OBS_CONTRACT,
  1144. QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
  1145. Ci.nsIDOMGlobalPropertyInitializer]),
  1146. init: function(win) { this._win = win; },
  1147. __init: function(dompc) {
  1148. this._dompc = dompc._innerObject;
  1149. },
  1150. newError: function(message, code) {
  1151. // These strings must match those defined in the WebRTC spec.
  1152. const reasonName = [
  1153. "",
  1154. "InternalError",
  1155. "InvalidCandidateError",
  1156. "InvalidParameterError",
  1157. "InvalidStateError",
  1158. "InvalidSessionDescriptionError",
  1159. "IncompatibleSessionDescriptionError",
  1160. "InternalError",
  1161. "IncompatibleMediaStreamTrackError",
  1162. "InternalError"
  1163. ];
  1164. let name = reasonName[Math.min(code, reasonName.length - 1)];
  1165. return new this._dompc._win.DOMException(message, name);
  1166. },
  1167. dispatchEvent: function(event) {
  1168. this._dompc.dispatchEvent(event);
  1169. },
  1170. onCreateOfferSuccess: function(sdp) {
  1171. this._dompc._onCreateOfferSuccess(sdp);
  1172. },
  1173. onCreateOfferError: function(code, message) {
  1174. this._dompc._onCreateOfferFailure(this.newError(message, code));
  1175. },
  1176. onCreateAnswerSuccess: function(sdp) {
  1177. this._dompc._onCreateAnswerSuccess(sdp);
  1178. },
  1179. onCreateAnswerError: function(code, message) {
  1180. this._dompc._onCreateAnswerFailure(this.newError(message, code));
  1181. },
  1182. onSetLocalDescriptionSuccess: function() {
  1183. this._dompc._onSetLocalDescriptionSuccess();
  1184. },
  1185. onSetRemoteDescriptionSuccess: function() {
  1186. this._dompc._onSetRemoteDescriptionSuccess();
  1187. },
  1188. onSetLocalDescriptionError: function(code, message) {
  1189. this._localType = null;
  1190. this._dompc._onSetLocalDescriptionFailure(this.newError(message, code));
  1191. },
  1192. onSetRemoteDescriptionError: function(code, message) {
  1193. this._remoteType = null;
  1194. this._dompc._onSetRemoteDescriptionFailure(this.newError(message, code));
  1195. },
  1196. onAddIceCandidateSuccess: function() {
  1197. this._dompc._onAddIceCandidateSuccess();
  1198. },
  1199. onAddIceCandidateError: function(code, message) {
  1200. this._dompc._onAddIceCandidateError(this.newError(message, code));
  1201. },
  1202. onIceCandidate: function(level, mid, candidate) {
  1203. if (candidate == "") {
  1204. this.foundIceCandidate(null);
  1205. } else {
  1206. this.foundIceCandidate(new this._dompc._win.RTCIceCandidate(
  1207. {
  1208. candidate: candidate,
  1209. sdpMid: mid,
  1210. sdpMLineIndex: level
  1211. }
  1212. ));
  1213. }
  1214. },
  1215. onNegotiationNeeded: function() {
  1216. this.dispatchEvent(new this._win.Event("negotiationneeded"));
  1217. },
  1218. // This method is primarily responsible for updating iceConnectionState.
  1219. // This state is defined in the WebRTC specification as follows:
  1220. //
  1221. // iceConnectionState:
  1222. // -------------------
  1223. // new The ICE Agent is gathering addresses and/or waiting for
  1224. // remote candidates to be supplied.
  1225. //
  1226. // checking The ICE Agent has received remote candidates on at least
  1227. // one component, and is checking candidate pairs but has not
  1228. // yet found a connection. In addition to checking, it may
  1229. // also still be gathering.
  1230. //
  1231. // connected The ICE Agent has found a usable connection for all
  1232. // components but is still checking other candidate pairs to
  1233. // see if there is a better connection. It may also still be
  1234. // gathering.
  1235. //
  1236. // completed The ICE Agent has finished gathering and checking and found
  1237. // a connection for all components. Open issue: it is not
  1238. // clear how the non controlling ICE side knows it is in the
  1239. // state.
  1240. //
  1241. // failed The ICE Agent is finished checking all candidate pairs and
  1242. // failed to find a connection for at least one component.
  1243. // Connections may have been found for some components.
  1244. //
  1245. // disconnected Liveness checks have failed for one or more components.
  1246. // This is more aggressive than failed, and may trigger
  1247. // intermittently (and resolve itself without action) on a
  1248. // flaky network.
  1249. //
  1250. // closed The ICE Agent has shut down and is no longer responding to
  1251. // STUN requests.
  1252. handleIceConnectionStateChange: function(iceConnectionState) {
  1253. let pc = this._dompc;
  1254. if (pc.iceConnectionState === 'new') {
  1255. var checking_histogram = Services.telemetry.getHistogramById("WEBRTC_ICE_CHECKING_RATE");
  1256. if (iceConnectionState === 'checking') {
  1257. checking_histogram.add(true);
  1258. } else if (iceConnectionState === 'failed') {
  1259. checking_histogram.add(false);
  1260. }
  1261. } else if (pc.iceConnectionState === 'checking') {
  1262. var success_histogram = Services.telemetry.getHistogramById("WEBRTC_ICE_SUCCESS_RATE");
  1263. if (iceConnectionState === 'completed' ||
  1264. iceConnectionState === 'connected') {
  1265. success_histogram.add(true);
  1266. } else if (iceConnectionState === 'failed') {
  1267. success_histogram.add(false);
  1268. }
  1269. }
  1270. if (iceConnectionState === 'failed') {
  1271. pc.logError("ICE failed, see about:webrtc for more details");
  1272. }
  1273. pc.changeIceConnectionState(iceConnectionState);
  1274. },
  1275. // This method is responsible for updating iceGatheringState. This
  1276. // state is defined in the WebRTC specification as follows:
  1277. //
  1278. // iceGatheringState:
  1279. // ------------------
  1280. // new The object was just created, and no networking has occurred
  1281. // yet.
  1282. //
  1283. // gathering The ICE engine is in the process of gathering candidates for
  1284. // this RTCPeerConnection.
  1285. //
  1286. // complete The ICE engine has completed gathering. Events such as adding
  1287. // a new interface or a new TURN server will cause the state to
  1288. // go back to gathering.
  1289. //
  1290. handleIceGatheringStateChange: function(gatheringState) {
  1291. this._dompc.changeIceGatheringState(gatheringState);
  1292. },
  1293. onStateChange: function(state) {
  1294. switch (state) {
  1295. case "SignalingState":
  1296. this.dispatchEvent(new this._win.Event("signalingstatechange"));
  1297. break;
  1298. case "IceConnectionState":
  1299. let connState = this._dompc._pc.iceConnectionState;
  1300. this._dompc._queueTaskWithClosedCheck(() => {
  1301. this.handleIceConnectionStateChange(connState);
  1302. });
  1303. break;
  1304. case "IceGatheringState":
  1305. this.handleIceGatheringStateChange(this._dompc._pc.iceGatheringState);
  1306. break;
  1307. case "SdpState":
  1308. // No-op
  1309. break;
  1310. case "ReadyState":
  1311. // No-op
  1312. break;
  1313. case "SipccState":
  1314. // No-op
  1315. break;
  1316. default:
  1317. this._dompc.logWarning("Unhandled state type: " + state);
  1318. break;
  1319. }
  1320. },
  1321. onGetStatsSuccess: function(dict) {
  1322. let pc = this._dompc;
  1323. let chromeobj = new RTCStatsReport(pc._win, dict);
  1324. let webidlobj = pc._win.RTCStatsReport._create(pc._win, chromeobj);
  1325. chromeobj.makeStatsPublic(pc._warnDeprecatedStatsAccessNullable);
  1326. pc._onGetStatsSuccess(webidlobj);
  1327. },
  1328. onGetStatsError: function(code, message) {
  1329. this._dompc._onGetStatsFailure(this.newError(message, code));
  1330. },
  1331. onAddStream: function(stream) {
  1332. let ev = new this._dompc._win.MediaStreamEvent("addstream",
  1333. { stream: stream });
  1334. this.dispatchEvent(ev);
  1335. },
  1336. onRemoveStream: function(stream) {
  1337. this.dispatchEvent(new this._dompc._win.MediaStreamEvent("removestream",
  1338. { stream: stream }));
  1339. },
  1340. onAddTrack: function(track, streams) {
  1341. let pc = this._dompc;
  1342. let receiver = pc._win.RTCRtpReceiver._create(pc._win,
  1343. new RTCRtpReceiver(this,
  1344. track));
  1345. pc._receivers.push(receiver);
  1346. let ev = new pc._win.RTCTrackEvent("track",
  1347. { receiver: receiver,
  1348. track: track,
  1349. streams: streams });
  1350. this.dispatchEvent(ev);
  1351. // Fire legacy event as well for a little bit.
  1352. ev = new pc._win.MediaStreamTrackEvent("addtrack", { track: track });
  1353. this.dispatchEvent(ev);
  1354. },
  1355. onRemoveTrack: function(track) {
  1356. let pc = this._dompc;
  1357. let i = pc._receivers.findIndex(receiver => receiver.track == track);
  1358. if (i >= 0) {
  1359. pc._receivers.splice(i, 1);
  1360. }
  1361. },
  1362. onReplaceTrackSuccess: function() {
  1363. var pc = this._dompc;
  1364. pc._onReplaceTrackSender.track = pc._onReplaceTrackWithTrack;
  1365. pc._onReplaceTrackWithTrack = null;
  1366. pc._onReplaceTrackSender = null;
  1367. pc._onReplaceTrackSuccess();
  1368. },
  1369. onReplaceTrackError: function(code, message) {
  1370. var pc = this._dompc;
  1371. pc._onReplaceTrackWithTrack = null;
  1372. pc._onReplaceTrackSender = null;
  1373. pc._onReplaceTrackFailure(this.newError(message, code));
  1374. },
  1375. foundIceCandidate: function(cand) {
  1376. this.dispatchEvent(new this._dompc._win.RTCPeerConnectionIceEvent("icecandidate",
  1377. { candidate: cand } ));
  1378. },
  1379. notifyDataChannel: function(channel) {
  1380. this.dispatchEvent(new this._dompc._win.RTCDataChannelEvent("datachannel",
  1381. { channel: channel }));
  1382. },
  1383. onDTMFToneChange: function(trackId, tone) {
  1384. var pc = this._dompc;
  1385. var sender = pc._senders.find(sender => sender.track.id == trackId)
  1386. sender.dtmf.dispatchEvent(new pc._win.RTCDTMFToneChangeEvent("tonechange",
  1387. { tone: tone }));
  1388. }
  1389. };
  1390. function RTCPeerConnectionStatic() {
  1391. }
  1392. RTCPeerConnectionStatic.prototype = {
  1393. classDescription: "RTCPeerConnectionStatic",
  1394. QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
  1395. Ci.nsIDOMGlobalPropertyInitializer]),
  1396. classID: PC_STATIC_CID,
  1397. contractID: PC_STATIC_CONTRACT,
  1398. init: function(win) {
  1399. this._winID = win.QueryInterface(Ci.nsIInterfaceRequestor)
  1400. .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
  1401. },
  1402. registerPeerConnectionLifecycleCallback: function(cb) {
  1403. _globalPCList._registerPeerConnectionLifecycleCallback(this._winID, cb);
  1404. },
  1405. };
  1406. function RTCDTMFSender(sender) {
  1407. this._sender = sender;
  1408. }
  1409. RTCDTMFSender.prototype = {
  1410. classDescription: "RTCDTMFSender",
  1411. classID: PC_DTMF_SENDER_CID,
  1412. contractID: PC_DTMF_SENDER_CONTRACT,
  1413. QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
  1414. get toneBuffer() {
  1415. return this._sender._pc._getDTMFToneBuffer(this._sender);
  1416. },
  1417. get ontonechange() {
  1418. return this.__DOM_IMPL__.getEventHandler("ontonechange");
  1419. },
  1420. set ontonechange(handler) {
  1421. this.__DOM_IMPL__.setEventHandler("ontonechange", handler);
  1422. },
  1423. insertDTMF: function(tones, duration, interToneGap) {
  1424. this._sender._pc._checkClosed();
  1425. if (this._sender._pc._senders.indexOf(this._sender.__DOM_IMPL__) == -1) {
  1426. throw new this._sender._pc._win.DOMException("RTCRtpSender is stopped",
  1427. "InvalidStateError");
  1428. }
  1429. duration = Math.max(40, Math.min(duration, 6000));
  1430. if (interToneGap < 30) interToneGap = 30;
  1431. tones = tones.toUpperCase();
  1432. if (tones.match(/[^0-9A-D#*,]/)) {
  1433. throw new this._sender._pc._win.DOMException("Invalid DTMF characters",
  1434. "InvalidCharacterError");
  1435. }
  1436. this._sender._pc._insertDTMF(this._sender, tones, duration, interToneGap);
  1437. },
  1438. };
  1439. function RTCRtpSender(pc, track, stream) {
  1440. this._pc = pc;
  1441. this.track = track;
  1442. this._stream = stream;
  1443. this.dtmf = pc._win.RTCDTMFSender._create(pc._win, new RTCDTMFSender(this));
  1444. }
  1445. RTCRtpSender.prototype = {
  1446. classDescription: "RTCRtpSender",
  1447. classID: PC_SENDER_CID,
  1448. contractID: PC_SENDER_CONTRACT,
  1449. QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
  1450. replaceTrack: function(withTrack) {
  1451. return this._pc._chain(() => this._pc._replaceTrack(this, withTrack));
  1452. },
  1453. setParameters: function(parameters) {
  1454. return this._pc._win.Promise.resolve()
  1455. .then(() => this._pc._setParameters(this, parameters));
  1456. },
  1457. getParameters: function() {
  1458. return this._pc._getParameters(this);
  1459. }
  1460. };
  1461. function RTCRtpReceiver(pc, track) {
  1462. this._pc = pc;
  1463. this.track = track;
  1464. }
  1465. RTCRtpReceiver.prototype = {
  1466. classDescription: "RTCRtpReceiver",
  1467. classID: PC_RECEIVER_CID,
  1468. contractID: PC_RECEIVER_CONTRACT,
  1469. QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
  1470. };
  1471. function CreateOfferRequest(windowID, innerWindowID, callID, isSecure) {
  1472. this.windowID = windowID;
  1473. this.innerWindowID = innerWindowID;
  1474. this.callID = callID;
  1475. this.isSecure = isSecure;
  1476. }
  1477. CreateOfferRequest.prototype = {
  1478. classDescription: "CreateOfferRequest",
  1479. classID: PC_COREQUEST_CID,
  1480. contractID: PC_COREQUEST_CONTRACT,
  1481. QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
  1482. };
  1483. this.NSGetFactory = XPCOMUtils.generateNSGetFactory(
  1484. [GlobalPCList,
  1485. RTCDTMFSender,
  1486. RTCIceCandidate,
  1487. RTCSessionDescription,
  1488. RTCPeerConnection,
  1489. RTCPeerConnectionStatic,
  1490. RTCRtpReceiver,
  1491. RTCRtpSender,
  1492. RTCStatsReport,
  1493. PeerConnectionObserver,
  1494. CreateOfferRequest]
  1495. );