Bug 1824856 - migrate android build-bundle tasks from firefox-android. r=bhearsum...
[gecko.git] / dom / media / PeerConnection.sys.mjs
blob00b4023c2fdb48b578dcb5b258062e5eea600197
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 const lazy = {};
6 ChromeUtils.defineESModuleGetters(lazy, {
7   PeerConnectionIdp: "resource://gre/modules/media/PeerConnectionIdp.sys.mjs",
8 });
10 const PC_CONTRACT = "@mozilla.org/dom/peerconnection;1";
11 const PC_OBS_CONTRACT = "@mozilla.org/dom/peerconnectionobserver;1";
12 const PC_ICE_CONTRACT = "@mozilla.org/dom/rtcicecandidate;1";
13 const PC_SESSION_CONTRACT = "@mozilla.org/dom/rtcsessiondescription;1";
14 const PC_STATIC_CONTRACT = "@mozilla.org/dom/peerconnectionstatic;1";
15 const PC_COREQUEST_CONTRACT = "@mozilla.org/dom/createofferrequest;1";
17 const PC_CID = Components.ID("{bdc2e533-b308-4708-ac8e-a8bfade6d851}");
18 const PC_OBS_CID = Components.ID("{d1748d4c-7f6a-4dc5-add6-d55b7678537e}");
19 const PC_ICE_CID = Components.ID("{02b9970c-433d-4cc2-923d-f7028ac66073}");
20 const PC_SESSION_CID = Components.ID("{1775081b-b62d-4954-8ffe-a067bbf508a7}");
21 const PC_MANAGER_CID = Components.ID("{7293e901-2be3-4c02-b4bd-cbef6fc24f78}");
22 const PC_STATIC_CID = Components.ID("{0fb47c47-a205-4583-a9fc-cbadf8c95880}");
23 const PC_COREQUEST_CID = Components.ID(
24   "{74b2122d-65a8-4824-aa9e-3d664cb75dc2}"
27 function logWebRTCMsg(msg, file, line, flag, win) {
28   let scriptErrorClass = Cc["@mozilla.org/scripterror;1"];
29   let scriptError = scriptErrorClass.createInstance(Ci.nsIScriptError);
30   scriptError.initWithWindowID(
31     `WebRTC: ${msg}`,
32     file,
33     null,
34     line,
35     0,
36     flag,
37     "content javascript",
38     win.windowGlobalChild.innerWindowId
39   );
40   Services.console.logMessage(scriptError);
41   if (
42     Services.prefs.getBoolPref("media.peerconnection.treat_warnings_as_errors")
43   ) {
44     throw new win.TypeError(msg);
45   }
48 let setupPrototype = (_class, dict) => {
49   _class.prototype.classDescription = _class.name;
50   Object.assign(_class.prototype, dict);
53 // Global list of PeerConnection objects, so they can be cleaned up when
54 // a page is torn down. (Maps inner window ID to an array of PC objects).
55 export class GlobalPCList {
56   constructor() {
57     this._list = {};
58     this._networkdown = false; // XXX Need to query current state somehow
59     this._lifecycleobservers = {};
60     this._nextId = 1;
61     Services.obs.addObserver(this, "inner-window-destroyed", true);
62     Services.obs.addObserver(this, "profile-change-net-teardown", true);
63     Services.obs.addObserver(this, "network:offline-about-to-go-offline", true);
64     Services.obs.addObserver(this, "network:offline-status-changed", true);
65     Services.obs.addObserver(this, "gmp-plugin-crash", true);
66     Services.obs.addObserver(this, "PeerConnection:response:allow", true);
67     Services.obs.addObserver(this, "PeerConnection:response:deny", true);
68     if (Services.cpmm) {
69       Services.cpmm.addMessageListener("gmp-plugin-crash", this);
70     }
71   }
73   notifyLifecycleObservers(pc, type) {
74     for (var key of Object.keys(this._lifecycleobservers)) {
75       this._lifecycleobservers[key](pc, pc._winID, type);
76     }
77   }
79   addPC(pc) {
80     let winID = pc._winID;
81     if (this._list[winID]) {
82       this._list[winID].push(Cu.getWeakReference(pc));
83     } else {
84       this._list[winID] = [Cu.getWeakReference(pc)];
85     }
86     pc._globalPCListId = this._nextId++;
87     this.removeNullRefs(winID);
88   }
90   findPC(globalPCListId) {
91     for (let winId in this._list) {
92       if (this._list.hasOwnProperty(winId)) {
93         for (let pcref of this._list[winId]) {
94           let pc = pcref.get();
95           if (pc && pc._globalPCListId == globalPCListId) {
96             return pc;
97           }
98         }
99       }
100     }
101     return null;
102   }
104   removeNullRefs(winID) {
105     if (this._list[winID] === undefined) {
106       return;
107     }
108     this._list[winID] = this._list[winID].filter(function (e) {
109       return e.get() !== null;
110     });
112     if (this._list[winID].length === 0) {
113       delete this._list[winID];
114     }
115   }
117   handleGMPCrash(data) {
118     let broadcastPluginCrash = function (list, winID, pluginID, pluginName) {
119       if (list.hasOwnProperty(winID)) {
120         list[winID].forEach(function (pcref) {
121           let pc = pcref.get();
122           if (pc) {
123             pc._pc.pluginCrash(pluginID, pluginName);
124           }
125         });
126       }
127     };
129     // a plugin crashed; if it's associated with any of our PCs, fire an
130     // event to the DOM window
131     for (let winId in this._list) {
132       broadcastPluginCrash(this._list, winId, data.pluginID, data.pluginName);
133     }
134   }
136   receiveMessage({ name, data }) {
137     if (name == "gmp-plugin-crash") {
138       this.handleGMPCrash(data);
139     }
140   }
142   observe(subject, topic, data) {
143     let cleanupPcRef = function (pcref) {
144       let pc = pcref.get();
145       if (pc) {
146         pc._suppressEvents = true;
147         pc.close();
148       }
149     };
151     let cleanupWinId = function (list, winID) {
152       if (list.hasOwnProperty(winID)) {
153         list[winID].forEach(cleanupPcRef);
154         delete list[winID];
155       }
156     };
158     if (topic == "inner-window-destroyed") {
159       let winID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
160       cleanupWinId(this._list, winID);
162       if (this._lifecycleobservers.hasOwnProperty(winID)) {
163         delete this._lifecycleobservers[winID];
164       }
165     } else if (
166       topic == "profile-change-net-teardown" ||
167       topic == "network:offline-about-to-go-offline"
168     ) {
169       // As Necko doesn't prevent us from accessing the network we still need to
170       // monitor the network offline/online state here. See bug 1326483
171       this._networkdown = true;
172     } else if (topic == "network:offline-status-changed") {
173       if (data == "offline") {
174         this._networkdown = true;
175       } else if (data == "online") {
176         this._networkdown = false;
177       }
178     } else if (topic == "gmp-plugin-crash") {
179       if (subject instanceof Ci.nsIWritablePropertyBag2) {
180         let pluginID = subject.getPropertyAsUint32("pluginID");
181         let pluginName = subject.getPropertyAsAString("pluginName");
182         let data = { pluginID, pluginName };
183         this.handleGMPCrash(data);
184       }
185     } else if (
186       topic == "PeerConnection:response:allow" ||
187       topic == "PeerConnection:response:deny"
188     ) {
189       var pc = this.findPC(data);
190       if (pc) {
191         if (topic == "PeerConnection:response:allow") {
192           pc._settlePermission.allow();
193         } else {
194           let err = new pc._win.DOMException(
195             "The request is not allowed by " +
196               "the user agent or the platform in the current context.",
197             "NotAllowedError"
198           );
199           pc._settlePermission.deny(err);
200         }
201       }
202     }
203   }
205   _registerPeerConnectionLifecycleCallback(winID, cb) {
206     this._lifecycleobservers[winID] = cb;
207   }
210 setupPrototype(GlobalPCList, {
211   QueryInterface: ChromeUtils.generateQI([
212     "nsIObserver",
213     "nsISupportsWeakReference",
214   ]),
215   classID: PC_MANAGER_CID,
218 var _globalPCList = new GlobalPCList();
220 export class RTCIceCandidate {
221   init(win) {
222     this._win = win;
223   }
225   __init(dict) {
226     if (dict.sdpMid == null && dict.sdpMLineIndex == null) {
227       throw new this._win.TypeError(
228         "Either sdpMid or sdpMLineIndex must be specified"
229       );
230     }
231     Object.assign(this, dict);
232   }
235 setupPrototype(RTCIceCandidate, {
236   classID: PC_ICE_CID,
237   contractID: PC_ICE_CONTRACT,
238   QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
241 export class RTCSessionDescription {
242   init(win) {
243     this._win = win;
244     this._winID = this._win.windowGlobalChild.innerWindowId;
245     this._legacyPref = Services.prefs.getBoolPref(
246       "media.peerconnection.description.legacy.enabled"
247     );
248   }
250   __init({ type, sdp }) {
251     Object.assign(this, { _type: type, _sdp: sdp });
252   }
254   get type() {
255     return this._type;
256   }
257   set type(type) {
258     if (!this._legacyPref) {
259       // TODO: this throws even in sloppy mode. Remove in bug 1883992
260       throw new this._win.TypeError("setting getter-only property type");
261     }
262     this.warn();
263     this._type = type;
264   }
266   get sdp() {
267     return this._sdp;
268   }
269   set sdp(sdp) {
270     if (!this._legacyPref) {
271       // TODO: this throws even in sloppy mode. Remove in bug 1883992
272       throw new this._win.TypeError("setting getter-only property sdp");
273     }
274     this.warn();
275     this._sdp = sdp;
276   }
278   warn() {
279     if (!this._warned) {
280       // Warn once per RTCSessionDescription about deprecated writable usage.
281       if (this._legacyPref) {
282         this.logMsg(
283           "RTCSessionDescription's members are readonly! " +
284             "Writing to them is deprecated and will break soon!",
285           Ci.nsIScriptError.warningFlag
286         );
287       } else {
288         this.logMsg(
289           "RTCSessionDescription's members are readonly! " +
290             "Writing to them no longer works!",
291           Ci.nsIScriptError.errorFlag
292         );
293       }
294       this._warned = true;
295     }
296   }
298   logMsg(msg, flag) {
299     let err = this._win.Error();
300     logWebRTCMsg(msg, err.fileName, err.lineNumber, flag, this._win);
301   }
304 setupPrototype(RTCSessionDescription, {
305   classID: PC_SESSION_CID,
306   contractID: PC_SESSION_CONTRACT,
307   QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
310 // Records PC related telemetry
311 class PeerConnectionTelemetry {
312   // ICE connection state enters connected or completed.
313   recordConnected() {
314     Services.telemetry.scalarAdd("webrtc.peerconnection.connected", 1);
315     this.recordConnected = () => {};
316   }
317   // DataChannel is created
318   _recordDataChannelCreated() {
319     Services.telemetry.scalarAdd(
320       "webrtc.peerconnection.datachannel_created",
321       1
322     );
323     this._recordDataChannelCreated = () => {};
324   }
325   // DataChannel initialized with maxRetransmitTime
326   _recordMaxRetransmitTime(maxRetransmitTime) {
327     if (maxRetransmitTime === undefined) {
328       return false;
329     }
330     Services.telemetry.scalarAdd(
331       "webrtc.peerconnection.datachannel_max_retx_used",
332       1
333     );
334     this._recordMaxRetransmitTime = () => true;
335     return true;
336   }
337   // DataChannel initialized with maxPacketLifeTime
338   _recordMaxPacketLifeTime(maxPacketLifeTime) {
339     if (maxPacketLifeTime === undefined) {
340       return false;
341     }
342     Services.telemetry.scalarAdd(
343       "webrtc.peerconnection.datachannel_max_life_used",
344       1
345     );
346     this._recordMaxPacketLifeTime = () => true;
347     return true;
348   }
349   // DataChannel initialized
350   recordDataChannelInit(maxRetransmitTime, maxPacketLifeTime) {
351     const retxUsed = this._recordMaxRetransmitTime(maxRetransmitTime);
352     if (this._recordMaxPacketLifeTime(maxPacketLifeTime) && retxUsed) {
353       Services.telemetry.scalarAdd(
354         "webrtc.peerconnection.datachannel_max_retx_and_life_used",
355         1
356       );
357       this.recordDataChannelInit = () => {};
358     }
359     this._recordDataChannelCreated();
360   }
363 export class RTCPeerConnection {
364   constructor() {
365     this._pc = null;
366     this._closed = false;
367     this._pendingLocalDescription = null;
368     this._pendingRemoteDescription = null;
369     this._currentLocalDescription = null;
370     this._currentRemoteDescription = null;
371     this._legacyPref = Services.prefs.getBoolPref(
372       "media.peerconnection.description.legacy.enabled"
373     );
375     // http://rtcweb-wg.github.io/jsep/#rfc.section.4.1.9
376     // canTrickle == null means unknown; when a remote description is received it
377     // is set to true or false based on the presence of the "trickle" ice-option
378     this._canTrickle = null;
380     // So we can record telemetry on state transitions
381     this._iceConnectionState = "new";
383     this._hasStunServer = this._hasTurnServer = false;
384     this._iceGatheredRelayCandidates = false;
385     // Records telemetry
386     this._pcTelemetry = new PeerConnectionTelemetry();
387   }
389   init(win) {
390     this._win = win;
391   }
393   // Pref-based overrides; will _not_ be reflected in getConfiguration
394   _applyPrefsToConfig(rtcConfig) {
395     if (
396       rtcConfig.iceTransportPolicy == "all" &&
397       Services.prefs.getBoolPref("media.peerconnection.ice.relay_only")
398     ) {
399       rtcConfig.iceTransportPolicy = "relay";
400     }
402     if (
403       !rtcConfig.iceServers ||
404       !Services.prefs.getBoolPref(
405         "media.peerconnection.use_document_iceservers"
406       )
407     ) {
408       try {
409         rtcConfig.iceServers = JSON.parse(
410           Services.prefs.getCharPref(
411             "media.peerconnection.default_iceservers"
412           ) || "[]"
413         );
414       } catch (e) {
415         this.logWarning(
416           "Ignoring invalid media.peerconnection.default_iceservers in about:config"
417         );
418         rtcConfig.iceServers = [];
419       }
420       try {
421         this._validateIceServers(
422           rtcConfig.iceServers,
423           "Ignoring invalid media.peerconnection.default_iceservers in about:config"
424         );
425       } catch (e) {
426         this.logWarning(e.message);
427         rtcConfig.iceServers = [];
428       }
429     }
430   }
432   _validateConfig(rtcConfig) {
433     if ("sdpSemantics" in rtcConfig) {
434       if (rtcConfig.sdpSemantics == "plan-b") {
435         this.logWarning(
436           `Outdated and non-standard {sdpSemantics: "plan-b"} is not ` +
437             `supported! WebRTC may be unreliable. Please update code to ` +
438             `follow standard "unified-plan".`
439         );
440       }
441       // Don't let it show up in getConfiguration.
442       delete rtcConfig.sdpSemantics;
443     }
445     if (this._config) {
446       // certificates must match
447       if (rtcConfig.certificates.length != this._config.certificates.length) {
448         throw new this._win.DOMException(
449           "Cannot change certificates with setConfiguration (length differs)",
450           "InvalidModificationError"
451         );
452       }
453       for (let i = 0; i < rtcConfig.certificates.length; i++) {
454         if (rtcConfig.certificates[i] != this._config.certificates[i]) {
455           throw new this._win.DOMException(
456             `Cannot change certificates with setConfiguration ` +
457               `(cert at index ${i} differs)`,
458             "InvalidModificationError"
459           );
460         }
461       }
463       // bundlePolicy must match
464       if (rtcConfig.bundlePolicy != this._config.bundlePolicy) {
465         throw new this._win.DOMException(
466           "Cannot change bundlePolicy with setConfiguration",
467           "InvalidModificationError"
468         );
469       }
471       // peerIdentity must match
472       if (
473         rtcConfig.peerIdentity &&
474         rtcConfig.peerIdentity != this._config.peerIdentity
475       ) {
476         throw new this._win.DOMException(
477           "Cannot change peerIdentity with setConfiguration",
478           "InvalidModificationError"
479         );
480       }
482       // TODO (bug 1339203): rtcpMuxPolicy must match
483       // TODO (bug 1529398): iceCandidatePoolSize must match if sLD has ever
484       // been called.
485     }
487     // This gets executed in the typical case when iceServers
488     // are passed in through the web page.
489     this._validateIceServers(
490       rtcConfig.iceServers,
491       "RTCPeerConnection constructor passed invalid RTCConfiguration"
492     );
493   }
495   _checkIfIceRestartRequired(rtcConfig) {
496     if (this._config) {
497       if (rtcConfig.iceTransportPolicy != this._config.iceTransportPolicy) {
498         this._pc.restartIceNoRenegotiationNeeded();
499         return;
500       }
501       if (
502         JSON.stringify(this._config.iceServers) !=
503         JSON.stringify(rtcConfig.iceServers)
504       ) {
505         this._pc.restartIceNoRenegotiationNeeded();
506       }
507     }
508   }
510   __init(rtcConfig) {
511     this._winID = this._win.windowGlobalChild.innerWindowId;
512     let certificates = rtcConfig.certificates || [];
514     if (certificates.some(c => c.expires <= Date.now())) {
515       throw new this._win.DOMException(
516         "Unable to create RTCPeerConnection with an expired certificate",
517         "InvalidAccessError"
518       );
519     }
521     // TODO(bug 1531875): Check origin of certs
523     // TODO(bug 1176518): Remove this code once we support multiple certs
524     let certificate;
525     if (certificates.length == 1) {
526       certificate = certificates[0];
527     } else if (certificates.length) {
528       throw new this._win.DOMException(
529         "RTCPeerConnection does not currently support multiple certificates",
530         "NotSupportedError"
531       );
532     }
534     this._documentPrincipal = Cu.getWebIDLCallerPrincipal();
536     if (_globalPCList._networkdown) {
537       throw new this._win.DOMException(
538         "Can't create RTCPeerConnections when the network is down",
539         "InvalidStateError"
540       );
541     }
543     this.makeGetterSetterEH("ontrack");
544     this.makeLegacyGetterSetterEH(
545       "onaddstream",
546       "Use peerConnection.ontrack instead."
547     );
548     this.makeLegacyGetterSetterEH(
549       "onaddtrack",
550       "Use peerConnection.ontrack instead."
551     );
552     this.makeGetterSetterEH("onicecandidate");
553     this.makeGetterSetterEH("onnegotiationneeded");
554     this.makeGetterSetterEH("onsignalingstatechange");
555     this.makeGetterSetterEH("ondatachannel");
556     this.makeGetterSetterEH("oniceconnectionstatechange");
557     this.makeGetterSetterEH("onicegatheringstatechange");
558     this.makeGetterSetterEH("onconnectionstatechange");
559     this.makeGetterSetterEH("onidentityresult");
560     this.makeGetterSetterEH("onpeeridentity");
561     this.makeGetterSetterEH("onidpassertionerror");
562     this.makeGetterSetterEH("onidpvalidationerror");
564     this._pc = new this._win.PeerConnectionImpl();
566     this.__DOM_IMPL__._innerObject = this;
567     const observer = new this._win.PeerConnectionObserver(this.__DOM_IMPL__);
569     // Add a reference to the PeerConnection to global list (before init).
570     _globalPCList.addPC(this);
572     this._pc.initialize(observer, this._win);
574     this.setConfiguration(rtcConfig);
576     this._certificateReady = this._initCertificate(certificate);
577     this._initIdp();
578     _globalPCList.notifyLifecycleObservers(this, "initialized");
579   }
581   getConfiguration() {
582     const config = Object.assign({}, this._config);
583     delete config.sdpSemantics;
584     return config;
585   }
587   setConfiguration(rtcConfig) {
588     this._checkClosed();
589     this._validateConfig(rtcConfig);
590     this._checkIfIceRestartRequired(rtcConfig);
592     // Allow prefs to tweak these settings before passing to c++, but hide all
593     // of that from JS.
594     const configWithPrefTweaks = Object.assign({}, rtcConfig);
595     this._applyPrefsToConfig(configWithPrefTweaks);
596     this._pc.setConfiguration(configWithPrefTweaks);
598     this._config = Object.assign({}, rtcConfig);
599   }
601   async _initCertificate(certificate) {
602     if (!certificate) {
603       certificate = await this._win.RTCPeerConnection.generateCertificate({
604         name: "ECDSA",
605         namedCurve: "P-256",
606       });
607     }
608     this._pc.certificate = certificate;
609   }
611   _resetPeerIdentityPromise() {
612     this._peerIdentity = new this._win.Promise((resolve, reject) => {
613       this._resolvePeerIdentity = resolve;
614       this._rejectPeerIdentity = reject;
615     });
616   }
618   _initIdp() {
619     this._resetPeerIdentityPromise();
620     this._lastIdentityValidation = this._win.Promise.resolve();
622     let prefName = "media.peerconnection.identity.timeout";
623     let idpTimeout = Services.prefs.getIntPref(prefName);
624     this._localIdp = new lazy.PeerConnectionIdp(this._win, idpTimeout);
625     this._remoteIdp = new lazy.PeerConnectionIdp(this._win, idpTimeout);
626   }
628   // Add a function to the internal operations chain.
630   _chain(operation) {
631     return this._pc.chain(operation);
632   }
634   // It's basically impossible to use async directly in JSImplemented code,
635   // because the implicit promise must be wrapped to the right type for content.
636   //
637   // The _async wrapper takes care of this. The _legacy wrapper implements
638   // legacy callbacks in a manner that produces correct line-numbers in errors,
639   // provided that methods validate their inputs before putting themselves on
640   // the pc's operations chain.
641   //
642   // These wrappers also serve as guards against settling promises past close().
644   _async(func) {
645     return this._win.Promise.resolve(this._closeWrapper(func));
646   }
648   _legacy(...args) {
649     return this._win.Promise.resolve(this._legacyCloseWrapper(...args));
650   }
652   _auto(onSucc, onErr, func) {
653     return typeof onSucc == "function"
654       ? this._legacy(onSucc, onErr, func)
655       : this._async(func);
656   }
658   async _closeWrapper(func) {
659     let closed = this._closed;
660     try {
661       let result = await func();
662       if (!closed && this._closed) {
663         await new Promise(() => {});
664       }
665       return result;
666     } catch (e) {
667       if (!closed && this._closed) {
668         await new Promise(() => {});
669       }
670       throw e;
671     }
672   }
674   async _legacyCloseWrapper(onSucc, onErr, func) {
675     let wrapCallback = cb => result => {
676       try {
677         cb && cb(result);
678       } catch (e) {
679         this.logErrorAndCallOnError(e);
680       }
681     };
683     try {
684       wrapCallback(onSucc)(await func());
685     } catch (e) {
686       wrapCallback(onErr)(e);
687     }
688   }
690   // This implements the fairly common "Queue a task" logic
691   async _queueTaskWithClosedCheck(func) {
692     const pc = this;
693     return new this._win.Promise((resolve, reject) => {
694       Services.tm.dispatchToMainThread({
695         run() {
696           try {
697             if (!pc._closed) {
698               func();
699               resolve();
700             }
701           } catch (e) {
702             reject(e);
703           }
704         },
705       });
706     });
707   }
709   /**
710    * An RTCConfiguration may look like this:
711    *
712    * { "iceServers": [ { urls: "stun:stun.example.org", },
713    *                   { url: "stun:stun.example.org", }, // deprecated version
714    *                   { urls: ["turn:turn1.x.org", "turn:turn2.x.org"],
715    *                     username:"jib", credential:"mypass"} ] }
716    *
717    * This function normalizes the structure of the input for rtcConfig.iceServers for us,
718    * so we test well-formed stun/turn urls before passing along to C++.
719    *   msg - Error message to detail which array-entry failed, if any.
720    */
721   _validateIceServers(iceServers, msg) {
722     // Normalize iceServers input
723     iceServers.forEach(server => {
724       if (typeof server.urls === "string") {
725         server.urls = [server.urls];
726       } else if (!server.urls && server.url) {
727         // TODO: Remove support for legacy iceServer.url eventually (Bug 1116766)
728         server.urls = [server.url];
729         this.logWarning("RTCIceServer.url is deprecated! Use urls instead.");
730       }
731     });
733     let nicerNewURI = uriStr => {
734       try {
735         return Services.io.newURI(uriStr);
736       } catch (e) {
737         if (e.result == Cr.NS_ERROR_MALFORMED_URI) {
738           throw new this._win.DOMException(
739             `${msg} - malformed URI: ${uriStr}`,
740             "SyntaxError"
741           );
742         }
743         throw e;
744       }
745     };
747     let stunServers = 0;
749     iceServers.forEach(({ urls, username, credential, credentialType }) => {
750       if (!urls) {
751         // TODO: Remove once url is deprecated (Bug 1369563)
752         throw new this._win.TypeError(
753           "Missing required 'urls' member of RTCIceServer"
754         );
755       }
756       if (!urls.length) {
757         throw new this._win.DOMException(
758           `${msg} - urls is empty`,
759           "SyntaxError"
760         );
761       }
762       urls
763         .map(url => nicerNewURI(url))
764         .forEach(({ scheme, spec, query }) => {
765           if (scheme in { turn: 1, turns: 1 }) {
766             if (username == undefined) {
767               throw new this._win.DOMException(
768                 `${msg} - missing username: ${spec}`,
769                 "InvalidAccessError"
770               );
771             }
772             if (username.length > 512) {
773               throw new this._win.DOMException(
774                 `${msg} - username longer then 512 bytes: ${username}`,
775                 "InvalidAccessError"
776               );
777             }
778             if (credential == undefined) {
779               throw new this._win.DOMException(
780                 `${msg} - missing credential: ${spec}`,
781                 "InvalidAccessError"
782               );
783             }
784             if (credentialType != "password") {
785               this.logWarning(
786                 `RTCConfiguration TURN credentialType \"${credentialType}\"` +
787                   " is not yet implemented. Treating as password." +
788                   " https://bugzil.la/1247616"
789               );
790             }
791             this._hasTurnServer = true;
792             // If this is not a TURN TCP/TLS server, it is also a STUN server
793             const parameters = query.split("&");
794             if (!parameters.includes("transport=tcp")) {
795               this._hasStunServer = true;
796             }
797             stunServers += 1;
798           } else if (scheme in { stun: 1, stuns: 1 }) {
799             this._hasStunServer = true;
800             stunServers += 1;
801           } else {
802             throw new this._win.DOMException(
803               `${msg} - improper scheme: ${scheme}`,
804               "SyntaxError"
805             );
806           }
807           if (scheme in { stuns: 1 }) {
808             this.logWarning(scheme.toUpperCase() + " is not yet supported.");
809           }
810           if (stunServers >= 5) {
811             this.logError(
812               "Using five or more STUN/TURN servers slows down discovery"
813             );
814           }
815         });
816     });
817   }
819   // Ideally, this should be of the form _checkState(state),
820   // where the state is taken from an enumeration containing
821   // the valid peer connection states defined in the WebRTC
822   // spec. See Bug 831756.
823   _checkClosed() {
824     if (this._closed) {
825       throw new this._win.DOMException(
826         "Peer connection is closed",
827         "InvalidStateError"
828       );
829     }
830   }
832   dispatchEvent(event) {
833     // PC can close while events are firing if there is an async dispatch
834     // in c++ land. But let through "closed" signaling and ice connection events.
835     if (!this._suppressEvents) {
836       this.__DOM_IMPL__.dispatchEvent(event);
837     }
838   }
840   // Log error message to web console and window.onerror, if present.
841   logErrorAndCallOnError(e) {
842     this.logMsg(
843       e.message,
844       e.fileName,
845       e.lineNumber,
846       Ci.nsIScriptError.errorFlag
847     );
849     // Safely call onerror directly if present (necessary for testing)
850     try {
851       if (typeof this._win.onerror === "function") {
852         this._win.onerror(e.message, e.fileName, e.lineNumber);
853       }
854     } catch (e) {
855       // If onerror itself throws, service it.
856       try {
857         this.logMsg(
858           e.message,
859           e.fileName,
860           e.lineNumber,
861           Ci.nsIScriptError.errorFlag
862         );
863       } catch (e) {}
864     }
865   }
867   logError(msg) {
868     this.logStackMsg(msg, Ci.nsIScriptError.errorFlag);
869   }
871   logWarning(msg) {
872     this.logStackMsg(msg, Ci.nsIScriptError.warningFlag);
873   }
875   logStackMsg(msg, flag) {
876     let err = this._win.Error();
877     this.logMsg(msg, err.fileName, err.lineNumber, flag);
878   }
880   logMsg(msg, file, line, flag) {
881     return logWebRTCMsg(msg, file, line, flag, this._win);
882   }
884   getEH(type) {
885     return this.__DOM_IMPL__.getEventHandler(type);
886   }
888   setEH(type, handler) {
889     this.__DOM_IMPL__.setEventHandler(type, handler);
890   }
892   makeGetterSetterEH(name) {
893     Object.defineProperty(this, name, {
894       get() {
895         return this.getEH(name);
896       },
897       set(h) {
898         this.setEH(name, h);
899       },
900     });
901   }
903   makeLegacyGetterSetterEH(name, msg) {
904     Object.defineProperty(this, name, {
905       get() {
906         return this.getEH(name);
907       },
908       set(h) {
909         this.logWarning(name + " is deprecated! " + msg);
910         this.setEH(name, h);
911       },
912     });
913   }
915   createOffer(optionsOrOnSucc, onErr, options) {
916     let onSuccess = null;
917     if (typeof optionsOrOnSucc == "function") {
918       onSuccess = optionsOrOnSucc;
919     } else {
920       options = optionsOrOnSucc;
921     }
922     // This entry-point handles both new and legacy call sig. Decipher which one
923     if (onSuccess) {
924       return this._legacy(onSuccess, onErr, () => this._createOffer(options));
925     }
926     return this._async(() => this._createOffer(options));
927   }
929   // Ensures that we have at least one transceiver of |kind| that is
930   // configured to receive. It will create one if necessary.
931   _ensureOfferToReceive(kind) {
932     let hasRecv = this.getTransceivers().some(
933       transceiver =>
934         transceiver.getKind() == kind &&
935         (transceiver.direction == "sendrecv" ||
936           transceiver.direction == "recvonly") &&
937         !transceiver.stopped
938     );
940     if (!hasRecv) {
941       this._addTransceiverNoEvents(kind, { direction: "recvonly" });
942     }
943   }
945   // Handles offerToReceiveAudio/Video
946   _ensureTransceiversForOfferToReceive(options) {
947     if (options.offerToReceiveAudio) {
948       this._ensureOfferToReceive("audio");
949     }
951     if (options.offerToReceiveVideo) {
952       this._ensureOfferToReceive("video");
953     }
955     this.getTransceivers()
956       .filter(transceiver => {
957         return (
958           (options.offerToReceiveVideo === false &&
959             transceiver.receiver.track.kind == "video") ||
960           (options.offerToReceiveAudio === false &&
961             transceiver.receiver.track.kind == "audio")
962         );
963       })
964       .forEach(transceiver => {
965         if (transceiver.direction == "sendrecv") {
966           transceiver.setDirectionInternal("sendonly");
967         } else if (transceiver.direction == "recvonly") {
968           transceiver.setDirectionInternal("inactive");
969         }
970       });
971   }
973   _createOffer(options) {
974     this._checkClosed();
975     this._ensureTransceiversForOfferToReceive(options);
976     return this._chain(() => this._createAnOffer(options));
977   }
979   async _createAnOffer(options = {}) {
980     switch (this.signalingState) {
981       case "stable":
982       case "have-local-offer":
983         break;
984       default:
985         throw new this._win.DOMException(
986           `Cannot create offer in ${this.signalingState}`,
987           "InvalidStateError"
988         );
989     }
990     let haveAssertion;
991     if (this._localIdp.enabled) {
992       haveAssertion = this._getIdentityAssertion();
993     }
994     await this._getPermission();
995     await this._certificateReady;
996     let sdp = await new Promise((resolve, reject) => {
997       this._onCreateOfferSuccess = resolve;
998       this._onCreateOfferFailure = reject;
999       this._pc.createOffer(options);
1000     });
1001     if (haveAssertion) {
1002       await haveAssertion;
1003       sdp = this._localIdp.addIdentityAttribute(sdp);
1004     }
1005     return Cu.cloneInto({ type: "offer", sdp }, this._win);
1006   }
1008   createAnswer(optionsOrOnSucc, onErr) {
1009     // This entry-point handles both new and legacy call sig. Decipher which one
1010     if (typeof optionsOrOnSucc == "function") {
1011       return this._legacy(optionsOrOnSucc, onErr, () => this._createAnswer({}));
1012     }
1013     return this._async(() => this._createAnswer(optionsOrOnSucc));
1014   }
1016   _createAnswer() {
1017     this._checkClosed();
1018     return this._chain(() => this._createAnAnswer());
1019   }
1021   async _createAnAnswer() {
1022     if (this.signalingState != "have-remote-offer") {
1023       throw new this._win.DOMException(
1024         `Cannot create answer in ${this.signalingState}`,
1025         "InvalidStateError"
1026       );
1027     }
1028     let haveAssertion;
1029     if (this._localIdp.enabled) {
1030       haveAssertion = this._getIdentityAssertion();
1031     }
1032     await this._getPermission();
1033     await this._certificateReady;
1034     let sdp = await new Promise((resolve, reject) => {
1035       this._onCreateAnswerSuccess = resolve;
1036       this._onCreateAnswerFailure = reject;
1037       this._pc.createAnswer();
1038     });
1039     if (haveAssertion) {
1040       await haveAssertion;
1041       sdp = this._localIdp.addIdentityAttribute(sdp);
1042     }
1043     return Cu.cloneInto({ type: "answer", sdp }, this._win);
1044   }
1046   async _getPermission() {
1047     if (!this._havePermission) {
1048       const privileged =
1049         this._documentPrincipal.isSystemPrincipal ||
1050         Services.prefs.getBoolPref("media.navigator.permission.disabled");
1052       if (privileged) {
1053         this._havePermission = Promise.resolve();
1054       } else {
1055         this._havePermission = new Promise((resolve, reject) => {
1056           this._settlePermission = { allow: resolve, deny: reject };
1057           let outerId = this._win.docShell.outerWindowID;
1059           let chrome = new CreateOfferRequest(
1060             outerId,
1061             this._winID,
1062             this._globalPCListId,
1063             false
1064           );
1065           let request = this._win.CreateOfferRequest._create(this._win, chrome);
1066           Services.obs.notifyObservers(request, "PeerConnection:request");
1067         });
1068       }
1069     }
1070     return this._havePermission;
1071   }
1073   _sanityCheckSdp(sdp) {
1074     // The fippo butter finger filter AKA non-ASCII chars
1075     // Note: SDP allows non-ASCII character in the subject (who cares?)
1076     // eslint-disable-next-line no-control-regex
1077     let pos = sdp.search(/[^\u0000-\u007f]/);
1078     if (pos != -1) {
1079       throw new this._win.DOMException(
1080         "SDP contains non ASCII characters at position " + pos,
1081         "InvalidParameterError"
1082       );
1083     }
1084   }
1086   setLocalDescription(desc, onSucc, onErr) {
1087     return this._auto(onSucc, onErr, () => this._setLocalDescription(desc));
1088   }
1090   _setLocalDescription({ type, sdp }) {
1091     if (type == "pranswer") {
1092       throw new this._win.DOMException(
1093         "pranswer not yet implemented",
1094         "NotSupportedError"
1095       );
1096     }
1097     this._checkClosed();
1098     return this._chain(async () => {
1099       // Avoid Promise.all ahead of synchronous part of spec algorithm, since it
1100       // defers. NOTE: The spec says to return an already-rejected promise in
1101       // some cases, which is difficult to achieve in practice from JS (would
1102       // require avoiding await and then() entirely), but we want to come as
1103       // close as we reasonably can.
1104       const p = this._getPermission();
1105       if (!type) {
1106         switch (this.signalingState) {
1107           case "stable":
1108           case "have-local-offer":
1109           case "have-remote-pranswer":
1110             type = "offer";
1111             break;
1112           default:
1113             type = "answer";
1114             break;
1115         }
1116       }
1117       if (!sdp) {
1118         if (type == "offer") {
1119           await this._createAnOffer();
1120         } else if (type == "answer") {
1121           await this._createAnAnswer();
1122         }
1123       } else {
1124         this._sanityCheckSdp(sdp);
1125       }
1127       try {
1128         await new Promise((resolve, reject) => {
1129           this._onSetDescriptionSuccess = resolve;
1130           this._onSetDescriptionFailure = reject;
1131           this._pc.setLocalDescription(this._actions[type], sdp);
1132         });
1133         await p;
1134       } catch (e) {
1135         this._pc.onSetDescriptionError();
1136         throw e;
1137       }
1138       await this._pc.onSetDescriptionSuccess(type, false);
1139     });
1140   }
1142   async _validateIdentity(sdp, origin) {
1143     // Only run a single identity verification at a time.  We have to do this to
1144     // avoid problems with the fact that identity validation doesn't block the
1145     // resolution of setRemoteDescription().
1146     const validate = async () => {
1147       // Access this._pc synchronously in case pc is closed later
1148       const identity = this._pc.peerIdentity;
1149       await this._lastIdentityValidation;
1150       const msg = await this._remoteIdp.verifyIdentityFromSDP(sdp, origin);
1151       // If this pc has an identity already, then the identity in sdp must match
1152       if (identity && (!msg || msg.identity !== identity)) {
1153         throw new this._win.DOMException(
1154           "Peer Identity mismatch, expected: " + identity,
1155           "OperationError"
1156         );
1157       }
1158       if (this._closed) {
1159         return;
1160       }
1161       if (msg) {
1162         // Set new identity and generate an event.
1163         this._pc.peerIdentity = msg.identity;
1164         this._resolvePeerIdentity(
1165           Cu.cloneInto(
1166             {
1167               idp: this._remoteIdp.provider,
1168               name: msg.identity,
1169             },
1170             this._win
1171           )
1172         );
1173       }
1174     };
1176     const haveValidation = validate();
1178     // Always eat errors on this chain
1179     this._lastIdentityValidation = haveValidation.catch(() => {});
1181     // If validation fails, we have some work to do. Fork it so it cannot
1182     // interfere with the validation chain itself, even if the catch function
1183     // throws.
1184     haveValidation.catch(e => {
1185       if (this._closed) {
1186         return;
1187       }
1188       this._rejectPeerIdentity(e);
1190       // If we don't expect a specific peer identity, failure to get a valid
1191       // peer identity is not a terminal state, so replace the promise to
1192       // allow another attempt.
1193       if (!this._pc.peerIdentity) {
1194         this._resetPeerIdentityPromise();
1195       }
1196     });
1198     if (this._closed) {
1199       return;
1200     }
1201     // Only wait for IdP validation if we need identity matching
1202     if (this._pc.peerIdentity) {
1203       await haveValidation;
1204     }
1205   }
1207   setRemoteDescription(desc, onSucc, onErr) {
1208     return this._auto(onSucc, onErr, () => this._setRemoteDescription(desc));
1209   }
1211   _setRemoteDescription({ type, sdp }) {
1212     if (type == "pranswer") {
1213       throw new this._win.DOMException(
1214         "pranswer not yet implemented",
1215         "NotSupportedError"
1216       );
1217     }
1218     this._checkClosed();
1219     return this._chain(async () => {
1220       try {
1221         if (type == "offer" && this.signalingState == "have-local-offer") {
1222           await new Promise((resolve, reject) => {
1223             this._onSetDescriptionSuccess = resolve;
1224             this._onSetDescriptionFailure = reject;
1225             this._pc.setLocalDescription(
1226               Ci.IPeerConnection.kActionRollback,
1227               ""
1228             );
1229           });
1230           await this._pc.onSetDescriptionSuccess("rollback", false);
1231           this._updateCanTrickle();
1232         }
1234         if (this._closed) {
1235           return;
1236         }
1238         this._sanityCheckSdp(sdp);
1240         const p = this._getPermission();
1242         const haveSetRemote = new Promise((resolve, reject) => {
1243           this._onSetDescriptionSuccess = resolve;
1244           this._onSetDescriptionFailure = reject;
1245           this._pc.setRemoteDescription(this._actions[type], sdp);
1246         });
1248         if (type != "rollback") {
1249           // Do setRemoteDescription and identity validation in parallel
1250           await this._validateIdentity(sdp);
1251         }
1252         await p;
1253         await haveSetRemote;
1254       } catch (e) {
1255         this._pc.onSetDescriptionError();
1256         throw e;
1257       }
1259       await this._pc.onSetDescriptionSuccess(type, true);
1260       this._updateCanTrickle();
1261     });
1262   }
1264   setIdentityProvider(provider, { protocol, usernameHint, peerIdentity } = {}) {
1265     this._checkClosed();
1266     peerIdentity = peerIdentity || this._pc.peerIdentity;
1267     this._localIdp.setIdentityProvider(
1268       provider,
1269       protocol,
1270       usernameHint,
1271       peerIdentity
1272     );
1273   }
1275   async _getIdentityAssertion() {
1276     await this._certificateReady;
1277     return this._localIdp.getIdentityAssertion(
1278       this._pc.fingerprint,
1279       this._documentPrincipal.origin
1280     );
1281   }
1283   getIdentityAssertion() {
1284     this._checkClosed();
1285     return this._win.Promise.resolve(
1286       this._chain(() => this._getIdentityAssertion())
1287     );
1288   }
1290   get canTrickleIceCandidates() {
1291     return this._canTrickle;
1292   }
1294   _updateCanTrickle() {
1295     let containsTrickle = section => {
1296       let lines = section.toLowerCase().split(/(?:\r\n?|\n)/);
1297       return lines.some(line => {
1298         let prefix = "a=ice-options:";
1299         if (line.substring(0, prefix.length) !== prefix) {
1300           return false;
1301         }
1302         let tokens = line.substring(prefix.length).split(" ");
1303         return tokens.some(x => x === "trickle");
1304       });
1305     };
1307     let desc = null;
1308     try {
1309       // The getter for remoteDescription can throw if the pc is closed.
1310       desc = this.remoteDescription;
1311     } catch (e) {}
1312     if (!desc) {
1313       this._canTrickle = null;
1314       return;
1315     }
1317     let sections = desc.sdp.split(/(?:\r\n?|\n)m=/);
1318     let topSection = sections.shift();
1319     this._canTrickle =
1320       containsTrickle(topSection) || sections.every(containsTrickle);
1321   }
1323   addIceCandidate(cand, onSucc, onErr) {
1324     if (
1325       cand.candidate != "" &&
1326       cand.sdpMid == null &&
1327       cand.sdpMLineIndex == null
1328     ) {
1329       throw new this._win.TypeError(
1330         "Cannot add a candidate without specifying either sdpMid or sdpMLineIndex"
1331       );
1332     }
1333     return this._auto(onSucc, onErr, () => this._addIceCandidate(cand));
1334   }
1336   async _addIceCandidate({
1337     candidate,
1338     sdpMid,
1339     sdpMLineIndex,
1340     usernameFragment,
1341   }) {
1342     this._checkClosed();
1343     return this._chain(async () => {
1344       if (
1345         !this._pc.pendingRemoteDescription.length &&
1346         !this._pc.currentRemoteDescription.length
1347       ) {
1348         throw new this._win.DOMException(
1349           "No remoteDescription.",
1350           "InvalidStateError"
1351         );
1352       }
1353       return new Promise((resolve, reject) => {
1354         this._onAddIceCandidateSuccess = resolve;
1355         this._onAddIceCandidateError = reject;
1356         this._pc.addIceCandidate(
1357           candidate,
1358           sdpMid || "",
1359           usernameFragment || "",
1360           sdpMLineIndex
1361         );
1362       });
1363     });
1364   }
1366   restartIce() {
1367     this._pc.restartIce();
1368   }
1370   addStream(stream) {
1371     stream.getTracks().forEach(track => this.addTrack(track, stream));
1372   }
1374   addTrack(track, ...streams) {
1375     this._checkClosed();
1377     if (
1378       this.getTransceivers().some(
1379         transceiver => transceiver.sender.track == track
1380       )
1381     ) {
1382       throw new this._win.DOMException(
1383         "This track is already set on a sender.",
1384         "InvalidAccessError"
1385       );
1386     }
1388     let transceiver = this.getTransceivers().find(transceiver => {
1389       return (
1390         transceiver.sender.track == null &&
1391         transceiver.getKind() == track.kind &&
1392         !transceiver.stopped &&
1393         !transceiver.hasBeenUsedToSend()
1394       );
1395     });
1397     if (transceiver) {
1398       transceiver.sender.setTrack(track);
1399       transceiver.sender.setStreamsImpl(...streams);
1400       if (transceiver.direction == "recvonly") {
1401         transceiver.setDirectionInternal("sendrecv");
1402       } else if (transceiver.direction == "inactive") {
1403         transceiver.setDirectionInternal("sendonly");
1404       }
1405     } else {
1406       transceiver = this._addTransceiverNoEvents(
1407         track,
1408         {
1409           streams,
1410           direction: "sendrecv",
1411         },
1412         true
1413       );
1414     }
1416     this.updateNegotiationNeeded();
1417     return transceiver.sender;
1418   }
1420   removeTrack(sender) {
1421     this._checkClosed();
1423     if (!this._pc.createdSender(sender)) {
1424       throw new this._win.DOMException(
1425         "This sender was not created by this PeerConnection",
1426         "InvalidAccessError"
1427       );
1428     }
1430     let transceiver = this.getTransceivers().find(
1431       transceiver => !transceiver.stopped && transceiver.sender == sender
1432     );
1434     // If the transceiver was removed due to rollback, let it slide.
1435     if (!transceiver || !sender.track) {
1436       return;
1437     }
1439     sender.setTrack(null);
1440     if (transceiver.direction == "sendrecv") {
1441       transceiver.setDirectionInternal("recvonly");
1442     } else if (transceiver.direction == "sendonly") {
1443       transceiver.setDirectionInternal("inactive");
1444     }
1446     this.updateNegotiationNeeded();
1447   }
1449   _addTransceiverNoEvents(sendTrackOrKind, init, addTrackMagic) {
1450     let sendTrack = null;
1451     let kind;
1452     if (typeof sendTrackOrKind == "string") {
1453       kind = sendTrackOrKind;
1454       switch (kind) {
1455         case "audio":
1456         case "video":
1457           break;
1458         default:
1459           throw new this._win.TypeError("Invalid media kind");
1460       }
1461     } else {
1462       sendTrack = sendTrackOrKind;
1463       kind = sendTrack.kind;
1464     }
1466     try {
1467       return this._pc.addTransceiver(init, kind, sendTrack, addTrackMagic);
1468     } catch (e) {
1469       // Exceptions thrown by c++ code do not propagate. In most cases, that's
1470       // fine because we're using Promises, which can be copied. But this is
1471       // not promise-based, so we have to do this sketchy stuff.
1472       const holder = new StructuredCloneHolder(
1473         "",
1474         "",
1475         new ClonedErrorHolder(e)
1476       );
1477       throw holder.deserialize(this._win);
1478     }
1479   }
1481   addTransceiver(sendTrackOrKind, init) {
1482     this._checkClosed();
1483     let transceiver = this._addTransceiverNoEvents(sendTrackOrKind, init);
1484     this.updateNegotiationNeeded();
1485     return transceiver;
1486   }
1488   updateNegotiationNeeded() {
1489     this._pc.updateNegotiationNeeded();
1490   }
1492   close() {
1493     if (this._closed) {
1494       return;
1495     }
1496     this._closed = true;
1497     this.changeIceConnectionState("closed");
1498     if (this._localIdp) {
1499       this._localIdp.close();
1500     }
1501     if (this._remoteIdp) {
1502       this._remoteIdp.close();
1503     }
1504     this._pc.close();
1505     this._suppressEvents = true;
1506   }
1508   getLocalStreams() {
1509     this._checkClosed();
1510     let localStreams = new Set();
1511     this.getTransceivers().forEach(transceiver => {
1512       transceiver.sender.getStreams().forEach(stream => {
1513         localStreams.add(stream);
1514       });
1515     });
1516     return [...localStreams.values()];
1517   }
1519   getRemoteStreams() {
1520     this._checkClosed();
1521     return this._pc.getRemoteStreams();
1522   }
1524   getSenders() {
1525     return this.getTransceivers()
1526       .filter(transceiver => !transceiver.stopped)
1527       .map(transceiver => transceiver.sender);
1528   }
1530   getReceivers() {
1531     return this.getTransceivers()
1532       .filter(transceiver => !transceiver.stopped)
1533       .map(transceiver => transceiver.receiver);
1534   }
1536   mozSetPacketCallback(callback) {
1537     this._onPacket = callback;
1538   }
1540   mozEnablePacketDump(level, type, sending) {
1541     this._pc.enablePacketDump(level, type, sending);
1542   }
1544   mozDisablePacketDump(level, type, sending) {
1545     this._pc.disablePacketDump(level, type, sending);
1546   }
1548   getTransceivers() {
1549     return this._pc.getTransceivers();
1550   }
1552   get localDescription() {
1553     return this.pendingLocalDescription || this.currentLocalDescription;
1554   }
1556   cacheDescription(name, type, sdp) {
1557     if (
1558       !this[name] ||
1559       this[name].type != type ||
1560       this[name].sdp != sdp ||
1561       this._legacyPref
1562     ) {
1563       this[name] = sdp.length
1564         ? new this._win.RTCSessionDescription({ type, sdp })
1565         : null;
1566     }
1567     return this[name];
1568   }
1570   get currentLocalDescription() {
1571     this._checkClosed();
1572     return this.cacheDescription(
1573       "_currentLocalDescription",
1574       this._pc.currentOfferer ? "offer" : "answer",
1575       this._pc.currentLocalDescription
1576     );
1577   }
1579   get pendingLocalDescription() {
1580     this._checkClosed();
1581     return this.cacheDescription(
1582       "_pendingLocalDescription",
1583       this._pc.pendingOfferer ? "offer" : "answer",
1584       this._pc.pendingLocalDescription
1585     );
1586   }
1588   get remoteDescription() {
1589     return this.pendingRemoteDescription || this.currentRemoteDescription;
1590   }
1592   get currentRemoteDescription() {
1593     this._checkClosed();
1594     return this.cacheDescription(
1595       "_currentRemoteDescription",
1596       this._pc.currentOfferer ? "answer" : "offer",
1597       this._pc.currentRemoteDescription
1598     );
1599   }
1601   get pendingRemoteDescription() {
1602     this._checkClosed();
1603     return this.cacheDescription(
1604       "_pendingRemoteDescription",
1605       this._pc.pendingOfferer ? "answer" : "offer",
1606       this._pc.pendingRemoteDescription
1607     );
1608   }
1610   get peerIdentity() {
1611     return this._peerIdentity;
1612   }
1613   get idpLoginUrl() {
1614     return this._localIdp.idpLoginUrl;
1615   }
1616   get id() {
1617     return this._pc.id;
1618   }
1619   set id(s) {
1620     this._pc.id = s;
1621   }
1622   get iceGatheringState() {
1623     return this._pc.iceGatheringState;
1624   }
1625   get iceConnectionState() {
1626     return this._iceConnectionState;
1627   }
1628   get connectionState() {
1629     return this._pc.connectionState;
1630   }
1632   get signalingState() {
1633     // checking for our local pc closed indication
1634     // before invoking the pc methods.
1635     if (this._closed) {
1636       return "closed";
1637     }
1638     return this._pc.signalingState;
1639   }
1641   handleIceGatheringStateChange() {
1642     _globalPCList.notifyLifecycleObservers(this, "icegatheringstatechange");
1643     this.dispatchEvent(new this._win.Event("icegatheringstatechange"));
1644     if (this.iceGatheringState === "complete") {
1645       this.dispatchEvent(
1646         new this._win.RTCPeerConnectionIceEvent("icecandidate", {
1647           candidate: null,
1648         })
1649       );
1650     }
1651   }
1653   changeIceConnectionState(state) {
1654     if (state != this._iceConnectionState) {
1655       this._iceConnectionState = state;
1656       _globalPCList.notifyLifecycleObservers(this, "iceconnectionstatechange");
1657       if (!this._closed) {
1658         this.dispatchEvent(new this._win.Event("iceconnectionstatechange"));
1659       }
1660     }
1661   }
1663   getStats(selector, onSucc, onErr) {
1664     if (selector !== null) {
1665       let matchingSenders = this.getSenders().filter(s => s.track === selector);
1666       let matchingReceivers = this.getReceivers().filter(
1667         r => r.track === selector
1668       );
1670       if (matchingSenders.length + matchingReceivers.length != 1) {
1671         throw new this._win.DOMException(
1672           "track must be associated with a unique sender or receiver, but " +
1673             " is associated with " +
1674             matchingSenders.length +
1675             " senders and " +
1676             matchingReceivers.length +
1677             " receivers.",
1678           "InvalidAccessError"
1679         );
1680       }
1681     }
1683     return this._auto(onSucc, onErr, () => this._pc.getStats(selector));
1684   }
1686   get sctp() {
1687     return this._pc.sctp;
1688   }
1690   createDataChannel(
1691     label,
1692     {
1693       maxRetransmits,
1694       ordered,
1695       negotiated,
1696       id = null,
1697       maxRetransmitTime,
1698       maxPacketLifeTime,
1699       protocol,
1700     } = {}
1701   ) {
1702     this._checkClosed();
1703     this._pcTelemetry.recordDataChannelInit(
1704       maxRetransmitTime,
1705       maxPacketLifeTime
1706     );
1708     if (maxPacketLifeTime === undefined) {
1709       maxPacketLifeTime = maxRetransmitTime;
1710     }
1712     if (maxRetransmitTime !== undefined) {
1713       this.logWarning(
1714         "Use maxPacketLifeTime instead of deprecated maxRetransmitTime which will stop working soon in createDataChannel!"
1715       );
1716     }
1718     if (protocol.length > 32767) {
1719       // At least 65536/2 UTF-16 characters. UTF-8 might be too long.
1720       // Spec says to check how long |protocol| and |label| are in _bytes_. This
1721       // is a little ambiguous. For now, examine the length of the utf-8 encoding.
1722       const byteCounter = new TextEncoder();
1724       if (byteCounter.encode(protocol).length > 65535) {
1725         throw new this._win.TypeError(
1726           "protocol cannot be longer than 65535 bytes"
1727         );
1728       }
1729     }
1731     if (label.length > 32767) {
1732       const byteCounter = new TextEncoder();
1733       if (byteCounter.encode(label).length > 65535) {
1734         throw new this._win.TypeError(
1735           "label cannot be longer than 65535 bytes"
1736         );
1737       }
1738     }
1740     if (!negotiated) {
1741       id = null;
1742     } else if (id === null) {
1743       throw new this._win.TypeError("id is required when negotiated is true");
1744     }
1745     if (maxPacketLifeTime !== undefined && maxRetransmits !== undefined) {
1746       throw new this._win.TypeError(
1747         "Both maxPacketLifeTime and maxRetransmits cannot be provided"
1748       );
1749     }
1750     if (id == 65535) {
1751       throw new this._win.TypeError("id cannot be 65535");
1752     }
1753     // Must determine the type where we still know if entries are undefined.
1754     let type;
1755     if (maxPacketLifeTime !== undefined) {
1756       type = Ci.IPeerConnection.kDataChannelPartialReliableTimed;
1757     } else if (maxRetransmits !== undefined) {
1758       type = Ci.IPeerConnection.kDataChannelPartialReliableRexmit;
1759     } else {
1760       type = Ci.IPeerConnection.kDataChannelReliable;
1761     }
1762     // Synchronous since it doesn't block.
1763     let dataChannel;
1764     try {
1765       dataChannel = this._pc.createDataChannel(
1766         label,
1767         protocol,
1768         type,
1769         ordered,
1770         maxPacketLifeTime,
1771         maxRetransmits,
1772         negotiated,
1773         id
1774       );
1775     } catch (e) {
1776       if (e.result != Cr.NS_ERROR_NOT_AVAILABLE) {
1777         throw e;
1778       }
1780       const msg =
1781         id === null ? "No available id could be generated" : "Id is in use";
1782       throw new this._win.DOMException(msg, "OperationError");
1783     }
1785     // Spec says to only do this if this is the first DataChannel created,
1786     // but the c++ code that does the "is negotiation needed" checking will
1787     // only ever return true on the first one.
1788     this.updateNegotiationNeeded();
1790     return dataChannel;
1791   }
1794 setupPrototype(RTCPeerConnection, {
1795   classID: PC_CID,
1796   contractID: PC_CONTRACT,
1797   QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
1798   _actions: {
1799     offer: Ci.IPeerConnection.kActionOffer,
1800     answer: Ci.IPeerConnection.kActionAnswer,
1801     pranswer: Ci.IPeerConnection.kActionPRAnswer,
1802     rollback: Ci.IPeerConnection.kActionRollback,
1803   },
1806 // This is a separate class because we don't want to expose it to DOM.
1808 export class PeerConnectionObserver {
1809   init(win) {
1810     this._win = win;
1811   }
1813   __init(dompc) {
1814     this._dompc = dompc._innerObject;
1815   }
1817   newError({ message, name }) {
1818     return new this._dompc._win.DOMException(message, name);
1819   }
1821   dispatchEvent(event) {
1822     this._dompc.dispatchEvent(event);
1823   }
1825   onCreateOfferSuccess(sdp) {
1826     this._dompc._onCreateOfferSuccess(sdp);
1827   }
1829   onCreateOfferError(error) {
1830     this._dompc._onCreateOfferFailure(this.newError(error));
1831   }
1833   onCreateAnswerSuccess(sdp) {
1834     this._dompc._onCreateAnswerSuccess(sdp);
1835   }
1837   onCreateAnswerError(error) {
1838     this._dompc._onCreateAnswerFailure(this.newError(error));
1839   }
1841   onSetDescriptionSuccess() {
1842     this._dompc._onSetDescriptionSuccess();
1843   }
1845   onSetDescriptionError(error) {
1846     this._dompc._onSetDescriptionFailure(this.newError(error));
1847   }
1849   onAddIceCandidateSuccess() {
1850     this._dompc._onAddIceCandidateSuccess();
1851   }
1853   onAddIceCandidateError(error) {
1854     this._dompc._onAddIceCandidateError(this.newError(error));
1855   }
1857   onIceCandidate(sdpMLineIndex, sdpMid, candidate, usernameFragment) {
1858     let win = this._dompc._win;
1859     if (candidate || sdpMid || usernameFragment) {
1860       if (candidate.includes(" typ relay ")) {
1861         this._dompc._iceGatheredRelayCandidates = true;
1862       }
1863       candidate = new win.RTCIceCandidate({
1864         candidate,
1865         sdpMid,
1866         sdpMLineIndex,
1867         usernameFragment,
1868       });
1869     }
1870     this.dispatchEvent(
1871       new win.RTCPeerConnectionIceEvent("icecandidate", { candidate })
1872     );
1873   }
1875   // This method is primarily responsible for updating iceConnectionState.
1876   // This state is defined in the WebRTC specification as follows:
1877   //
1878   // iceConnectionState:
1879   // -------------------
1880   //   new           Any of the RTCIceTransports are in the new state and none
1881   //                 of them are in the checking, failed or disconnected state.
1882   //
1883   //   checking      Any of the RTCIceTransports are in the checking state and
1884   //                 none of them are in the failed or disconnected state.
1885   //
1886   //   connected     All RTCIceTransports are in the connected, completed or
1887   //                 closed state and at least one of them is in the connected
1888   //                 state.
1889   //
1890   //   completed     All RTCIceTransports are in the completed or closed state
1891   //                 and at least one of them is in the completed state.
1892   //
1893   //   failed        Any of the RTCIceTransports are in the failed state.
1894   //
1895   //   disconnected  Any of the RTCIceTransports are in the disconnected state
1896   //                 and none of them are in the failed state.
1897   //
1898   //   closed        All of the RTCIceTransports are in the closed state.
1900   handleIceConnectionStateChange(iceConnectionState) {
1901     let pc = this._dompc;
1902     if (pc.iceConnectionState === iceConnectionState) {
1903       return;
1904     }
1906     if (iceConnectionState === "failed") {
1907       if (!pc._hasStunServer) {
1908         pc.logError(
1909           "ICE failed, add a STUN server and see about:webrtc for more details"
1910         );
1911       } else if (!pc._hasTurnServer) {
1912         pc.logError(
1913           "ICE failed, add a TURN server and see about:webrtc for more details"
1914         );
1915       } else if (pc._hasTurnServer && !pc._iceGatheredRelayCandidates) {
1916         pc.logError(
1917           "ICE failed, your TURN server appears to be broken, see about:webrtc for more details"
1918         );
1919       } else {
1920         pc.logError("ICE failed, see about:webrtc for more details");
1921       }
1922     }
1924     pc.changeIceConnectionState(iceConnectionState);
1925   }
1927   onStateChange(state) {
1928     if (!this._dompc) {
1929       return;
1930     }
1932     if (state == "SignalingState") {
1933       this.dispatchEvent(new this._win.Event("signalingstatechange"));
1934       return;
1935     }
1937     if (!this._dompc._pc) {
1938       return;
1939     }
1941     switch (state) {
1942       case "IceConnectionState":
1943         this.handleIceConnectionStateChange(this._dompc._pc.iceConnectionState);
1944         break;
1946       case "IceGatheringState":
1947         this._dompc.handleIceGatheringStateChange();
1948         break;
1950       case "ConnectionState":
1951         _globalPCList.notifyLifecycleObservers(this, "connectionstatechange");
1952         this.dispatchEvent(new this._win.Event("connectionstatechange"));
1953         break;
1955       default:
1956         this._dompc.logWarning("Unhandled state type: " + state);
1957         break;
1958     }
1959   }
1961   onTransceiverNeeded(kind, transceiverImpl) {
1962     this._dompc._onTransceiverNeeded(kind, transceiverImpl);
1963   }
1965   notifyDataChannel(channel) {
1966     this.dispatchEvent(
1967       new this._dompc._win.RTCDataChannelEvent("datachannel", { channel })
1968     );
1969   }
1971   fireTrackEvent(receiver, streams) {
1972     const pc = this._dompc;
1973     const transceiver = pc.getTransceivers().find(t => t.receiver == receiver);
1974     if (!transceiver) {
1975       return;
1976     }
1977     const track = receiver.track;
1978     this.dispatchEvent(
1979       new this._win.RTCTrackEvent("track", {
1980         transceiver,
1981         receiver,
1982         track,
1983         streams,
1984       })
1985     );
1986     // Fire legacy event as well for a little bit.
1987     this.dispatchEvent(
1988       new this._win.MediaStreamTrackEvent("addtrack", { track })
1989     );
1990   }
1992   fireStreamEvent(stream) {
1993     const ev = new this._win.MediaStreamEvent("addstream", { stream });
1994     this.dispatchEvent(ev);
1995   }
1997   fireNegotiationNeededEvent() {
1998     this.dispatchEvent(new this._win.Event("negotiationneeded"));
1999   }
2001   onPacket(level, type, sending, packet) {
2002     var pc = this._dompc;
2003     if (pc._onPacket) {
2004       pc._onPacket(level, type, sending, packet);
2005     }
2006   }
2009 setupPrototype(PeerConnectionObserver, {
2010   classID: PC_OBS_CID,
2011   contractID: PC_OBS_CONTRACT,
2012   QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
2015 export class RTCPeerConnectionStatic {
2016   init(win) {
2017     this._winID = win.windowGlobalChild.innerWindowId;
2018   }
2020   registerPeerConnectionLifecycleCallback(cb) {
2021     _globalPCList._registerPeerConnectionLifecycleCallback(this._winID, cb);
2022   }
2025 setupPrototype(RTCPeerConnectionStatic, {
2026   classID: PC_STATIC_CID,
2027   contractID: PC_STATIC_CONTRACT,
2028   QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
2031 export class CreateOfferRequest {
2032   constructor(windowID, innerWindowID, callID, isSecure) {
2033     Object.assign(this, { windowID, innerWindowID, callID, isSecure });
2034   }
2037 setupPrototype(CreateOfferRequest, {
2038   classID: PC_COREQUEST_CID,
2039   contractID: PC_COREQUEST_CONTRACT,
2040   QueryInterface: ChromeUtils.generateQI([]),