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/. */
6 ChromeUtils.defineESModuleGetters(lazy, {
7 PeerConnectionIdp: "resource://gre/modules/media/PeerConnectionIdp.sys.mjs",
10 const PC_CONTRACT = "@mozilla.org/dom/peerconnection;1";
11 const PC_OBS_CONTRACT = "@mozilla.org/dom/peerconnectionobserver;1";
12 const PC_ICE_CONTRACT = "@mozilla.org/dom/rtcicecandidate;1";
13 const PC_SESSION_CONTRACT = "@mozilla.org/dom/rtcsessiondescription;1";
14 const PC_STATIC_CONTRACT = "@mozilla.org/dom/peerconnectionstatic;1";
15 const PC_COREQUEST_CONTRACT = "@mozilla.org/dom/createofferrequest;1";
17 const PC_CID = Components.ID("{bdc2e533-b308-4708-ac8e-a8bfade6d851}");
18 const PC_OBS_CID = Components.ID("{d1748d4c-7f6a-4dc5-add6-d55b7678537e}");
19 const PC_ICE_CID = Components.ID("{02b9970c-433d-4cc2-923d-f7028ac66073}");
20 const PC_SESSION_CID = Components.ID("{1775081b-b62d-4954-8ffe-a067bbf508a7}");
21 const PC_MANAGER_CID = Components.ID("{7293e901-2be3-4c02-b4bd-cbef6fc24f78}");
22 const PC_STATIC_CID = Components.ID("{0fb47c47-a205-4583-a9fc-cbadf8c95880}");
23 const PC_COREQUEST_CID = Components.ID(
24 "{74b2122d-65a8-4824-aa9e-3d664cb75dc2}"
27 function logWebRTCMsg(msg, file, line, flag, win) {
28 let scriptErrorClass = Cc["@mozilla.org/scripterror;1"];
29 let scriptError = scriptErrorClass.createInstance(Ci.nsIScriptError);
30 scriptError.initWithWindowID(
38 win.windowGlobalChild.innerWindowId
40 Services.console.logMessage(scriptError);
42 Services.prefs.getBoolPref("media.peerconnection.treat_warnings_as_errors")
44 throw new win.TypeError(msg);
48 let setupPrototype = (_class, dict) => {
49 _class.prototype.classDescription = _class.name;
50 Object.assign(_class.prototype, dict);
53 // Global list of PeerConnection objects, so they can be cleaned up when
54 // a page is torn down. (Maps inner window ID to an array of PC objects).
55 export class GlobalPCList {
58 this._networkdown = false; // XXX Need to query current state somehow
59 this._lifecycleobservers = {};
61 Services.obs.addObserver(this, "inner-window-destroyed", true);
62 Services.obs.addObserver(this, "profile-change-net-teardown", true);
63 Services.obs.addObserver(this, "network:offline-about-to-go-offline", true);
64 Services.obs.addObserver(this, "network:offline-status-changed", true);
65 Services.obs.addObserver(this, "gmp-plugin-crash", true);
66 Services.obs.addObserver(this, "PeerConnection:response:allow", true);
67 Services.obs.addObserver(this, "PeerConnection:response:deny", true);
69 Services.cpmm.addMessageListener("gmp-plugin-crash", this);
73 notifyLifecycleObservers(pc, type) {
74 for (var key of Object.keys(this._lifecycleobservers)) {
75 this._lifecycleobservers[key](pc, pc._winID, type);
80 let winID = pc._winID;
81 if (this._list[winID]) {
82 this._list[winID].push(Cu.getWeakReference(pc));
84 this._list[winID] = [Cu.getWeakReference(pc)];
86 pc._globalPCListId = this._nextId++;
87 this.removeNullRefs(winID);
90 findPC(globalPCListId) {
91 for (let winId in this._list) {
92 if (this._list.hasOwnProperty(winId)) {
93 for (let pcref of this._list[winId]) {
95 if (pc && pc._globalPCListId == globalPCListId) {
104 removeNullRefs(winID) {
105 if (this._list[winID] === undefined) {
108 this._list[winID] = this._list[winID].filter(function (e) {
109 return e.get() !== null;
112 if (this._list[winID].length === 0) {
113 delete this._list[winID];
117 handleGMPCrash(data) {
118 let broadcastPluginCrash = function (list, winID, pluginID, pluginName) {
119 if (list.hasOwnProperty(winID)) {
120 list[winID].forEach(function (pcref) {
121 let pc = pcref.get();
123 pc._pc.pluginCrash(pluginID, pluginName);
129 // a plugin crashed; if it's associated with any of our PCs, fire an
130 // event to the DOM window
131 for (let winId in this._list) {
132 broadcastPluginCrash(this._list, winId, data.pluginID, data.pluginName);
136 receiveMessage({ name, data }) {
137 if (name == "gmp-plugin-crash") {
138 this.handleGMPCrash(data);
142 observe(subject, topic, data) {
143 let cleanupPcRef = function (pcref) {
144 let pc = pcref.get();
146 pc._suppressEvents = true;
151 let cleanupWinId = function (list, winID) {
152 if (list.hasOwnProperty(winID)) {
153 list[winID].forEach(cleanupPcRef);
158 if (topic == "inner-window-destroyed") {
159 let winID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
160 cleanupWinId(this._list, winID);
162 if (this._lifecycleobservers.hasOwnProperty(winID)) {
163 delete this._lifecycleobservers[winID];
166 topic == "profile-change-net-teardown" ||
167 topic == "network:offline-about-to-go-offline"
169 // As Necko doesn't prevent us from accessing the network we still need to
170 // monitor the network offline/online state here. See bug 1326483
171 this._networkdown = true;
172 } else if (topic == "network:offline-status-changed") {
173 if (data == "offline") {
174 this._networkdown = true;
175 } else if (data == "online") {
176 this._networkdown = false;
178 } else if (topic == "gmp-plugin-crash") {
179 if (subject instanceof Ci.nsIWritablePropertyBag2) {
180 let pluginID = subject.getPropertyAsUint32("pluginID");
181 let pluginName = subject.getPropertyAsAString("pluginName");
182 let data = { pluginID, pluginName };
183 this.handleGMPCrash(data);
186 topic == "PeerConnection:response:allow" ||
187 topic == "PeerConnection:response:deny"
189 var pc = this.findPC(data);
191 if (topic == "PeerConnection:response:allow") {
192 pc._settlePermission.allow();
194 let err = new pc._win.DOMException(
195 "The request is not allowed by " +
196 "the user agent or the platform in the current context.",
199 pc._settlePermission.deny(err);
205 _registerPeerConnectionLifecycleCallback(winID, cb) {
206 this._lifecycleobservers[winID] = cb;
210 setupPrototype(GlobalPCList, {
211 QueryInterface: ChromeUtils.generateQI([
213 "nsISupportsWeakReference",
215 classID: PC_MANAGER_CID,
218 var _globalPCList = new GlobalPCList();
220 export class RTCIceCandidate {
226 if (dict.sdpMid == null && dict.sdpMLineIndex == null) {
227 throw new this._win.TypeError(
228 "Either sdpMid or sdpMLineIndex must be specified"
231 Object.assign(this, dict);
235 setupPrototype(RTCIceCandidate, {
237 contractID: PC_ICE_CONTRACT,
238 QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
241 export class RTCSessionDescription {
244 this._winID = this._win.windowGlobalChild.innerWindowId;
245 this._legacyPref = Services.prefs.getBoolPref(
246 "media.peerconnection.description.legacy.enabled"
250 __init({ type, sdp }) {
251 Object.assign(this, { _type: type, _sdp: sdp });
258 if (!this._legacyPref) {
259 // TODO: this throws even in sloppy mode. Remove in bug 1883992
260 throw new this._win.TypeError("setting getter-only property type");
270 if (!this._legacyPref) {
271 // TODO: this throws even in sloppy mode. Remove in bug 1883992
272 throw new this._win.TypeError("setting getter-only property sdp");
280 // Warn once per RTCSessionDescription about deprecated writable usage.
281 if (this._legacyPref) {
283 "RTCSessionDescription's members are readonly! " +
284 "Writing to them is deprecated and will break soon!",
285 Ci.nsIScriptError.warningFlag
289 "RTCSessionDescription's members are readonly! " +
290 "Writing to them no longer works!",
291 Ci.nsIScriptError.errorFlag
299 let err = this._win.Error();
300 logWebRTCMsg(msg, err.fileName, err.lineNumber, flag, this._win);
304 setupPrototype(RTCSessionDescription, {
305 classID: PC_SESSION_CID,
306 contractID: PC_SESSION_CONTRACT,
307 QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
310 // Records PC related telemetry
311 class PeerConnectionTelemetry {
312 // ICE connection state enters connected or completed.
314 Services.telemetry.scalarAdd("webrtc.peerconnection.connected", 1);
315 this.recordConnected = () => {};
317 // DataChannel is created
318 _recordDataChannelCreated() {
319 Services.telemetry.scalarAdd(
320 "webrtc.peerconnection.datachannel_created",
323 this._recordDataChannelCreated = () => {};
325 // DataChannel initialized with maxRetransmitTime
326 _recordMaxRetransmitTime(maxRetransmitTime) {
327 if (maxRetransmitTime === undefined) {
330 Services.telemetry.scalarAdd(
331 "webrtc.peerconnection.datachannel_max_retx_used",
334 this._recordMaxRetransmitTime = () => true;
337 // DataChannel initialized with maxPacketLifeTime
338 _recordMaxPacketLifeTime(maxPacketLifeTime) {
339 if (maxPacketLifeTime === undefined) {
342 Services.telemetry.scalarAdd(
343 "webrtc.peerconnection.datachannel_max_life_used",
346 this._recordMaxPacketLifeTime = () => true;
349 // DataChannel initialized
350 recordDataChannelInit(maxRetransmitTime, maxPacketLifeTime) {
351 const retxUsed = this._recordMaxRetransmitTime(maxRetransmitTime);
352 if (this._recordMaxPacketLifeTime(maxPacketLifeTime) && retxUsed) {
353 Services.telemetry.scalarAdd(
354 "webrtc.peerconnection.datachannel_max_retx_and_life_used",
357 this.recordDataChannelInit = () => {};
359 this._recordDataChannelCreated();
363 export class RTCPeerConnection {
366 this._closed = false;
367 this._pendingLocalDescription = null;
368 this._pendingRemoteDescription = null;
369 this._currentLocalDescription = null;
370 this._currentRemoteDescription = null;
371 this._legacyPref = Services.prefs.getBoolPref(
372 "media.peerconnection.description.legacy.enabled"
375 // http://rtcweb-wg.github.io/jsep/#rfc.section.4.1.9
376 // canTrickle == null means unknown; when a remote description is received it
377 // is set to true or false based on the presence of the "trickle" ice-option
378 this._canTrickle = null;
380 // So we can record telemetry on state transitions
381 this._iceConnectionState = "new";
383 this._hasStunServer = this._hasTurnServer = false;
384 this._iceGatheredRelayCandidates = false;
386 this._pcTelemetry = new PeerConnectionTelemetry();
393 // Pref-based overrides; will _not_ be reflected in getConfiguration
394 _applyPrefsToConfig(rtcConfig) {
396 rtcConfig.iceTransportPolicy == "all" &&
397 Services.prefs.getBoolPref("media.peerconnection.ice.relay_only")
399 rtcConfig.iceTransportPolicy = "relay";
403 !rtcConfig.iceServers ||
404 !Services.prefs.getBoolPref(
405 "media.peerconnection.use_document_iceservers"
409 rtcConfig.iceServers = JSON.parse(
410 Services.prefs.getCharPref(
411 "media.peerconnection.default_iceservers"
416 "Ignoring invalid media.peerconnection.default_iceservers in about:config"
418 rtcConfig.iceServers = [];
421 this._validateIceServers(
422 rtcConfig.iceServers,
423 "Ignoring invalid media.peerconnection.default_iceservers in about:config"
426 this.logWarning(e.message);
427 rtcConfig.iceServers = [];
432 _validateConfig(rtcConfig) {
433 if ("sdpSemantics" in rtcConfig) {
434 if (rtcConfig.sdpSemantics == "plan-b") {
436 `Outdated and non-standard {sdpSemantics: "plan-b"} is not ` +
437 `supported! WebRTC may be unreliable. Please update code to ` +
438 `follow standard "unified-plan".`
441 // Don't let it show up in getConfiguration.
442 delete rtcConfig.sdpSemantics;
446 // certificates must match
447 if (rtcConfig.certificates.length != this._config.certificates.length) {
448 throw new this._win.DOMException(
449 "Cannot change certificates with setConfiguration (length differs)",
450 "InvalidModificationError"
453 for (let i = 0; i < rtcConfig.certificates.length; i++) {
454 if (rtcConfig.certificates[i] != this._config.certificates[i]) {
455 throw new this._win.DOMException(
456 `Cannot change certificates with setConfiguration ` +
457 `(cert at index ${i} differs)`,
458 "InvalidModificationError"
463 // bundlePolicy must match
464 if (rtcConfig.bundlePolicy != this._config.bundlePolicy) {
465 throw new this._win.DOMException(
466 "Cannot change bundlePolicy with setConfiguration",
467 "InvalidModificationError"
471 // peerIdentity must match
473 rtcConfig.peerIdentity &&
474 rtcConfig.peerIdentity != this._config.peerIdentity
476 throw new this._win.DOMException(
477 "Cannot change peerIdentity with setConfiguration",
478 "InvalidModificationError"
482 // TODO (bug 1339203): rtcpMuxPolicy must match
483 // TODO (bug 1529398): iceCandidatePoolSize must match if sLD has ever
487 // This gets executed in the typical case when iceServers
488 // are passed in through the web page.
489 this._validateIceServers(
490 rtcConfig.iceServers,
491 "RTCPeerConnection constructor passed invalid RTCConfiguration"
495 _checkIfIceRestartRequired(rtcConfig) {
497 if (rtcConfig.iceTransportPolicy != this._config.iceTransportPolicy) {
498 this._pc.restartIceNoRenegotiationNeeded();
502 JSON.stringify(this._config.iceServers) !=
503 JSON.stringify(rtcConfig.iceServers)
505 this._pc.restartIceNoRenegotiationNeeded();
511 this._winID = this._win.windowGlobalChild.innerWindowId;
512 let certificates = rtcConfig.certificates || [];
514 if (certificates.some(c => c.expires <= Date.now())) {
515 throw new this._win.DOMException(
516 "Unable to create RTCPeerConnection with an expired certificate",
521 // TODO(bug 1531875): Check origin of certs
523 // TODO(bug 1176518): Remove this code once we support multiple certs
525 if (certificates.length == 1) {
526 certificate = certificates[0];
527 } else if (certificates.length) {
528 throw new this._win.DOMException(
529 "RTCPeerConnection does not currently support multiple certificates",
534 this._documentPrincipal = Cu.getWebIDLCallerPrincipal();
536 if (_globalPCList._networkdown) {
537 throw new this._win.DOMException(
538 "Can't create RTCPeerConnections when the network is down",
543 this.makeGetterSetterEH("ontrack");
544 this.makeLegacyGetterSetterEH(
546 "Use peerConnection.ontrack instead."
548 this.makeLegacyGetterSetterEH(
550 "Use peerConnection.ontrack instead."
552 this.makeGetterSetterEH("onicecandidate");
553 this.makeGetterSetterEH("onnegotiationneeded");
554 this.makeGetterSetterEH("onsignalingstatechange");
555 this.makeGetterSetterEH("ondatachannel");
556 this.makeGetterSetterEH("oniceconnectionstatechange");
557 this.makeGetterSetterEH("onicegatheringstatechange");
558 this.makeGetterSetterEH("onconnectionstatechange");
559 this.makeGetterSetterEH("onidentityresult");
560 this.makeGetterSetterEH("onpeeridentity");
561 this.makeGetterSetterEH("onidpassertionerror");
562 this.makeGetterSetterEH("onidpvalidationerror");
564 this._pc = new this._win.PeerConnectionImpl();
566 this.__DOM_IMPL__._innerObject = this;
567 const observer = new this._win.PeerConnectionObserver(this.__DOM_IMPL__);
569 // Add a reference to the PeerConnection to global list (before init).
570 _globalPCList.addPC(this);
572 this._pc.initialize(observer, this._win);
574 this.setConfiguration(rtcConfig);
576 this._certificateReady = this._initCertificate(certificate);
578 _globalPCList.notifyLifecycleObservers(this, "initialized");
582 const config = Object.assign({}, this._config);
583 delete config.sdpSemantics;
587 setConfiguration(rtcConfig) {
589 this._validateConfig(rtcConfig);
590 this._checkIfIceRestartRequired(rtcConfig);
592 // Allow prefs to tweak these settings before passing to c++, but hide all
594 const configWithPrefTweaks = Object.assign({}, rtcConfig);
595 this._applyPrefsToConfig(configWithPrefTweaks);
596 this._pc.setConfiguration(configWithPrefTweaks);
598 this._config = Object.assign({}, rtcConfig);
601 async _initCertificate(certificate) {
603 certificate = await this._win.RTCPeerConnection.generateCertificate({
608 this._pc.certificate = certificate;
611 _resetPeerIdentityPromise() {
612 this._peerIdentity = new this._win.Promise((resolve, reject) => {
613 this._resolvePeerIdentity = resolve;
614 this._rejectPeerIdentity = reject;
619 this._resetPeerIdentityPromise();
620 this._lastIdentityValidation = this._win.Promise.resolve();
622 let prefName = "media.peerconnection.identity.timeout";
623 let idpTimeout = Services.prefs.getIntPref(prefName);
624 this._localIdp = new lazy.PeerConnectionIdp(this._win, idpTimeout);
625 this._remoteIdp = new lazy.PeerConnectionIdp(this._win, idpTimeout);
628 // Add a function to the internal operations chain.
631 return this._pc.chain(operation);
634 // It's basically impossible to use async directly in JSImplemented code,
635 // because the implicit promise must be wrapped to the right type for content.
637 // The _async wrapper takes care of this. The _legacy wrapper implements
638 // legacy callbacks in a manner that produces correct line-numbers in errors,
639 // provided that methods validate their inputs before putting themselves on
640 // the pc's operations chain.
642 // These wrappers also serve as guards against settling promises past close().
645 return this._win.Promise.resolve(this._closeWrapper(func));
649 return this._win.Promise.resolve(this._legacyCloseWrapper(...args));
652 _auto(onSucc, onErr, func) {
653 return typeof onSucc == "function"
654 ? this._legacy(onSucc, onErr, func)
658 async _closeWrapper(func) {
659 let closed = this._closed;
661 let result = await func();
662 if (!closed && this._closed) {
663 await new Promise(() => {});
667 if (!closed && this._closed) {
668 await new Promise(() => {});
674 async _legacyCloseWrapper(onSucc, onErr, func) {
675 let wrapCallback = cb => result => {
679 this.logErrorAndCallOnError(e);
684 wrapCallback(onSucc)(await func());
686 wrapCallback(onErr)(e);
690 // This implements the fairly common "Queue a task" logic
691 async _queueTaskWithClosedCheck(func) {
693 return new this._win.Promise((resolve, reject) => {
694 Services.tm.dispatchToMainThread({
710 * An RTCConfiguration may look like this:
712 * { "iceServers": [ { urls: "stun:stun.example.org", },
713 * { url: "stun:stun.example.org", }, // deprecated version
714 * { urls: ["turn:turn1.x.org", "turn:turn2.x.org"],
715 * username:"jib", credential:"mypass"} ] }
717 * This function normalizes the structure of the input for rtcConfig.iceServers for us,
718 * so we test well-formed stun/turn urls before passing along to C++.
719 * msg - Error message to detail which array-entry failed, if any.
721 _validateIceServers(iceServers, msg) {
722 // Normalize iceServers input
723 iceServers.forEach(server => {
724 if (typeof server.urls === "string") {
725 server.urls = [server.urls];
726 } else if (!server.urls && server.url) {
727 // TODO: Remove support for legacy iceServer.url eventually (Bug 1116766)
728 server.urls = [server.url];
729 this.logWarning("RTCIceServer.url is deprecated! Use urls instead.");
733 let nicerNewURI = uriStr => {
735 return Services.io.newURI(uriStr);
737 if (e.result == Cr.NS_ERROR_MALFORMED_URI) {
738 throw new this._win.DOMException(
739 `${msg} - malformed URI: ${uriStr}`,
749 iceServers.forEach(({ urls, username, credential, credentialType }) => {
751 // TODO: Remove once url is deprecated (Bug 1369563)
752 throw new this._win.TypeError(
753 "Missing required 'urls' member of RTCIceServer"
757 throw new this._win.DOMException(
758 `${msg} - urls is empty`,
763 .map(url => nicerNewURI(url))
764 .forEach(({ scheme, spec, query }) => {
765 if (scheme in { turn: 1, turns: 1 }) {
766 if (username == undefined) {
767 throw new this._win.DOMException(
768 `${msg} - missing username: ${spec}`,
772 if (username.length > 512) {
773 throw new this._win.DOMException(
774 `${msg} - username longer then 512 bytes: ${username}`,
778 if (credential == undefined) {
779 throw new this._win.DOMException(
780 `${msg} - missing credential: ${spec}`,
784 if (credentialType != "password") {
786 `RTCConfiguration TURN credentialType \"${credentialType}\"` +
787 " is not yet implemented. Treating as password." +
788 " https://bugzil.la/1247616"
791 this._hasTurnServer = true;
792 // If this is not a TURN TCP/TLS server, it is also a STUN server
793 const parameters = query.split("&");
794 if (!parameters.includes("transport=tcp")) {
795 this._hasStunServer = true;
798 } else if (scheme in { stun: 1, stuns: 1 }) {
799 this._hasStunServer = true;
802 throw new this._win.DOMException(
803 `${msg} - improper scheme: ${scheme}`,
807 if (scheme in { stuns: 1 }) {
808 this.logWarning(scheme.toUpperCase() + " is not yet supported.");
810 if (stunServers >= 5) {
812 "Using five or more STUN/TURN servers slows down discovery"
819 // Ideally, this should be of the form _checkState(state),
820 // where the state is taken from an enumeration containing
821 // the valid peer connection states defined in the WebRTC
822 // spec. See Bug 831756.
825 throw new this._win.DOMException(
826 "Peer connection is closed",
832 dispatchEvent(event) {
833 // PC can close while events are firing if there is an async dispatch
834 // in c++ land. But let through "closed" signaling and ice connection events.
835 if (!this._suppressEvents) {
836 this.__DOM_IMPL__.dispatchEvent(event);
840 // Log error message to web console and window.onerror, if present.
841 logErrorAndCallOnError(e) {
846 Ci.nsIScriptError.errorFlag
849 // Safely call onerror directly if present (necessary for testing)
851 if (typeof this._win.onerror === "function") {
852 this._win.onerror(e.message, e.fileName, e.lineNumber);
855 // If onerror itself throws, service it.
861 Ci.nsIScriptError.errorFlag
868 this.logStackMsg(msg, Ci.nsIScriptError.errorFlag);
872 this.logStackMsg(msg, Ci.nsIScriptError.warningFlag);
875 logStackMsg(msg, flag) {
876 let err = this._win.Error();
877 this.logMsg(msg, err.fileName, err.lineNumber, flag);
880 logMsg(msg, file, line, flag) {
881 return logWebRTCMsg(msg, file, line, flag, this._win);
885 return this.__DOM_IMPL__.getEventHandler(type);
888 setEH(type, handler) {
889 this.__DOM_IMPL__.setEventHandler(type, handler);
892 makeGetterSetterEH(name) {
893 Object.defineProperty(this, name, {
895 return this.getEH(name);
903 makeLegacyGetterSetterEH(name, msg) {
904 Object.defineProperty(this, name, {
906 return this.getEH(name);
909 this.logWarning(name + " is deprecated! " + msg);
915 createOffer(optionsOrOnSucc, onErr, options) {
916 let onSuccess = null;
917 if (typeof optionsOrOnSucc == "function") {
918 onSuccess = optionsOrOnSucc;
920 options = optionsOrOnSucc;
922 // This entry-point handles both new and legacy call sig. Decipher which one
924 return this._legacy(onSuccess, onErr, () => this._createOffer(options));
926 return this._async(() => this._createOffer(options));
929 // Ensures that we have at least one transceiver of |kind| that is
930 // configured to receive. It will create one if necessary.
931 _ensureOfferToReceive(kind) {
932 let hasRecv = this.getTransceivers().some(
934 transceiver.getKind() == kind &&
935 (transceiver.direction == "sendrecv" ||
936 transceiver.direction == "recvonly") &&
941 this._addTransceiverNoEvents(kind, { direction: "recvonly" });
945 // Handles offerToReceiveAudio/Video
946 _ensureTransceiversForOfferToReceive(options) {
947 if (options.offerToReceiveAudio) {
948 this._ensureOfferToReceive("audio");
951 if (options.offerToReceiveVideo) {
952 this._ensureOfferToReceive("video");
955 this.getTransceivers()
956 .filter(transceiver => {
958 (options.offerToReceiveVideo === false &&
959 transceiver.receiver.track.kind == "video") ||
960 (options.offerToReceiveAudio === false &&
961 transceiver.receiver.track.kind == "audio")
964 .forEach(transceiver => {
965 if (transceiver.direction == "sendrecv") {
966 transceiver.setDirectionInternal("sendonly");
967 } else if (transceiver.direction == "recvonly") {
968 transceiver.setDirectionInternal("inactive");
973 _createOffer(options) {
975 this._ensureTransceiversForOfferToReceive(options);
976 return this._chain(() => this._createAnOffer(options));
979 async _createAnOffer(options = {}) {
980 switch (this.signalingState) {
982 case "have-local-offer":
985 throw new this._win.DOMException(
986 `Cannot create offer in ${this.signalingState}`,
991 if (this._localIdp.enabled) {
992 haveAssertion = this._getIdentityAssertion();
994 await this._getPermission();
995 await this._certificateReady;
996 let sdp = await new Promise((resolve, reject) => {
997 this._onCreateOfferSuccess = resolve;
998 this._onCreateOfferFailure = reject;
999 this._pc.createOffer(options);
1001 if (haveAssertion) {
1002 await haveAssertion;
1003 sdp = this._localIdp.addIdentityAttribute(sdp);
1005 return Cu.cloneInto({ type: "offer", sdp }, this._win);
1008 createAnswer(optionsOrOnSucc, onErr) {
1009 // This entry-point handles both new and legacy call sig. Decipher which one
1010 if (typeof optionsOrOnSucc == "function") {
1011 return this._legacy(optionsOrOnSucc, onErr, () => this._createAnswer({}));
1013 return this._async(() => this._createAnswer(optionsOrOnSucc));
1017 this._checkClosed();
1018 return this._chain(() => this._createAnAnswer());
1021 async _createAnAnswer() {
1022 if (this.signalingState != "have-remote-offer") {
1023 throw new this._win.DOMException(
1024 `Cannot create answer in ${this.signalingState}`,
1029 if (this._localIdp.enabled) {
1030 haveAssertion = this._getIdentityAssertion();
1032 await this._getPermission();
1033 await this._certificateReady;
1034 let sdp = await new Promise((resolve, reject) => {
1035 this._onCreateAnswerSuccess = resolve;
1036 this._onCreateAnswerFailure = reject;
1037 this._pc.createAnswer();
1039 if (haveAssertion) {
1040 await haveAssertion;
1041 sdp = this._localIdp.addIdentityAttribute(sdp);
1043 return Cu.cloneInto({ type: "answer", sdp }, this._win);
1046 async _getPermission() {
1047 if (!this._havePermission) {
1049 this._documentPrincipal.isSystemPrincipal ||
1050 Services.prefs.getBoolPref("media.navigator.permission.disabled");
1053 this._havePermission = Promise.resolve();
1055 this._havePermission = new Promise((resolve, reject) => {
1056 this._settlePermission = { allow: resolve, deny: reject };
1057 let outerId = this._win.docShell.outerWindowID;
1059 let chrome = new CreateOfferRequest(
1062 this._globalPCListId,
1065 let request = this._win.CreateOfferRequest._create(this._win, chrome);
1066 Services.obs.notifyObservers(request, "PeerConnection:request");
1070 return this._havePermission;
1073 _sanityCheckSdp(sdp) {
1074 // The fippo butter finger filter AKA non-ASCII chars
1075 // Note: SDP allows non-ASCII character in the subject (who cares?)
1076 // eslint-disable-next-line no-control-regex
1077 let pos = sdp.search(/[^\u0000-\u007f]/);
1079 throw new this._win.DOMException(
1080 "SDP contains non ASCII characters at position " + pos,
1081 "InvalidParameterError"
1086 setLocalDescription(desc, onSucc, onErr) {
1087 return this._auto(onSucc, onErr, () => this._setLocalDescription(desc));
1090 _setLocalDescription({ type, sdp }) {
1091 if (type == "pranswer") {
1092 throw new this._win.DOMException(
1093 "pranswer not yet implemented",
1097 this._checkClosed();
1098 return this._chain(async () => {
1099 // Avoid Promise.all ahead of synchronous part of spec algorithm, since it
1100 // defers. NOTE: The spec says to return an already-rejected promise in
1101 // some cases, which is difficult to achieve in practice from JS (would
1102 // require avoiding await and then() entirely), but we want to come as
1103 // close as we reasonably can.
1104 const p = this._getPermission();
1106 switch (this.signalingState) {
1108 case "have-local-offer":
1109 case "have-remote-pranswer":
1118 if (type == "offer") {
1119 await this._createAnOffer();
1120 } else if (type == "answer") {
1121 await this._createAnAnswer();
1124 this._sanityCheckSdp(sdp);
1128 await new Promise((resolve, reject) => {
1129 this._onSetDescriptionSuccess = resolve;
1130 this._onSetDescriptionFailure = reject;
1131 this._pc.setLocalDescription(this._actions[type], sdp);
1135 this._pc.onSetDescriptionError();
1138 await this._pc.onSetDescriptionSuccess(type, false);
1142 async _validateIdentity(sdp, origin) {
1143 // Only run a single identity verification at a time. We have to do this to
1144 // avoid problems with the fact that identity validation doesn't block the
1145 // resolution of setRemoteDescription().
1146 const validate = async () => {
1147 // Access this._pc synchronously in case pc is closed later
1148 const identity = this._pc.peerIdentity;
1149 await this._lastIdentityValidation;
1150 const msg = await this._remoteIdp.verifyIdentityFromSDP(sdp, origin);
1151 // If this pc has an identity already, then the identity in sdp must match
1152 if (identity && (!msg || msg.identity !== identity)) {
1153 throw new this._win.DOMException(
1154 "Peer Identity mismatch, expected: " + identity,
1162 // Set new identity and generate an event.
1163 this._pc.peerIdentity = msg.identity;
1164 this._resolvePeerIdentity(
1167 idp: this._remoteIdp.provider,
1176 const haveValidation = validate();
1178 // Always eat errors on this chain
1179 this._lastIdentityValidation = haveValidation.catch(() => {});
1181 // If validation fails, we have some work to do. Fork it so it cannot
1182 // interfere with the validation chain itself, even if the catch function
1184 haveValidation.catch(e => {
1188 this._rejectPeerIdentity(e);
1190 // If we don't expect a specific peer identity, failure to get a valid
1191 // peer identity is not a terminal state, so replace the promise to
1192 // allow another attempt.
1193 if (!this._pc.peerIdentity) {
1194 this._resetPeerIdentityPromise();
1201 // Only wait for IdP validation if we need identity matching
1202 if (this._pc.peerIdentity) {
1203 await haveValidation;
1207 setRemoteDescription(desc, onSucc, onErr) {
1208 return this._auto(onSucc, onErr, () => this._setRemoteDescription(desc));
1211 _setRemoteDescription({ type, sdp }) {
1212 if (type == "pranswer") {
1213 throw new this._win.DOMException(
1214 "pranswer not yet implemented",
1218 this._checkClosed();
1219 return this._chain(async () => {
1221 if (type == "offer" && this.signalingState == "have-local-offer") {
1222 await new Promise((resolve, reject) => {
1223 this._onSetDescriptionSuccess = resolve;
1224 this._onSetDescriptionFailure = reject;
1225 this._pc.setLocalDescription(
1226 Ci.IPeerConnection.kActionRollback,
1230 await this._pc.onSetDescriptionSuccess("rollback", false);
1231 this._updateCanTrickle();
1238 this._sanityCheckSdp(sdp);
1240 const p = this._getPermission();
1242 const haveSetRemote = new Promise((resolve, reject) => {
1243 this._onSetDescriptionSuccess = resolve;
1244 this._onSetDescriptionFailure = reject;
1245 this._pc.setRemoteDescription(this._actions[type], sdp);
1248 if (type != "rollback") {
1249 // Do setRemoteDescription and identity validation in parallel
1250 await this._validateIdentity(sdp);
1253 await haveSetRemote;
1255 this._pc.onSetDescriptionError();
1259 await this._pc.onSetDescriptionSuccess(type, true);
1260 this._updateCanTrickle();
1264 setIdentityProvider(provider, { protocol, usernameHint, peerIdentity } = {}) {
1265 this._checkClosed();
1266 peerIdentity = peerIdentity || this._pc.peerIdentity;
1267 this._localIdp.setIdentityProvider(
1275 async _getIdentityAssertion() {
1276 await this._certificateReady;
1277 return this._localIdp.getIdentityAssertion(
1278 this._pc.fingerprint,
1279 this._documentPrincipal.origin
1283 getIdentityAssertion() {
1284 this._checkClosed();
1285 return this._win.Promise.resolve(
1286 this._chain(() => this._getIdentityAssertion())
1290 get canTrickleIceCandidates() {
1291 return this._canTrickle;
1294 _updateCanTrickle() {
1295 let containsTrickle = section => {
1296 let lines = section.toLowerCase().split(/(?:\r\n?|\n)/);
1297 return lines.some(line => {
1298 let prefix = "a=ice-options:";
1299 if (line.substring(0, prefix.length) !== prefix) {
1302 let tokens = line.substring(prefix.length).split(" ");
1303 return tokens.some(x => x === "trickle");
1309 // The getter for remoteDescription can throw if the pc is closed.
1310 desc = this.remoteDescription;
1313 this._canTrickle = null;
1317 let sections = desc.sdp.split(/(?:\r\n?|\n)m=/);
1318 let topSection = sections.shift();
1320 containsTrickle(topSection) || sections.every(containsTrickle);
1323 addIceCandidate(cand, onSucc, onErr) {
1325 cand.candidate != "" &&
1326 cand.sdpMid == null &&
1327 cand.sdpMLineIndex == null
1329 throw new this._win.TypeError(
1330 "Cannot add a candidate without specifying either sdpMid or sdpMLineIndex"
1333 return this._auto(onSucc, onErr, () => this._addIceCandidate(cand));
1336 async _addIceCandidate({
1342 this._checkClosed();
1343 return this._chain(async () => {
1345 !this._pc.pendingRemoteDescription.length &&
1346 !this._pc.currentRemoteDescription.length
1348 throw new this._win.DOMException(
1349 "No remoteDescription.",
1353 return new Promise((resolve, reject) => {
1354 this._onAddIceCandidateSuccess = resolve;
1355 this._onAddIceCandidateError = reject;
1356 this._pc.addIceCandidate(
1359 usernameFragment || "",
1367 this._pc.restartIce();
1371 stream.getTracks().forEach(track => this.addTrack(track, stream));
1374 addTrack(track, ...streams) {
1375 this._checkClosed();
1378 this.getTransceivers().some(
1379 transceiver => transceiver.sender.track == track
1382 throw new this._win.DOMException(
1383 "This track is already set on a sender.",
1384 "InvalidAccessError"
1388 let transceiver = this.getTransceivers().find(transceiver => {
1390 transceiver.sender.track == null &&
1391 transceiver.getKind() == track.kind &&
1392 !transceiver.stopped &&
1393 !transceiver.hasBeenUsedToSend()
1398 transceiver.sender.setTrack(track);
1399 transceiver.sender.setStreamsImpl(...streams);
1400 if (transceiver.direction == "recvonly") {
1401 transceiver.setDirectionInternal("sendrecv");
1402 } else if (transceiver.direction == "inactive") {
1403 transceiver.setDirectionInternal("sendonly");
1406 transceiver = this._addTransceiverNoEvents(
1410 direction: "sendrecv",
1416 this.updateNegotiationNeeded();
1417 return transceiver.sender;
1420 removeTrack(sender) {
1421 this._checkClosed();
1423 if (!this._pc.createdSender(sender)) {
1424 throw new this._win.DOMException(
1425 "This sender was not created by this PeerConnection",
1426 "InvalidAccessError"
1430 let transceiver = this.getTransceivers().find(
1431 transceiver => !transceiver.stopped && transceiver.sender == sender
1434 // If the transceiver was removed due to rollback, let it slide.
1435 if (!transceiver || !sender.track) {
1439 sender.setTrack(null);
1440 if (transceiver.direction == "sendrecv") {
1441 transceiver.setDirectionInternal("recvonly");
1442 } else if (transceiver.direction == "sendonly") {
1443 transceiver.setDirectionInternal("inactive");
1446 this.updateNegotiationNeeded();
1449 _addTransceiverNoEvents(sendTrackOrKind, init, addTrackMagic) {
1450 let sendTrack = null;
1452 if (typeof sendTrackOrKind == "string") {
1453 kind = sendTrackOrKind;
1459 throw new this._win.TypeError("Invalid media kind");
1462 sendTrack = sendTrackOrKind;
1463 kind = sendTrack.kind;
1467 return this._pc.addTransceiver(init, kind, sendTrack, addTrackMagic);
1469 // Exceptions thrown by c++ code do not propagate. In most cases, that's
1470 // fine because we're using Promises, which can be copied. But this is
1471 // not promise-based, so we have to do this sketchy stuff.
1472 const holder = new StructuredCloneHolder(
1475 new ClonedErrorHolder(e)
1477 throw holder.deserialize(this._win);
1481 addTransceiver(sendTrackOrKind, init) {
1482 this._checkClosed();
1483 let transceiver = this._addTransceiverNoEvents(sendTrackOrKind, init);
1484 this.updateNegotiationNeeded();
1488 updateNegotiationNeeded() {
1489 this._pc.updateNegotiationNeeded();
1496 this._closed = true;
1497 this.changeIceConnectionState("closed");
1498 if (this._localIdp) {
1499 this._localIdp.close();
1501 if (this._remoteIdp) {
1502 this._remoteIdp.close();
1505 this._suppressEvents = true;
1509 this._checkClosed();
1510 let localStreams = new Set();
1511 this.getTransceivers().forEach(transceiver => {
1512 transceiver.sender.getStreams().forEach(stream => {
1513 localStreams.add(stream);
1516 return [...localStreams.values()];
1519 getRemoteStreams() {
1520 this._checkClosed();
1521 return this._pc.getRemoteStreams();
1525 return this.getTransceivers()
1526 .filter(transceiver => !transceiver.stopped)
1527 .map(transceiver => transceiver.sender);
1531 return this.getTransceivers()
1532 .filter(transceiver => !transceiver.stopped)
1533 .map(transceiver => transceiver.receiver);
1536 mozSetPacketCallback(callback) {
1537 this._onPacket = callback;
1540 mozEnablePacketDump(level, type, sending) {
1541 this._pc.enablePacketDump(level, type, sending);
1544 mozDisablePacketDump(level, type, sending) {
1545 this._pc.disablePacketDump(level, type, sending);
1549 return this._pc.getTransceivers();
1552 get localDescription() {
1553 return this.pendingLocalDescription || this.currentLocalDescription;
1556 cacheDescription(name, type, sdp) {
1559 this[name].type != type ||
1560 this[name].sdp != sdp ||
1563 this[name] = sdp.length
1564 ? new this._win.RTCSessionDescription({ type, sdp })
1570 get currentLocalDescription() {
1571 this._checkClosed();
1572 return this.cacheDescription(
1573 "_currentLocalDescription",
1574 this._pc.currentOfferer ? "offer" : "answer",
1575 this._pc.currentLocalDescription
1579 get pendingLocalDescription() {
1580 this._checkClosed();
1581 return this.cacheDescription(
1582 "_pendingLocalDescription",
1583 this._pc.pendingOfferer ? "offer" : "answer",
1584 this._pc.pendingLocalDescription
1588 get remoteDescription() {
1589 return this.pendingRemoteDescription || this.currentRemoteDescription;
1592 get currentRemoteDescription() {
1593 this._checkClosed();
1594 return this.cacheDescription(
1595 "_currentRemoteDescription",
1596 this._pc.currentOfferer ? "answer" : "offer",
1597 this._pc.currentRemoteDescription
1601 get pendingRemoteDescription() {
1602 this._checkClosed();
1603 return this.cacheDescription(
1604 "_pendingRemoteDescription",
1605 this._pc.pendingOfferer ? "answer" : "offer",
1606 this._pc.pendingRemoteDescription
1610 get peerIdentity() {
1611 return this._peerIdentity;
1614 return this._localIdp.idpLoginUrl;
1622 get iceGatheringState() {
1623 return this._pc.iceGatheringState;
1625 get iceConnectionState() {
1626 return this._iceConnectionState;
1628 get connectionState() {
1629 return this._pc.connectionState;
1632 get signalingState() {
1633 // checking for our local pc closed indication
1634 // before invoking the pc methods.
1638 return this._pc.signalingState;
1641 handleIceGatheringStateChange() {
1642 _globalPCList.notifyLifecycleObservers(this, "icegatheringstatechange");
1643 this.dispatchEvent(new this._win.Event("icegatheringstatechange"));
1644 if (this.iceGatheringState === "complete") {
1646 new this._win.RTCPeerConnectionIceEvent("icecandidate", {
1653 changeIceConnectionState(state) {
1654 if (state != this._iceConnectionState) {
1655 this._iceConnectionState = state;
1656 _globalPCList.notifyLifecycleObservers(this, "iceconnectionstatechange");
1657 if (!this._closed) {
1658 this.dispatchEvent(new this._win.Event("iceconnectionstatechange"));
1663 getStats(selector, onSucc, onErr) {
1664 if (selector !== null) {
1665 let matchingSenders = this.getSenders().filter(s => s.track === selector);
1666 let matchingReceivers = this.getReceivers().filter(
1667 r => r.track === selector
1670 if (matchingSenders.length + matchingReceivers.length != 1) {
1671 throw new this._win.DOMException(
1672 "track must be associated with a unique sender or receiver, but " +
1673 " is associated with " +
1674 matchingSenders.length +
1676 matchingReceivers.length +
1678 "InvalidAccessError"
1683 return this._auto(onSucc, onErr, () => this._pc.getStats(selector));
1687 return this._pc.sctp;
1702 this._checkClosed();
1703 this._pcTelemetry.recordDataChannelInit(
1708 if (maxPacketLifeTime === undefined) {
1709 maxPacketLifeTime = maxRetransmitTime;
1712 if (maxRetransmitTime !== undefined) {
1714 "Use maxPacketLifeTime instead of deprecated maxRetransmitTime which will stop working soon in createDataChannel!"
1718 if (protocol.length > 32767) {
1719 // At least 65536/2 UTF-16 characters. UTF-8 might be too long.
1720 // Spec says to check how long |protocol| and |label| are in _bytes_. This
1721 // is a little ambiguous. For now, examine the length of the utf-8 encoding.
1722 const byteCounter = new TextEncoder();
1724 if (byteCounter.encode(protocol).length > 65535) {
1725 throw new this._win.TypeError(
1726 "protocol cannot be longer than 65535 bytes"
1731 if (label.length > 32767) {
1732 const byteCounter = new TextEncoder();
1733 if (byteCounter.encode(label).length > 65535) {
1734 throw new this._win.TypeError(
1735 "label cannot be longer than 65535 bytes"
1742 } else if (id === null) {
1743 throw new this._win.TypeError("id is required when negotiated is true");
1745 if (maxPacketLifeTime !== undefined && maxRetransmits !== undefined) {
1746 throw new this._win.TypeError(
1747 "Both maxPacketLifeTime and maxRetransmits cannot be provided"
1751 throw new this._win.TypeError("id cannot be 65535");
1753 // Must determine the type where we still know if entries are undefined.
1755 if (maxPacketLifeTime !== undefined) {
1756 type = Ci.IPeerConnection.kDataChannelPartialReliableTimed;
1757 } else if (maxRetransmits !== undefined) {
1758 type = Ci.IPeerConnection.kDataChannelPartialReliableRexmit;
1760 type = Ci.IPeerConnection.kDataChannelReliable;
1762 // Synchronous since it doesn't block.
1765 dataChannel = this._pc.createDataChannel(
1776 if (e.result != Cr.NS_ERROR_NOT_AVAILABLE) {
1781 id === null ? "No available id could be generated" : "Id is in use";
1782 throw new this._win.DOMException(msg, "OperationError");
1785 // Spec says to only do this if this is the first DataChannel created,
1786 // but the c++ code that does the "is negotiation needed" checking will
1787 // only ever return true on the first one.
1788 this.updateNegotiationNeeded();
1794 setupPrototype(RTCPeerConnection, {
1796 contractID: PC_CONTRACT,
1797 QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
1799 offer: Ci.IPeerConnection.kActionOffer,
1800 answer: Ci.IPeerConnection.kActionAnswer,
1801 pranswer: Ci.IPeerConnection.kActionPRAnswer,
1802 rollback: Ci.IPeerConnection.kActionRollback,
1806 // This is a separate class because we don't want to expose it to DOM.
1808 export class PeerConnectionObserver {
1814 this._dompc = dompc._innerObject;
1817 newError({ message, name }) {
1818 return new this._dompc._win.DOMException(message, name);
1821 dispatchEvent(event) {
1822 this._dompc.dispatchEvent(event);
1825 onCreateOfferSuccess(sdp) {
1826 this._dompc._onCreateOfferSuccess(sdp);
1829 onCreateOfferError(error) {
1830 this._dompc._onCreateOfferFailure(this.newError(error));
1833 onCreateAnswerSuccess(sdp) {
1834 this._dompc._onCreateAnswerSuccess(sdp);
1837 onCreateAnswerError(error) {
1838 this._dompc._onCreateAnswerFailure(this.newError(error));
1841 onSetDescriptionSuccess() {
1842 this._dompc._onSetDescriptionSuccess();
1845 onSetDescriptionError(error) {
1846 this._dompc._onSetDescriptionFailure(this.newError(error));
1849 onAddIceCandidateSuccess() {
1850 this._dompc._onAddIceCandidateSuccess();
1853 onAddIceCandidateError(error) {
1854 this._dompc._onAddIceCandidateError(this.newError(error));
1857 onIceCandidate(sdpMLineIndex, sdpMid, candidate, usernameFragment) {
1858 let win = this._dompc._win;
1859 if (candidate || sdpMid || usernameFragment) {
1860 if (candidate.includes(" typ relay ")) {
1861 this._dompc._iceGatheredRelayCandidates = true;
1863 candidate = new win.RTCIceCandidate({
1871 new win.RTCPeerConnectionIceEvent("icecandidate", { candidate })
1875 // This method is primarily responsible for updating iceConnectionState.
1876 // This state is defined in the WebRTC specification as follows:
1878 // iceConnectionState:
1879 // -------------------
1880 // new Any of the RTCIceTransports are in the new state and none
1881 // of them are in the checking, failed or disconnected state.
1883 // checking Any of the RTCIceTransports are in the checking state and
1884 // none of them are in the failed or disconnected state.
1886 // connected All RTCIceTransports are in the connected, completed or
1887 // closed state and at least one of them is in the connected
1890 // completed All RTCIceTransports are in the completed or closed state
1891 // and at least one of them is in the completed state.
1893 // failed Any of the RTCIceTransports are in the failed state.
1895 // disconnected Any of the RTCIceTransports are in the disconnected state
1896 // and none of them are in the failed state.
1898 // closed All of the RTCIceTransports are in the closed state.
1900 handleIceConnectionStateChange(iceConnectionState) {
1901 let pc = this._dompc;
1902 if (pc.iceConnectionState === iceConnectionState) {
1906 if (iceConnectionState === "failed") {
1907 if (!pc._hasStunServer) {
1909 "ICE failed, add a STUN server and see about:webrtc for more details"
1911 } else if (!pc._hasTurnServer) {
1913 "ICE failed, add a TURN server and see about:webrtc for more details"
1915 } else if (pc._hasTurnServer && !pc._iceGatheredRelayCandidates) {
1917 "ICE failed, your TURN server appears to be broken, see about:webrtc for more details"
1920 pc.logError("ICE failed, see about:webrtc for more details");
1924 pc.changeIceConnectionState(iceConnectionState);
1927 onStateChange(state) {
1932 if (state == "SignalingState") {
1933 this.dispatchEvent(new this._win.Event("signalingstatechange"));
1937 if (!this._dompc._pc) {
1942 case "IceConnectionState":
1943 this.handleIceConnectionStateChange(this._dompc._pc.iceConnectionState);
1946 case "IceGatheringState":
1947 this._dompc.handleIceGatheringStateChange();
1950 case "ConnectionState":
1951 _globalPCList.notifyLifecycleObservers(this, "connectionstatechange");
1952 this.dispatchEvent(new this._win.Event("connectionstatechange"));
1956 this._dompc.logWarning("Unhandled state type: " + state);
1961 onTransceiverNeeded(kind, transceiverImpl) {
1962 this._dompc._onTransceiverNeeded(kind, transceiverImpl);
1965 notifyDataChannel(channel) {
1967 new this._dompc._win.RTCDataChannelEvent("datachannel", { channel })
1971 fireTrackEvent(receiver, streams) {
1972 const pc = this._dompc;
1973 const transceiver = pc.getTransceivers().find(t => t.receiver == receiver);
1977 const track = receiver.track;
1979 new this._win.RTCTrackEvent("track", {
1986 // Fire legacy event as well for a little bit.
1988 new this._win.MediaStreamTrackEvent("addtrack", { track })
1992 fireStreamEvent(stream) {
1993 const ev = new this._win.MediaStreamEvent("addstream", { stream });
1994 this.dispatchEvent(ev);
1997 fireNegotiationNeededEvent() {
1998 this.dispatchEvent(new this._win.Event("negotiationneeded"));
2001 onPacket(level, type, sending, packet) {
2002 var pc = this._dompc;
2004 pc._onPacket(level, type, sending, packet);
2009 setupPrototype(PeerConnectionObserver, {
2010 classID: PC_OBS_CID,
2011 contractID: PC_OBS_CONTRACT,
2012 QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
2015 export class RTCPeerConnectionStatic {
2017 this._winID = win.windowGlobalChild.innerWindowId;
2020 registerPeerConnectionLifecycleCallback(cb) {
2021 _globalPCList._registerPeerConnectionLifecycleCallback(this._winID, cb);
2025 setupPrototype(RTCPeerConnectionStatic, {
2026 classID: PC_STATIC_CID,
2027 contractID: PC_STATIC_CONTRACT,
2028 QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
2031 export class CreateOfferRequest {
2032 constructor(windowID, innerWindowID, callID, isSecure) {
2033 Object.assign(this, { windowID, innerWindowID, callID, isSecure });
2037 setupPrototype(CreateOfferRequest, {
2038 classID: PC_COREQUEST_CID,
2039 contractID: PC_COREQUEST_CONTRACT,
2040 QueryInterface: ChromeUtils.generateQI([]),