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