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 logMsg(msg, file, line, flag, winID) {
28 let scriptErrorClass = Cc["@mozilla.org/scripterror;1"];
29 let scriptError = scriptErrorClass.createInstance(Ci.nsIScriptError);
30 scriptError.initWithWindowID(
40 Services.console.logMessage(scriptError);
43 let setupPrototype = (_class, dict) => {
44 _class.prototype.classDescription = _class.name;
45 Object.assign(_class.prototype, dict);
48 // Global list of PeerConnection objects, so they can be cleaned up when
49 // a page is torn down. (Maps inner window ID to an array of PC objects).
50 export class GlobalPCList {
53 this._networkdown = false; // XXX Need to query current state somehow
54 this._lifecycleobservers = {};
56 Services.obs.addObserver(this, "inner-window-destroyed", true);
57 Services.obs.addObserver(this, "profile-change-net-teardown", true);
58 Services.obs.addObserver(this, "network:offline-about-to-go-offline", true);
59 Services.obs.addObserver(this, "network:offline-status-changed", true);
60 Services.obs.addObserver(this, "gmp-plugin-crash", true);
61 Services.obs.addObserver(this, "PeerConnection:response:allow", true);
62 Services.obs.addObserver(this, "PeerConnection:response:deny", true);
64 Services.cpmm.addMessageListener("gmp-plugin-crash", this);
68 notifyLifecycleObservers(pc, type) {
69 for (var key of Object.keys(this._lifecycleobservers)) {
70 this._lifecycleobservers[key](pc, pc._winID, type);
75 let winID = pc._winID;
76 if (this._list[winID]) {
77 this._list[winID].push(Cu.getWeakReference(pc));
79 this._list[winID] = [Cu.getWeakReference(pc)];
81 pc._globalPCListId = this._nextId++;
82 this.removeNullRefs(winID);
85 findPC(globalPCListId) {
86 for (let winId in this._list) {
87 if (this._list.hasOwnProperty(winId)) {
88 for (let pcref of this._list[winId]) {
90 if (pc && pc._globalPCListId == globalPCListId) {
99 removeNullRefs(winID) {
100 if (this._list[winID] === undefined) {
103 this._list[winID] = this._list[winID].filter(function (e, i, a) {
104 return e.get() !== null;
107 if (this._list[winID].length === 0) {
108 delete this._list[winID];
112 handleGMPCrash(data) {
113 let broadcastPluginCrash = function (list, winID, pluginID, pluginName) {
114 if (list.hasOwnProperty(winID)) {
115 list[winID].forEach(function (pcref) {
116 let pc = pcref.get();
118 pc._pc.pluginCrash(pluginID, pluginName);
124 // a plugin crashed; if it's associated with any of our PCs, fire an
125 // event to the DOM window
126 for (let winId in this._list) {
127 broadcastPluginCrash(this._list, winId, data.pluginID, data.pluginName);
131 receiveMessage({ name, data }) {
132 if (name == "gmp-plugin-crash") {
133 this.handleGMPCrash(data);
137 observe(subject, topic, data) {
138 let cleanupPcRef = function (pcref) {
139 let pc = pcref.get();
141 pc._suppressEvents = true;
146 let cleanupWinId = function (list, winID) {
147 if (list.hasOwnProperty(winID)) {
148 list[winID].forEach(cleanupPcRef);
153 if (topic == "inner-window-destroyed") {
154 let winID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
155 cleanupWinId(this._list, winID);
157 if (this._lifecycleobservers.hasOwnProperty(winID)) {
158 delete this._lifecycleobservers[winID];
161 topic == "profile-change-net-teardown" ||
162 topic == "network:offline-about-to-go-offline"
164 // As Necko doesn't prevent us from accessing the network we still need to
165 // monitor the network offline/online state here. See bug 1326483
166 this._networkdown = true;
167 } else if (topic == "network:offline-status-changed") {
168 if (data == "offline") {
169 this._networkdown = true;
170 } else if (data == "online") {
171 this._networkdown = false;
173 } else if (topic == "gmp-plugin-crash") {
174 if (subject instanceof Ci.nsIWritablePropertyBag2) {
175 let pluginID = subject.getPropertyAsUint32("pluginID");
176 let pluginName = subject.getPropertyAsAString("pluginName");
177 let data = { pluginID, pluginName };
178 this.handleGMPCrash(data);
181 topic == "PeerConnection:response:allow" ||
182 topic == "PeerConnection:response:deny"
184 var pc = this.findPC(data);
186 if (topic == "PeerConnection:response:allow") {
187 pc._settlePermission.allow();
189 let err = new pc._win.DOMException(
190 "The request is not allowed by " +
191 "the user agent or the platform in the current context.",
194 pc._settlePermission.deny(err);
200 _registerPeerConnectionLifecycleCallback(winID, cb) {
201 this._lifecycleobservers[winID] = cb;
205 setupPrototype(GlobalPCList, {
206 QueryInterface: ChromeUtils.generateQI([
208 "nsISupportsWeakReference",
210 classID: PC_MANAGER_CID,
213 var _globalPCList = new GlobalPCList();
215 export class RTCIceCandidate {
221 if (dict.sdpMid == null && dict.sdpMLineIndex == null) {
222 throw new this._win.TypeError(
223 "Either sdpMid or sdpMLineIndex must be specified"
226 Object.assign(this, dict);
230 setupPrototype(RTCIceCandidate, {
232 contractID: PC_ICE_CONTRACT,
233 QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
236 export class RTCSessionDescription {
239 this._winID = this._win.windowGlobalChild.innerWindowId;
242 __init({ type, sdp }) {
244 throw new this._win.TypeError(
245 "Missing required 'type' member of RTCSessionDescriptionInit"
248 Object.assign(this, { _type: type, _sdp: sdp });
269 // Warn once per RTCSessionDescription about deprecated writable usage.
271 "RTCSessionDescription's members are readonly! " +
272 "Writing to them is deprecated and will break soon!"
279 let err = this._win.Error();
284 Ci.nsIScriptError.warningFlag,
290 setupPrototype(RTCSessionDescription, {
291 classID: PC_SESSION_CID,
292 contractID: PC_SESSION_CONTRACT,
293 QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
296 // Records PC related telemetry
297 class PeerConnectionTelemetry {
298 // ICE connection state enters connected or completed.
300 Services.telemetry.scalarAdd("webrtc.peerconnection.connected", 1);
301 this.recordConnected = () => {};
303 // DataChannel is created
304 _recordDataChannelCreated() {
305 Services.telemetry.scalarAdd(
306 "webrtc.peerconnection.datachannel_created",
309 this._recordDataChannelCreated = () => {};
311 // DataChannel initialized with maxRetransmitTime
312 _recordMaxRetransmitTime(maxRetransmitTime) {
313 if (maxRetransmitTime === undefined) {
316 Services.telemetry.scalarAdd(
317 "webrtc.peerconnection.datachannel_max_retx_used",
320 this._recordMaxRetransmitTime = () => true;
323 // DataChannel initialized with maxPacketLifeTime
324 _recordMaxPacketLifeTime(maxPacketLifeTime) {
325 if (maxPacketLifeTime === undefined) {
328 Services.telemetry.scalarAdd(
329 "webrtc.peerconnection.datachannel_max_life_used",
332 this._recordMaxPacketLifeTime = () => true;
335 // DataChannel initialized
336 recordDataChannelInit(maxRetransmitTime, maxPacketLifeTime) {
337 const retxUsed = this._recordMaxRetransmitTime(maxRetransmitTime);
338 if (this._recordMaxPacketLifeTime(maxPacketLifeTime) && retxUsed) {
339 Services.telemetry.scalarAdd(
340 "webrtc.peerconnection.datachannel_max_retx_and_life_used",
343 this.recordDataChannelInit = () => {};
345 this._recordDataChannelCreated();
349 export class RTCPeerConnection {
352 this._closed = false;
354 // http://rtcweb-wg.github.io/jsep/#rfc.section.4.1.9
355 // canTrickle == null means unknown; when a remote description is received it
356 // is set to true or false based on the presence of the "trickle" ice-option
357 this._canTrickle = null;
359 // So we can record telemetry on state transitions
360 this._iceConnectionState = "new";
362 this._hasStunServer = this._hasTurnServer = false;
363 this._iceGatheredRelayCandidates = false;
365 this._pcTelemetry = new PeerConnectionTelemetry();
372 // Pref-based overrides; will _not_ be reflected in getConfiguration
373 _applyPrefsToConfig(rtcConfig) {
375 rtcConfig.iceTransportPolicy == "all" &&
376 Services.prefs.getBoolPref("media.peerconnection.ice.relay_only")
378 rtcConfig.iceTransportPolicy = "relay";
382 !rtcConfig.iceServers ||
383 !Services.prefs.getBoolPref(
384 "media.peerconnection.use_document_iceservers"
388 rtcConfig.iceServers = JSON.parse(
389 Services.prefs.getCharPref(
390 "media.peerconnection.default_iceservers"
395 "Ignoring invalid media.peerconnection.default_iceservers in about:config"
397 rtcConfig.iceServers = [];
400 this._validateIceServers(
401 rtcConfig.iceServers,
402 "Ignoring invalid media.peerconnection.default_iceservers in about:config"
405 this.logWarning(e.message);
406 rtcConfig.iceServers = [];
411 _validateConfig(rtcConfig) {
412 if ("sdpSemantics" in rtcConfig) {
413 if (rtcConfig.sdpSemantics == "plan-b") {
415 `Outdated and non-standard {sdpSemantics: "plan-b"} is not ` +
416 `supported! WebRTC may be unreliable. Please update code to ` +
417 `follow standard "unified-plan".`
420 // Don't let it show up in getConfiguration.
421 delete rtcConfig.sdpSemantics;
425 // certificates must match
426 if (rtcConfig.certificates.length != this._config.certificates.length) {
427 throw new this._win.DOMException(
428 "Cannot change certificates with setConfiguration (length differs)",
429 "InvalidModificationError"
432 for (let i = 0; i < rtcConfig.certificates.length; i++) {
433 if (rtcConfig.certificates[i] != this._config.certificates[i]) {
434 throw new this._win.DOMException(
435 `Cannot change certificates with setConfiguration ` +
436 `(cert at index ${i} differs)`,
437 "InvalidModificationError"
442 // bundlePolicy must match
443 if (rtcConfig.bundlePolicy != this._config.bundlePolicy) {
444 throw new this._win.DOMException(
445 "Cannot change bundlePolicy with setConfiguration",
446 "InvalidModificationError"
450 // peerIdentity must match
452 rtcConfig.peerIdentity &&
453 rtcConfig.peerIdentity != this._config.peerIdentity
455 throw new this._win.DOMException(
456 "Cannot change peerIdentity with setConfiguration",
457 "InvalidModificationError"
461 // TODO (bug 1339203): rtcpMuxPolicy must match
462 // TODO (bug 1529398): iceCandidatePoolSize must match if sLD has ever
466 // This gets executed in the typical case when iceServers
467 // are passed in through the web page.
468 this._validateIceServers(
469 rtcConfig.iceServers,
470 "RTCPeerConnection constructor passed invalid RTCConfiguration"
474 _checkIfIceRestartRequired(rtcConfig) {
476 if (rtcConfig.iceTransportPolicy != this._config.iceTransportPolicy) {
477 this._pc.restartIceNoRenegotiationNeeded();
481 JSON.stringify(this._config.iceServers) !=
482 JSON.stringify(rtcConfig.iceServers)
484 this._pc.restartIceNoRenegotiationNeeded();
490 this._winID = this._win.windowGlobalChild.innerWindowId;
491 let certificates = rtcConfig.certificates || [];
493 if (certificates.some(c => c.expires <= Date.now())) {
494 throw new this._win.DOMException(
495 "Unable to create RTCPeerConnection with an expired certificate",
500 // TODO(bug 1531875): Check origin of certs
502 // TODO(bug 1176518): Remove this code once we support multiple certs
504 if (certificates.length == 1) {
505 certificate = certificates[0];
506 } else if (certificates.length) {
507 throw new this._win.DOMException(
508 "RTCPeerConnection does not currently support multiple certificates",
513 this._documentPrincipal = Cu.getWebIDLCallerPrincipal();
515 if (_globalPCList._networkdown) {
516 throw new this._win.DOMException(
517 "Can't create RTCPeerConnections when the network is down",
522 this.makeGetterSetterEH("ontrack");
523 this.makeLegacyGetterSetterEH(
525 "Use peerConnection.ontrack instead."
527 this.makeLegacyGetterSetterEH(
529 "Use peerConnection.ontrack instead."
531 this.makeGetterSetterEH("onicecandidate");
532 this.makeGetterSetterEH("onnegotiationneeded");
533 this.makeGetterSetterEH("onsignalingstatechange");
534 this.makeGetterSetterEH("ondatachannel");
535 this.makeGetterSetterEH("oniceconnectionstatechange");
536 this.makeGetterSetterEH("onicegatheringstatechange");
537 this.makeGetterSetterEH("onconnectionstatechange");
538 this.makeGetterSetterEH("onidentityresult");
539 this.makeGetterSetterEH("onpeeridentity");
540 this.makeGetterSetterEH("onidpassertionerror");
541 this.makeGetterSetterEH("onidpvalidationerror");
543 this._pc = new this._win.PeerConnectionImpl();
545 this.__DOM_IMPL__._innerObject = this;
546 const observer = new this._win.PeerConnectionObserver(this.__DOM_IMPL__);
548 // Add a reference to the PeerConnection to global list (before init).
549 _globalPCList.addPC(this);
551 this._pc.initialize(observer, this._win);
553 this.setConfiguration(rtcConfig);
555 this._certificateReady = this._initCertificate(certificate);
557 _globalPCList.notifyLifecycleObservers(this, "initialized");
561 const config = Object.assign({}, this._config);
562 delete config.sdpSemantics;
566 setConfiguration(rtcConfig) {
568 this._validateConfig(rtcConfig);
569 this._checkIfIceRestartRequired(rtcConfig);
571 // Allow prefs to tweak these settings before passing to c++, but hide all
573 const configWithPrefTweaks = Object.assign({}, rtcConfig);
574 this._applyPrefsToConfig(configWithPrefTweaks);
575 this._pc.setConfiguration(configWithPrefTweaks);
577 this._config = Object.assign({}, rtcConfig);
580 async _initCertificate(certificate) {
582 certificate = await this._win.RTCPeerConnection.generateCertificate({
587 this._pc.certificate = certificate;
590 _resetPeerIdentityPromise() {
591 this._peerIdentity = new this._win.Promise((resolve, reject) => {
592 this._resolvePeerIdentity = resolve;
593 this._rejectPeerIdentity = reject;
598 this._resetPeerIdentityPromise();
599 this._lastIdentityValidation = this._win.Promise.resolve();
601 let prefName = "media.peerconnection.identity.timeout";
602 let idpTimeout = Services.prefs.getIntPref(prefName);
603 this._localIdp = new lazy.PeerConnectionIdp(this._win, idpTimeout);
604 this._remoteIdp = new lazy.PeerConnectionIdp(this._win, idpTimeout);
607 // Add a function to the internal operations chain.
610 return this._pc.chain(operation);
613 // It's basically impossible to use async directly in JSImplemented code,
614 // because the implicit promise must be wrapped to the right type for content.
616 // The _async wrapper takes care of this. The _legacy wrapper implements
617 // legacy callbacks in a manner that produces correct line-numbers in errors,
618 // provided that methods validate their inputs before putting themselves on
619 // the pc's operations chain.
621 // These wrappers also serve as guards against settling promises past close().
624 return this._win.Promise.resolve(this._closeWrapper(func));
628 return this._win.Promise.resolve(this._legacyCloseWrapper(...args));
631 _auto(onSucc, onErr, func) {
632 return typeof onSucc == "function"
633 ? this._legacy(onSucc, onErr, func)
637 async _closeWrapper(func) {
638 let closed = this._closed;
640 let result = await func();
641 if (!closed && this._closed) {
642 await new Promise(() => {});
646 if (!closed && this._closed) {
647 await new Promise(() => {});
653 async _legacyCloseWrapper(onSucc, onErr, func) {
654 let wrapCallback = cb => result => {
658 this.logErrorAndCallOnError(e);
663 wrapCallback(onSucc)(await func());
665 wrapCallback(onErr)(e);
669 // This implements the fairly common "Queue a task" logic
670 async _queueTaskWithClosedCheck(func) {
672 return new this._win.Promise((resolve, reject) => {
673 Services.tm.dispatchToMainThread({
689 * An RTCConfiguration may look like this:
691 * { "iceServers": [ { urls: "stun:stun.example.org", },
692 * { url: "stun:stun.example.org", }, // deprecated version
693 * { urls: ["turn:turn1.x.org", "turn:turn2.x.org"],
694 * username:"jib", credential:"mypass"} ] }
696 * This function normalizes the structure of the input for rtcConfig.iceServers for us,
697 * so we test well-formed stun/turn urls before passing along to C++.
698 * msg - Error message to detail which array-entry failed, if any.
700 _validateIceServers(iceServers, msg) {
701 // Normalize iceServers input
702 iceServers.forEach(server => {
703 if (typeof server.urls === "string") {
704 server.urls = [server.urls];
705 } else if (!server.urls && server.url) {
706 // TODO: Remove support for legacy iceServer.url eventually (Bug 1116766)
707 server.urls = [server.url];
708 this.logWarning("RTCIceServer.url is deprecated! Use urls instead.");
712 let nicerNewURI = uriStr => {
714 return Services.io.newURI(uriStr);
716 if (e.result == Cr.NS_ERROR_MALFORMED_URI) {
717 throw new this._win.DOMException(
718 `${msg} - malformed URI: ${uriStr}`,
728 iceServers.forEach(({ urls, username, credential, credentialType }) => {
730 // TODO: Remove once url is deprecated (Bug 1369563)
731 throw new this._win.TypeError(
732 "Missing required 'urls' member of RTCIceServer"
736 throw new this._win.DOMException(
737 `${msg} - urls is empty`,
742 .map(url => nicerNewURI(url))
743 .forEach(({ scheme, spec, query }) => {
744 if (scheme in { turn: 1, turns: 1 }) {
745 if (username == undefined) {
746 throw new this._win.DOMException(
747 `${msg} - missing username: ${spec}`,
751 if (username.length > 512) {
752 throw new this._win.DOMException(
753 `${msg} - username longer then 512 bytes: ${username}`,
757 if (credential == undefined) {
758 throw new this._win.DOMException(
759 `${msg} - missing credential: ${spec}`,
763 if (credentialType != "password") {
765 `RTCConfiguration TURN credentialType \"${credentialType}\"` +
766 " is not yet implemented. Treating as password." +
767 " https://bugzil.la/1247616"
770 this._hasTurnServer = true;
771 // If this is not a TURN TCP/TLS server, it is also a STUN server
772 const parameters = query.split("&");
773 if (!parameters.includes("transport=tcp")) {
774 this._hasStunServer = true;
777 } else if (scheme in { stun: 1, stuns: 1 }) {
778 this._hasStunServer = true;
781 throw new this._win.DOMException(
782 `${msg} - improper scheme: ${scheme}`,
786 if (scheme in { stuns: 1 }) {
787 this.logWarning(scheme.toUpperCase() + " is not yet supported.");
789 if (stunServers >= 5) {
791 "Using five or more STUN/TURN servers causes problems"
793 } else if (stunServers > 2) {
795 "Using more than two STUN/TURN servers slows down discovery"
802 // Ideally, this should be of the form _checkState(state),
803 // where the state is taken from an enumeration containing
804 // the valid peer connection states defined in the WebRTC
805 // spec. See Bug 831756.
808 throw new this._win.DOMException(
809 "Peer connection is closed",
815 dispatchEvent(event) {
816 // PC can close while events are firing if there is an async dispatch
817 // in c++ land. But let through "closed" signaling and ice connection events.
818 if (!this._suppressEvents) {
819 this.__DOM_IMPL__.dispatchEvent(event);
823 // Log error message to web console and window.onerror, if present.
824 logErrorAndCallOnError(e) {
829 Ci.nsIScriptError.errorFlag
832 // Safely call onerror directly if present (necessary for testing)
834 if (typeof this._win.onerror === "function") {
835 this._win.onerror(e.message, e.fileName, e.lineNumber);
838 // If onerror itself throws, service it.
844 Ci.nsIScriptError.errorFlag
851 this.logStackMsg(msg, Ci.nsIScriptError.errorFlag);
855 this.logStackMsg(msg, Ci.nsIScriptError.warningFlag);
858 logStackMsg(msg, flag) {
859 let err = this._win.Error();
860 this.logMsg(msg, err.fileName, err.lineNumber, flag);
863 logMsg(msg, file, line, flag) {
864 return logMsg(msg, file, line, flag, this._winID);
868 return this.__DOM_IMPL__.getEventHandler(type);
871 setEH(type, handler) {
872 this.__DOM_IMPL__.setEventHandler(type, handler);
875 makeGetterSetterEH(name) {
876 Object.defineProperty(this, name, {
878 return this.getEH(name);
886 makeLegacyGetterSetterEH(name, msg) {
887 Object.defineProperty(this, name, {
889 return this.getEH(name);
892 this.logWarning(name + " is deprecated! " + msg);
898 createOffer(optionsOrOnSucc, onErr, options) {
899 let onSuccess = null;
900 if (typeof optionsOrOnSucc == "function") {
901 onSuccess = optionsOrOnSucc;
903 options = optionsOrOnSucc;
905 // This entry-point handles both new and legacy call sig. Decipher which one
907 return this._legacy(onSuccess, onErr, () => this._createOffer(options));
909 return this._async(() => this._createOffer(options));
912 // Ensures that we have at least one transceiver of |kind| that is
913 // configured to receive. It will create one if necessary.
914 _ensureOfferToReceive(kind) {
915 let hasRecv = this.getTransceivers().some(
917 transceiver.getKind() == kind &&
918 (transceiver.direction == "sendrecv" ||
919 transceiver.direction == "recvonly") &&
924 this._addTransceiverNoEvents(kind, { direction: "recvonly" });
928 // Handles offerToReceiveAudio/Video
929 _ensureTransceiversForOfferToReceive(options) {
930 if (options.offerToReceiveAudio) {
931 this._ensureOfferToReceive("audio");
934 if (options.offerToReceiveVideo) {
935 this._ensureOfferToReceive("video");
938 this.getTransceivers()
939 .filter(transceiver => {
941 (options.offerToReceiveVideo === false &&
942 transceiver.receiver.track.kind == "video") ||
943 (options.offerToReceiveAudio === false &&
944 transceiver.receiver.track.kind == "audio")
947 .forEach(transceiver => {
948 if (transceiver.direction == "sendrecv") {
949 transceiver.setDirectionInternal("sendonly");
950 } else if (transceiver.direction == "recvonly") {
951 transceiver.setDirectionInternal("inactive");
956 _createOffer(options) {
958 this._ensureTransceiversForOfferToReceive(options);
959 return this._chain(() => this._createAnOffer(options));
962 async _createAnOffer(options = {}) {
963 switch (this.signalingState) {
965 case "have-local-offer":
968 throw new this._win.DOMException(
969 `Cannot create offer in ${this.signalingState}`,
974 if (this._localIdp.enabled) {
975 haveAssertion = this._getIdentityAssertion();
977 await this._getPermission();
978 await this._certificateReady;
979 let sdp = await new Promise((resolve, reject) => {
980 this._onCreateOfferSuccess = resolve;
981 this._onCreateOfferFailure = reject;
982 this._pc.createOffer(options);
986 sdp = this._localIdp.addIdentityAttribute(sdp);
988 return Cu.cloneInto({ type: "offer", sdp }, this._win);
991 createAnswer(optionsOrOnSucc, onErr) {
992 // This entry-point handles both new and legacy call sig. Decipher which one
993 if (typeof optionsOrOnSucc == "function") {
994 return this._legacy(optionsOrOnSucc, onErr, () => this._createAnswer({}));
996 return this._async(() => this._createAnswer(optionsOrOnSucc));
999 _createAnswer(options) {
1000 this._checkClosed();
1001 return this._chain(() => this._createAnAnswer());
1004 async _createAnAnswer() {
1005 if (this.signalingState != "have-remote-offer") {
1006 throw new this._win.DOMException(
1007 `Cannot create answer in ${this.signalingState}`,
1012 if (this._localIdp.enabled) {
1013 haveAssertion = this._getIdentityAssertion();
1015 await this._getPermission();
1016 await this._certificateReady;
1017 let sdp = await new Promise((resolve, reject) => {
1018 this._onCreateAnswerSuccess = resolve;
1019 this._onCreateAnswerFailure = reject;
1020 this._pc.createAnswer();
1022 if (haveAssertion) {
1023 await haveAssertion;
1024 sdp = this._localIdp.addIdentityAttribute(sdp);
1026 return Cu.cloneInto({ type: "answer", sdp }, this._win);
1029 async _getPermission() {
1030 if (!this._havePermission) {
1032 this._documentPrincipal.isSystemPrincipal ||
1033 Services.prefs.getBoolPref("media.navigator.permission.disabled");
1036 this._havePermission = Promise.resolve();
1038 this._havePermission = new Promise((resolve, reject) => {
1039 this._settlePermission = { allow: resolve, deny: reject };
1040 let outerId = this._win.docShell.outerWindowID;
1042 let chrome = new CreateOfferRequest(
1045 this._globalPCListId,
1048 let request = this._win.CreateOfferRequest._create(this._win, chrome);
1049 Services.obs.notifyObservers(request, "PeerConnection:request");
1053 return this._havePermission;
1056 _sanityCheckSdp(sdp) {
1057 // The fippo butter finger filter AKA non-ASCII chars
1058 // Note: SDP allows non-ASCII character in the subject (who cares?)
1059 // eslint-disable-next-line no-control-regex
1060 let pos = sdp.search(/[^\u0000-\u007f]/);
1062 throw new this._win.DOMException(
1063 "SDP contains non ASCII characters at position " + pos,
1064 "InvalidParameterError"
1069 setLocalDescription(desc, onSucc, onErr) {
1070 return this._auto(onSucc, onErr, () => this._setLocalDescription(desc));
1073 _setLocalDescription({ type, sdp }) {
1074 if (type == "pranswer") {
1075 throw new this._win.DOMException(
1076 "pranswer not yet implemented",
1080 this._checkClosed();
1081 return this._chain(async () => {
1082 // Avoid Promise.all ahead of synchronous part of spec algorithm, since it
1083 // defers. NOTE: The spec says to return an already-rejected promise in
1084 // some cases, which is difficult to achieve in practice from JS (would
1085 // require avoiding await and then() entirely), but we want to come as
1086 // close as we reasonably can.
1087 const p = this._getPermission();
1089 switch (this.signalingState) {
1091 case "have-local-offer":
1092 case "have-remote-pranswer":
1101 if (type == "offer") {
1102 await this._createAnOffer();
1103 } else if (type == "answer") {
1104 await this._createAnAnswer();
1107 this._sanityCheckSdp(sdp);
1111 await new Promise((resolve, reject) => {
1112 this._onSetDescriptionSuccess = resolve;
1113 this._onSetDescriptionFailure = reject;
1114 this._pc.setLocalDescription(this._actions[type], sdp);
1118 this._pc.onSetDescriptionError();
1121 await this._pc.onSetDescriptionSuccess(type, false);
1125 async _validateIdentity(sdp, origin) {
1126 // Only run a single identity verification at a time. We have to do this to
1127 // avoid problems with the fact that identity validation doesn't block the
1128 // resolution of setRemoteDescription().
1129 const validate = async () => {
1130 // Access this._pc synchronously in case pc is closed later
1131 const identity = this._pc.peerIdentity;
1132 await this._lastIdentityValidation;
1133 const msg = await this._remoteIdp.verifyIdentityFromSDP(sdp, origin);
1134 // If this pc has an identity already, then the identity in sdp must match
1135 if (identity && (!msg || msg.identity !== identity)) {
1136 throw new this._win.DOMException(
1137 "Peer Identity mismatch, expected: " + identity,
1145 // Set new identity and generate an event.
1146 this._pc.peerIdentity = msg.identity;
1147 this._resolvePeerIdentity(
1150 idp: this._remoteIdp.provider,
1159 const haveValidation = validate();
1161 // Always eat errors on this chain
1162 this._lastIdentityValidation = haveValidation.catch(() => {});
1164 // If validation fails, we have some work to do. Fork it so it cannot
1165 // interfere with the validation chain itself, even if the catch function
1167 haveValidation.catch(e => {
1171 this._rejectPeerIdentity(e);
1173 // If we don't expect a specific peer identity, failure to get a valid
1174 // peer identity is not a terminal state, so replace the promise to
1175 // allow another attempt.
1176 if (!this._pc.peerIdentity) {
1177 this._resetPeerIdentityPromise();
1184 // Only wait for IdP validation if we need identity matching
1185 if (this._pc.peerIdentity) {
1186 await haveValidation;
1190 setRemoteDescription(desc, onSucc, onErr) {
1191 return this._auto(onSucc, onErr, () => this._setRemoteDescription(desc));
1194 _setRemoteDescription({ type, sdp }) {
1196 throw new this._win.TypeError(
1197 "Missing required 'type' member of RTCSessionDescriptionInit"
1200 if (type == "pranswer") {
1201 throw new this._win.DOMException(
1202 "pranswer not yet implemented",
1206 this._checkClosed();
1207 return this._chain(async () => {
1209 if (type == "offer" && this.signalingState == "have-local-offer") {
1210 await new Promise((resolve, reject) => {
1211 this._onSetDescriptionSuccess = resolve;
1212 this._onSetDescriptionFailure = reject;
1213 this._pc.setLocalDescription(
1214 Ci.IPeerConnection.kActionRollback,
1218 await this._pc.onSetDescriptionSuccess("rollback", false);
1219 this._updateCanTrickle();
1226 this._sanityCheckSdp(sdp);
1228 const p = this._getPermission();
1230 const haveSetRemote = new Promise((resolve, reject) => {
1231 this._onSetDescriptionSuccess = resolve;
1232 this._onSetDescriptionFailure = reject;
1233 this._pc.setRemoteDescription(this._actions[type], sdp);
1236 if (type != "rollback") {
1237 // Do setRemoteDescription and identity validation in parallel
1238 await this._validateIdentity(sdp);
1241 await haveSetRemote;
1243 this._pc.onSetDescriptionError();
1247 await this._pc.onSetDescriptionSuccess(type, true);
1248 this._updateCanTrickle();
1252 setIdentityProvider(provider, { protocol, usernameHint, peerIdentity } = {}) {
1253 this._checkClosed();
1254 peerIdentity = peerIdentity || this._pc.peerIdentity;
1255 this._localIdp.setIdentityProvider(
1263 async _getIdentityAssertion() {
1264 await this._certificateReady;
1265 return this._localIdp.getIdentityAssertion(
1266 this._pc.fingerprint,
1267 this._documentPrincipal.origin
1271 getIdentityAssertion() {
1272 this._checkClosed();
1273 return this._win.Promise.resolve(
1274 this._chain(() => this._getIdentityAssertion())
1278 get canTrickleIceCandidates() {
1279 return this._canTrickle;
1282 _updateCanTrickle() {
1283 let containsTrickle = section => {
1284 let lines = section.toLowerCase().split(/(?:\r\n?|\n)/);
1285 return lines.some(line => {
1286 let prefix = "a=ice-options:";
1287 if (line.substring(0, prefix.length) !== prefix) {
1290 let tokens = line.substring(prefix.length).split(" ");
1291 return tokens.some(x => x === "trickle");
1297 // The getter for remoteDescription can throw if the pc is closed.
1298 desc = this.remoteDescription;
1301 this._canTrickle = null;
1305 let sections = desc.sdp.split(/(?:\r\n?|\n)m=/);
1306 let topSection = sections.shift();
1308 containsTrickle(topSection) || sections.every(containsTrickle);
1311 addIceCandidate(cand, onSucc, onErr) {
1313 cand.candidate != "" &&
1314 cand.sdpMid == null &&
1315 cand.sdpMLineIndex == null
1317 throw new this._win.TypeError(
1318 "Cannot add a candidate without specifying either sdpMid or sdpMLineIndex"
1321 return this._auto(onSucc, onErr, () => this._addIceCandidate(cand));
1324 async _addIceCandidate({
1330 this._checkClosed();
1331 return this._chain(async () => {
1333 !this._pc.pendingRemoteDescription.length &&
1334 !this._pc.currentRemoteDescription.length
1336 throw new this._win.DOMException(
1337 "No remoteDescription.",
1341 return new Promise((resolve, reject) => {
1342 this._onAddIceCandidateSuccess = resolve;
1343 this._onAddIceCandidateError = reject;
1344 this._pc.addIceCandidate(
1347 usernameFragment || "",
1355 this._pc.restartIce();
1359 stream.getTracks().forEach(track => this.addTrack(track, stream));
1362 addTrack(track, ...streams) {
1363 this._checkClosed();
1366 this.getTransceivers().some(
1367 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.getTransceivers().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.setStreamsImpl(...streams);
1388 if (transceiver.direction == "recvonly") {
1389 transceiver.setDirectionInternal("sendrecv");
1390 } else if (transceiver.direction == "inactive") {
1391 transceiver.setDirectionInternal("sendonly");
1394 transceiver = this._addTransceiverNoEvents(
1398 direction: "sendrecv",
1404 this.updateNegotiationNeeded();
1405 return transceiver.sender;
1408 removeTrack(sender) {
1409 this._checkClosed();
1411 if (!this._pc.createdSender(sender)) {
1412 throw new this._win.DOMException(
1413 "This sender was not created by this PeerConnection",
1414 "InvalidAccessError"
1418 let transceiver = this.getTransceivers().find(
1419 transceiver => !transceiver.stopped && transceiver.sender == sender
1422 // If the transceiver was removed due to rollback, let it slide.
1423 if (!transceiver || !sender.track) {
1427 sender.setTrack(null);
1428 if (transceiver.direction == "sendrecv") {
1429 transceiver.setDirectionInternal("recvonly");
1430 } else if (transceiver.direction == "sendonly") {
1431 transceiver.setDirectionInternal("inactive");
1434 this.updateNegotiationNeeded();
1437 _addTransceiverNoEvents(sendTrackOrKind, init, addTrackMagic) {
1438 let sendTrack = null;
1440 if (typeof sendTrackOrKind == "string") {
1441 kind = sendTrackOrKind;
1447 throw new this._win.TypeError("Invalid media kind");
1450 sendTrack = sendTrackOrKind;
1451 kind = sendTrack.kind;
1455 return this._pc.addTransceiver(init, kind, sendTrack, addTrackMagic);
1457 // Exceptions thrown by c++ code do not propagate. In most cases, that's
1458 // fine because we're using Promises, which can be copied. But this is
1459 // not promise-based, so we have to do this sketchy stuff.
1460 const holder = new StructuredCloneHolder(
1463 new ClonedErrorHolder(e)
1465 throw holder.deserialize(this._win);
1469 addTransceiver(sendTrackOrKind, init) {
1470 this._checkClosed();
1471 let transceiver = this._addTransceiverNoEvents(sendTrackOrKind, init);
1472 this.updateNegotiationNeeded();
1476 updateNegotiationNeeded() {
1477 this._pc.updateNegotiationNeeded();
1484 this._closed = true;
1485 this.changeIceConnectionState("closed");
1486 if (this._localIdp) {
1487 this._localIdp.close();
1489 if (this._remoteIdp) {
1490 this._remoteIdp.close();
1493 this._suppressEvents = true;
1497 this._checkClosed();
1498 let localStreams = new Set();
1499 this.getTransceivers().forEach(transceiver => {
1500 transceiver.sender.getStreams().forEach(stream => {
1501 localStreams.add(stream);
1504 return [...localStreams.values()];
1507 getRemoteStreams() {
1508 this._checkClosed();
1509 return this._pc.getRemoteStreams();
1513 return this.getTransceivers()
1514 .filter(transceiver => !transceiver.stopped)
1515 .map(transceiver => transceiver.sender);
1519 return this.getTransceivers()
1520 .filter(transceiver => !transceiver.stopped)
1521 .map(transceiver => transceiver.receiver);
1524 mozSetPacketCallback(callback) {
1525 this._onPacket = callback;
1528 mozEnablePacketDump(level, type, sending) {
1529 this._pc.enablePacketDump(level, type, sending);
1532 mozDisablePacketDump(level, type, sending) {
1533 this._pc.disablePacketDump(level, type, sending);
1537 return this._pc.getTransceivers();
1540 get localDescription() {
1541 return this.pendingLocalDescription || this.currentLocalDescription;
1544 get currentLocalDescription() {
1545 this._checkClosed();
1546 const sdp = this._pc.currentLocalDescription;
1550 const type = this._pc.currentOfferer ? "offer" : "answer";
1551 return new this._win.RTCSessionDescription({ type, sdp });
1554 get pendingLocalDescription() {
1555 this._checkClosed();
1556 const sdp = this._pc.pendingLocalDescription;
1560 const type = this._pc.pendingOfferer ? "offer" : "answer";
1561 return new this._win.RTCSessionDescription({ type, sdp });
1564 get remoteDescription() {
1565 return this.pendingRemoteDescription || this.currentRemoteDescription;
1568 get currentRemoteDescription() {
1569 this._checkClosed();
1570 const sdp = this._pc.currentRemoteDescription;
1574 const type = this._pc.currentOfferer ? "answer" : "offer";
1575 return new this._win.RTCSessionDescription({ type, sdp });
1578 get pendingRemoteDescription() {
1579 this._checkClosed();
1580 const sdp = this._pc.pendingRemoteDescription;
1584 const type = this._pc.pendingOfferer ? "answer" : "offer";
1585 return new this._win.RTCSessionDescription({ type, sdp });
1588 get peerIdentity() {
1589 return this._peerIdentity;
1592 return this._localIdp.idpLoginUrl;
1600 get iceGatheringState() {
1601 return this._pc.iceGatheringState;
1603 get iceConnectionState() {
1604 return this._iceConnectionState;
1606 get connectionState() {
1607 return this._pc.connectionState;
1610 get signalingState() {
1611 // checking for our local pc closed indication
1612 // before invoking the pc methods.
1616 return this._pc.signalingState;
1619 handleIceGatheringStateChange() {
1620 _globalPCList.notifyLifecycleObservers(this, "icegatheringstatechange");
1621 this.dispatchEvent(new this._win.Event("icegatheringstatechange"));
1622 if (this.iceGatheringState === "complete") {
1624 new this._win.RTCPeerConnectionIceEvent("icecandidate", {
1631 changeIceConnectionState(state) {
1632 if (state != this._iceConnectionState) {
1633 this._iceConnectionState = state;
1634 _globalPCList.notifyLifecycleObservers(this, "iceconnectionstatechange");
1635 if (!this._closed) {
1636 this.dispatchEvent(new this._win.Event("iceconnectionstatechange"));
1641 getStats(selector, onSucc, onErr) {
1642 if (selector !== null) {
1643 let matchingSenders = this.getSenders().filter(s => s.track === selector);
1644 let matchingReceivers = this.getReceivers().filter(
1645 r => r.track === selector
1648 if (matchingSenders.length + matchingReceivers.length != 1) {
1649 throw new this._win.DOMException(
1650 "track must be associated with a unique sender or receiver, but " +
1651 " is associated with " +
1652 matchingSenders.length +
1654 matchingReceivers.length +
1656 "InvalidAccessError"
1661 return this._auto(onSucc, onErr, () => this._pc.getStats(selector));
1665 return this._pc.sctp;
1680 this._checkClosed();
1681 this._pcTelemetry.recordDataChannelInit(
1686 if (maxPacketLifeTime === undefined) {
1687 maxPacketLifeTime = maxRetransmitTime;
1690 if (maxRetransmitTime !== undefined) {
1692 "Use maxPacketLifeTime instead of deprecated maxRetransmitTime which will stop working soon in createDataChannel!"
1696 if (protocol.length > 32767) {
1697 // At least 65536/2 UTF-16 characters. UTF-8 might be too long.
1698 // Spec says to check how long |protocol| and |label| are in _bytes_. This
1699 // is a little ambiguous. For now, examine the length of the utf-8 encoding.
1700 const byteCounter = new TextEncoder();
1702 if (byteCounter.encode(protocol).length > 65535) {
1703 throw new this._win.TypeError(
1704 "protocol cannot be longer than 65535 bytes"
1709 if (label.length > 32767) {
1710 const byteCounter = new TextEncoder();
1711 if (byteCounter.encode(label).length > 65535) {
1712 throw new this._win.TypeError(
1713 "label cannot be longer than 65535 bytes"
1720 } else if (id === null) {
1721 throw new this._win.TypeError("id is required when negotiated is true");
1723 if (maxPacketLifeTime !== undefined && maxRetransmits !== undefined) {
1724 throw new this._win.TypeError(
1725 "Both maxPacketLifeTime and maxRetransmits cannot be provided"
1729 throw new this._win.TypeError("id cannot be 65535");
1731 // Must determine the type where we still know if entries are undefined.
1733 if (maxPacketLifeTime !== undefined) {
1734 type = Ci.IPeerConnection.kDataChannelPartialReliableTimed;
1735 } else if (maxRetransmits !== undefined) {
1736 type = Ci.IPeerConnection.kDataChannelPartialReliableRexmit;
1738 type = Ci.IPeerConnection.kDataChannelReliable;
1740 // Synchronous since it doesn't block.
1743 dataChannel = this._pc.createDataChannel(
1754 if (e.result != Cr.NS_ERROR_NOT_AVAILABLE) {
1759 id === null ? "No available id could be generated" : "Id is in use";
1760 throw new this._win.DOMException(msg, "OperationError");
1763 // Spec says to only do this if this is the first DataChannel created,
1764 // but the c++ code that does the "is negotiation needed" checking will
1765 // only ever return true on the first one.
1766 this.updateNegotiationNeeded();
1772 setupPrototype(RTCPeerConnection, {
1774 contractID: PC_CONTRACT,
1775 QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
1777 offer: Ci.IPeerConnection.kActionOffer,
1778 answer: Ci.IPeerConnection.kActionAnswer,
1779 pranswer: Ci.IPeerConnection.kActionPRAnswer,
1780 rollback: Ci.IPeerConnection.kActionRollback,
1784 // This is a separate class because we don't want to expose it to DOM.
1786 export class PeerConnectionObserver {
1792 this._dompc = dompc._innerObject;
1795 newError({ message, name }) {
1796 return new this._dompc._win.DOMException(message, name);
1799 dispatchEvent(event) {
1800 this._dompc.dispatchEvent(event);
1803 onCreateOfferSuccess(sdp) {
1804 this._dompc._onCreateOfferSuccess(sdp);
1807 onCreateOfferError(error) {
1808 this._dompc._onCreateOfferFailure(this.newError(error));
1811 onCreateAnswerSuccess(sdp) {
1812 this._dompc._onCreateAnswerSuccess(sdp);
1815 onCreateAnswerError(error) {
1816 this._dompc._onCreateAnswerFailure(this.newError(error));
1819 onSetDescriptionSuccess() {
1820 this._dompc._onSetDescriptionSuccess();
1823 onSetDescriptionError(error) {
1824 this._dompc._onSetDescriptionFailure(this.newError(error));
1827 onAddIceCandidateSuccess() {
1828 this._dompc._onAddIceCandidateSuccess();
1831 onAddIceCandidateError(error) {
1832 this._dompc._onAddIceCandidateError(this.newError(error));
1835 onIceCandidate(sdpMLineIndex, sdpMid, candidate, usernameFragment) {
1836 let win = this._dompc._win;
1837 if (candidate || sdpMid || usernameFragment) {
1838 if (candidate.includes(" typ relay ")) {
1839 this._dompc._iceGatheredRelayCandidates = true;
1841 candidate = new win.RTCIceCandidate({
1849 new win.RTCPeerConnectionIceEvent("icecandidate", { candidate })
1853 // This method is primarily responsible for updating iceConnectionState.
1854 // This state is defined in the WebRTC specification as follows:
1856 // iceConnectionState:
1857 // -------------------
1858 // new Any of the RTCIceTransports are in the new state and none
1859 // of them are in the checking, failed or disconnected state.
1861 // checking Any of the RTCIceTransports are in the checking state and
1862 // none of them are in the failed or disconnected state.
1864 // connected All RTCIceTransports are in the connected, completed or
1865 // closed state and at least one of them is in the connected
1868 // completed All RTCIceTransports are in the completed or closed state
1869 // and at least one of them is in the completed state.
1871 // failed Any of the RTCIceTransports are in the failed state.
1873 // disconnected Any of the RTCIceTransports are in the disconnected state
1874 // and none of them are in the failed state.
1876 // closed All of the RTCIceTransports are in the closed state.
1878 handleIceConnectionStateChange(iceConnectionState) {
1879 let pc = this._dompc;
1880 if (pc.iceConnectionState === iceConnectionState) {
1884 if (iceConnectionState === "failed") {
1885 if (!pc._hasStunServer) {
1887 "ICE failed, add a STUN server and see about:webrtc for more details"
1889 } else if (!pc._hasTurnServer) {
1891 "ICE failed, add a TURN server and see about:webrtc for more details"
1893 } else if (pc._hasTurnServer && !pc._iceGatheredRelayCandidates) {
1895 "ICE failed, your TURN server appears to be broken, see about:webrtc for more details"
1898 pc.logError("ICE failed, see about:webrtc for more details");
1902 pc.changeIceConnectionState(iceConnectionState);
1905 onStateChange(state) {
1910 if (state == "SignalingState") {
1911 this.dispatchEvent(new this._win.Event("signalingstatechange"));
1915 if (!this._dompc._pc) {
1920 case "IceConnectionState":
1921 let connState = this._dompc._pc.iceConnectionState;
1922 this.handleIceConnectionStateChange(connState);
1925 case "IceGatheringState":
1926 this._dompc.handleIceGatheringStateChange();
1929 case "ConnectionState":
1930 _globalPCList.notifyLifecycleObservers(this, "connectionstatechange");
1931 this.dispatchEvent(new this._win.Event("connectionstatechange"));
1935 this._dompc.logWarning("Unhandled state type: " + state);
1940 onTransceiverNeeded(kind, transceiverImpl) {
1941 this._dompc._onTransceiverNeeded(kind, transceiverImpl);
1944 notifyDataChannel(channel) {
1946 new this._dompc._win.RTCDataChannelEvent("datachannel", { channel })
1950 fireTrackEvent(receiver, streams) {
1951 const pc = this._dompc;
1952 const transceiver = pc.getTransceivers().find(t => t.receiver == receiver);
1956 const track = receiver.track;
1958 new this._win.RTCTrackEvent("track", {
1965 // Fire legacy event as well for a little bit.
1967 new this._win.MediaStreamTrackEvent("addtrack", { track })
1971 fireStreamEvent(stream) {
1972 const ev = new this._win.MediaStreamEvent("addstream", { stream });
1973 this.dispatchEvent(ev);
1976 fireNegotiationNeededEvent() {
1977 this.dispatchEvent(new this._win.Event("negotiationneeded"));
1980 onPacket(level, type, sending, packet) {
1981 var pc = this._dompc;
1983 pc._onPacket(level, type, sending, packet);
1988 setupPrototype(PeerConnectionObserver, {
1989 classID: PC_OBS_CID,
1990 contractID: PC_OBS_CONTRACT,
1991 QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
1994 export class RTCPeerConnectionStatic {
1996 this._winID = win.windowGlobalChild.innerWindowId;
1999 registerPeerConnectionLifecycleCallback(cb) {
2000 _globalPCList._registerPeerConnectionLifecycleCallback(this._winID, cb);
2004 setupPrototype(RTCPeerConnectionStatic, {
2005 classID: PC_STATIC_CID,
2006 contractID: PC_STATIC_CONTRACT,
2007 QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
2010 export class CreateOfferRequest {
2011 constructor(windowID, innerWindowID, callID, isSecure) {
2012 Object.assign(this, { windowID, innerWindowID, callID, isSecure });
2016 setupPrototype(CreateOfferRequest, {
2017 classID: PC_COREQUEST_CID,
2018 contractID: PC_COREQUEST_CONTRACT,
2019 QueryInterface: ChromeUtils.generateQI([]),