Bug 1708422: part 16) Rename `mozInlineSpellChecker::SpellCheckerTimeSlice` to `mozIn...
[gecko.git] / dom / media / PeerConnection.jsm
blob6449bd110cbeaaa2e84f38cae1415bb26f3c4d2d
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
8 ChromeUtils.defineModuleGetter(
9   this,
10   "PeerConnectionIdp",
11   "resource://gre/modules/media/PeerConnectionIdp.jsm"
14 const PC_CONTRACT = "@mozilla.org/dom/peerconnection;1";
15 const PC_OBS_CONTRACT = "@mozilla.org/dom/peerconnectionobserver;1";
16 const PC_ICE_CONTRACT = "@mozilla.org/dom/rtcicecandidate;1";
17 const PC_SESSION_CONTRACT = "@mozilla.org/dom/rtcsessiondescription;1";
18 const PC_STATIC_CONTRACT = "@mozilla.org/dom/peerconnectionstatic;1";
19 const PC_SENDER_CONTRACT = "@mozilla.org/dom/rtpsender;1";
20 const PC_TRANSCEIVER_CONTRACT = "@mozilla.org/dom/rtptransceiver;1";
21 const PC_COREQUEST_CONTRACT = "@mozilla.org/dom/createofferrequest;1";
23 const PC_CID = Components.ID("{bdc2e533-b308-4708-ac8e-a8bfade6d851}");
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_STATIC_CID = Components.ID("{0fb47c47-a205-4583-a9fc-cbadf8c95880}");
29 const PC_SENDER_CID = Components.ID("{4fff5d46-d827-4cd4-a970-8fd53977440e}");
30 const PC_TRANSCEIVER_CID = Components.ID(
31   "{09475754-103a-41f5-a2d0-e1f27eb0b537}"
33 const PC_COREQUEST_CID = Components.ID(
34   "{74b2122d-65a8-4824-aa9e-3d664cb75dc2}"
37 function logMsg(msg, file, line, flag, winID) {
38   let scriptErrorClass = Cc["@mozilla.org/scripterror;1"];
39   let scriptError = scriptErrorClass.createInstance(Ci.nsIScriptError);
40   scriptError.initWithWindowID(
41     `WebRTC: ${msg}`,
42     file,
43     null,
44     line,
45     0,
46     flag,
47     "content javascript",
48     winID
49   );
50   Services.console.logMessage(scriptError);
53 let setupPrototype = (_class, dict) => {
54   _class.prototype.classDescription = _class.name;
55   Object.assign(_class.prototype, dict);
58 // Global list of PeerConnection objects, so they can be cleaned up when
59 // a page is torn down. (Maps inner window ID to an array of PC objects).
60 class GlobalPCList {
61   constructor() {
62     this._list = {};
63     this._networkdown = false; // XXX Need to query current state somehow
64     this._lifecycleobservers = {};
65     this._nextId = 1;
66     Services.obs.addObserver(this, "inner-window-destroyed", true);
67     Services.obs.addObserver(this, "profile-change-net-teardown", true);
68     Services.obs.addObserver(this, "network:offline-about-to-go-offline", true);
69     Services.obs.addObserver(this, "network:offline-status-changed", true);
70     Services.obs.addObserver(this, "gmp-plugin-crash", true);
71     Services.obs.addObserver(this, "PeerConnection:response:allow", true);
72     Services.obs.addObserver(this, "PeerConnection:response:deny", true);
73     if (Services.cpmm) {
74       Services.cpmm.addMessageListener("gmp-plugin-crash", this);
75     }
76   }
78   notifyLifecycleObservers(pc, type) {
79     for (var key of Object.keys(this._lifecycleobservers)) {
80       this._lifecycleobservers[key](pc, pc._winID, type);
81     }
82   }
84   addPC(pc) {
85     let winID = pc._winID;
86     if (this._list[winID]) {
87       this._list[winID].push(Cu.getWeakReference(pc));
88     } else {
89       this._list[winID] = [Cu.getWeakReference(pc)];
90     }
91     pc._globalPCListId = this._nextId++;
92     this.removeNullRefs(winID);
93   }
95   findPC(globalPCListId) {
96     for (let winId in this._list) {
97       if (this._list.hasOwnProperty(winId)) {
98         for (let pcref of this._list[winId]) {
99           let pc = pcref.get();
100           if (pc && pc._globalPCListId == globalPCListId) {
101             return pc;
102           }
103         }
104       }
105     }
106     return null;
107   }
109   removeNullRefs(winID) {
110     if (this._list[winID] === undefined) {
111       return;
112     }
113     this._list[winID] = this._list[winID].filter(function(e, i, a) {
114       return e.get() !== null;
115     });
117     if (this._list[winID].length === 0) {
118       delete this._list[winID];
119     }
120   }
122   handleGMPCrash(data) {
123     let broadcastPluginCrash = function(list, winID, pluginID, pluginName) {
124       if (list.hasOwnProperty(winID)) {
125         list[winID].forEach(function(pcref) {
126           let pc = pcref.get();
127           if (pc) {
128             pc._pc.pluginCrash(pluginID, pluginName);
129           }
130         });
131       }
132     };
134     // a plugin crashed; if it's associated with any of our PCs, fire an
135     // event to the DOM window
136     for (let winId in this._list) {
137       broadcastPluginCrash(this._list, winId, data.pluginID, data.pluginName);
138     }
139   }
141   receiveMessage({ name, data }) {
142     if (name == "gmp-plugin-crash") {
143       this.handleGMPCrash(data);
144     }
145   }
147   observe(subject, topic, data) {
148     let cleanupPcRef = function(pcref) {
149       let pc = pcref.get();
150       if (pc) {
151         pc._suppressEvents = true;
152         pc.close();
153       }
154     };
156     let cleanupWinId = function(list, winID) {
157       if (list.hasOwnProperty(winID)) {
158         list[winID].forEach(cleanupPcRef);
159         delete list[winID];
160       }
161     };
163     if (topic == "inner-window-destroyed") {
164       let winID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
165       cleanupWinId(this._list, winID);
167       if (this._lifecycleobservers.hasOwnProperty(winID)) {
168         delete this._lifecycleobservers[winID];
169       }
170     } else if (
171       topic == "profile-change-net-teardown" ||
172       topic == "network:offline-about-to-go-offline"
173     ) {
174       // As Necko doesn't prevent us from accessing the network we still need to
175       // monitor the network offline/online state here. See bug 1326483
176       this._networkdown = true;
177     } else if (topic == "network:offline-status-changed") {
178       if (data == "offline") {
179         this._networkdown = true;
180       } else if (data == "online") {
181         this._networkdown = false;
182       }
183     } else if (topic == "gmp-plugin-crash") {
184       if (subject instanceof Ci.nsIWritablePropertyBag2) {
185         let pluginID = subject.getPropertyAsUint32("pluginID");
186         let pluginName = subject.getPropertyAsAString("pluginName");
187         let data = { pluginID, pluginName };
188         this.handleGMPCrash(data);
189       }
190     } else if (
191       topic == "PeerConnection:response:allow" ||
192       topic == "PeerConnection:response:deny"
193     ) {
194       var pc = this.findPC(data);
195       if (pc) {
196         if (topic == "PeerConnection:response:allow") {
197           pc._settlePermission.allow();
198         } else {
199           let err = new pc._win.DOMException(
200             "The request is not allowed by " +
201               "the user agent or the platform in the current context.",
202             "NotAllowedError"
203           );
204           pc._settlePermission.deny(err);
205         }
206       }
207     }
208   }
210   _registerPeerConnectionLifecycleCallback(winID, cb) {
211     this._lifecycleobservers[winID] = cb;
212   }
214 setupPrototype(GlobalPCList, {
215   QueryInterface: ChromeUtils.generateQI([
216     "nsIObserver",
217     "nsISupportsWeakReference",
218   ]),
219   classID: PC_MANAGER_CID,
220   _xpcom_factory: {
221     createInstance(outer, iid) {
222       if (outer) {
223         throw Components.Exception("", Cr.NS_ERROR_NO_AGGREGATION);
224       }
225       return _globalPCList.QueryInterface(iid);
226     },
227   },
230 var _globalPCList = new GlobalPCList();
232 class RTCIceCandidate {
233   init(win) {
234     this._win = win;
235   }
237   __init(dict) {
238     if (dict.sdpMid == null && dict.sdpMLineIndex == null) {
239       throw new this._win.TypeError(
240         "Either sdpMid or sdpMLineIndex must be specified"
241       );
242     }
243     Object.assign(this, dict);
244   }
246 setupPrototype(RTCIceCandidate, {
247   classID: PC_ICE_CID,
248   contractID: PC_ICE_CONTRACT,
249   QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
252 class RTCSessionDescription {
253   init(win) {
254     this._win = win;
255     this._winID = this._win.windowGlobalChild.innerWindowId;
256   }
258   __init({ type, sdp }) {
259     if (!type) {
260       throw new this._win.TypeError(
261         "Missing required 'type' member of RTCSessionDescriptionInit"
262       );
263     }
264     Object.assign(this, { _type: type, _sdp: sdp });
265   }
267   get type() {
268     return this._type;
269   }
270   set type(type) {
271     this.warn();
272     this._type = type;
273   }
275   get sdp() {
276     return this._sdp;
277   }
278   set sdp(sdp) {
279     this.warn();
280     this._sdp = sdp;
281   }
283   warn() {
284     if (!this._warned) {
285       // Warn once per RTCSessionDescription about deprecated writable usage.
286       this.logWarning(
287         "RTCSessionDescription's members are readonly! " +
288           "Writing to them is deprecated and will break soon!"
289       );
290       this._warned = true;
291     }
292   }
294   logWarning(msg) {
295     let err = this._win.Error();
296     logMsg(
297       msg,
298       err.fileName,
299       err.lineNumber,
300       Ci.nsIScriptError.warningFlag,
301       this._winID
302     );
303   }
305 setupPrototype(RTCSessionDescription, {
306   classID: PC_SESSION_CID,
307   contractID: PC_SESSION_CONTRACT,
308   QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
311 // Records PC related telemetry
312 class PeerConnectionTelemetry {
313   // ICE connection state enters connected or completed.
314   recordConnected() {
315     Services.telemetry.scalarAdd("webrtc.peerconnection.connected", 1);
316     this.recordConnected = () => {};
317   }
318   // DataChannel is created
319   _recordDataChannelCreated() {
320     Services.telemetry.scalarAdd(
321       "webrtc.peerconnection.datachannel_created",
322       1
323     );
324     this._recordDataChannelCreated = () => {};
325   }
326   // DataChannel initialized with maxRetransmitTime
327   _recordMaxRetransmitTime(maxRetransmitTime) {
328     if (maxRetransmitTime === undefined) {
329       return false;
330     }
331     Services.telemetry.scalarAdd(
332       "webrtc.peerconnection.datachannel_max_retx_used",
333       1
334     );
335     this._recordMaxRetransmitTime = () => true;
336     return true;
337   }
338   // DataChannel initialized with maxPacketLifeTime
339   _recordMaxPacketLifeTime(maxPacketLifeTime) {
340     if (maxPacketLifeTime === undefined) {
341       return false;
342     }
343     Services.telemetry.scalarAdd(
344       "webrtc.peerconnection.datachannel_max_life_used",
345       1
346     );
347     this._recordMaxPacketLifeTime = () => true;
348     return true;
349   }
350   // DataChannel initialized
351   recordDataChannelInit(maxRetransmitTime, maxPacketLifeTime) {
352     const retxUsed = this._recordMaxRetransmitTime(maxRetransmitTime);
353     if (this._recordMaxPacketLifeTime(maxPacketLifeTime) && retxUsed) {
354       Services.telemetry.scalarAdd(
355         "webrtc.peerconnection.datachannel_max_retx_and_life_used",
356         1
357       );
358       this.recordDataChannelInit = () => {};
359     }
360     this._recordDataChannelCreated();
361   }
364 class RTCPeerConnection {
365   constructor() {
366     this._transceivers = [];
368     this._pc = null;
369     this._closed = false;
371     // http://rtcweb-wg.github.io/jsep/#rfc.section.4.1.9
372     // canTrickle == null means unknown; when a remote description is received it
373     // is set to true or false based on the presence of the "trickle" ice-option
374     this._canTrickle = null;
375     this._localUfragsToReplace = new Set();
377     // So we can record telemetry on state transitions
378     this._iceConnectionState = "new";
380     this._hasStunServer = this._hasTurnServer = false;
381     this._iceGatheredRelayCandidates = false;
382     // Records telemetry
383     this._pcTelemetry = new PeerConnectionTelemetry();
384   }
386   init(win) {
387     this._win = win;
388   }
390   __init(rtcConfig) {
391     this._winID = this._win.windowGlobalChild.innerWindowId;
392     // TODO: Update this code once we support pc.setConfiguration, to track
393     // setting from content independently from pref (Bug 1181768).
394     if (
395       rtcConfig.iceTransportPolicy == "all" &&
396       Services.prefs.getBoolPref("media.peerconnection.ice.relay_only")
397     ) {
398       rtcConfig.iceTransportPolicy = "relay";
399     }
400     if ("sdpSemantics" in rtcConfig) {
401       if (rtcConfig.sdpSemantics == "plan-b") {
402         this.logWarning(
403           `Outdated and non-standard {sdpSemantics: "plan-b"} is not ` +
404             `supported! WebRTC may be unreliable. Please update code to ` +
405             `follow standard "unified-plan".`
406         );
407       }
408       // Don't let it show up in getConfiguration.
409       delete rtcConfig.sdpSemantics;
410     }
411     this._config = Object.assign({}, rtcConfig);
413     if (
414       !rtcConfig.iceServers ||
415       !Services.prefs.getBoolPref(
416         "media.peerconnection.use_document_iceservers"
417       )
418     ) {
419       try {
420         rtcConfig.iceServers = JSON.parse(
421           Services.prefs.getCharPref(
422             "media.peerconnection.default_iceservers"
423           ) || "[]"
424         );
425       } catch (e) {
426         this.logWarning(
427           "Ignoring invalid media.peerconnection.default_iceservers in about:config"
428         );
429         rtcConfig.iceServers = [];
430       }
431       try {
432         this._mustValidateRTCConfiguration(
433           rtcConfig,
434           "Ignoring invalid media.peerconnection.default_iceservers in about:config"
435         );
436       } catch (e) {
437         this.logWarning(e.message);
438         rtcConfig.iceServers = [];
439       }
440     } else {
441       // This gets executed in the typical case when iceServers
442       // are passed in through the web page.
443       this._mustValidateRTCConfiguration(
444         rtcConfig,
445         "RTCPeerConnection constructor passed invalid RTCConfiguration"
446       );
447     }
449     let certificates = rtcConfig.certificates || [];
451     if (certificates.some(c => c.expires <= Date.now())) {
452       throw new this._win.DOMException(
453         "Unable to create RTCPeerConnection with an expired certificate",
454         "InvalidAccessError"
455       );
456     }
458     // TODO(bug 1531875): Check origin of certs
460     // TODO(bug 1176518): Remove this code once we support multiple certs
461     let certificate;
462     if (certificates.length == 1) {
463       certificate = certificates[0];
464     } else if (certificates.length) {
465       throw new this._win.DOMException(
466         "RTCPeerConnection does not currently support multiple certificates",
467         "NotSupportedError"
468       );
469     }
471     this._documentPrincipal = Cu.getWebIDLCallerPrincipal();
473     if (_globalPCList._networkdown) {
474       throw new this._win.DOMException(
475         "Can't create RTCPeerConnections when the network is down",
476         "InvalidStateError"
477       );
478     }
480     this.makeGetterSetterEH("ontrack");
481     this.makeLegacyGetterSetterEH(
482       "onaddstream",
483       "Use peerConnection.ontrack instead."
484     );
485     this.makeLegacyGetterSetterEH(
486       "onaddtrack",
487       "Use peerConnection.ontrack instead."
488     );
489     this.makeGetterSetterEH("onicecandidate");
490     this.makeGetterSetterEH("onnegotiationneeded");
491     this.makeGetterSetterEH("onsignalingstatechange");
492     this.makeGetterSetterEH("ondatachannel");
493     this.makeGetterSetterEH("oniceconnectionstatechange");
494     this.makeGetterSetterEH("onicegatheringstatechange");
495     this.makeGetterSetterEH("onidentityresult");
496     this.makeGetterSetterEH("onpeeridentity");
497     this.makeGetterSetterEH("onidpassertionerror");
498     this.makeGetterSetterEH("onidpvalidationerror");
500     this._pc = new this._win.PeerConnectionImpl();
501     this._operations = [];
502     this._updateNegotiationNeededOnEmptyChain = false;
504     this.__DOM_IMPL__._innerObject = this;
505     const observer = new this._win.PeerConnectionObserver(this.__DOM_IMPL__);
507     // Add a reference to the PeerConnection to global list (before init).
508     _globalPCList.addPC(this);
510     this._impl.initialize(
511       observer,
512       this._win,
513       rtcConfig,
514       Services.tm.currentThread
515     );
517     this._certificateReady = this._initCertificate(certificate);
518     this._initIdp();
519     _globalPCList.notifyLifecycleObservers(this, "initialized");
520   }
522   get _impl() {
523     if (!this._pc) {
524       throw new this._win.DOMException(
525         "RTCPeerConnection is gone (did you enter Offline mode?)",
526         "InvalidStateError"
527       );
528     }
529     return this._pc;
530   }
532   getConfiguration() {
533     return this._config;
534   }
536   async _initCertificate(certificate) {
537     if (!certificate) {
538       certificate = await this._win.RTCPeerConnection.generateCertificate({
539         name: "ECDSA",
540         namedCurve: "P-256",
541       });
542     }
543     // Is the PC still around after the await?
544     if (!this._closed) {
545       this._impl.certificate = certificate;
546     }
547   }
549   _resetPeerIdentityPromise() {
550     this._peerIdentity = new this._win.Promise((resolve, reject) => {
551       this._resolvePeerIdentity = resolve;
552       this._rejectPeerIdentity = reject;
553     });
554   }
556   _initIdp() {
557     this._resetPeerIdentityPromise();
558     this._lastIdentityValidation = this._win.Promise.resolve();
560     let prefName = "media.peerconnection.identity.timeout";
561     let idpTimeout = Services.prefs.getIntPref(prefName);
562     this._localIdp = new PeerConnectionIdp(this._win, idpTimeout);
563     this._remoteIdp = new PeerConnectionIdp(this._win, idpTimeout);
564   }
566   // Add a function to the internal operations chain.
568   _chain(operation) {
569     let resolveP, rejectP;
570     const p = new Promise((r, e) => {
571       resolveP = r;
572       rejectP = e;
573     });
574     this._operations.push(() => {
575       operation().then(resolveP, rejectP);
576       const doNextOperation = () => {
577         if (this._closed) {
578           return;
579         }
580         this._operations.shift();
581         if (this._operations.length) {
582           this._operations[0]();
583         } else if (this._updateNegotiationNeededOnEmptyChain) {
584           this._updateNegotiationNeededOnEmptyChain = false;
585           this.updateNegotiationNeeded();
586         }
587       };
588       p.then(doNextOperation, doNextOperation);
589     });
590     if (this._operations.length == 1) {
591       this._operations[0]();
592     }
593     return p;
594   }
596   // It's basically impossible to use async directly in JSImplemented code,
597   // because the implicit promise must be wrapped to the right type for content.
598   //
599   // The _async wrapper takes care of this. The _legacy wrapper implements
600   // legacy callbacks in a manner that produces correct line-numbers in errors,
601   // provided that methods validate their inputs before putting themselves on
602   // the pc's operations chain.
603   //
604   // These wrappers also serve as guards against settling promises past close().
606   _async(func) {
607     return this._win.Promise.resolve(this._closeWrapper(func));
608   }
610   _legacy(...args) {
611     return this._win.Promise.resolve(this._legacyCloseWrapper(...args));
612   }
614   _auto(onSucc, onErr, func) {
615     return typeof onSucc == "function"
616       ? this._legacy(onSucc, onErr, func)
617       : this._async(func);
618   }
620   async _closeWrapper(func) {
621     let closed = this._closed;
622     try {
623       let result = await func();
624       if (!closed && this._closed) {
625         await new Promise(() => {});
626       }
627       return result;
628     } catch (e) {
629       if (!closed && this._closed) {
630         await new Promise(() => {});
631       }
632       throw e;
633     }
634   }
636   async _legacyCloseWrapper(onSucc, onErr, func) {
637     let wrapCallback = cb => result => {
638       try {
639         cb && cb(result);
640       } catch (e) {
641         this.logErrorAndCallOnError(e);
642       }
643     };
645     try {
646       wrapCallback(onSucc)(await func());
647     } catch (e) {
648       wrapCallback(onErr)(e);
649     }
650   }
652   // This implements the fairly common "Queue a task" logic
653   async _queueTaskWithClosedCheck(func) {
654     const pc = this;
655     return new this._win.Promise((resolve, reject) => {
656       Services.tm.dispatchToMainThread({
657         run() {
658           try {
659             if (!pc._closed) {
660               func();
661               resolve();
662             }
663           } catch (e) {
664             reject(e);
665           }
666         },
667       });
668     });
669   }
671   /**
672    * An RTCConfiguration may look like this:
673    *
674    * { "iceServers": [ { urls: "stun:stun.example.org", },
675    *                   { url: "stun:stun.example.org", }, // deprecated version
676    *                   { urls: ["turn:turn1.x.org", "turn:turn2.x.org"],
677    *                     username:"jib", credential:"mypass"} ] }
678    *
679    * This function normalizes the structure of the input for rtcConfig.iceServers for us,
680    * so we test well-formed stun/turn urls before passing along to C++.
681    *   msg - Error message to detail which array-entry failed, if any.
682    */
683   _mustValidateRTCConfiguration({ iceServers }, msg) {
684     // Normalize iceServers input
685     iceServers.forEach(server => {
686       if (typeof server.urls === "string") {
687         server.urls = [server.urls];
688       } else if (!server.urls && server.url) {
689         // TODO: Remove support for legacy iceServer.url eventually (Bug 1116766)
690         server.urls = [server.url];
691         this.logWarning("RTCIceServer.url is deprecated! Use urls instead.");
692       }
693     });
695     let nicerNewURI = uriStr => {
696       try {
697         return Services.io.newURI(uriStr);
698       } catch (e) {
699         if (e.result == Cr.NS_ERROR_MALFORMED_URI) {
700           throw new this._win.DOMException(
701             `${msg} - malformed URI: ${uriStr}`,
702             "SyntaxError"
703           );
704         }
705         throw e;
706       }
707     };
709     var stunServers = 0;
711     iceServers.forEach(({ urls, username, credential, credentialType }) => {
712       if (!urls) {
713         // TODO: Remove once url is deprecated (Bug 1369563)
714         throw new this._win.TypeError(
715           "Missing required 'urls' member of RTCIceServer"
716         );
717       }
718       if (urls.length == 0) {
719         throw new this._win.DOMException(
720           `${msg} - urls is empty`,
721           "SyntaxError"
722         );
723       }
724       urls
725         .map(url => nicerNewURI(url))
726         .forEach(({ scheme, spec }) => {
727           if (scheme in { turn: 1, turns: 1 }) {
728             if (username == undefined) {
729               throw new this._win.DOMException(
730                 `${msg} - missing username: ${spec}`,
731                 "InvalidAccessError"
732               );
733             }
734             if (username.length > 512) {
735               throw new this._win.DOMException(
736                 `${msg} - username longer then 512 bytes: ${username}`,
737                 "InvalidAccessError"
738               );
739             }
740             if (credential == undefined) {
741               throw new this._win.DOMException(
742                 `${msg} - missing credential: ${spec}`,
743                 "InvalidAccessError"
744               );
745             }
746             if (credentialType != "password") {
747               this.logWarning(
748                 `RTCConfiguration TURN credentialType \"${credentialType}\"` +
749                   " is not yet implemented. Treating as password." +
750                   " https://bugzil.la/1247616"
751               );
752             }
753             this._hasTurnServer = true;
754             stunServers += 1;
755           } else if (scheme in { stun: 1, stuns: 1 }) {
756             this._hasStunServer = true;
757             stunServers += 1;
758           } else {
759             throw new this._win.DOMException(
760               `${msg} - improper scheme: ${scheme}`,
761               "SyntaxError"
762             );
763           }
764           if (scheme in { stuns: 1 }) {
765             this.logWarning(scheme.toUpperCase() + " is not yet supported.");
766           }
767           if (stunServers >= 5) {
768             this.logError(
769               "Using five or more STUN/TURN servers causes problems"
770             );
771           } else if (stunServers > 2) {
772             this.logWarning(
773               "Using more than two STUN/TURN servers slows down discovery"
774             );
775           }
776         });
777     });
778   }
780   // Ideally, this should be of the form _checkState(state),
781   // where the state is taken from an enumeration containing
782   // the valid peer connection states defined in the WebRTC
783   // spec. See Bug 831756.
784   _checkClosed() {
785     if (this._closed) {
786       throw new this._win.DOMException(
787         "Peer connection is closed",
788         "InvalidStateError"
789       );
790     }
791   }
793   dispatchEvent(event) {
794     // PC can close while events are firing if there is an async dispatch
795     // in c++ land. But let through "closed" signaling and ice connection events.
796     if (!this._suppressEvents) {
797       this.__DOM_IMPL__.dispatchEvent(event);
798     }
799   }
801   // Log error message to web console and window.onerror, if present.
802   logErrorAndCallOnError(e) {
803     this.logMsg(
804       e.message,
805       e.fileName,
806       e.lineNumber,
807       Ci.nsIScriptError.errorFlag
808     );
810     // Safely call onerror directly if present (necessary for testing)
811     try {
812       if (typeof this._win.onerror === "function") {
813         this._win.onerror(e.message, e.fileName, e.lineNumber);
814       }
815     } catch (e) {
816       // If onerror itself throws, service it.
817       try {
818         this.logMsg(
819           e.message,
820           e.fileName,
821           e.lineNumber,
822           Ci.nsIScriptError.errorFlag
823         );
824       } catch (e) {}
825     }
826   }
828   logError(msg) {
829     this.logStackMsg(msg, Ci.nsIScriptError.errorFlag);
830   }
832   logWarning(msg) {
833     this.logStackMsg(msg, Ci.nsIScriptError.warningFlag);
834   }
836   logStackMsg(msg, flag) {
837     let err = this._win.Error();
838     this.logMsg(msg, err.fileName, err.lineNumber, flag);
839   }
841   logMsg(msg, file, line, flag) {
842     return logMsg(msg, file, line, flag, this._winID);
843   }
845   getEH(type) {
846     return this.__DOM_IMPL__.getEventHandler(type);
847   }
849   setEH(type, handler) {
850     this.__DOM_IMPL__.setEventHandler(type, handler);
851   }
853   makeGetterSetterEH(name) {
854     Object.defineProperty(this, name, {
855       get() {
856         return this.getEH(name);
857       },
858       set(h) {
859         this.setEH(name, h);
860       },
861     });
862   }
864   makeLegacyGetterSetterEH(name, msg) {
865     Object.defineProperty(this, name, {
866       get() {
867         return this.getEH(name);
868       },
869       set(h) {
870         this.logWarning(name + " is deprecated! " + msg);
871         this.setEH(name, h);
872       },
873     });
874   }
876   createOffer(optionsOrOnSucc, onErr, options) {
877     let onSuccess = null;
878     if (typeof optionsOrOnSucc == "function") {
879       onSuccess = optionsOrOnSucc;
880     } else {
881       options = optionsOrOnSucc;
882     }
883     // This entry-point handles both new and legacy call sig. Decipher which one
884     if (onSuccess) {
885       return this._legacy(onSuccess, onErr, () => this._createOffer(options));
886     }
887     return this._async(() => this._createOffer(options));
888   }
890   // Ensures that we have at least one transceiver of |kind| that is
891   // configured to receive. It will create one if necessary.
892   _ensureOfferToReceive(kind) {
893     let hasRecv = this._transceivers.some(
894       transceiver =>
895         transceiver.getKind() == kind &&
896         (transceiver.direction == "sendrecv" ||
897           transceiver.direction == "recvonly") &&
898         !transceiver.stopped
899     );
901     if (!hasRecv) {
902       this._addTransceiverNoEvents(kind, { direction: "recvonly" });
903     }
904   }
906   // Handles offerToReceiveAudio/Video
907   _ensureTransceiversForOfferToReceive(options) {
908     if (options.offerToReceiveAudio) {
909       this._ensureOfferToReceive("audio");
910     }
912     if (options.offerToReceiveVideo) {
913       this._ensureOfferToReceive("video");
914     }
916     this._transceivers
917       .filter(transceiver => {
918         return (
919           (options.offerToReceiveVideo === false &&
920             transceiver.receiver.track.kind == "video") ||
921           (options.offerToReceiveAudio === false &&
922             transceiver.receiver.track.kind == "audio")
923         );
924       })
925       .forEach(transceiver => {
926         if (transceiver.direction == "sendrecv") {
927           transceiver.setDirectionInternal("sendonly");
928         } else if (transceiver.direction == "recvonly") {
929           transceiver.setDirectionInternal("inactive");
930         }
931       });
932   }
934   _createOffer(options) {
935     this._checkClosed();
936     this._ensureTransceiversForOfferToReceive(options);
937     this._syncTransceivers();
938     return this._chain(() => this._createAnOffer(options));
939   }
941   async _createAnOffer(options = {}) {
942     switch (this.signalingState) {
943       case "stable":
944       case "have-local-offer":
945         break;
946       default:
947         throw new this._win.DOMException(
948           `Cannot create offer in ${this.signalingState}`,
949           "InvalidStateError"
950         );
951     }
952     if (this._localUfragsToReplace.size > 0) {
953       options.iceRestart = true;
954     }
955     let haveAssertion;
956     if (this._localIdp.enabled) {
957       haveAssertion = this._getIdentityAssertion();
958     }
959     await this._getPermission();
960     await this._certificateReady;
961     let sdp = await new Promise((resolve, reject) => {
962       this._onCreateOfferSuccess = resolve;
963       this._onCreateOfferFailure = reject;
964       this._impl.createOffer(options);
965     });
966     if (haveAssertion) {
967       await haveAssertion;
968       sdp = this._localIdp.addIdentityAttribute(sdp);
969     }
970     return Cu.cloneInto({ type: "offer", sdp }, this._win);
971   }
973   createAnswer(optionsOrOnSucc, onErr) {
974     // This entry-point handles both new and legacy call sig. Decipher which one
975     if (typeof optionsOrOnSucc == "function") {
976       return this._legacy(optionsOrOnSucc, onErr, () => this._createAnswer({}));
977     }
978     return this._async(() => this._createAnswer(optionsOrOnSucc));
979   }
981   _createAnswer(options) {
982     this._checkClosed();
983     this._syncTransceivers();
984     return this._chain(() => this._createAnAnswer());
985   }
987   async _createAnAnswer() {
988     if (this.signalingState != "have-remote-offer") {
989       throw new this._win.DOMException(
990         `Cannot create answer in ${this.signalingState}`,
991         "InvalidStateError"
992       );
993     }
994     let haveAssertion;
995     if (this._localIdp.enabled) {
996       haveAssertion = this._getIdentityAssertion();
997     }
998     await this._getPermission();
999     await this._certificateReady;
1000     let sdp = await new Promise((resolve, reject) => {
1001       this._onCreateAnswerSuccess = resolve;
1002       this._onCreateAnswerFailure = reject;
1003       this._impl.createAnswer();
1004     });
1005     if (haveAssertion) {
1006       await haveAssertion;
1007       sdp = this._localIdp.addIdentityAttribute(sdp);
1008     }
1009     return Cu.cloneInto({ type: "answer", sdp }, this._win);
1010   }
1012   async _getPermission() {
1013     if (!this._havePermission) {
1014       const privileged =
1015         this._documentPrincipal.isSystemPrincipal ||
1016         Services.prefs.getBoolPref("media.navigator.permission.disabled");
1018       if (privileged) {
1019         this._havePermission = Promise.resolve();
1020       } else {
1021         this._havePermission = new Promise((resolve, reject) => {
1022           this._settlePermission = { allow: resolve, deny: reject };
1023           let outerId = this._win.docShell.outerWindowID;
1025           let chrome = new CreateOfferRequest(
1026             outerId,
1027             this._winID,
1028             this._globalPCListId,
1029             false
1030           );
1031           let request = this._win.CreateOfferRequest._create(this._win, chrome);
1032           Services.obs.notifyObservers(request, "PeerConnection:request");
1033         });
1034       }
1035     }
1036     return this._havePermission;
1037   }
1039   _sanityCheckSdp(sdp) {
1040     // The fippo butter finger filter AKA non-ASCII chars
1041     // Note: SDP allows non-ASCII character in the subject (who cares?)
1042     // eslint-disable-next-line no-control-regex
1043     let pos = sdp.search(/[^\u0000-\u007f]/);
1044     if (pos != -1) {
1045       throw new this._win.DOMException(
1046         "SDP contains non ASCII characters at position " + pos,
1047         "InvalidParameterError"
1048       );
1049     }
1050   }
1052   setLocalDescription(desc, onSucc, onErr) {
1053     return this._auto(onSucc, onErr, () => this._setLocalDescription(desc));
1054   }
1056   _setLocalDescription({ type, sdp }) {
1057     if (type == "pranswer") {
1058       throw new this._win.DOMException(
1059         "pranswer not yet implemented",
1060         "NotSupportedError"
1061       );
1062     }
1063     this._checkClosed();
1064     return this._chain(async () => {
1065       // Avoid Promise.all ahead of synchronous part of spec algorithm, since it
1066       // defers. NOTE: The spec says to return an already-rejected promise in
1067       // some cases, which is difficult to achieve in practice from JS (would
1068       // require avoiding await and then() entirely), but we want to come as
1069       // close as we reasonably can.
1070       const p = this._getPermission();
1071       if (!type) {
1072         switch (this.signalingState) {
1073           case "stable":
1074           case "have-local-offer":
1075           case "have-remote-pranswer":
1076             type = "offer";
1077             break;
1078           default:
1079             type = "answer";
1080             break;
1081         }
1082       }
1083       if (!sdp) {
1084         if (type == "offer") {
1085           await this._createAnOffer();
1086         } else if (type == "answer") {
1087           await this._createAnAnswer();
1088         }
1089       } else {
1090         this._sanityCheckSdp(sdp);
1091       }
1092       await new Promise((resolve, reject) => {
1093         this._onSetDescriptionSuccess = resolve;
1094         this._onSetDescriptionFailure = reject;
1095         this._impl.setLocalDescription(this._actions[type], sdp);
1096       });
1097       await p;
1098       this._negotiationNeeded = false;
1099       if (type == "answer") {
1100         if (this._localUfragsToReplace.size > 0) {
1101           const ufrags = new Set(this._getUfragsWithPwds(sdp));
1102           if (![...this._localUfragsToReplace].some(uf => ufrags.has(uf))) {
1103             this._localUfragsToReplace.clear();
1104           }
1105         }
1106       }
1107       this.updateNegotiationNeeded();
1108     });
1109   }
1111   async _validateIdentity(sdp, origin) {
1112     // Only run a single identity verification at a time.  We have to do this to
1113     // avoid problems with the fact that identity validation doesn't block the
1114     // resolution of setRemoteDescription().
1115     const validate = async () => {
1116       await this._lastIdentityValidation;
1117       const msg = await this._remoteIdp.verifyIdentityFromSDP(sdp, origin);
1118       // If this pc has an identity already, then the identity in sdp must match
1119       if (
1120         this._impl.peerIdentity &&
1121         (!msg || msg.identity !== this._impl.peerIdentity)
1122       ) {
1123         throw new this._win.DOMException(
1124           "Peer Identity mismatch, expected: " + this._impl.peerIdentity,
1125           "OperationError"
1126         );
1127       }
1129       if (msg) {
1130         // Set new identity and generate an event.
1131         this._impl.peerIdentity = msg.identity;
1132         this._resolvePeerIdentity(
1133           Cu.cloneInto(
1134             {
1135               idp: this._remoteIdp.provider,
1136               name: msg.identity,
1137             },
1138             this._win
1139           )
1140         );
1141       }
1142     };
1144     const haveValidation = validate();
1146     // Always eat errors on this chain
1147     this._lastIdentityValidation = haveValidation.catch(() => {});
1149     // If validation fails, we have some work to do. Fork it so it cannot
1150     // interfere with the validation chain itself, even if the catch function
1151     // throws.
1152     haveValidation.catch(e => {
1153       this._rejectPeerIdentity(e);
1155       // If we don't expect a specific peer identity, failure to get a valid
1156       // peer identity is not a terminal state, so replace the promise to
1157       // allow another attempt.
1158       if (!this._impl.peerIdentity) {
1159         this._resetPeerIdentityPromise();
1160       }
1161     });
1163     // Only wait for IdP validation if we need identity matching
1164     if (this._impl.peerIdentity) {
1165       await haveValidation;
1166     }
1167   }
1169   setRemoteDescription(desc, onSucc, onErr) {
1170     return this._auto(onSucc, onErr, () => this._setRemoteDescription(desc));
1171   }
1173   _setRemoteDescription({ type, sdp }) {
1174     if (!type) {
1175       throw new this._win.TypeError(
1176         "Missing required 'type' member of RTCSessionDescriptionInit"
1177       );
1178     }
1179     if (type == "pranswer") {
1180       throw new this._win.DOMException(
1181         "pranswer not yet implemented",
1182         "NotSupportedError"
1183       );
1184     }
1185     this._checkClosed();
1186     return this._chain(async () => {
1187       const haveSetRemote = (async () => {
1188         if (type == "offer" && this.signalingState == "have-local-offer") {
1189           await new Promise((resolve, reject) => {
1190             this._onSetDescriptionSuccess = resolve;
1191             this._onSetDescriptionFailure = reject;
1192             this._impl.setLocalDescription(
1193               Ci.IPeerConnection.kActionRollback,
1194               ""
1195             );
1196           });
1197           this._transceivers = this._transceivers.filter(t => !t.shouldRemove);
1198           this._updateCanTrickle();
1199         }
1200         this._sanityCheckSdp(sdp);
1201         const p = this._getPermission();
1202         await new Promise((resolve, reject) => {
1203           this._onSetDescriptionSuccess = resolve;
1204           this._onSetDescriptionFailure = reject;
1205           this._impl.setRemoteDescription(this._actions[type], sdp);
1206         });
1207         await p;
1208         this._transceivers = this._transceivers.filter(t => !t.shouldRemove);
1209         this._updateCanTrickle();
1210       })();
1212       if (type != "rollback") {
1213         // Do setRemoteDescription and identity validation in parallel
1214         await this._validateIdentity(sdp);
1215       }
1216       await haveSetRemote;
1217       this._negotiationNeeded = false;
1218       if (type == "answer") {
1219         if (this._localUfragsToReplace.size > 0) {
1220           const ufrags = new Set(
1221             this._getUfragsWithPwds(this._impl.currentLocalDescription)
1222           );
1223           if (![...this._localUfragsToReplace].some(uf => ufrags.has(uf))) {
1224             this._localUfragsToReplace.clear();
1225           }
1226         }
1227       }
1228       this.updateNegotiationNeeded();
1229     });
1230   }
1232   setIdentityProvider(provider, { protocol, usernameHint, peerIdentity } = {}) {
1233     this._checkClosed();
1234     peerIdentity = peerIdentity || this._impl.peerIdentity;
1235     this._localIdp.setIdentityProvider(
1236       provider,
1237       protocol,
1238       usernameHint,
1239       peerIdentity
1240     );
1241   }
1243   async _getIdentityAssertion() {
1244     await this._certificateReady;
1245     return this._localIdp.getIdentityAssertion(
1246       this._impl.fingerprint,
1247       this._documentPrincipal.origin
1248     );
1249   }
1251   getIdentityAssertion() {
1252     this._checkClosed();
1253     return this._win.Promise.resolve(
1254       this._chain(() => this._getIdentityAssertion())
1255     );
1256   }
1258   get canTrickleIceCandidates() {
1259     return this._canTrickle;
1260   }
1262   _updateCanTrickle() {
1263     let containsTrickle = section => {
1264       let lines = section.toLowerCase().split(/(?:\r\n?|\n)/);
1265       return lines.some(line => {
1266         let prefix = "a=ice-options:";
1267         if (line.substring(0, prefix.length) !== prefix) {
1268           return false;
1269         }
1270         let tokens = line.substring(prefix.length).split(" ");
1271         return tokens.some(x => x === "trickle");
1272       });
1273     };
1275     let desc = null;
1276     try {
1277       // The getter for remoteDescription can throw if the pc is closed.
1278       desc = this.remoteDescription;
1279     } catch (e) {}
1280     if (!desc) {
1281       this._canTrickle = null;
1282       return;
1283     }
1285     let sections = desc.sdp.split(/(?:\r\n?|\n)m=/);
1286     let topSection = sections.shift();
1287     this._canTrickle =
1288       containsTrickle(topSection) || sections.every(containsTrickle);
1289   }
1291   addIceCandidate(cand, onSucc, onErr) {
1292     if (
1293       cand.candidate != "" &&
1294       cand.sdpMid == null &&
1295       cand.sdpMLineIndex == null
1296     ) {
1297       throw new this._win.TypeError(
1298         "Cannot add a candidate without specifying either sdpMid or sdpMLineIndex"
1299       );
1300     }
1301     return this._auto(onSucc, onErr, () => this._addIceCandidate(cand));
1302   }
1304   async _addIceCandidate({
1305     candidate,
1306     sdpMid,
1307     sdpMLineIndex,
1308     usernameFragment,
1309   }) {
1310     this._checkClosed();
1311     return this._chain(async () => {
1312       if (
1313         !this._impl.pendingRemoteDescription.length &&
1314         !this._impl.currentRemoteDescription.length
1315       ) {
1316         throw new this._win.DOMException(
1317           "No remoteDescription.",
1318           "InvalidStateError"
1319         );
1320       }
1321       return new Promise((resolve, reject) => {
1322         this._onAddIceCandidateSuccess = resolve;
1323         this._onAddIceCandidateError = reject;
1324         this._impl.addIceCandidate(
1325           candidate,
1326           sdpMid || "",
1327           usernameFragment || "",
1328           sdpMLineIndex
1329         );
1330       });
1331     });
1332   }
1334   restartIce() {
1335     this._localUfragsToReplace = new Set([
1336       ...this._getUfragsWithPwds(this._impl.currentLocalDescription),
1337       ...this._getUfragsWithPwds(this._impl.pendingLocalDescription),
1338     ]);
1339     this.updateNegotiationNeeded();
1340   }
1342   _getUfragsWithPwds(sdp) {
1343     return (
1344       sdp
1345         .split("\r\nm=")
1346         .map(block => block.split("\r\n"))
1347         .map(lines => [
1348           lines.find(l => l.startsWith("a=ice-ufrag:")),
1349           lines.find(l => l.startsWith("a=ice-pwd:")),
1350         ])
1351         // Even though our own SDP doesn't currently do this: JSEP says properties
1352         // found in the session (array[0]) apply to all m-lines that don't specify
1353         // them, like default values.
1354         .map(([a, b], i, array) => [a || array[0][0], b || array[0][1]])
1355         .filter(([a, b]) => a && b)
1356         .map(array => array.join())
1357     );
1358   }
1360   addStream(stream) {
1361     stream.getTracks().forEach(track => this.addTrack(track, stream));
1362   }
1364   addTrack(track, ...streams) {
1365     this._checkClosed();
1367     if (
1368       this._transceivers.some(transceiver => transceiver.sender.track == track)
1369     ) {
1370       throw new this._win.DOMException(
1371         "This track is already set on a sender.",
1372         "InvalidAccessError"
1373       );
1374     }
1376     let transceiver = this._transceivers.find(transceiver => {
1377       return (
1378         transceiver.sender.track == null &&
1379         transceiver.getKind() == track.kind &&
1380         !transceiver.stopped &&
1381         !transceiver.hasBeenUsedToSend()
1382       );
1383     });
1385     if (transceiver) {
1386       transceiver.sender.setTrack(track);
1387       transceiver.sender.setStreams(streams);
1388       if (transceiver.direction == "recvonly") {
1389         transceiver.setDirectionInternal("sendrecv");
1390       } else if (transceiver.direction == "inactive") {
1391         transceiver.setDirectionInternal("sendonly");
1392       }
1393     } else {
1394       transceiver = this._addTransceiverNoEvents(track, {
1395         streams,
1396         direction: "sendrecv",
1397       });
1398     }
1400     transceiver.setAddTrackMagic();
1401     transceiver.sync();
1402     this.updateNegotiationNeeded();
1403     return transceiver.sender;
1404   }
1406   removeTrack(sender) {
1407     this._checkClosed();
1409     sender.checkWasCreatedByPc(this.__DOM_IMPL__);
1411     let transceiver = this._transceivers.find(
1412       transceiver => !transceiver.stopped && transceiver.sender == sender
1413     );
1415     // If the transceiver was removed due to rollback, let it slide.
1416     if (!transceiver || !sender.track) {
1417       return;
1418     }
1420     sender.setTrack(null);
1421     if (transceiver.direction == "sendrecv") {
1422       transceiver.setDirectionInternal("recvonly");
1423     } else if (transceiver.direction == "sendonly") {
1424       transceiver.setDirectionInternal("inactive");
1425     }
1427     transceiver.sync();
1428     this.updateNegotiationNeeded();
1429   }
1431   _addTransceiverNoEvents(sendTrackOrKind, init) {
1432     let sendTrack = null;
1433     let kind;
1434     if (typeof sendTrackOrKind == "string") {
1435       kind = sendTrackOrKind;
1436       switch (kind) {
1437         case "audio":
1438         case "video":
1439           break;
1440         default:
1441           throw new this._win.TypeError("Invalid media kind");
1442       }
1443     } else {
1444       sendTrack = sendTrackOrKind;
1445       kind = sendTrack.kind;
1446     }
1448     let transceiverImpl = this._impl.createTransceiverImpl(kind, sendTrack);
1449     let transceiver = this._win.RTCRtpTransceiver._create(
1450       this._win,
1451       new RTCRtpTransceiver(this, transceiverImpl, init, kind, sendTrack)
1452     );
1453     transceiver.sync();
1454     this._transceivers.push(transceiver);
1455     return transceiver;
1456   }
1458   _onTransceiverNeeded(kind, transceiverImpl) {
1459     let init = { direction: "recvonly" };
1460     let transceiver = this._win.RTCRtpTransceiver._create(
1461       this._win,
1462       new RTCRtpTransceiver(this, transceiverImpl, init, kind, null)
1463     );
1464     transceiver.sync();
1465     this._transceivers.push(transceiver);
1466   }
1468   addTransceiver(sendTrackOrKind, init) {
1469     this._checkClosed();
1470     let transceiver = this._addTransceiverNoEvents(sendTrackOrKind, init);
1471     this.updateNegotiationNeeded();
1472     return transceiver;
1473   }
1475   _syncTransceivers() {
1476     this._transceivers.forEach(transceiver => transceiver.sync());
1477   }
1479   updateNegotiationNeeded() {
1480     if (this._operations.length) {
1481       this._updateNegotiationNeededOnEmptyChain = true;
1482       return;
1483     }
1484     this._queueTaskWithClosedCheck(() => {
1485       if (this._operations.length) {
1486         this._updateNegotiationNeededOnEmptyChain = true;
1487         return;
1488       }
1489       if (this.signalingState != "stable") {
1490         return;
1491       }
1493       const negotiationNeeded =
1494         this._impl.checkNegotiationNeeded() ||
1495         this._localUfragsToReplace.size > 0;
1496       if (!negotiationNeeded) {
1497         this._negotiationNeeded = false;
1498         return;
1499       }
1501       if (this._negotiationNeeded) {
1502         return;
1503       }
1505       this._negotiationNeeded = true;
1506       this.dispatchEvent(new this._win.Event("negotiationneeded"));
1507     });
1508   }
1510   _replaceTrackNoRenegotiation(transceiverImpl, withTrack) {
1511     this._impl.replaceTrackNoRenegotiation(transceiverImpl, withTrack);
1512   }
1514   close() {
1515     if (this._closed) {
1516       return;
1517     }
1518     this._closed = true;
1519     this.changeIceConnectionState("closed");
1520     if (this._localIdp) {
1521       this._localIdp.close();
1522     }
1523     if (this._remoteIdp) {
1524       this._remoteIdp.close();
1525     }
1526     if (!this._suppressEvents) {
1527       this._transceivers.forEach(t => t.setStopped());
1528     }
1529     this._impl.close();
1530     this._suppressEvents = true;
1531     delete this._pc;
1532   }
1534   getLocalStreams() {
1535     this._checkClosed();
1536     let localStreams = new Set();
1537     this._transceivers.forEach(transceiver => {
1538       transceiver.sender.getStreams().forEach(stream => {
1539         localStreams.add(stream);
1540       });
1541     });
1542     return [...localStreams.values()];
1543   }
1545   getRemoteStreams() {
1546     this._checkClosed();
1547     return this._impl.getRemoteStreams();
1548   }
1550   getSenders() {
1551     return this.getTransceivers()
1552       .filter(transceiver => !transceiver.stopped)
1553       .map(transceiver => transceiver.sender);
1554   }
1556   getReceivers() {
1557     return this.getTransceivers()
1558       .filter(transceiver => !transceiver.stopped)
1559       .map(transceiver => transceiver.receiver);
1560   }
1562   mozSetPacketCallback(callback) {
1563     this._onPacket = callback;
1564   }
1566   mozEnablePacketDump(level, type, sending) {
1567     this._impl.enablePacketDump(level, type, sending);
1568   }
1570   mozDisablePacketDump(level, type, sending) {
1571     this._impl.disablePacketDump(level, type, sending);
1572   }
1574   getTransceivers() {
1575     return this._transceivers;
1576   }
1578   get localDescription() {
1579     return this.pendingLocalDescription || this.currentLocalDescription;
1580   }
1582   get currentLocalDescription() {
1583     this._checkClosed();
1584     const sdp = this._impl.currentLocalDescription;
1585     if (sdp.length == 0) {
1586       return null;
1587     }
1588     const type = this._impl.currentOfferer ? "offer" : "answer";
1589     return new this._win.RTCSessionDescription({ type, sdp });
1590   }
1592   get pendingLocalDescription() {
1593     this._checkClosed();
1594     const sdp = this._impl.pendingLocalDescription;
1595     if (sdp.length == 0) {
1596       return null;
1597     }
1598     const type = this._impl.pendingOfferer ? "offer" : "answer";
1599     return new this._win.RTCSessionDescription({ type, sdp });
1600   }
1602   get remoteDescription() {
1603     return this.pendingRemoteDescription || this.currentRemoteDescription;
1604   }
1606   get currentRemoteDescription() {
1607     this._checkClosed();
1608     const sdp = this._impl.currentRemoteDescription;
1609     if (sdp.length == 0) {
1610       return null;
1611     }
1612     const type = this._impl.currentOfferer ? "answer" : "offer";
1613     return new this._win.RTCSessionDescription({ type, sdp });
1614   }
1616   get pendingRemoteDescription() {
1617     this._checkClosed();
1618     const sdp = this._impl.pendingRemoteDescription;
1619     if (sdp.length == 0) {
1620       return null;
1621     }
1622     const type = this._impl.pendingOfferer ? "answer" : "offer";
1623     return new this._win.RTCSessionDescription({ type, sdp });
1624   }
1626   get peerIdentity() {
1627     return this._peerIdentity;
1628   }
1629   get idpLoginUrl() {
1630     return this._localIdp.idpLoginUrl;
1631   }
1632   get id() {
1633     return this._impl.id;
1634   }
1635   set id(s) {
1636     this._impl.id = s;
1637   }
1638   get iceGatheringState() {
1639     return this._pc.iceGatheringState;
1640   }
1641   get iceConnectionState() {
1642     return this._iceConnectionState;
1643   }
1645   get signalingState() {
1646     // checking for our local pc closed indication
1647     // before invoking the pc methods.
1648     if (this._closed) {
1649       return "closed";
1650     }
1651     return this._impl.signalingState;
1652   }
1654   handleIceGatheringStateChange() {
1655     _globalPCList.notifyLifecycleObservers(this, "icegatheringstatechange");
1656     this.dispatchEvent(new this._win.Event("icegatheringstatechange"));
1657     if (this.iceGatheringState === "complete") {
1658       this.dispatchEvent(
1659         new this._win.RTCPeerConnectionIceEvent("icecandidate", {
1660           candidate: null,
1661         })
1662       );
1663     }
1664   }
1666   changeIceConnectionState(state) {
1667     if (state != this._iceConnectionState) {
1668       this._iceConnectionState = state;
1669       _globalPCList.notifyLifecycleObservers(this, "iceconnectionstatechange");
1670       if (!this._closed) {
1671         this.dispatchEvent(new this._win.Event("iceconnectionstatechange"));
1672       }
1673     }
1674   }
1676   getStats(selector, onSucc, onErr) {
1677     if (selector !== null) {
1678       let matchingSenders = this.getSenders().filter(s => s.track === selector);
1679       let matchingReceivers = this.getReceivers().filter(
1680         r => r.track === selector
1681       );
1683       if (matchingSenders.length + matchingReceivers.length != 1) {
1684         throw new this._win.DOMException(
1685           "track must be associated with a unique sender or receiver, but " +
1686             " is associated with " +
1687             matchingSenders.length +
1688             " senders and " +
1689             matchingReceivers.length +
1690             " receivers.",
1691           "InvalidAccessError"
1692         );
1693       }
1694     }
1696     return this._auto(onSucc, onErr, () => this._impl.getStats(selector));
1697   }
1699   createDataChannel(
1700     label,
1701     {
1702       maxRetransmits,
1703       ordered,
1704       negotiated,
1705       id = null,
1706       maxRetransmitTime,
1707       maxPacketLifeTime,
1708       protocol,
1709     } = {}
1710   ) {
1711     this._checkClosed();
1712     this._pcTelemetry.recordDataChannelInit(
1713       maxRetransmitTime,
1714       maxPacketLifeTime
1715     );
1717     if (maxPacketLifeTime === undefined) {
1718       maxPacketLifeTime = maxRetransmitTime;
1719     }
1721     if (maxRetransmitTime !== undefined) {
1722       this.logWarning(
1723         "Use maxPacketLifeTime instead of deprecated maxRetransmitTime which will stop working soon in createDataChannel!"
1724       );
1725     }
1727     if (protocol.length > 32767) {
1728       // At least 65536/2 UTF-16 characters. UTF-8 might be too long.
1729       // Spec says to check how long |protocol| and |label| are in _bytes_. This
1730       // is a little ambiguous. For now, examine the length of the utf-8 encoding.
1731       const byteCounter = new TextEncoder("utf-8");
1733       if (byteCounter.encode(protocol).length > 65535) {
1734         throw new this._win.TypeError(
1735           "protocol cannot be longer than 65535 bytes"
1736         );
1737       }
1738     }
1740     if (label.length > 32767) {
1741       const byteCounter = new TextEncoder("utf-8");
1742       if (byteCounter.encode(label).length > 65535) {
1743         throw new this._win.TypeError(
1744           "label cannot be longer than 65535 bytes"
1745         );
1746       }
1747     }
1749     if (!negotiated) {
1750       id = null;
1751     } else if (id === null) {
1752       throw new this._win.TypeError("id is required when negotiated is true");
1753     }
1754     if (maxPacketLifeTime !== undefined && maxRetransmits !== undefined) {
1755       throw new this._win.TypeError(
1756         "Both maxPacketLifeTime and maxRetransmits cannot be provided"
1757       );
1758     }
1759     if (id == 65535) {
1760       throw new this._win.TypeError("id cannot be 65535");
1761     }
1762     // Must determine the type where we still know if entries are undefined.
1763     let type;
1764     if (maxPacketLifeTime !== undefined) {
1765       type = Ci.IPeerConnection.kDataChannelPartialReliableTimed;
1766     } else if (maxRetransmits !== undefined) {
1767       type = Ci.IPeerConnection.kDataChannelPartialReliableRexmit;
1768     } else {
1769       type = Ci.IPeerConnection.kDataChannelReliable;
1770     }
1771     // Synchronous since it doesn't block.
1772     let dataChannel;
1773     try {
1774       dataChannel = this._impl.createDataChannel(
1775         label,
1776         protocol,
1777         type,
1778         ordered,
1779         maxPacketLifeTime,
1780         maxRetransmits,
1781         negotiated,
1782         id
1783       );
1784     } catch (e) {
1785       if (e.result != Cr.NS_ERROR_NOT_AVAILABLE) {
1786         throw e;
1787       }
1789       const msg =
1790         id === null ? "No available id could be generated" : "Id is in use";
1791       throw new this._win.DOMException(msg, "OperationError");
1792     }
1794     // Spec says to only do this if this is the first DataChannel created,
1795     // but the c++ code that does the "is negotiation needed" checking will
1796     // only ever return true on the first one.
1797     this.updateNegotiationNeeded();
1799     return dataChannel;
1800   }
1802 setupPrototype(RTCPeerConnection, {
1803   classID: PC_CID,
1804   contractID: PC_CONTRACT,
1805   QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
1806   _actions: {
1807     offer: Ci.IPeerConnection.kActionOffer,
1808     answer: Ci.IPeerConnection.kActionAnswer,
1809     pranswer: Ci.IPeerConnection.kActionPRAnswer,
1810     rollback: Ci.IPeerConnection.kActionRollback,
1811   },
1814 // This is a separate class because we don't want to expose it to DOM.
1816 class PeerConnectionObserver {
1817   init(win) {
1818     this._win = win;
1819   }
1821   __init(dompc) {
1822     this._dompc = dompc._innerObject;
1823   }
1825   newError({ message, name }) {
1826     return new this._dompc._win.DOMException(message, name);
1827   }
1829   dispatchEvent(event) {
1830     this._dompc.dispatchEvent(event);
1831   }
1833   onCreateOfferSuccess(sdp) {
1834     this._dompc._onCreateOfferSuccess(sdp);
1835   }
1837   onCreateOfferError(error) {
1838     this._dompc._onCreateOfferFailure(this.newError(error));
1839   }
1841   onCreateAnswerSuccess(sdp) {
1842     this._dompc._onCreateAnswerSuccess(sdp);
1843   }
1845   onCreateAnswerError(error) {
1846     this._dompc._onCreateAnswerFailure(this.newError(error));
1847   }
1849   onSetDescriptionSuccess() {
1850     this._dompc._onSetDescriptionSuccess();
1851   }
1853   onSetDescriptionError(error) {
1854     this._dompc._onSetDescriptionFailure(this.newError(error));
1855   }
1857   onAddIceCandidateSuccess() {
1858     this._dompc._onAddIceCandidateSuccess();
1859   }
1861   onAddIceCandidateError(error) {
1862     this._dompc._onAddIceCandidateError(this.newError(error));
1863   }
1865   onIceCandidate(sdpMLineIndex, sdpMid, candidate, usernameFragment) {
1866     let win = this._dompc._win;
1867     if (candidate || sdpMid || usernameFragment) {
1868       if (candidate.includes(" typ relay ")) {
1869         this._dompc._iceGatheredRelayCandidates = true;
1870       }
1871       candidate = new win.RTCIceCandidate({
1872         candidate,
1873         sdpMid,
1874         sdpMLineIndex,
1875         usernameFragment,
1876       });
1877     }
1878     this.dispatchEvent(
1879       new win.RTCPeerConnectionIceEvent("icecandidate", { candidate })
1880     );
1881   }
1883   // This method is primarily responsible for updating iceConnectionState.
1884   // This state is defined in the WebRTC specification as follows:
1885   //
1886   // iceConnectionState:
1887   // -------------------
1888   //   new           Any of the RTCIceTransports are in the new state and none
1889   //                 of them are in the checking, failed or disconnected state.
1890   //
1891   //   checking      Any of the RTCIceTransports are in the checking state and
1892   //                 none of them are in the failed or disconnected state.
1893   //
1894   //   connected     All RTCIceTransports are in the connected, completed or
1895   //                 closed state and at least one of them is in the connected
1896   //                 state.
1897   //
1898   //   completed     All RTCIceTransports are in the completed or closed state
1899   //                 and at least one of them is in the completed state.
1900   //
1901   //   failed        Any of the RTCIceTransports are in the failed state.
1902   //
1903   //   disconnected  Any of the RTCIceTransports are in the disconnected state
1904   //                 and none of them are in the failed state.
1905   //
1906   //   closed        All of the RTCIceTransports are in the closed state.
1908   handleIceConnectionStateChange(iceConnectionState) {
1909     let pc = this._dompc;
1910     if (pc.iceConnectionState === iceConnectionState) {
1911       return;
1912     }
1914     if (iceConnectionState === "failed") {
1915       if (!pc._hasStunServer) {
1916         pc.logError(
1917           "ICE failed, add a STUN server and see about:webrtc for more details"
1918         );
1919       } else if (!pc._hasTurnServer) {
1920         pc.logError(
1921           "ICE failed, add a TURN server and see about:webrtc for more details"
1922         );
1923       } else if (pc._hasTurnServer && !pc._iceGatheredRelayCandidates) {
1924         pc.logError(
1925           "ICE failed, your TURN server appears to be broken, see about:webrtc for more details"
1926         );
1927       } else {
1928         pc.logError("ICE failed, see about:webrtc for more details");
1929       }
1930     }
1932     pc.changeIceConnectionState(iceConnectionState);
1933   }
1935   onStateChange(state) {
1936     if (!this._dompc) {
1937       return;
1938     }
1940     if (state == "SignalingState") {
1941       this.dispatchEvent(new this._win.Event("signalingstatechange"));
1942       return;
1943     }
1945     if (!this._dompc._pc) {
1946       return;
1947     }
1949     switch (state) {
1950       case "IceConnectionState":
1951         let connState = this._dompc._pc.iceConnectionState;
1952         this._dompc._queueTaskWithClosedCheck(() => {
1953           this.handleIceConnectionStateChange(connState);
1954         });
1955         break;
1957       case "IceGatheringState":
1958         this._dompc.handleIceGatheringStateChange();
1959         break;
1961       default:
1962         this._dompc.logWarning("Unhandled state type: " + state);
1963         break;
1964     }
1965   }
1967   onTransceiverNeeded(kind, transceiverImpl) {
1968     this._dompc._onTransceiverNeeded(kind, transceiverImpl);
1969   }
1971   notifyDataChannel(channel) {
1972     this.dispatchEvent(
1973       new this._dompc._win.RTCDataChannelEvent("datachannel", { channel })
1974     );
1975   }
1977   fireTrackEvent(receiver, streams) {
1978     const pc = this._dompc;
1979     const transceiver = pc.getTransceivers().find(t => t.receiver == receiver);
1980     if (!transceiver) {
1981       return;
1982     }
1983     const track = receiver.track;
1984     this.dispatchEvent(
1985       new this._win.RTCTrackEvent("track", {
1986         transceiver,
1987         receiver,
1988         track,
1989         streams,
1990       })
1991     );
1992     // Fire legacy event as well for a little bit.
1993     this.dispatchEvent(
1994       new this._win.MediaStreamTrackEvent("addtrack", { track })
1995     );
1996   }
1998   fireStreamEvent(stream) {
1999     const ev = new this._win.MediaStreamEvent("addstream", { stream });
2000     this.dispatchEvent(ev);
2001   }
2003   onPacket(level, type, sending, packet) {
2004     var pc = this._dompc;
2005     if (pc._onPacket) {
2006       pc._onPacket(level, type, sending, packet);
2007     }
2008   }
2010   syncTransceivers() {
2011     this._dompc._syncTransceivers();
2012   }
2014 setupPrototype(PeerConnectionObserver, {
2015   classID: PC_OBS_CID,
2016   contractID: PC_OBS_CONTRACT,
2017   QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
2020 class RTCPeerConnectionStatic {
2021   init(win) {
2022     this._winID = win.windowGlobalChild.innerWindowId;
2023   }
2025   registerPeerConnectionLifecycleCallback(cb) {
2026     _globalPCList._registerPeerConnectionLifecycleCallback(this._winID, cb);
2027   }
2029 setupPrototype(RTCPeerConnectionStatic, {
2030   classID: PC_STATIC_CID,
2031   contractID: PC_STATIC_CONTRACT,
2032   QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
2035 class RTCRtpSender {
2036   constructor(pc, transceiverImpl, transceiver, track, kind, streams) {
2037     let dtmf = null;
2038     if (kind == "audio") {
2039       dtmf = transceiverImpl.dtmf;
2040     }
2042     Object.assign(this, {
2043       _pc: pc,
2044       _transceiverImpl: transceiverImpl,
2045       _transceiver: transceiver,
2046       track,
2047       _streams: streams,
2048       dtmf,
2049     });
2050   }
2052   replaceTrack(withTrack) {
2053     // async functions in here return a chrome promise, which is not something
2054     // content can use. This wraps that promise in something content can use.
2055     return this._pc._win.Promise.resolve(this._replaceTrack(withTrack));
2056   }
2058   async _replaceTrack(withTrack) {
2059     let pc = this._pc;
2060     if (withTrack && withTrack.kind != this._transceiver.getKind()) {
2061       throw new pc._win.TypeError("Cannot replaceTrack with a different kind!");
2062     }
2064     pc._checkClosed();
2066     await pc._chain(async () => {
2067       if (this._transceiver.stopped) {
2068         throw new pc._win.DOMException(
2069           "Cannot call replaceTrack when transceiver is stopped",
2070           "InvalidStateError"
2071         );
2072       }
2074       await pc._queueTaskWithClosedCheck(() => {
2075         // Updates the track on the MediaPipeline, will throw on failure.
2076         try {
2077           pc._replaceTrackNoRenegotiation(this._transceiverImpl, withTrack);
2078         } catch (e) {
2079           throw new pc._win.DOMException(
2080             "Track could not be replaced without renegotiation",
2081             "InvalidModificationError"
2082           );
2083         }
2084         this.track = withTrack;
2085         this._transceiver.sync();
2086       });
2087     });
2088   }
2090   setParameters(parameters) {
2091     return this._pc._win.Promise.resolve(this._setParameters(parameters));
2092   }
2094   async _setParameters(parameters) {
2095     this._pc._checkClosed();
2097     if (this._transceiver.stopped) {
2098       throw new this._pc._win.DOMException(
2099         "This sender's transceiver is stopped",
2100         "InvalidStateError"
2101       );
2102     }
2104     if (!Services.prefs.getBoolPref("media.peerconnection.simulcast")) {
2105       return;
2106     }
2108     parameters.encodings = parameters.encodings || [];
2110     parameters.encodings.reduce(
2111       (uniqueRids, { rid, scaleResolutionDownBy }) => {
2112         if (scaleResolutionDownBy < 1.0) {
2113           throw new this._pc._win.RangeError(
2114             "scaleResolutionDownBy must be >= 1.0"
2115           );
2116         }
2117         if (!rid && parameters.encodings.length > 1) {
2118           throw new this._pc._win.TypeError("Missing rid");
2119         }
2120         if (uniqueRids[rid]) {
2121           throw new this._pc._win.TypeError("Duplicate rid");
2122         }
2123         uniqueRids[rid] = true;
2124         return uniqueRids;
2125       },
2126       {}
2127     );
2129     // TODO(bug 1401592): transaction ids, timing changes
2131     await this._pc._queueTaskWithClosedCheck(() => {
2132       this.parameters = parameters;
2133       this._transceiver.sync();
2134     });
2135   }
2137   getParameters() {
2138     // TODO(bug 1401592): transaction ids
2140     // All the other stuff that the spec says to update is handled when
2141     // transceivers are synced.
2142     return this.parameters;
2143   }
2145   setStreams(streams) {
2146     this._streams = streams;
2147   }
2149   getStreams() {
2150     return this._streams;
2151   }
2153   setTrack(track) {
2154     this._pc._replaceTrackNoRenegotiation(this._transceiverImpl, track);
2155     this.track = track;
2156   }
2158   get transport() {
2159     return this._transceiverImpl.dtlsTransport;
2160   }
2162   getStats() {
2163     if (this.track) {
2164       return this._pc._async(async () => this._pc._impl.getStats(this.track));
2165     }
2166     return this._pc._win.Promise.resolve().then(
2167       () => new this._pc._win.RTCStatsReport()
2168     );
2169   }
2171   checkWasCreatedByPc(pc) {
2172     if (pc != this._pc.__DOM_IMPL__) {
2173       throw new this._pc._win.DOMException(
2174         "This sender was not created by this PeerConnection",
2175         "InvalidAccessError"
2176       );
2177     }
2178   }
2180 setupPrototype(RTCRtpSender, {
2181   classID: PC_SENDER_CID,
2182   contractID: PC_SENDER_CONTRACT,
2183   QueryInterface: ChromeUtils.generateQI([]),
2186 class RTCRtpTransceiver {
2187   constructor(pc, transceiverImpl, init, kind, sendTrack) {
2188     let receiver = transceiverImpl.receiver;
2189     let streams = (init && init.streams) || [];
2190     let sender = pc._win.RTCRtpSender._create(
2191       pc._win,
2192       new RTCRtpSender(pc, transceiverImpl, this, sendTrack, kind, streams)
2193     );
2195     let direction = (init && init.direction) || "sendrecv";
2196     Object.assign(this, {
2197       _pc: pc,
2198       mid: null,
2199       sender,
2200       receiver,
2201       stopped: false,
2202       _direction: direction,
2203       currentDirection: null,
2204       addTrackMagic: false,
2205       shouldRemove: false,
2206       _hasBeenUsedToSend: false,
2207       // the receiver starts out without a track, so record this here
2208       _kind: kind,
2209       _transceiverImpl: transceiverImpl,
2210     });
2211   }
2213   set direction(direction) {
2214     this._pc._checkClosed();
2216     if (this.stopped) {
2217       throw new this._pc._win.DOMException(
2218         "Transceiver is stopped!",
2219         "InvalidStateError"
2220       );
2221     }
2223     if (this._direction == direction) {
2224       return;
2225     }
2227     this._direction = direction;
2228     this.sync();
2229     this._pc.updateNegotiationNeeded();
2230   }
2232   get direction() {
2233     return this._direction;
2234   }
2236   setDirectionInternal(direction) {
2237     this._direction = direction;
2238   }
2240   stop() {
2241     this._pc._checkClosed();
2243     if (this.stopped) {
2244       return;
2245     }
2247     this.setStopped();
2248     this.sync();
2249     this._pc.updateNegotiationNeeded();
2250   }
2252   setStopped() {
2253     this.stopped = true;
2254     this.currentDirection = null;
2255   }
2257   getKind() {
2258     return this._kind;
2259   }
2261   hasBeenUsedToSend() {
2262     return this._hasBeenUsedToSend;
2263   }
2265   setAddTrackMagic() {
2266     this.addTrackMagic = true;
2267   }
2269   sync() {
2270     if (this._syncing) {
2271       throw new DOMException("Reentrant sync! This is a bug!", "InternalError");
2272     }
2273     this._syncing = true;
2274     this._transceiverImpl.syncWithJS(this.__DOM_IMPL__);
2275     this._syncing = false;
2276   }
2278   // Used by _transceiverImpl.syncWithJS, don't call sync again!
2279   setCurrentDirection(direction) {
2280     if (this.stopped) {
2281       return;
2282     }
2284     switch (direction) {
2285       case "sendrecv":
2286       case "sendonly":
2287         this._hasBeenUsedToSend = true;
2288         break;
2289       default:
2290     }
2292     this.currentDirection = direction;
2293   }
2295   // Used by _transceiverImpl.syncWithJS, don't call sync again!
2296   setMid(mid) {
2297     this.mid = mid;
2298   }
2300   // Used by _transceiverImpl.syncWithJS, don't call sync again!
2301   unsetMid() {
2302     this.mid = null;
2303   }
2306 setupPrototype(RTCRtpTransceiver, {
2307   classID: PC_TRANSCEIVER_CID,
2308   contractID: PC_TRANSCEIVER_CONTRACT,
2309   QueryInterface: ChromeUtils.generateQI([]),
2312 class CreateOfferRequest {
2313   constructor(windowID, innerWindowID, callID, isSecure) {
2314     Object.assign(this, { windowID, innerWindowID, callID, isSecure });
2315   }
2317 setupPrototype(CreateOfferRequest, {
2318   classID: PC_COREQUEST_CID,
2319   contractID: PC_COREQUEST_CONTRACT,
2320   QueryInterface: ChromeUtils.generateQI([]),
2323 var EXPORTED_SYMBOLS = [
2324   "GlobalPCList",
2325   "RTCIceCandidate",
2326   "RTCSessionDescription",
2327   "RTCPeerConnection",
2328   "RTCPeerConnectionStatic",
2329   "RTCRtpSender",
2330   "RTCRtpTransceiver",
2331   "PeerConnectionObserver",
2332   "CreateOfferRequest",