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/. */
7 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
8 ChromeUtils.defineModuleGetter(
11 "resource://gre/modules/media/PeerConnectionIdp.jsm"
14 const PC_CONTRACT = "@mozilla.org/dom/peerconnection;1";
15 const PC_OBS_CONTRACT = "@mozilla.org/dom/peerconnectionobserver;1";
16 const PC_ICE_CONTRACT = "@mozilla.org/dom/rtcicecandidate;1";
17 const PC_SESSION_CONTRACT = "@mozilla.org/dom/rtcsessiondescription;1";
18 const PC_STATIC_CONTRACT = "@mozilla.org/dom/peerconnectionstatic;1";
19 const PC_SENDER_CONTRACT = "@mozilla.org/dom/rtpsender;1";
20 const PC_TRANSCEIVER_CONTRACT = "@mozilla.org/dom/rtptransceiver;1";
21 const PC_COREQUEST_CONTRACT = "@mozilla.org/dom/createofferrequest;1";
23 const PC_CID = Components.ID("{bdc2e533-b308-4708-ac8e-a8bfade6d851}");
24 const PC_OBS_CID = Components.ID("{d1748d4c-7f6a-4dc5-add6-d55b7678537e}");
25 const PC_ICE_CID = Components.ID("{02b9970c-433d-4cc2-923d-f7028ac66073}");
26 const PC_SESSION_CID = Components.ID("{1775081b-b62d-4954-8ffe-a067bbf508a7}");
27 const PC_MANAGER_CID = Components.ID("{7293e901-2be3-4c02-b4bd-cbef6fc24f78}");
28 const PC_STATIC_CID = Components.ID("{0fb47c47-a205-4583-a9fc-cbadf8c95880}");
29 const PC_SENDER_CID = Components.ID("{4fff5d46-d827-4cd4-a970-8fd53977440e}");
30 const PC_TRANSCEIVER_CID = Components.ID(
31 "{09475754-103a-41f5-a2d0-e1f27eb0b537}"
33 const PC_COREQUEST_CID = Components.ID(
34 "{74b2122d-65a8-4824-aa9e-3d664cb75dc2}"
37 function logMsg(msg, file, line, flag, winID) {
38 let scriptErrorClass = Cc["@mozilla.org/scripterror;1"];
39 let scriptError = scriptErrorClass.createInstance(Ci.nsIScriptError);
40 scriptError.initWithWindowID(
50 Services.console.logMessage(scriptError);
53 let setupPrototype = (_class, dict) => {
54 _class.prototype.classDescription = _class.name;
55 Object.assign(_class.prototype, dict);
58 // Global list of PeerConnection objects, so they can be cleaned up when
59 // a page is torn down. (Maps inner window ID to an array of PC objects).
63 this._networkdown = false; // XXX Need to query current state somehow
64 this._lifecycleobservers = {};
66 Services.obs.addObserver(this, "inner-window-destroyed", true);
67 Services.obs.addObserver(this, "profile-change-net-teardown", true);
68 Services.obs.addObserver(this, "network:offline-about-to-go-offline", true);
69 Services.obs.addObserver(this, "network:offline-status-changed", true);
70 Services.obs.addObserver(this, "gmp-plugin-crash", true);
71 Services.obs.addObserver(this, "PeerConnection:response:allow", true);
72 Services.obs.addObserver(this, "PeerConnection:response:deny", true);
74 Services.cpmm.addMessageListener("gmp-plugin-crash", this);
78 notifyLifecycleObservers(pc, type) {
79 for (var key of Object.keys(this._lifecycleobservers)) {
80 this._lifecycleobservers[key](pc, pc._winID, type);
85 let winID = pc._winID;
86 if (this._list[winID]) {
87 this._list[winID].push(Cu.getWeakReference(pc));
89 this._list[winID] = [Cu.getWeakReference(pc)];
91 pc._globalPCListId = this._nextId++;
92 this.removeNullRefs(winID);
95 findPC(globalPCListId) {
96 for (let winId in this._list) {
97 if (this._list.hasOwnProperty(winId)) {
98 for (let pcref of this._list[winId]) {
100 if (pc && pc._globalPCListId == globalPCListId) {
109 removeNullRefs(winID) {
110 if (this._list[winID] === undefined) {
113 this._list[winID] = this._list[winID].filter(function(e, i, a) {
114 return e.get() !== null;
117 if (this._list[winID].length === 0) {
118 delete this._list[winID];
122 handleGMPCrash(data) {
123 let broadcastPluginCrash = function(list, winID, pluginID, pluginName) {
124 if (list.hasOwnProperty(winID)) {
125 list[winID].forEach(function(pcref) {
126 let pc = pcref.get();
128 pc._pc.pluginCrash(pluginID, pluginName);
134 // a plugin crashed; if it's associated with any of our PCs, fire an
135 // event to the DOM window
136 for (let winId in this._list) {
137 broadcastPluginCrash(this._list, winId, data.pluginID, data.pluginName);
141 receiveMessage({ name, data }) {
142 if (name == "gmp-plugin-crash") {
143 this.handleGMPCrash(data);
147 observe(subject, topic, data) {
148 let cleanupPcRef = function(pcref) {
149 let pc = pcref.get();
151 pc._suppressEvents = true;
156 let cleanupWinId = function(list, winID) {
157 if (list.hasOwnProperty(winID)) {
158 list[winID].forEach(cleanupPcRef);
163 if (topic == "inner-window-destroyed") {
164 let winID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
165 cleanupWinId(this._list, winID);
167 if (this._lifecycleobservers.hasOwnProperty(winID)) {
168 delete this._lifecycleobservers[winID];
171 topic == "profile-change-net-teardown" ||
172 topic == "network:offline-about-to-go-offline"
174 // As Necko doesn't prevent us from accessing the network we still need to
175 // monitor the network offline/online state here. See bug 1326483
176 this._networkdown = true;
177 } else if (topic == "network:offline-status-changed") {
178 if (data == "offline") {
179 this._networkdown = true;
180 } else if (data == "online") {
181 this._networkdown = false;
183 } else if (topic == "gmp-plugin-crash") {
184 if (subject instanceof Ci.nsIWritablePropertyBag2) {
185 let pluginID = subject.getPropertyAsUint32("pluginID");
186 let pluginName = subject.getPropertyAsAString("pluginName");
187 let data = { pluginID, pluginName };
188 this.handleGMPCrash(data);
191 topic == "PeerConnection:response:allow" ||
192 topic == "PeerConnection:response:deny"
194 var pc = this.findPC(data);
196 if (topic == "PeerConnection:response:allow") {
197 pc._settlePermission.allow();
199 let err = new pc._win.DOMException(
200 "The request is not allowed by " +
201 "the user agent or the platform in the current context.",
204 pc._settlePermission.deny(err);
210 _registerPeerConnectionLifecycleCallback(winID, cb) {
211 this._lifecycleobservers[winID] = cb;
214 setupPrototype(GlobalPCList, {
215 QueryInterface: ChromeUtils.generateQI([
217 "nsISupportsWeakReference",
219 classID: PC_MANAGER_CID,
221 createInstance(outer, iid) {
223 throw Components.Exception("", Cr.NS_ERROR_NO_AGGREGATION);
225 return _globalPCList.QueryInterface(iid);
230 var _globalPCList = new GlobalPCList();
232 class RTCIceCandidate {
238 if (dict.sdpMid == null && dict.sdpMLineIndex == null) {
239 throw new this._win.TypeError(
240 "Either sdpMid or sdpMLineIndex must be specified"
243 Object.assign(this, dict);
246 setupPrototype(RTCIceCandidate, {
248 contractID: PC_ICE_CONTRACT,
249 QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
252 class RTCSessionDescription {
255 this._winID = this._win.windowGlobalChild.innerWindowId;
258 __init({ type, sdp }) {
260 throw new this._win.TypeError(
261 "Missing required 'type' member of RTCSessionDescriptionInit"
264 Object.assign(this, { _type: type, _sdp: sdp });
285 // Warn once per RTCSessionDescription about deprecated writable usage.
287 "RTCSessionDescription's members are readonly! " +
288 "Writing to them is deprecated and will break soon!"
295 let err = this._win.Error();
300 Ci.nsIScriptError.warningFlag,
305 setupPrototype(RTCSessionDescription, {
306 classID: PC_SESSION_CID,
307 contractID: PC_SESSION_CONTRACT,
308 QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
311 // Records PC related telemetry
312 class PeerConnectionTelemetry {
313 // ICE connection state enters connected or completed.
315 Services.telemetry.scalarAdd("webrtc.peerconnection.connected", 1);
316 this.recordConnected = () => {};
318 // DataChannel is created
319 _recordDataChannelCreated() {
320 Services.telemetry.scalarAdd(
321 "webrtc.peerconnection.datachannel_created",
324 this._recordDataChannelCreated = () => {};
326 // DataChannel initialized with maxRetransmitTime
327 _recordMaxRetransmitTime(maxRetransmitTime) {
328 if (maxRetransmitTime === undefined) {
331 Services.telemetry.scalarAdd(
332 "webrtc.peerconnection.datachannel_max_retx_used",
335 this._recordMaxRetransmitTime = () => true;
338 // DataChannel initialized with maxPacketLifeTime
339 _recordMaxPacketLifeTime(maxPacketLifeTime) {
340 if (maxPacketLifeTime === undefined) {
343 Services.telemetry.scalarAdd(
344 "webrtc.peerconnection.datachannel_max_life_used",
347 this._recordMaxPacketLifeTime = () => true;
350 // DataChannel initialized
351 recordDataChannelInit(maxRetransmitTime, maxPacketLifeTime) {
352 const retxUsed = this._recordMaxRetransmitTime(maxRetransmitTime);
353 if (this._recordMaxPacketLifeTime(maxPacketLifeTime) && retxUsed) {
354 Services.telemetry.scalarAdd(
355 "webrtc.peerconnection.datachannel_max_retx_and_life_used",
358 this.recordDataChannelInit = () => {};
360 this._recordDataChannelCreated();
364 class RTCPeerConnection {
366 this._transceivers = [];
369 this._closed = false;
371 // http://rtcweb-wg.github.io/jsep/#rfc.section.4.1.9
372 // canTrickle == null means unknown; when a remote description is received it
373 // is set to true or false based on the presence of the "trickle" ice-option
374 this._canTrickle = null;
375 this._localUfragsToReplace = new Set();
377 // So we can record telemetry on state transitions
378 this._iceConnectionState = "new";
380 this._hasStunServer = this._hasTurnServer = false;
381 this._iceGatheredRelayCandidates = false;
383 this._pcTelemetry = new PeerConnectionTelemetry();
391 this._winID = this._win.windowGlobalChild.innerWindowId;
392 // TODO: Update this code once we support pc.setConfiguration, to track
393 // setting from content independently from pref (Bug 1181768).
395 rtcConfig.iceTransportPolicy == "all" &&
396 Services.prefs.getBoolPref("media.peerconnection.ice.relay_only")
398 rtcConfig.iceTransportPolicy = "relay";
400 if ("sdpSemantics" in rtcConfig) {
401 if (rtcConfig.sdpSemantics == "plan-b") {
403 `Outdated and non-standard {sdpSemantics: "plan-b"} is not ` +
404 `supported! WebRTC may be unreliable. Please update code to ` +
405 `follow standard "unified-plan".`
408 // Don't let it show up in getConfiguration.
409 delete rtcConfig.sdpSemantics;
411 this._config = Object.assign({}, rtcConfig);
414 !rtcConfig.iceServers ||
415 !Services.prefs.getBoolPref(
416 "media.peerconnection.use_document_iceservers"
420 rtcConfig.iceServers = JSON.parse(
421 Services.prefs.getCharPref(
422 "media.peerconnection.default_iceservers"
427 "Ignoring invalid media.peerconnection.default_iceservers in about:config"
429 rtcConfig.iceServers = [];
432 this._mustValidateRTCConfiguration(
434 "Ignoring invalid media.peerconnection.default_iceservers in about:config"
437 this.logWarning(e.message);
438 rtcConfig.iceServers = [];
441 // This gets executed in the typical case when iceServers
442 // are passed in through the web page.
443 this._mustValidateRTCConfiguration(
445 "RTCPeerConnection constructor passed invalid RTCConfiguration"
449 let certificates = rtcConfig.certificates || [];
451 if (certificates.some(c => c.expires <= Date.now())) {
452 throw new this._win.DOMException(
453 "Unable to create RTCPeerConnection with an expired certificate",
458 // TODO(bug 1531875): Check origin of certs
460 // TODO(bug 1176518): Remove this code once we support multiple certs
462 if (certificates.length == 1) {
463 certificate = certificates[0];
464 } else if (certificates.length) {
465 throw new this._win.DOMException(
466 "RTCPeerConnection does not currently support multiple certificates",
471 this._documentPrincipal = Cu.getWebIDLCallerPrincipal();
473 if (_globalPCList._networkdown) {
474 throw new this._win.DOMException(
475 "Can't create RTCPeerConnections when the network is down",
480 this.makeGetterSetterEH("ontrack");
481 this.makeLegacyGetterSetterEH(
483 "Use peerConnection.ontrack instead."
485 this.makeLegacyGetterSetterEH(
487 "Use peerConnection.ontrack instead."
489 this.makeGetterSetterEH("onicecandidate");
490 this.makeGetterSetterEH("onnegotiationneeded");
491 this.makeGetterSetterEH("onsignalingstatechange");
492 this.makeGetterSetterEH("ondatachannel");
493 this.makeGetterSetterEH("oniceconnectionstatechange");
494 this.makeGetterSetterEH("onicegatheringstatechange");
495 this.makeGetterSetterEH("onidentityresult");
496 this.makeGetterSetterEH("onpeeridentity");
497 this.makeGetterSetterEH("onidpassertionerror");
498 this.makeGetterSetterEH("onidpvalidationerror");
500 this._pc = new this._win.PeerConnectionImpl();
501 this._operations = [];
502 this._updateNegotiationNeededOnEmptyChain = false;
504 this.__DOM_IMPL__._innerObject = this;
505 const observer = new this._win.PeerConnectionObserver(this.__DOM_IMPL__);
507 // Add a reference to the PeerConnection to global list (before init).
508 _globalPCList.addPC(this);
510 this._impl.initialize(
514 Services.tm.currentThread
517 this._certificateReady = this._initCertificate(certificate);
519 _globalPCList.notifyLifecycleObservers(this, "initialized");
524 throw new this._win.DOMException(
525 "RTCPeerConnection is gone (did you enter Offline mode?)",
536 async _initCertificate(certificate) {
538 certificate = await this._win.RTCPeerConnection.generateCertificate({
543 // Is the PC still around after the await?
545 this._impl.certificate = certificate;
549 _resetPeerIdentityPromise() {
550 this._peerIdentity = new this._win.Promise((resolve, reject) => {
551 this._resolvePeerIdentity = resolve;
552 this._rejectPeerIdentity = reject;
557 this._resetPeerIdentityPromise();
558 this._lastIdentityValidation = this._win.Promise.resolve();
560 let prefName = "media.peerconnection.identity.timeout";
561 let idpTimeout = Services.prefs.getIntPref(prefName);
562 this._localIdp = new PeerConnectionIdp(this._win, idpTimeout);
563 this._remoteIdp = new PeerConnectionIdp(this._win, idpTimeout);
566 // Add a function to the internal operations chain.
569 let resolveP, rejectP;
570 const p = new Promise((r, e) => {
574 this._operations.push(() => {
575 operation().then(resolveP, rejectP);
576 const doNextOperation = () => {
580 this._operations.shift();
581 if (this._operations.length) {
582 this._operations[0]();
583 } else if (this._updateNegotiationNeededOnEmptyChain) {
584 this._updateNegotiationNeededOnEmptyChain = false;
585 this.updateNegotiationNeeded();
588 p.then(doNextOperation, doNextOperation);
590 if (this._operations.length == 1) {
591 this._operations[0]();
596 // It's basically impossible to use async directly in JSImplemented code,
597 // because the implicit promise must be wrapped to the right type for content.
599 // The _async wrapper takes care of this. The _legacy wrapper implements
600 // legacy callbacks in a manner that produces correct line-numbers in errors,
601 // provided that methods validate their inputs before putting themselves on
602 // the pc's operations chain.
604 // These wrappers also serve as guards against settling promises past close().
607 return this._win.Promise.resolve(this._closeWrapper(func));
611 return this._win.Promise.resolve(this._legacyCloseWrapper(...args));
614 _auto(onSucc, onErr, func) {
615 return typeof onSucc == "function"
616 ? this._legacy(onSucc, onErr, func)
620 async _closeWrapper(func) {
621 let closed = this._closed;
623 let result = await func();
624 if (!closed && this._closed) {
625 await new Promise(() => {});
629 if (!closed && this._closed) {
630 await new Promise(() => {});
636 async _legacyCloseWrapper(onSucc, onErr, func) {
637 let wrapCallback = cb => result => {
641 this.logErrorAndCallOnError(e);
646 wrapCallback(onSucc)(await func());
648 wrapCallback(onErr)(e);
652 // This implements the fairly common "Queue a task" logic
653 async _queueTaskWithClosedCheck(func) {
655 return new this._win.Promise((resolve, reject) => {
656 Services.tm.dispatchToMainThread({
672 * An RTCConfiguration may look like this:
674 * { "iceServers": [ { urls: "stun:stun.example.org", },
675 * { url: "stun:stun.example.org", }, // deprecated version
676 * { urls: ["turn:turn1.x.org", "turn:turn2.x.org"],
677 * username:"jib", credential:"mypass"} ] }
679 * This function normalizes the structure of the input for rtcConfig.iceServers for us,
680 * so we test well-formed stun/turn urls before passing along to C++.
681 * msg - Error message to detail which array-entry failed, if any.
683 _mustValidateRTCConfiguration({ iceServers }, msg) {
684 // Normalize iceServers input
685 iceServers.forEach(server => {
686 if (typeof server.urls === "string") {
687 server.urls = [server.urls];
688 } else if (!server.urls && server.url) {
689 // TODO: Remove support for legacy iceServer.url eventually (Bug 1116766)
690 server.urls = [server.url];
691 this.logWarning("RTCIceServer.url is deprecated! Use urls instead.");
695 let nicerNewURI = uriStr => {
697 return Services.io.newURI(uriStr);
699 if (e.result == Cr.NS_ERROR_MALFORMED_URI) {
700 throw new this._win.DOMException(
701 `${msg} - malformed URI: ${uriStr}`,
711 iceServers.forEach(({ urls, username, credential, credentialType }) => {
713 // TODO: Remove once url is deprecated (Bug 1369563)
714 throw new this._win.TypeError(
715 "Missing required 'urls' member of RTCIceServer"
718 if (urls.length == 0) {
719 throw new this._win.DOMException(
720 `${msg} - urls is empty`,
725 .map(url => nicerNewURI(url))
726 .forEach(({ scheme, spec }) => {
727 if (scheme in { turn: 1, turns: 1 }) {
728 if (username == undefined) {
729 throw new this._win.DOMException(
730 `${msg} - missing username: ${spec}`,
734 if (username.length > 512) {
735 throw new this._win.DOMException(
736 `${msg} - username longer then 512 bytes: ${username}`,
740 if (credential == undefined) {
741 throw new this._win.DOMException(
742 `${msg} - missing credential: ${spec}`,
746 if (credentialType != "password") {
748 `RTCConfiguration TURN credentialType \"${credentialType}\"` +
749 " is not yet implemented. Treating as password." +
750 " https://bugzil.la/1247616"
753 this._hasTurnServer = true;
755 } else if (scheme in { stun: 1, stuns: 1 }) {
756 this._hasStunServer = true;
759 throw new this._win.DOMException(
760 `${msg} - improper scheme: ${scheme}`,
764 if (scheme in { stuns: 1 }) {
765 this.logWarning(scheme.toUpperCase() + " is not yet supported.");
767 if (stunServers >= 5) {
769 "Using five or more STUN/TURN servers causes problems"
771 } else if (stunServers > 2) {
773 "Using more than two STUN/TURN servers slows down discovery"
780 // Ideally, this should be of the form _checkState(state),
781 // where the state is taken from an enumeration containing
782 // the valid peer connection states defined in the WebRTC
783 // spec. See Bug 831756.
786 throw new this._win.DOMException(
787 "Peer connection is closed",
793 dispatchEvent(event) {
794 // PC can close while events are firing if there is an async dispatch
795 // in c++ land. But let through "closed" signaling and ice connection events.
796 if (!this._suppressEvents) {
797 this.__DOM_IMPL__.dispatchEvent(event);
801 // Log error message to web console and window.onerror, if present.
802 logErrorAndCallOnError(e) {
807 Ci.nsIScriptError.errorFlag
810 // Safely call onerror directly if present (necessary for testing)
812 if (typeof this._win.onerror === "function") {
813 this._win.onerror(e.message, e.fileName, e.lineNumber);
816 // If onerror itself throws, service it.
822 Ci.nsIScriptError.errorFlag
829 this.logStackMsg(msg, Ci.nsIScriptError.errorFlag);
833 this.logStackMsg(msg, Ci.nsIScriptError.warningFlag);
836 logStackMsg(msg, flag) {
837 let err = this._win.Error();
838 this.logMsg(msg, err.fileName, err.lineNumber, flag);
841 logMsg(msg, file, line, flag) {
842 return logMsg(msg, file, line, flag, this._winID);
846 return this.__DOM_IMPL__.getEventHandler(type);
849 setEH(type, handler) {
850 this.__DOM_IMPL__.setEventHandler(type, handler);
853 makeGetterSetterEH(name) {
854 Object.defineProperty(this, name, {
856 return this.getEH(name);
864 makeLegacyGetterSetterEH(name, msg) {
865 Object.defineProperty(this, name, {
867 return this.getEH(name);
870 this.logWarning(name + " is deprecated! " + msg);
876 createOffer(optionsOrOnSucc, onErr, options) {
877 let onSuccess = null;
878 if (typeof optionsOrOnSucc == "function") {
879 onSuccess = optionsOrOnSucc;
881 options = optionsOrOnSucc;
883 // This entry-point handles both new and legacy call sig. Decipher which one
885 return this._legacy(onSuccess, onErr, () => this._createOffer(options));
887 return this._async(() => this._createOffer(options));
890 // Ensures that we have at least one transceiver of |kind| that is
891 // configured to receive. It will create one if necessary.
892 _ensureOfferToReceive(kind) {
893 let hasRecv = this._transceivers.some(
895 transceiver.getKind() == kind &&
896 (transceiver.direction == "sendrecv" ||
897 transceiver.direction == "recvonly") &&
902 this._addTransceiverNoEvents(kind, { direction: "recvonly" });
906 // Handles offerToReceiveAudio/Video
907 _ensureTransceiversForOfferToReceive(options) {
908 if (options.offerToReceiveAudio) {
909 this._ensureOfferToReceive("audio");
912 if (options.offerToReceiveVideo) {
913 this._ensureOfferToReceive("video");
917 .filter(transceiver => {
919 (options.offerToReceiveVideo === false &&
920 transceiver.receiver.track.kind == "video") ||
921 (options.offerToReceiveAudio === false &&
922 transceiver.receiver.track.kind == "audio")
925 .forEach(transceiver => {
926 if (transceiver.direction == "sendrecv") {
927 transceiver.setDirectionInternal("sendonly");
928 } else if (transceiver.direction == "recvonly") {
929 transceiver.setDirectionInternal("inactive");
934 _createOffer(options) {
936 this._ensureTransceiversForOfferToReceive(options);
937 this._syncTransceivers();
938 return this._chain(() => this._createAnOffer(options));
941 async _createAnOffer(options = {}) {
942 switch (this.signalingState) {
944 case "have-local-offer":
947 throw new this._win.DOMException(
948 `Cannot create offer in ${this.signalingState}`,
952 if (this._localUfragsToReplace.size > 0) {
953 options.iceRestart = true;
956 if (this._localIdp.enabled) {
957 haveAssertion = this._getIdentityAssertion();
959 await this._getPermission();
960 await this._certificateReady;
961 let sdp = await new Promise((resolve, reject) => {
962 this._onCreateOfferSuccess = resolve;
963 this._onCreateOfferFailure = reject;
964 this._impl.createOffer(options);
968 sdp = this._localIdp.addIdentityAttribute(sdp);
970 return Cu.cloneInto({ type: "offer", sdp }, this._win);
973 createAnswer(optionsOrOnSucc, onErr) {
974 // This entry-point handles both new and legacy call sig. Decipher which one
975 if (typeof optionsOrOnSucc == "function") {
976 return this._legacy(optionsOrOnSucc, onErr, () => this._createAnswer({}));
978 return this._async(() => this._createAnswer(optionsOrOnSucc));
981 _createAnswer(options) {
983 this._syncTransceivers();
984 return this._chain(() => this._createAnAnswer());
987 async _createAnAnswer() {
988 if (this.signalingState != "have-remote-offer") {
989 throw new this._win.DOMException(
990 `Cannot create answer in ${this.signalingState}`,
995 if (this._localIdp.enabled) {
996 haveAssertion = this._getIdentityAssertion();
998 await this._getPermission();
999 await this._certificateReady;
1000 let sdp = await new Promise((resolve, reject) => {
1001 this._onCreateAnswerSuccess = resolve;
1002 this._onCreateAnswerFailure = reject;
1003 this._impl.createAnswer();
1005 if (haveAssertion) {
1006 await haveAssertion;
1007 sdp = this._localIdp.addIdentityAttribute(sdp);
1009 return Cu.cloneInto({ type: "answer", sdp }, this._win);
1012 async _getPermission() {
1013 if (!this._havePermission) {
1015 this._documentPrincipal.isSystemPrincipal ||
1016 Services.prefs.getBoolPref("media.navigator.permission.disabled");
1019 this._havePermission = Promise.resolve();
1021 this._havePermission = new Promise((resolve, reject) => {
1022 this._settlePermission = { allow: resolve, deny: reject };
1023 let outerId = this._win.docShell.outerWindowID;
1025 let chrome = new CreateOfferRequest(
1028 this._globalPCListId,
1031 let request = this._win.CreateOfferRequest._create(this._win, chrome);
1032 Services.obs.notifyObservers(request, "PeerConnection:request");
1036 return this._havePermission;
1039 _sanityCheckSdp(sdp) {
1040 // The fippo butter finger filter AKA non-ASCII chars
1041 // Note: SDP allows non-ASCII character in the subject (who cares?)
1042 // eslint-disable-next-line no-control-regex
1043 let pos = sdp.search(/[^\u0000-\u007f]/);
1045 throw new this._win.DOMException(
1046 "SDP contains non ASCII characters at position " + pos,
1047 "InvalidParameterError"
1052 setLocalDescription(desc, onSucc, onErr) {
1053 return this._auto(onSucc, onErr, () => this._setLocalDescription(desc));
1056 _setLocalDescription({ type, sdp }) {
1057 if (type == "pranswer") {
1058 throw new this._win.DOMException(
1059 "pranswer not yet implemented",
1063 this._checkClosed();
1064 return this._chain(async () => {
1065 // Avoid Promise.all ahead of synchronous part of spec algorithm, since it
1066 // defers. NOTE: The spec says to return an already-rejected promise in
1067 // some cases, which is difficult to achieve in practice from JS (would
1068 // require avoiding await and then() entirely), but we want to come as
1069 // close as we reasonably can.
1070 const p = this._getPermission();
1072 switch (this.signalingState) {
1074 case "have-local-offer":
1075 case "have-remote-pranswer":
1084 if (type == "offer") {
1085 await this._createAnOffer();
1086 } else if (type == "answer") {
1087 await this._createAnAnswer();
1090 this._sanityCheckSdp(sdp);
1092 await new Promise((resolve, reject) => {
1093 this._onSetDescriptionSuccess = resolve;
1094 this._onSetDescriptionFailure = reject;
1095 this._impl.setLocalDescription(this._actions[type], sdp);
1098 this._negotiationNeeded = false;
1099 if (type == "answer") {
1100 if (this._localUfragsToReplace.size > 0) {
1101 const ufrags = new Set(this._getUfragsWithPwds(sdp));
1102 if (![...this._localUfragsToReplace].some(uf => ufrags.has(uf))) {
1103 this._localUfragsToReplace.clear();
1107 this.updateNegotiationNeeded();
1111 async _validateIdentity(sdp, origin) {
1112 // Only run a single identity verification at a time. We have to do this to
1113 // avoid problems with the fact that identity validation doesn't block the
1114 // resolution of setRemoteDescription().
1115 const validate = async () => {
1116 await this._lastIdentityValidation;
1117 const msg = await this._remoteIdp.verifyIdentityFromSDP(sdp, origin);
1118 // If this pc has an identity already, then the identity in sdp must match
1120 this._impl.peerIdentity &&
1121 (!msg || msg.identity !== this._impl.peerIdentity)
1123 throw new this._win.DOMException(
1124 "Peer Identity mismatch, expected: " + this._impl.peerIdentity,
1130 // Set new identity and generate an event.
1131 this._impl.peerIdentity = msg.identity;
1132 this._resolvePeerIdentity(
1135 idp: this._remoteIdp.provider,
1144 const haveValidation = validate();
1146 // Always eat errors on this chain
1147 this._lastIdentityValidation = haveValidation.catch(() => {});
1149 // If validation fails, we have some work to do. Fork it so it cannot
1150 // interfere with the validation chain itself, even if the catch function
1152 haveValidation.catch(e => {
1153 this._rejectPeerIdentity(e);
1155 // If we don't expect a specific peer identity, failure to get a valid
1156 // peer identity is not a terminal state, so replace the promise to
1157 // allow another attempt.
1158 if (!this._impl.peerIdentity) {
1159 this._resetPeerIdentityPromise();
1163 // Only wait for IdP validation if we need identity matching
1164 if (this._impl.peerIdentity) {
1165 await haveValidation;
1169 setRemoteDescription(desc, onSucc, onErr) {
1170 return this._auto(onSucc, onErr, () => this._setRemoteDescription(desc));
1173 _setRemoteDescription({ type, sdp }) {
1175 throw new this._win.TypeError(
1176 "Missing required 'type' member of RTCSessionDescriptionInit"
1179 if (type == "pranswer") {
1180 throw new this._win.DOMException(
1181 "pranswer not yet implemented",
1185 this._checkClosed();
1186 return this._chain(async () => {
1187 const haveSetRemote = (async () => {
1188 if (type == "offer" && this.signalingState == "have-local-offer") {
1189 await new Promise((resolve, reject) => {
1190 this._onSetDescriptionSuccess = resolve;
1191 this._onSetDescriptionFailure = reject;
1192 this._impl.setLocalDescription(
1193 Ci.IPeerConnection.kActionRollback,
1197 this._transceivers = this._transceivers.filter(t => !t.shouldRemove);
1198 this._updateCanTrickle();
1200 this._sanityCheckSdp(sdp);
1201 const p = this._getPermission();
1202 await new Promise((resolve, reject) => {
1203 this._onSetDescriptionSuccess = resolve;
1204 this._onSetDescriptionFailure = reject;
1205 this._impl.setRemoteDescription(this._actions[type], sdp);
1208 this._transceivers = this._transceivers.filter(t => !t.shouldRemove);
1209 this._updateCanTrickle();
1212 if (type != "rollback") {
1213 // Do setRemoteDescription and identity validation in parallel
1214 await this._validateIdentity(sdp);
1216 await haveSetRemote;
1217 this._negotiationNeeded = false;
1218 if (type == "answer") {
1219 if (this._localUfragsToReplace.size > 0) {
1220 const ufrags = new Set(
1221 this._getUfragsWithPwds(this._impl.currentLocalDescription)
1223 if (![...this._localUfragsToReplace].some(uf => ufrags.has(uf))) {
1224 this._localUfragsToReplace.clear();
1228 this.updateNegotiationNeeded();
1232 setIdentityProvider(provider, { protocol, usernameHint, peerIdentity } = {}) {
1233 this._checkClosed();
1234 peerIdentity = peerIdentity || this._impl.peerIdentity;
1235 this._localIdp.setIdentityProvider(
1243 async _getIdentityAssertion() {
1244 await this._certificateReady;
1245 return this._localIdp.getIdentityAssertion(
1246 this._impl.fingerprint,
1247 this._documentPrincipal.origin
1251 getIdentityAssertion() {
1252 this._checkClosed();
1253 return this._win.Promise.resolve(
1254 this._chain(() => this._getIdentityAssertion())
1258 get canTrickleIceCandidates() {
1259 return this._canTrickle;
1262 _updateCanTrickle() {
1263 let containsTrickle = section => {
1264 let lines = section.toLowerCase().split(/(?:\r\n?|\n)/);
1265 return lines.some(line => {
1266 let prefix = "a=ice-options:";
1267 if (line.substring(0, prefix.length) !== prefix) {
1270 let tokens = line.substring(prefix.length).split(" ");
1271 return tokens.some(x => x === "trickle");
1277 // The getter for remoteDescription can throw if the pc is closed.
1278 desc = this.remoteDescription;
1281 this._canTrickle = null;
1285 let sections = desc.sdp.split(/(?:\r\n?|\n)m=/);
1286 let topSection = sections.shift();
1288 containsTrickle(topSection) || sections.every(containsTrickle);
1291 addIceCandidate(cand, onSucc, onErr) {
1293 cand.candidate != "" &&
1294 cand.sdpMid == null &&
1295 cand.sdpMLineIndex == null
1297 throw new this._win.TypeError(
1298 "Cannot add a candidate without specifying either sdpMid or sdpMLineIndex"
1301 return this._auto(onSucc, onErr, () => this._addIceCandidate(cand));
1304 async _addIceCandidate({
1310 this._checkClosed();
1311 return this._chain(async () => {
1313 !this._impl.pendingRemoteDescription.length &&
1314 !this._impl.currentRemoteDescription.length
1316 throw new this._win.DOMException(
1317 "No remoteDescription.",
1321 return new Promise((resolve, reject) => {
1322 this._onAddIceCandidateSuccess = resolve;
1323 this._onAddIceCandidateError = reject;
1324 this._impl.addIceCandidate(
1327 usernameFragment || "",
1335 this._localUfragsToReplace = new Set([
1336 ...this._getUfragsWithPwds(this._impl.currentLocalDescription),
1337 ...this._getUfragsWithPwds(this._impl.pendingLocalDescription),
1339 this.updateNegotiationNeeded();
1342 _getUfragsWithPwds(sdp) {
1346 .map(block => block.split("\r\n"))
1348 lines.find(l => l.startsWith("a=ice-ufrag:")),
1349 lines.find(l => l.startsWith("a=ice-pwd:")),
1351 // Even though our own SDP doesn't currently do this: JSEP says properties
1352 // found in the session (array[0]) apply to all m-lines that don't specify
1353 // them, like default values.
1354 .map(([a, b], i, array) => [a || array[0][0], b || array[0][1]])
1355 .filter(([a, b]) => a && b)
1356 .map(array => array.join())
1361 stream.getTracks().forEach(track => this.addTrack(track, stream));
1364 addTrack(track, ...streams) {
1365 this._checkClosed();
1368 this._transceivers.some(transceiver => transceiver.sender.track == track)
1370 throw new this._win.DOMException(
1371 "This track is already set on a sender.",
1372 "InvalidAccessError"
1376 let transceiver = this._transceivers.find(transceiver => {
1378 transceiver.sender.track == null &&
1379 transceiver.getKind() == track.kind &&
1380 !transceiver.stopped &&
1381 !transceiver.hasBeenUsedToSend()
1386 transceiver.sender.setTrack(track);
1387 transceiver.sender.setStreams(streams);
1388 if (transceiver.direction == "recvonly") {
1389 transceiver.setDirectionInternal("sendrecv");
1390 } else if (transceiver.direction == "inactive") {
1391 transceiver.setDirectionInternal("sendonly");
1394 transceiver = this._addTransceiverNoEvents(track, {
1396 direction: "sendrecv",
1400 transceiver.setAddTrackMagic();
1402 this.updateNegotiationNeeded();
1403 return transceiver.sender;
1406 removeTrack(sender) {
1407 this._checkClosed();
1409 sender.checkWasCreatedByPc(this.__DOM_IMPL__);
1411 let transceiver = this._transceivers.find(
1412 transceiver => !transceiver.stopped && transceiver.sender == sender
1415 // If the transceiver was removed due to rollback, let it slide.
1416 if (!transceiver || !sender.track) {
1420 sender.setTrack(null);
1421 if (transceiver.direction == "sendrecv") {
1422 transceiver.setDirectionInternal("recvonly");
1423 } else if (transceiver.direction == "sendonly") {
1424 transceiver.setDirectionInternal("inactive");
1428 this.updateNegotiationNeeded();
1431 _addTransceiverNoEvents(sendTrackOrKind, init) {
1432 let sendTrack = null;
1434 if (typeof sendTrackOrKind == "string") {
1435 kind = sendTrackOrKind;
1441 throw new this._win.TypeError("Invalid media kind");
1444 sendTrack = sendTrackOrKind;
1445 kind = sendTrack.kind;
1448 let transceiverImpl = this._impl.createTransceiverImpl(kind, sendTrack);
1449 let transceiver = this._win.RTCRtpTransceiver._create(
1451 new RTCRtpTransceiver(this, transceiverImpl, init, kind, sendTrack)
1454 this._transceivers.push(transceiver);
1458 _onTransceiverNeeded(kind, transceiverImpl) {
1459 let init = { direction: "recvonly" };
1460 let transceiver = this._win.RTCRtpTransceiver._create(
1462 new RTCRtpTransceiver(this, transceiverImpl, init, kind, null)
1465 this._transceivers.push(transceiver);
1468 addTransceiver(sendTrackOrKind, init) {
1469 this._checkClosed();
1470 let transceiver = this._addTransceiverNoEvents(sendTrackOrKind, init);
1471 this.updateNegotiationNeeded();
1475 _syncTransceivers() {
1476 this._transceivers.forEach(transceiver => transceiver.sync());
1479 updateNegotiationNeeded() {
1480 if (this._operations.length) {
1481 this._updateNegotiationNeededOnEmptyChain = true;
1484 this._queueTaskWithClosedCheck(() => {
1485 if (this._operations.length) {
1486 this._updateNegotiationNeededOnEmptyChain = true;
1489 if (this.signalingState != "stable") {
1493 const negotiationNeeded =
1494 this._impl.checkNegotiationNeeded() ||
1495 this._localUfragsToReplace.size > 0;
1496 if (!negotiationNeeded) {
1497 this._negotiationNeeded = false;
1501 if (this._negotiationNeeded) {
1505 this._negotiationNeeded = true;
1506 this.dispatchEvent(new this._win.Event("negotiationneeded"));
1510 _replaceTrackNoRenegotiation(transceiverImpl, withTrack) {
1511 this._impl.replaceTrackNoRenegotiation(transceiverImpl, withTrack);
1518 this._closed = true;
1519 this.changeIceConnectionState("closed");
1520 if (this._localIdp) {
1521 this._localIdp.close();
1523 if (this._remoteIdp) {
1524 this._remoteIdp.close();
1526 if (!this._suppressEvents) {
1527 this._transceivers.forEach(t => t.setStopped());
1530 this._suppressEvents = true;
1535 this._checkClosed();
1536 let localStreams = new Set();
1537 this._transceivers.forEach(transceiver => {
1538 transceiver.sender.getStreams().forEach(stream => {
1539 localStreams.add(stream);
1542 return [...localStreams.values()];
1545 getRemoteStreams() {
1546 this._checkClosed();
1547 return this._impl.getRemoteStreams();
1551 return this.getTransceivers()
1552 .filter(transceiver => !transceiver.stopped)
1553 .map(transceiver => transceiver.sender);
1557 return this.getTransceivers()
1558 .filter(transceiver => !transceiver.stopped)
1559 .map(transceiver => transceiver.receiver);
1562 mozSetPacketCallback(callback) {
1563 this._onPacket = callback;
1566 mozEnablePacketDump(level, type, sending) {
1567 this._impl.enablePacketDump(level, type, sending);
1570 mozDisablePacketDump(level, type, sending) {
1571 this._impl.disablePacketDump(level, type, sending);
1575 return this._transceivers;
1578 get localDescription() {
1579 return this.pendingLocalDescription || this.currentLocalDescription;
1582 get currentLocalDescription() {
1583 this._checkClosed();
1584 const sdp = this._impl.currentLocalDescription;
1585 if (sdp.length == 0) {
1588 const type = this._impl.currentOfferer ? "offer" : "answer";
1589 return new this._win.RTCSessionDescription({ type, sdp });
1592 get pendingLocalDescription() {
1593 this._checkClosed();
1594 const sdp = this._impl.pendingLocalDescription;
1595 if (sdp.length == 0) {
1598 const type = this._impl.pendingOfferer ? "offer" : "answer";
1599 return new this._win.RTCSessionDescription({ type, sdp });
1602 get remoteDescription() {
1603 return this.pendingRemoteDescription || this.currentRemoteDescription;
1606 get currentRemoteDescription() {
1607 this._checkClosed();
1608 const sdp = this._impl.currentRemoteDescription;
1609 if (sdp.length == 0) {
1612 const type = this._impl.currentOfferer ? "answer" : "offer";
1613 return new this._win.RTCSessionDescription({ type, sdp });
1616 get pendingRemoteDescription() {
1617 this._checkClosed();
1618 const sdp = this._impl.pendingRemoteDescription;
1619 if (sdp.length == 0) {
1622 const type = this._impl.pendingOfferer ? "answer" : "offer";
1623 return new this._win.RTCSessionDescription({ type, sdp });
1626 get peerIdentity() {
1627 return this._peerIdentity;
1630 return this._localIdp.idpLoginUrl;
1633 return this._impl.id;
1638 get iceGatheringState() {
1639 return this._pc.iceGatheringState;
1641 get iceConnectionState() {
1642 return this._iceConnectionState;
1645 get signalingState() {
1646 // checking for our local pc closed indication
1647 // before invoking the pc methods.
1651 return this._impl.signalingState;
1654 handleIceGatheringStateChange() {
1655 _globalPCList.notifyLifecycleObservers(this, "icegatheringstatechange");
1656 this.dispatchEvent(new this._win.Event("icegatheringstatechange"));
1657 if (this.iceGatheringState === "complete") {
1659 new this._win.RTCPeerConnectionIceEvent("icecandidate", {
1666 changeIceConnectionState(state) {
1667 if (state != this._iceConnectionState) {
1668 this._iceConnectionState = state;
1669 _globalPCList.notifyLifecycleObservers(this, "iceconnectionstatechange");
1670 if (!this._closed) {
1671 this.dispatchEvent(new this._win.Event("iceconnectionstatechange"));
1676 getStats(selector, onSucc, onErr) {
1677 if (selector !== null) {
1678 let matchingSenders = this.getSenders().filter(s => s.track === selector);
1679 let matchingReceivers = this.getReceivers().filter(
1680 r => r.track === selector
1683 if (matchingSenders.length + matchingReceivers.length != 1) {
1684 throw new this._win.DOMException(
1685 "track must be associated with a unique sender or receiver, but " +
1686 " is associated with " +
1687 matchingSenders.length +
1689 matchingReceivers.length +
1691 "InvalidAccessError"
1696 return this._auto(onSucc, onErr, () => this._impl.getStats(selector));
1711 this._checkClosed();
1712 this._pcTelemetry.recordDataChannelInit(
1717 if (maxPacketLifeTime === undefined) {
1718 maxPacketLifeTime = maxRetransmitTime;
1721 if (maxRetransmitTime !== undefined) {
1723 "Use maxPacketLifeTime instead of deprecated maxRetransmitTime which will stop working soon in createDataChannel!"
1727 if (protocol.length > 32767) {
1728 // At least 65536/2 UTF-16 characters. UTF-8 might be too long.
1729 // Spec says to check how long |protocol| and |label| are in _bytes_. This
1730 // is a little ambiguous. For now, examine the length of the utf-8 encoding.
1731 const byteCounter = new TextEncoder("utf-8");
1733 if (byteCounter.encode(protocol).length > 65535) {
1734 throw new this._win.TypeError(
1735 "protocol cannot be longer than 65535 bytes"
1740 if (label.length > 32767) {
1741 const byteCounter = new TextEncoder("utf-8");
1742 if (byteCounter.encode(label).length > 65535) {
1743 throw new this._win.TypeError(
1744 "label cannot be longer than 65535 bytes"
1751 } else if (id === null) {
1752 throw new this._win.TypeError("id is required when negotiated is true");
1754 if (maxPacketLifeTime !== undefined && maxRetransmits !== undefined) {
1755 throw new this._win.TypeError(
1756 "Both maxPacketLifeTime and maxRetransmits cannot be provided"
1760 throw new this._win.TypeError("id cannot be 65535");
1762 // Must determine the type where we still know if entries are undefined.
1764 if (maxPacketLifeTime !== undefined) {
1765 type = Ci.IPeerConnection.kDataChannelPartialReliableTimed;
1766 } else if (maxRetransmits !== undefined) {
1767 type = Ci.IPeerConnection.kDataChannelPartialReliableRexmit;
1769 type = Ci.IPeerConnection.kDataChannelReliable;
1771 // Synchronous since it doesn't block.
1774 dataChannel = this._impl.createDataChannel(
1785 if (e.result != Cr.NS_ERROR_NOT_AVAILABLE) {
1790 id === null ? "No available id could be generated" : "Id is in use";
1791 throw new this._win.DOMException(msg, "OperationError");
1794 // Spec says to only do this if this is the first DataChannel created,
1795 // but the c++ code that does the "is negotiation needed" checking will
1796 // only ever return true on the first one.
1797 this.updateNegotiationNeeded();
1802 setupPrototype(RTCPeerConnection, {
1804 contractID: PC_CONTRACT,
1805 QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
1807 offer: Ci.IPeerConnection.kActionOffer,
1808 answer: Ci.IPeerConnection.kActionAnswer,
1809 pranswer: Ci.IPeerConnection.kActionPRAnswer,
1810 rollback: Ci.IPeerConnection.kActionRollback,
1814 // This is a separate class because we don't want to expose it to DOM.
1816 class PeerConnectionObserver {
1822 this._dompc = dompc._innerObject;
1825 newError({ message, name }) {
1826 return new this._dompc._win.DOMException(message, name);
1829 dispatchEvent(event) {
1830 this._dompc.dispatchEvent(event);
1833 onCreateOfferSuccess(sdp) {
1834 this._dompc._onCreateOfferSuccess(sdp);
1837 onCreateOfferError(error) {
1838 this._dompc._onCreateOfferFailure(this.newError(error));
1841 onCreateAnswerSuccess(sdp) {
1842 this._dompc._onCreateAnswerSuccess(sdp);
1845 onCreateAnswerError(error) {
1846 this._dompc._onCreateAnswerFailure(this.newError(error));
1849 onSetDescriptionSuccess() {
1850 this._dompc._onSetDescriptionSuccess();
1853 onSetDescriptionError(error) {
1854 this._dompc._onSetDescriptionFailure(this.newError(error));
1857 onAddIceCandidateSuccess() {
1858 this._dompc._onAddIceCandidateSuccess();
1861 onAddIceCandidateError(error) {
1862 this._dompc._onAddIceCandidateError(this.newError(error));
1865 onIceCandidate(sdpMLineIndex, sdpMid, candidate, usernameFragment) {
1866 let win = this._dompc._win;
1867 if (candidate || sdpMid || usernameFragment) {
1868 if (candidate.includes(" typ relay ")) {
1869 this._dompc._iceGatheredRelayCandidates = true;
1871 candidate = new win.RTCIceCandidate({
1879 new win.RTCPeerConnectionIceEvent("icecandidate", { candidate })
1883 // This method is primarily responsible for updating iceConnectionState.
1884 // This state is defined in the WebRTC specification as follows:
1886 // iceConnectionState:
1887 // -------------------
1888 // new Any of the RTCIceTransports are in the new state and none
1889 // of them are in the checking, failed or disconnected state.
1891 // checking Any of the RTCIceTransports are in the checking state and
1892 // none of them are in the failed or disconnected state.
1894 // connected All RTCIceTransports are in the connected, completed or
1895 // closed state and at least one of them is in the connected
1898 // completed All RTCIceTransports are in the completed or closed state
1899 // and at least one of them is in the completed state.
1901 // failed Any of the RTCIceTransports are in the failed state.
1903 // disconnected Any of the RTCIceTransports are in the disconnected state
1904 // and none of them are in the failed state.
1906 // closed All of the RTCIceTransports are in the closed state.
1908 handleIceConnectionStateChange(iceConnectionState) {
1909 let pc = this._dompc;
1910 if (pc.iceConnectionState === iceConnectionState) {
1914 if (iceConnectionState === "failed") {
1915 if (!pc._hasStunServer) {
1917 "ICE failed, add a STUN server and see about:webrtc for more details"
1919 } else if (!pc._hasTurnServer) {
1921 "ICE failed, add a TURN server and see about:webrtc for more details"
1923 } else if (pc._hasTurnServer && !pc._iceGatheredRelayCandidates) {
1925 "ICE failed, your TURN server appears to be broken, see about:webrtc for more details"
1928 pc.logError("ICE failed, see about:webrtc for more details");
1932 pc.changeIceConnectionState(iceConnectionState);
1935 onStateChange(state) {
1940 if (state == "SignalingState") {
1941 this.dispatchEvent(new this._win.Event("signalingstatechange"));
1945 if (!this._dompc._pc) {
1950 case "IceConnectionState":
1951 let connState = this._dompc._pc.iceConnectionState;
1952 this._dompc._queueTaskWithClosedCheck(() => {
1953 this.handleIceConnectionStateChange(connState);
1957 case "IceGatheringState":
1958 this._dompc.handleIceGatheringStateChange();
1962 this._dompc.logWarning("Unhandled state type: " + state);
1967 onTransceiverNeeded(kind, transceiverImpl) {
1968 this._dompc._onTransceiverNeeded(kind, transceiverImpl);
1971 notifyDataChannel(channel) {
1973 new this._dompc._win.RTCDataChannelEvent("datachannel", { channel })
1977 fireTrackEvent(receiver, streams) {
1978 const pc = this._dompc;
1979 const transceiver = pc.getTransceivers().find(t => t.receiver == receiver);
1983 const track = receiver.track;
1985 new this._win.RTCTrackEvent("track", {
1992 // Fire legacy event as well for a little bit.
1994 new this._win.MediaStreamTrackEvent("addtrack", { track })
1998 fireStreamEvent(stream) {
1999 const ev = new this._win.MediaStreamEvent("addstream", { stream });
2000 this.dispatchEvent(ev);
2003 onPacket(level, type, sending, packet) {
2004 var pc = this._dompc;
2006 pc._onPacket(level, type, sending, packet);
2010 syncTransceivers() {
2011 this._dompc._syncTransceivers();
2014 setupPrototype(PeerConnectionObserver, {
2015 classID: PC_OBS_CID,
2016 contractID: PC_OBS_CONTRACT,
2017 QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
2020 class RTCPeerConnectionStatic {
2022 this._winID = win.windowGlobalChild.innerWindowId;
2025 registerPeerConnectionLifecycleCallback(cb) {
2026 _globalPCList._registerPeerConnectionLifecycleCallback(this._winID, cb);
2029 setupPrototype(RTCPeerConnectionStatic, {
2030 classID: PC_STATIC_CID,
2031 contractID: PC_STATIC_CONTRACT,
2032 QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
2035 class RTCRtpSender {
2036 constructor(pc, transceiverImpl, transceiver, track, kind, streams) {
2038 if (kind == "audio") {
2039 dtmf = transceiverImpl.dtmf;
2042 Object.assign(this, {
2044 _transceiverImpl: transceiverImpl,
2045 _transceiver: transceiver,
2052 replaceTrack(withTrack) {
2053 // async functions in here return a chrome promise, which is not something
2054 // content can use. This wraps that promise in something content can use.
2055 return this._pc._win.Promise.resolve(this._replaceTrack(withTrack));
2058 async _replaceTrack(withTrack) {
2060 if (withTrack && withTrack.kind != this._transceiver.getKind()) {
2061 throw new pc._win.TypeError("Cannot replaceTrack with a different kind!");
2066 await pc._chain(async () => {
2067 if (this._transceiver.stopped) {
2068 throw new pc._win.DOMException(
2069 "Cannot call replaceTrack when transceiver is stopped",
2074 await pc._queueTaskWithClosedCheck(() => {
2075 // Updates the track on the MediaPipeline, will throw on failure.
2077 pc._replaceTrackNoRenegotiation(this._transceiverImpl, withTrack);
2079 throw new pc._win.DOMException(
2080 "Track could not be replaced without renegotiation",
2081 "InvalidModificationError"
2084 this.track = withTrack;
2085 this._transceiver.sync();
2090 setParameters(parameters) {
2091 return this._pc._win.Promise.resolve(this._setParameters(parameters));
2094 async _setParameters(parameters) {
2095 this._pc._checkClosed();
2097 if (this._transceiver.stopped) {
2098 throw new this._pc._win.DOMException(
2099 "This sender's transceiver is stopped",
2104 if (!Services.prefs.getBoolPref("media.peerconnection.simulcast")) {
2108 parameters.encodings = parameters.encodings || [];
2110 parameters.encodings.reduce(
2111 (uniqueRids, { rid, scaleResolutionDownBy }) => {
2112 if (scaleResolutionDownBy < 1.0) {
2113 throw new this._pc._win.RangeError(
2114 "scaleResolutionDownBy must be >= 1.0"
2117 if (!rid && parameters.encodings.length > 1) {
2118 throw new this._pc._win.TypeError("Missing rid");
2120 if (uniqueRids[rid]) {
2121 throw new this._pc._win.TypeError("Duplicate rid");
2123 uniqueRids[rid] = true;
2129 // TODO(bug 1401592): transaction ids, timing changes
2131 await this._pc._queueTaskWithClosedCheck(() => {
2132 this.parameters = parameters;
2133 this._transceiver.sync();
2138 // TODO(bug 1401592): transaction ids
2140 // All the other stuff that the spec says to update is handled when
2141 // transceivers are synced.
2142 return this.parameters;
2145 setStreams(streams) {
2146 this._streams = streams;
2150 return this._streams;
2154 this._pc._replaceTrackNoRenegotiation(this._transceiverImpl, track);
2159 return this._transceiverImpl.dtlsTransport;
2164 return this._pc._async(async () => this._pc._impl.getStats(this.track));
2166 return this._pc._win.Promise.resolve().then(
2167 () => new this._pc._win.RTCStatsReport()
2171 checkWasCreatedByPc(pc) {
2172 if (pc != this._pc.__DOM_IMPL__) {
2173 throw new this._pc._win.DOMException(
2174 "This sender was not created by this PeerConnection",
2175 "InvalidAccessError"
2180 setupPrototype(RTCRtpSender, {
2181 classID: PC_SENDER_CID,
2182 contractID: PC_SENDER_CONTRACT,
2183 QueryInterface: ChromeUtils.generateQI([]),
2186 class RTCRtpTransceiver {
2187 constructor(pc, transceiverImpl, init, kind, sendTrack) {
2188 let receiver = transceiverImpl.receiver;
2189 let streams = (init && init.streams) || [];
2190 let sender = pc._win.RTCRtpSender._create(
2192 new RTCRtpSender(pc, transceiverImpl, this, sendTrack, kind, streams)
2195 let direction = (init && init.direction) || "sendrecv";
2196 Object.assign(this, {
2202 _direction: direction,
2203 currentDirection: null,
2204 addTrackMagic: false,
2205 shouldRemove: false,
2206 _hasBeenUsedToSend: false,
2207 // the receiver starts out without a track, so record this here
2209 _transceiverImpl: transceiverImpl,
2213 set direction(direction) {
2214 this._pc._checkClosed();
2217 throw new this._pc._win.DOMException(
2218 "Transceiver is stopped!",
2223 if (this._direction == direction) {
2227 this._direction = direction;
2229 this._pc.updateNegotiationNeeded();
2233 return this._direction;
2236 setDirectionInternal(direction) {
2237 this._direction = direction;
2241 this._pc._checkClosed();
2249 this._pc.updateNegotiationNeeded();
2253 this.stopped = true;
2254 this.currentDirection = null;
2261 hasBeenUsedToSend() {
2262 return this._hasBeenUsedToSend;
2265 setAddTrackMagic() {
2266 this.addTrackMagic = true;
2270 if (this._syncing) {
2271 throw new DOMException("Reentrant sync! This is a bug!", "InternalError");
2273 this._syncing = true;
2274 this._transceiverImpl.syncWithJS(this.__DOM_IMPL__);
2275 this._syncing = false;
2278 // Used by _transceiverImpl.syncWithJS, don't call sync again!
2279 setCurrentDirection(direction) {
2284 switch (direction) {
2287 this._hasBeenUsedToSend = true;
2292 this.currentDirection = direction;
2295 // Used by _transceiverImpl.syncWithJS, don't call sync again!
2300 // Used by _transceiverImpl.syncWithJS, don't call sync again!
2306 setupPrototype(RTCRtpTransceiver, {
2307 classID: PC_TRANSCEIVER_CID,
2308 contractID: PC_TRANSCEIVER_CONTRACT,
2309 QueryInterface: ChromeUtils.generateQI([]),
2312 class CreateOfferRequest {
2313 constructor(windowID, innerWindowID, callID, isSecure) {
2314 Object.assign(this, { windowID, innerWindowID, callID, isSecure });
2317 setupPrototype(CreateOfferRequest, {
2318 classID: PC_COREQUEST_CID,
2319 contractID: PC_COREQUEST_CONTRACT,
2320 QueryInterface: ChromeUtils.generateQI([]),
2323 var EXPORTED_SYMBOLS = [
2326 "RTCSessionDescription",
2327 "RTCPeerConnection",
2328 "RTCPeerConnectionStatic",
2330 "RTCRtpTransceiver",
2331 "PeerConnectionObserver",
2332 "CreateOfferRequest",