1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693 |
- /* jshint moz:true, browser:true */
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- "use strict";
- const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
- Cu.import("resource://gre/modules/Services.jsm");
- Cu.import("resource://gre/modules/XPCOMUtils.jsm");
- XPCOMUtils.defineLazyModuleGetter(this, "PeerConnectionIdp",
- "resource://gre/modules/media/PeerConnectionIdp.jsm");
- XPCOMUtils.defineLazyModuleGetter(this, "convertToRTCStatsReport",
- "resource://gre/modules/media/RTCStatsReport.jsm");
- const PC_CONTRACT = "@mozilla.org/dom/peerconnection;1";
- const PC_OBS_CONTRACT = "@mozilla.org/dom/peerconnectionobserver;1";
- const PC_ICE_CONTRACT = "@mozilla.org/dom/rtcicecandidate;1";
- const PC_SESSION_CONTRACT = "@mozilla.org/dom/rtcsessiondescription;1";
- const PC_MANAGER_CONTRACT = "@mozilla.org/dom/peerconnectionmanager;1";
- const PC_STATS_CONTRACT = "@mozilla.org/dom/rtcstatsreport;1";
- const PC_STATIC_CONTRACT = "@mozilla.org/dom/peerconnectionstatic;1";
- const PC_SENDER_CONTRACT = "@mozilla.org/dom/rtpsender;1";
- const PC_RECEIVER_CONTRACT = "@mozilla.org/dom/rtpreceiver;1";
- const PC_COREQUEST_CONTRACT = "@mozilla.org/dom/createofferrequest;1";
- const PC_DTMF_SENDER_CONTRACT = "@mozilla.org/dom/rtcdtmfsender;1";
- const PC_CID = Components.ID("{bdc2e533-b308-4708-ac8e-a8bfade6d851}");
- const PC_OBS_CID = Components.ID("{d1748d4c-7f6a-4dc5-add6-d55b7678537e}");
- const PC_ICE_CID = Components.ID("{02b9970c-433d-4cc2-923d-f7028ac66073}");
- const PC_SESSION_CID = Components.ID("{1775081b-b62d-4954-8ffe-a067bbf508a7}");
- const PC_MANAGER_CID = Components.ID("{7293e901-2be3-4c02-b4bd-cbef6fc24f78}");
- const PC_STATS_CID = Components.ID("{7fe6e18b-0da3-4056-bf3b-440ef3809e06}");
- const PC_STATIC_CID = Components.ID("{0fb47c47-a205-4583-a9fc-cbadf8c95880}");
- const PC_SENDER_CID = Components.ID("{4fff5d46-d827-4cd4-a970-8fd53977440e}");
- const PC_RECEIVER_CID = Components.ID("{d974b814-8fde-411c-8c45-b86791b81030}");
- const PC_COREQUEST_CID = Components.ID("{74b2122d-65a8-4824-aa9e-3d664cb75dc2}");
- const PC_DTMF_SENDER_CID = Components.ID("{3610C242-654E-11E6-8EC0-6D1BE389A607}");
- // Global list of PeerConnection objects, so they can be cleaned up when
- // a page is torn down. (Maps inner window ID to an array of PC objects).
- function GlobalPCList() {
- this._list = {};
- this._networkdown = false; // XXX Need to query current state somehow
- this._lifecycleobservers = {};
- this._nextId = 1;
- Services.obs.addObserver(this, "inner-window-destroyed", true);
- Services.obs.addObserver(this, "profile-change-net-teardown", true);
- Services.obs.addObserver(this, "network:offline-about-to-go-offline", true);
- Services.obs.addObserver(this, "network:offline-status-changed", true);
- Services.obs.addObserver(this, "gmp-plugin-crash", true);
- Services.obs.addObserver(this, "PeerConnection:response:allow", true);
- Services.obs.addObserver(this, "PeerConnection:response:deny", true);
- if (Cc["@mozilla.org/childprocessmessagemanager;1"]) {
- let mm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
- mm.addMessageListener("gmp-plugin-crash", this);
- }
- }
- GlobalPCList.prototype = {
- QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
- Ci.nsIMessageListener,
- Ci.nsISupportsWeakReference,
- Ci.IPeerConnectionManager]),
- classID: PC_MANAGER_CID,
- _xpcom_factory: {
- createInstance: function(outer, iid) {
- if (outer) {
- throw Cr.NS_ERROR_NO_AGGREGATION;
- }
- return _globalPCList.QueryInterface(iid);
- }
- },
- notifyLifecycleObservers: function(pc, type) {
- for (var key of Object.keys(this._lifecycleobservers)) {
- this._lifecycleobservers[key](pc, pc._winID, type);
- }
- },
- addPC: function(pc) {
- let winID = pc._winID;
- if (this._list[winID]) {
- this._list[winID].push(Cu.getWeakReference(pc));
- } else {
- this._list[winID] = [Cu.getWeakReference(pc)];
- }
- pc._globalPCListId = this._nextId++;
- this.removeNullRefs(winID);
- },
- findPC: function(globalPCListId) {
- for (let winId in this._list) {
- if (this._list.hasOwnProperty(winId)) {
- for (let pcref of this._list[winId]) {
- let pc = pcref.get();
- if (pc && pc._globalPCListId == globalPCListId) {
- return pc;
- }
- }
- }
- }
- },
- removeNullRefs: function(winID) {
- if (this._list[winID] === undefined) {
- return;
- }
- this._list[winID] = this._list[winID].filter(
- function (e,i,a) { return e.get() !== null; });
- if (this._list[winID].length === 0) {
- delete this._list[winID];
- }
- },
- hasActivePeerConnection: function(winID) {
- this.removeNullRefs(winID);
- return this._list[winID] ? true : false;
- },
- handleGMPCrash: function(data) {
- let broadcastPluginCrash = function(list, winID, pluginID, pluginName) {
- if (list.hasOwnProperty(winID)) {
- list[winID].forEach(function(pcref) {
- let pc = pcref.get();
- if (pc) {
- pc._pc.pluginCrash(pluginID, pluginName);
- }
- });
- }
- };
- // a plugin crashed; if it's associated with any of our PCs, fire an
- // event to the DOM window
- for (let winId in this._list) {
- broadcastPluginCrash(this._list, winId, data.pluginID, data.pluginName);
- }
- },
- receiveMessage: function(message) {
- if (message.name == "gmp-plugin-crash") {
- this.handleGMPCrash(message.data);
- }
- },
- observe: function(subject, topic, data) {
- let cleanupPcRef = function(pcref) {
- let pc = pcref.get();
- if (pc) {
- pc._pc.close();
- delete pc._observer;
- pc._pc = null;
- }
- };
- let cleanupWinId = function(list, winID) {
- if (list.hasOwnProperty(winID)) {
- list[winID].forEach(cleanupPcRef);
- delete list[winID];
- }
- };
- if (topic == "inner-window-destroyed") {
- let winID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
- cleanupWinId(this._list, winID);
- if (this._lifecycleobservers.hasOwnProperty(winID)) {
- delete this._lifecycleobservers[winID];
- }
- } else if (topic == "profile-change-net-teardown" ||
- topic == "network:offline-about-to-go-offline") {
- // Delete all peerconnections on shutdown - mostly synchronously (we
- // need them to be done deleting transports and streams before we
- // return)! All socket operations must be queued to STS thread
- // before we return to here.
- // Also kill them if "Work Offline" is selected - more can be created
- // while offline, but attempts to connect them should fail.
- for (let winId in this._list) {
- cleanupWinId(this._list, winId);
- }
- this._networkdown = true;
- }
- else if (topic == "network:offline-status-changed") {
- if (data == "offline") {
- // this._list shold be empty here
- this._networkdown = true;
- } else if (data == "online") {
- this._networkdown = false;
- }
- } else if (topic == "gmp-plugin-crash") {
- if (subject instanceof Ci.nsIWritablePropertyBag2) {
- let pluginID = subject.getPropertyAsUint32("pluginID");
- let pluginName = subject.getPropertyAsAString("pluginName");
- let data = { pluginID, pluginName };
- this.handleGMPCrash(data);
- }
- } else if (topic == "PeerConnection:response:allow" ||
- topic == "PeerConnection:response:deny") {
- var pc = this.findPC(data);
- if (pc) {
- if (topic == "PeerConnection:response:allow") {
- pc._settlePermission.allow();
- } else {
- let err = new pc._win.DOMException("The request is not allowed by " +
- "the user agent or the platform in the current context.",
- "NotAllowedError");
- pc._settlePermission.deny(err);
- }
- }
- }
- },
- _registerPeerConnectionLifecycleCallback: function(winID, cb) {
- this._lifecycleobservers[winID] = cb;
- },
- };
- var _globalPCList = new GlobalPCList();
- function RTCIceCandidate() {
- this.candidate = this.sdpMid = this.sdpMLineIndex = null;
- }
- RTCIceCandidate.prototype = {
- classDescription: "RTCIceCandidate",
- classID: PC_ICE_CID,
- contractID: PC_ICE_CONTRACT,
- QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
- Ci.nsIDOMGlobalPropertyInitializer]),
- init: function(win) { this._win = win; },
- __init: function(dict) {
- this.candidate = dict.candidate;
- this.sdpMid = dict.sdpMid;
- this.sdpMLineIndex = ("sdpMLineIndex" in dict)? dict.sdpMLineIndex : null;
- }
- };
- function RTCSessionDescription() {
- this.type = this.sdp = null;
- }
- RTCSessionDescription.prototype = {
- classDescription: "RTCSessionDescription",
- classID: PC_SESSION_CID,
- contractID: PC_SESSION_CONTRACT,
- QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
- Ci.nsIDOMGlobalPropertyInitializer]),
- init: function(win) { this._win = win; },
- __init: function(dict) {
- this.type = dict.type;
- this.sdp = dict.sdp;
- }
- };
- function RTCStatsReport(win, dict) {
- this._win = win;
- this._pcid = dict.pcid;
- this._report = convertToRTCStatsReport(dict);
- }
- RTCStatsReport.prototype = {
- classDescription: "RTCStatsReport",
- classID: PC_STATS_CID,
- contractID: PC_STATS_CONTRACT,
- QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
- setInternal: function(aKey, aObj) {
- return this.__DOM_IMPL__.__set(aKey, aObj);
- },
- // TODO: Remove legacy API eventually
- //
- // Since maplike is recent, we still also make the stats available as legacy
- // enumerable read-only properties directly on our content-facing object.
- // Must be called after our webidl sandwich is made.
- makeStatsPublic: function(warnNullable) {
- let legacyProps = {};
- for (let key in this._report) {
- let value = Cu.cloneInto(this._report[key], this._win);
- this.setInternal(key, value);
- legacyProps[key] = {
- enumerable: true, configurable: false,
- get: Cu.exportFunction(function() {
- if (warnNullable.warn) {
- warnNullable.warn();
- warnNullable.warn = null;
- }
- return value;
- }, this.__DOM_IMPL__.wrappedJSObject)
- };
- }
- Object.defineProperties(this.__DOM_IMPL__.wrappedJSObject, legacyProps);
- },
- get mozPcid() { return this._pcid; }
- };
- function RTCPeerConnection() {
- this._senders = [];
- this._receivers = [];
- this._pc = null;
- this._observer = null;
- this._closed = false;
- this._onCreateOfferSuccess = null;
- this._onCreateOfferFailure = null;
- this._onCreateAnswerSuccess = null;
- this._onCreateAnswerFailure = null;
- this._onGetStatsSuccess = null;
- this._onGetStatsFailure = null;
- this._onReplaceTrackSender = null;
- this._onReplaceTrackWithTrack = null;
- this._onReplaceTrackSuccess = null;
- this._onReplaceTrackFailure = null;
- this._localType = null;
- this._remoteType = null;
- // http://rtcweb-wg.github.io/jsep/#rfc.section.4.1.9
- // canTrickle == null means unknown; when a remote description is received it
- // is set to true or false based on the presence of the "trickle" ice-option
- this._canTrickle = null;
- // States
- this._iceGatheringState = this._iceConnectionState = "new";
- }
- RTCPeerConnection.prototype = {
- classDescription: "RTCPeerConnection",
- classID: PC_CID,
- contractID: PC_CONTRACT,
- QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
- Ci.nsIDOMGlobalPropertyInitializer]),
- init: function(win) { this._win = win; },
- __init: function(rtcConfig) {
- this._winID = this._win.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
- // TODO: Update this code once we support pc.setConfiguration, to track
- // setting from content independently from pref (Bug 1181768).
- if (rtcConfig.iceTransportPolicy == "all" &&
- Services.prefs.getBoolPref("media.peerconnection.ice.relay_only")) {
- rtcConfig.iceTransportPolicy = "relay";
- }
- this._config = Object.assign({}, rtcConfig);
- if (!rtcConfig.iceServers ||
- !Services.prefs.getBoolPref("media.peerconnection.use_document_iceservers")) {
- try {
- rtcConfig.iceServers =
- JSON.parse(Services.prefs.getCharPref("media.peerconnection.default_iceservers") || "[]");
- } catch (e) {
- this.logWarning(
- "Ignoring invalid media.peerconnection.default_iceservers in about:config");
- rtcConfig.iceServers = [];
- }
- try {
- this._mustValidateRTCConfiguration(rtcConfig,
- "Ignoring invalid media.peerconnection.default_iceservers in about:config");
- } catch (e) {
- this.logWarning(e.message);
- rtcConfig.iceServers = [];
- }
- } else {
- // This gets executed in the typical case when iceServers
- // are passed in through the web page.
- this._mustValidateRTCConfiguration(rtcConfig,
- "RTCPeerConnection constructor passed invalid RTCConfiguration");
- }
- var principal = Cu.getWebIDLCallerPrincipal();
- this._isChrome = Services.scriptSecurityManager.isSystemPrincipal(principal);
- if (_globalPCList._networkdown) {
- throw new this._win.DOMException(
- "Can't create RTCPeerConnections when the network is down",
- "InvalidStateError");
- }
- this.makeGetterSetterEH("ontrack");
- this.makeLegacyGetterSetterEH("onaddstream", "Use peerConnection.ontrack instead.");
- this.makeLegacyGetterSetterEH("onaddtrack", "Use peerConnection.ontrack instead.");
- this.makeGetterSetterEH("onicecandidate");
- this.makeGetterSetterEH("onnegotiationneeded");
- this.makeGetterSetterEH("onsignalingstatechange");
- this.makeGetterSetterEH("onremovestream");
- this.makeGetterSetterEH("ondatachannel");
- this.makeGetterSetterEH("oniceconnectionstatechange");
- this.makeGetterSetterEH("onidentityresult");
- this.makeGetterSetterEH("onpeeridentity");
- this.makeGetterSetterEH("onidpassertionerror");
- this.makeGetterSetterEH("onidpvalidationerror");
- this._pc = new this._win.PeerConnectionImpl();
- this._operationsChain = this._win.Promise.resolve();
- this.__DOM_IMPL__._innerObject = this;
- this._observer = new this._win.PeerConnectionObserver(this.__DOM_IMPL__);
- var location = "" + this._win.location;
- // Warn just once per PeerConnection about deprecated getStats usage.
- this._warnDeprecatedStatsAccessNullable = { warn: () =>
- this.logWarning("non-maplike pc.getStats access is deprecated! " +
- "See http://w3c.github.io/webrtc-pc/#example for usage.") };
- // Add a reference to the PeerConnection to global list (before init).
- _globalPCList.addPC(this);
- this._impl.initialize(this._observer, this._win, rtcConfig,
- Services.tm.currentThread);
- this._initCertificate(rtcConfig.certificates);
- this._initIdp();
- _globalPCList.notifyLifecycleObservers(this, "initialized");
- },
- get _impl() {
- if (!this._pc) {
- throw new this._win.DOMException(
- "RTCPeerConnection is gone (did you enter Offline mode?)",
- "InvalidStateError");
- }
- return this._pc;
- },
- getConfiguration: function() {
- return this._config;
- },
- _initCertificate: function(certificates) {
- let certPromise;
- if (certificates && certificates.length > 0) {
- if (certificates.length > 1) {
- throw new this._win.DOMException(
- "RTCPeerConnection does not currently support multiple certificates",
- "NotSupportedError");
- }
- let cert = certificates.find(c => c.expires > Date.now());
- if (!cert) {
- throw new this._win.DOMException(
- "Unable to create RTCPeerConnection with an expired certificate",
- "InvalidParameterError");
- }
- certPromise = Promise.resolve(cert);
- } else {
- certPromise = this._win.RTCPeerConnection.generateCertificate({
- name: "ECDSA", namedCurve: "P-256"
- });
- }
- this._certificateReady = certPromise
- .then(cert => this._impl.certificate = cert);
- },
- _initIdp: function() {
- this._peerIdentity = new this._win.Promise((resolve, reject) => {
- this._resolvePeerIdentity = resolve;
- this._rejectPeerIdentity = reject;
- });
- this._lastIdentityValidation = this._win.Promise.resolve();
- let prefName = "media.peerconnection.identity.timeout";
- let idpTimeout = Services.prefs.getIntPref(prefName);
- this._localIdp = new PeerConnectionIdp(this._win, idpTimeout);
- this._remoteIdp = new PeerConnectionIdp(this._win, idpTimeout);
- },
- // Add a function to the internal operations chain.
- _chain: function(func) {
- this._checkClosed(); // out here DOMException line-numbers work.
- let p = this._operationsChain.then(() => {
- // Don't _checkClosed() inside the chain, because it throws, and spec
- // behavior as of this writing is to NOT reject outstanding promises on
- // close. This is what happens most of the time anyways, as the c++ code
- // stops calling us once closed, hanging the chain. However, c++ may
- // already have queued tasks on us, so if we're one of those then sit back.
- if (!this._closed) {
- return func();
- }
- });
- // don't propagate errors in the operations chain (this is a fork of p).
- this._operationsChain = p.catch(() => {});
- return p;
- },
- // This wrapper helps implement legacy callbacks in a manner that produces
- // correct line-numbers in errors, provided that methods validate their inputs
- // before putting themselves on the pc's operations chain.
- //
- // It also serves as guard against settling promises past close().
- _legacyCatchAndCloseGuard: function(onSuccess, onError, func) {
- if (!onSuccess) {
- return func().then(v => (this._closed ? new Promise(() => {}) : v),
- e => (this._closed ? new Promise(() => {}) : Promise.reject(e)));
- }
- try {
- return func().then(this._wrapLegacyCallback(onSuccess),
- this._wrapLegacyCallback(onError));
- } catch (e) {
- this._wrapLegacyCallback(onError)(e);
- return this._win.Promise.resolve(); // avoid webidl TypeError
- }
- },
- _wrapLegacyCallback: function(func) {
- return result => {
- try {
- func && func(result);
- } catch (e) {
- this.logErrorAndCallOnError(e);
- }
- };
- },
- // This implements the fairly common "Queue a task" logic
- async _queueTaskWithClosedCheck(func) {
- return new Promise(resolve => {
- Services.tm.mainThread.dispatch({ run() {
- if (!this._closed) {
- func();
- resolve();
- }
- }}, Ci.nsIThread.DISPATCH_NORMAL);
- });
- },
- /**
- * An RTCConfiguration may look like this:
- *
- * { "iceServers": [ { urls: "stun:stun.example.org", },
- * { url: "stun:stun.example.org", }, // deprecated version
- * { urls: ["turn:turn1.x.org", "turn:turn2.x.org"],
- * username:"jib", credential:"mypass"} ] }
- *
- * This function normalizes the structure of the input for rtcConfig.iceServers for us,
- * so we test well-formed stun/turn urls before passing along to C++.
- * msg - Error message to detail which array-entry failed, if any.
- */
- _mustValidateRTCConfiguration: function(rtcConfig, msg) {
- // Normalize iceServers input
- rtcConfig.iceServers.forEach(server => {
- if (typeof server.urls === "string") {
- server.urls = [server.urls];
- } else if (!server.urls && server.url) {
- // TODO: Remove support for legacy iceServer.url eventually (Bug 1116766)
- server.urls = [server.url];
- this.logWarning("RTCIceServer.url is deprecated! Use urls instead.");
- }
- });
- let ios = Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService);
- let nicerNewURI = uriStr => {
- try {
- return ios.newURI(uriStr, null, null);
- } catch (e if (e.result == Cr.NS_ERROR_MALFORMED_URI)) {
- throw new this._win.DOMException(msg + " - malformed URI: " + uriStr,
- "SyntaxError");
- }
- };
- rtcConfig.iceServers.forEach(server => {
- if (!server.urls) {
- throw new this._win.DOMException(msg + " - missing urls", "InvalidAccessError");
- }
- server.urls.forEach(urlStr => {
- let url = nicerNewURI(urlStr);
- if (url.scheme in { turn:1, turns:1 }) {
- if (server.username == undefined) {
- throw new this._win.DOMException(msg + " - missing username: " + urlStr,
- "InvalidAccessError");
- }
- if (server.credential == undefined) {
- throw new this._win.DOMException(msg + " - missing credential: " + urlStr,
- "InvalidAccessError");
- }
- if (server.credentialType != "password") {
- this.logWarning("RTCConfiguration TURN credentialType \""+
- server.credentialType +
- "\" is not yet implemented. Treating as password."+
- " https://bugzil.la/1247616");
- }
- }
- else if (!(url.scheme in { stun:1, stuns:1 })) {
- throw new this._win.DOMException(msg + " - improper scheme: " + url.scheme,
- "SyntaxError");
- }
- if (url.scheme in { stuns:1, turns:1 }) {
- this.logWarning(url.scheme.toUpperCase() + " is not yet supported.");
- }
- });
- });
- },
- // Ideally, this should be of the form _checkState(state),
- // where the state is taken from an enumeration containing
- // the valid peer connection states defined in the WebRTC
- // spec. See Bug 831756.
- _checkClosed: function() {
- if (this._closed) {
- throw new this._win.DOMException("Peer connection is closed",
- "InvalidStateError");
- }
- },
- dispatchEvent: function(event) {
- // PC can close while events are firing if there is an async dispatch
- // in c++ land. But let through "closed" signaling and ice connection events.
- if (!this._closed || this._inClose) {
- this.__DOM_IMPL__.dispatchEvent(event);
- }
- },
- // Log error message to web console and window.onerror, if present.
- logErrorAndCallOnError: function(e) {
- this.logMsg(e.message, e.fileName, e.lineNumber, Ci.nsIScriptError.exceptionFlag);
- // Safely call onerror directly if present (necessary for testing)
- try {
- if (typeof this._win.onerror === "function") {
- this._win.onerror(e.message, e.fileName, e.lineNumber);
- }
- } catch(e) {
- // If onerror itself throws, service it.
- try {
- this.logMsg(e.message, e.fileName, e.lineNumber, Ci.nsIScriptError.errorFlag);
- } catch(e) {}
- }
- },
- logError: function(msg) {
- this.logStackMsg(msg, Ci.nsIScriptError.errorFlag);
- },
- logWarning: function(msg) {
- this.logStackMsg(msg, Ci.nsIScriptError.warningFlag);
- },
- logStackMsg: function(msg, flag) {
- let err = this._win.Error();
- this.logMsg(msg, err.fileName, err.lineNumber, flag);
- },
- logMsg: function(msg, file, line, flag) {
- let scriptErrorClass = Cc["@mozilla.org/scripterror;1"];
- let scriptError = scriptErrorClass.createInstance(Ci.nsIScriptError);
- scriptError.initWithWindowID(msg, file, null, line, 0, flag,
- "content javascript", this._winID);
- let console = Cc["@mozilla.org/consoleservice;1"].
- getService(Ci.nsIConsoleService);
- console.logMessage(scriptError);
- },
- getEH: function(type) {
- return this.__DOM_IMPL__.getEventHandler(type);
- },
- setEH: function(type, handler) {
- this.__DOM_IMPL__.setEventHandler(type, handler);
- },
- makeGetterSetterEH: function(name) {
- Object.defineProperty(this, name,
- {
- get:function() { return this.getEH(name); },
- set:function(h) { return this.setEH(name, h); }
- });
- },
- makeLegacyGetterSetterEH: function(name, msg) {
- Object.defineProperty(this, name,
- {
- get:function() { return this.getEH(name); },
- set:function(h) {
- this.logWarning(name + " is deprecated! " + msg);
- return this.setEH(name, h);
- }
- });
- },
- _addIdentityAssertion: function(sdpPromise, origin) {
- if (!this._localIdp.enabled) {
- return sdpPromise;
- }
- return Promise.all([
- this._certificateReady
- .then(() => this._localIdp.getIdentityAssertion(this._impl.fingerprint,
- origin)),
- sdpPromise
- ]).then(([,sdp]) => this._localIdp.addIdentityAttribute(sdp));
- },
- createOffer: function(optionsOrOnSuccess, onError, options) {
- // This entry-point handles both new and legacy call sig. Decipher which one
- let onSuccess;
- if (typeof optionsOrOnSuccess == "function") {
- onSuccess = optionsOrOnSuccess;
- } else {
- options = optionsOrOnSuccess;
- }
- return this._legacyCatchAndCloseGuard(onSuccess, onError, () => {
- // TODO: Remove error on constraint-like RTCOptions next cycle (1197021).
- // Note that webidl bindings make o.mandatory implicit but not o.optional.
- function convertLegacyOptions(o) {
- // Detect (mandatory OR optional) AND no other top-level members.
- let lcy = ((o.mandatory && Object.keys(o.mandatory).length) || o.optional) &&
- Object.keys(o).length == (o.mandatory? 1 : 0) + (o.optional? 1 : 0);
- if (!lcy) {
- return false;
- }
- let old = o.mandatory || {};
- if (o.mandatory) {
- delete o.mandatory;
- }
- if (o.optional) {
- o.optional.forEach(one => {
- // The old spec had optional as an array of objects w/1 attribute each.
- // Assumes our JS-webidl bindings only populate passed-in properties.
- let key = Object.keys(one)[0];
- if (key && old[key] === undefined) {
- old[key] = one[key];
- }
- });
- delete o.optional;
- }
- o.offerToReceiveAudio = old.OfferToReceiveAudio;
- o.offerToReceiveVideo = old.OfferToReceiveVideo;
- o.mozDontOfferDataChannel = old.MozDontOfferDataChannel;
- o.mozBundleOnly = old.MozBundleOnly;
- Object.keys(o).forEach(k => {
- if (o[k] === undefined) {
- delete o[k];
- }
- });
- return true;
- }
- if (options && convertLegacyOptions(options)) {
- this.logError(
- "Mandatory/optional in createOffer options no longer works! Use " +
- JSON.stringify(options) + " instead (note the case difference)!");
- options = {};
- }
- let origin = Cu.getWebIDLCallerPrincipal().origin;
- return this._chain(() => {
- let p = Promise.all([this.getPermission(), this._certificateReady])
- .then(() => new this._win.Promise((resolve, reject) => {
- this._onCreateOfferSuccess = resolve;
- this._onCreateOfferFailure = reject;
- this._impl.createOffer(options);
- }));
- p = this._addIdentityAssertion(p, origin);
- return p.then(
- sdp => new this._win.RTCSessionDescription({ type: "offer", sdp: sdp }));
- });
- });
- },
- createAnswer: function(optionsOrOnSuccess, onError) {
- // This entry-point handles both new and legacy call sig. Decipher which one
- let onSuccess, options;
- if (typeof optionsOrOnSuccess == "function") {
- onSuccess = optionsOrOnSuccess;
- } else {
- options = optionsOrOnSuccess;
- }
- return this._legacyCatchAndCloseGuard(onSuccess, onError, () => {
- let origin = Cu.getWebIDLCallerPrincipal().origin;
- return this._chain(() => {
- let p = Promise.all([this.getPermission(), this._certificateReady])
- .then(() => new this._win.Promise((resolve, reject) => {
- // We give up line-numbers in errors by doing this here, but do all
- // state-checks inside the chain, to support the legacy feature that
- // callers don't have to wait for setRemoteDescription to finish.
- if (!this.remoteDescription) {
- throw new this._win.DOMException("setRemoteDescription not called",
- "InvalidStateError");
- }
- if (this.remoteDescription.type != "offer") {
- throw new this._win.DOMException("No outstanding offer",
- "InvalidStateError");
- }
- this._onCreateAnswerSuccess = resolve;
- this._onCreateAnswerFailure = reject;
- this._impl.createAnswer();
- }));
- p = this._addIdentityAssertion(p, origin);
- return p.then(sdp => {
- return new this._win.RTCSessionDescription({ type: "answer", sdp: sdp });
- });
- });
- });
- },
- getPermission: function() {
- if (this._havePermission) {
- return this._havePermission;
- }
- if (this._isChrome ||
- Services.prefs.getBoolPref("media.navigator.permission.disabled")) {
- return this._havePermission = Promise.resolve();
- }
- return this._havePermission = new Promise((resolve, reject) => {
- this._settlePermission = { allow: resolve, deny: reject };
- let outerId = this._win.QueryInterface(Ci.nsIInterfaceRequestor).
- getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
- let chrome = new CreateOfferRequest(outerId, this._winID,
- this._globalPCListId, false);
- let request = this._win.CreateOfferRequest._create(this._win, chrome);
- Services.obs.notifyObservers(request, "PeerConnection:request", null);
- });
- },
- setLocalDescription: function(desc, onSuccess, onError) {
- return this._legacyCatchAndCloseGuard(onSuccess, onError, () => {
- this._localType = desc.type;
- let type;
- switch (desc.type) {
- case "offer":
- type = Ci.IPeerConnection.kActionOffer;
- break;
- case "answer":
- type = Ci.IPeerConnection.kActionAnswer;
- break;
- case "pranswer":
- throw new this._win.DOMException("pranswer not yet implemented",
- "NotSupportedError");
- case "rollback":
- type = Ci.IPeerConnection.kActionRollback;
- break;
- default:
- throw new this._win.DOMException(
- "Invalid type " + desc.type + " provided to setLocalDescription",
- "InvalidParameterError");
- }
- if (desc.type !== "rollback" && !desc.sdp) {
- throw new this._win.DOMException(
- "Empty or null SDP provided to setLocalDescription",
- "InvalidParameterError");
- }
- return this._chain(() => this.getPermission()
- .then(() => new this._win.Promise((resolve, reject) => {
- this._onSetLocalDescriptionSuccess = resolve;
- this._onSetLocalDescriptionFailure = reject;
- this._impl.setLocalDescription(type, desc.sdp);
- })));
- });
- },
- _validateIdentity: function(sdp, origin) {
- let expectedIdentity;
- // Only run a single identity verification at a time. We have to do this to
- // avoid problems with the fact that identity validation doesn't block the
- // resolution of setRemoteDescription().
- let validation = this._lastIdentityValidation
- .then(() => this._remoteIdp.verifyIdentityFromSDP(sdp, origin))
- .then(msg => {
- expectedIdentity = this._impl.peerIdentity;
- // If this pc has an identity already, then the identity in sdp must match
- if (expectedIdentity && (!msg || msg.identity !== expectedIdentity)) {
- this.close();
- throw new this._win.DOMException(
- "Peer Identity mismatch, expected: " + expectedIdentity,
- "IncompatibleSessionDescriptionError");
- }
- if (msg) {
- // Set new identity and generate an event.
- this._impl.peerIdentity = msg.identity;
- this._resolvePeerIdentity(Cu.cloneInto({
- idp: this._remoteIdp.provider,
- name: msg.identity
- }, this._win));
- }
- })
- .catch(e => {
- this._rejectPeerIdentity(e);
- // If we don't expect a specific peer identity, failure to get a valid
- // peer identity is not a terminal state, so replace the promise to
- // allow another attempt.
- if (!this._impl.peerIdentity) {
- this._peerIdentity = new this._win.Promise((resolve, reject) => {
- this._resolvePeerIdentity = resolve;
- this._rejectPeerIdentity = reject;
- });
- }
- throw e;
- });
- this._lastIdentityValidation = validation.catch(() => {});
- // Only wait for IdP validation if we need identity matching
- return expectedIdentity ? validation : this._win.Promise.resolve();
- },
- setRemoteDescription: function(desc, onSuccess, onError) {
- return this._legacyCatchAndCloseGuard(onSuccess, onError, () => {
- this._remoteType = desc.type;
- let type;
- switch (desc.type) {
- case "offer":
- type = Ci.IPeerConnection.kActionOffer;
- break;
- case "answer":
- type = Ci.IPeerConnection.kActionAnswer;
- break;
- case "pranswer":
- throw new this._win.DOMException("pranswer not yet implemented",
- "NotSupportedError");
- case "rollback":
- type = Ci.IPeerConnection.kActionRollback;
- break;
- default:
- throw new this._win.DOMException(
- "Invalid type " + desc.type + " provided to setRemoteDescription",
- "InvalidParameterError");
- }
- if (!desc.sdp && desc.type !== "rollback") {
- throw new this._win.DOMException(
- "Empty or null SDP provided to setRemoteDescription",
- "InvalidParameterError");
- }
- // Get caller's origin before hitting the promise chain
- let origin = Cu.getWebIDLCallerPrincipal().origin;
- return this._chain(() => {
- let setRem = this.getPermission()
- .then(() => new this._win.Promise((resolve, reject) => {
- this._onSetRemoteDescriptionSuccess = resolve;
- this._onSetRemoteDescriptionFailure = reject;
- this._impl.setRemoteDescription(type, desc.sdp);
- })).then(() => { this._updateCanTrickle(); });
- if (desc.type === "rollback") {
- return setRem;
- }
- // Do setRemoteDescription and identity validation in parallel
- let validId = this._validateIdentity(desc.sdp, origin);
- return this._win.Promise.all([setRem, validId])
- .then(() => {}); // must return undefined
- });
- });
- },
- setIdentityProvider: function(provider, protocol, username) {
- this._checkClosed();
- this._localIdp.setIdentityProvider(provider, protocol, username);
- },
- getIdentityAssertion: function() {
- let origin = Cu.getWebIDLCallerPrincipal().origin;
- return this._chain(
- () => this._certificateReady.then(
- () => this._localIdp.getIdentityAssertion(this._impl.fingerprint, origin)
- )
- );
- },
- get canTrickleIceCandidates() {
- return this._canTrickle;
- },
- _updateCanTrickle: function() {
- let containsTrickle = section => {
- let lines = section.toLowerCase().split(/(?:\r\n?|\n)/);
- return lines.some(line => {
- let prefix = "a=ice-options:";
- if (line.substring(0, prefix.length) !== prefix) {
- return false;
- }
- let tokens = line.substring(prefix.length).split(" ");
- return tokens.some(x => x === "trickle");
- });
- };
- let desc = null;
- try {
- // The getter for remoteDescription can throw if the pc is closed.
- desc = this.remoteDescription;
- } catch (e) {}
- if (!desc) {
- this._canTrickle = null;
- return;
- }
- let sections = desc.sdp.split(/(?:\r\n?|\n)m=/);
- let topSection = sections.shift();
- this._canTrickle =
- containsTrickle(topSection) || sections.every(containsTrickle);
- },
- addIceCandidate: function(c, onSuccess, onError) {
- return this._legacyCatchAndCloseGuard(onSuccess, onError, () => {
- if (!c.candidate && !c.sdpMLineIndex) {
- throw new this._win.DOMException("Invalid candidate passed to addIceCandidate!",
- "InvalidParameterError");
- }
- return this._chain(() => new this._win.Promise((resolve, reject) => {
- this._onAddIceCandidateSuccess = resolve;
- this._onAddIceCandidateError = reject;
- this._impl.addIceCandidate(c.candidate, c.sdpMid || "", c.sdpMLineIndex);
- }));
- });
- },
- addStream: function(stream) {
- stream.getTracks().forEach(track => this.addTrack(track, stream));
- },
- getStreamById: function(id) {
- throw new this._win.DOMException("getStreamById not yet implemented",
- "NotSupportedError");
- },
- addTrack: function(track, stream) {
- if (stream.currentTime === undefined) {
- throw new this._win.DOMException("invalid stream.", "InvalidParameterError");
- }
- this._checkClosed();
- this._senders.forEach(sender => {
- if (sender.track == track) {
- throw new this._win.DOMException("already added.",
- "InvalidParameterError");
- }
- });
- this._impl.addTrack(track, stream);
- let sender = this._win.RTCRtpSender._create(this._win,
- new RTCRtpSender(this, track,
- stream));
- this._senders.push(sender);
- return sender;
- },
- removeTrack: function(sender) {
- this._checkClosed();
- var i = this._senders.indexOf(sender);
- if (i >= 0) {
- this._senders.splice(i, 1);
- this._impl.removeTrack(sender.track); // fires negotiation needed
- }
- },
- _insertDTMF: function(sender, tones, duration, interToneGap) {
- return this._impl.insertDTMF(sender.__DOM_IMPL__, tones, duration, interToneGap);
- },
- _getDTMFToneBuffer: function(sender) {
- return this._impl.getDTMFToneBuffer(sender.__DOM_IMPL__);
- },
- _replaceTrack: function(sender, withTrack) {
- // TODO: Do a (sender._stream.getTracks().indexOf(track) < 0) check
- // on both track args someday.
- //
- // The proposed API will be that both tracks must already be in the same
- // stream. However, since our MediaStreams currently are limited to one
- // track per type, we allow replacement with an outside track not already
- // in the same stream.
- //
- // Since a track may be replaced more than once, the track being replaced
- // may not be in the stream either, so we check neither arg right now.
- return new this._win.Promise((resolve, reject) => {
- this._onReplaceTrackSender = sender;
- this._onReplaceTrackWithTrack = withTrack;
- this._onReplaceTrackSuccess = resolve;
- this._onReplaceTrackFailure = reject;
- this._impl.replaceTrack(sender.track, withTrack);
- });
- },
- _setParameters: function(sender, parameters) {
- if (!Services.prefs.getBoolPref("media.peerconnection.simulcast")) {
- return;
- }
- // validate parameters input
- var encodings = parameters.encodings || [];
- encodings.reduce((uniqueRids, encoding) => {
- if (encoding.scaleResolutionDownBy < 1.0) {
- throw new this._win.RangeError("scaleResolutionDownBy must be >= 1.0");
- }
- if (!encoding.rid && encodings.length > 1) {
- throw new this._win.DOMException("Missing rid", "TypeError");
- }
- if (uniqueRids[encoding.rid]) {
- throw new this._win.DOMException("Duplicate rid", "TypeError");
- }
- uniqueRids[encoding.rid] = true;
- return uniqueRids;
- }, {});
- this._impl.setParameters(sender.track, parameters);
- },
- _getParameters: function(sender) {
- if (!Services.prefs.getBoolPref("media.peerconnection.simulcast")) {
- return;
- }
- return this._impl.getParameters(sender.track);
- },
- close: function() {
- if (this._closed) {
- return;
- }
- this._closed = true;
- this._inClose = true;
- this.changeIceConnectionState("closed");
- this._localIdp.close();
- this._remoteIdp.close();
- this._impl.close();
- this._inClose = false;
- },
- getLocalStreams: function() {
- this._checkClosed();
- return this._impl.getLocalStreams();
- },
- getRemoteStreams: function() {
- this._checkClosed();
- return this._impl.getRemoteStreams();
- },
- getSenders: function() {
- return this._senders;
- },
- getReceivers: function() {
- return this._receivers;
- },
- mozSelectSsrc: function(receiver, ssrcIndex) {
- this._impl.selectSsrc(receiver.track, ssrcIndex);
- },
- get localDescription() {
- this._checkClosed();
- let sdp = this._impl.localDescription;
- if (sdp.length == 0) {
- return null;
- }
- return new this._win.RTCSessionDescription({ type: this._localType,
- sdp: sdp });
- },
- get remoteDescription() {
- this._checkClosed();
- let sdp = this._impl.remoteDescription;
- if (sdp.length == 0) {
- return null;
- }
- return new this._win.RTCSessionDescription({ type: this._remoteType,
- sdp: sdp });
- },
- get peerIdentity() { return this._peerIdentity; },
- get idpLoginUrl() { return this._localIdp.idpLoginUrl; },
- get id() { return this._impl.id; },
- set id(s) { this._impl.id = s; },
- get iceGatheringState() { return this._iceGatheringState; },
- get iceConnectionState() { return this._iceConnectionState; },
- get signalingState() {
- // checking for our local pc closed indication
- // before invoking the pc methods.
- if (this._closed) {
- return "closed";
- }
- return {
- "SignalingInvalid": "",
- "SignalingStable": "stable",
- "SignalingHaveLocalOffer": "have-local-offer",
- "SignalingHaveRemoteOffer": "have-remote-offer",
- "SignalingHaveLocalPranswer": "have-local-pranswer",
- "SignalingHaveRemotePranswer": "have-remote-pranswer",
- "SignalingClosed": "closed"
- }[this._impl.signalingState];
- },
- changeIceGatheringState: function(state) {
- this._iceGatheringState = state;
- _globalPCList.notifyLifecycleObservers(this, "icegatheringstatechange");
- },
- changeIceConnectionState: function(state) {
- this._iceConnectionState = state;
- _globalPCList.notifyLifecycleObservers(this, "iceconnectionstatechange");
- this.dispatchEvent(new this._win.Event("iceconnectionstatechange"));
- },
- getStats: function(selector, onSuccess, onError) {
- return this._legacyCatchAndCloseGuard(onSuccess, onError, () => {
- return this._chain(() => new this._win.Promise((resolve, reject) => {
- this._onGetStatsSuccess = resolve;
- this._onGetStatsFailure = reject;
- this._impl.getStats(selector);
- }));
- });
- },
- createDataChannel: function(label, dict) {
- this._checkClosed();
- if (dict == undefined) {
- dict = {};
- }
- if (dict.maxRetransmitNum != undefined) {
- dict.maxRetransmits = dict.maxRetransmitNum;
- this.logWarning("Deprecated RTCDataChannelInit dictionary entry maxRetransmitNum used!");
- }
- if (dict.outOfOrderAllowed != undefined) {
- dict.ordered = !dict.outOfOrderAllowed; // the meaning is swapped with
- // the name change
- this.logWarning("Deprecated RTCDataChannelInit dictionary entry outOfOrderAllowed used!");
- }
- if (dict.preset != undefined) {
- dict.negotiated = dict.preset;
- this.logWarning("Deprecated RTCDataChannelInit dictionary entry preset used!");
- }
- if (dict.stream != undefined) {
- dict.id = dict.stream;
- this.logWarning("Deprecated RTCDataChannelInit dictionary entry stream used!");
- }
- if (dict.maxRetransmitTime !== null && dict.maxRetransmits !== null) {
- throw new this._win.DOMException(
- "Both maxRetransmitTime and maxRetransmits cannot be provided",
- "InvalidParameterError");
- }
- let protocol;
- if (dict.protocol == undefined) {
- protocol = "";
- } else {
- protocol = dict.protocol;
- }
- // Must determine the type where we still know if entries are undefined.
- let type;
- if (dict.maxRetransmitTime != undefined) {
- type = Ci.IPeerConnection.kDataChannelPartialReliableTimed;
- } else if (dict.maxRetransmits != undefined) {
- type = Ci.IPeerConnection.kDataChannelPartialReliableRexmit;
- } else {
- type = Ci.IPeerConnection.kDataChannelReliable;
- }
- // Synchronous since it doesn't block.
- let channel = this._impl.createDataChannel(
- label, protocol, type, !dict.ordered, dict.maxRetransmitTime,
- dict.maxRetransmits, dict.negotiated ? true : false,
- dict.id != undefined ? dict.id : 0xFFFF
- );
- return channel;
- }
- };
- // This is a separate object because we don't want to expose it to DOM.
- function PeerConnectionObserver() {
- this._dompc = null;
- }
- PeerConnectionObserver.prototype = {
- classDescription: "PeerConnectionObserver",
- classID: PC_OBS_CID,
- contractID: PC_OBS_CONTRACT,
- QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
- Ci.nsIDOMGlobalPropertyInitializer]),
- init: function(win) { this._win = win; },
- __init: function(dompc) {
- this._dompc = dompc._innerObject;
- },
- newError: function(message, code) {
- // These strings must match those defined in the WebRTC spec.
- const reasonName = [
- "",
- "InternalError",
- "InvalidCandidateError",
- "InvalidParameterError",
- "InvalidStateError",
- "InvalidSessionDescriptionError",
- "IncompatibleSessionDescriptionError",
- "InternalError",
- "IncompatibleMediaStreamTrackError",
- "InternalError"
- ];
- let name = reasonName[Math.min(code, reasonName.length - 1)];
- return new this._dompc._win.DOMException(message, name);
- },
- dispatchEvent: function(event) {
- this._dompc.dispatchEvent(event);
- },
- onCreateOfferSuccess: function(sdp) {
- this._dompc._onCreateOfferSuccess(sdp);
- },
- onCreateOfferError: function(code, message) {
- this._dompc._onCreateOfferFailure(this.newError(message, code));
- },
- onCreateAnswerSuccess: function(sdp) {
- this._dompc._onCreateAnswerSuccess(sdp);
- },
- onCreateAnswerError: function(code, message) {
- this._dompc._onCreateAnswerFailure(this.newError(message, code));
- },
- onSetLocalDescriptionSuccess: function() {
- this._dompc._onSetLocalDescriptionSuccess();
- },
- onSetRemoteDescriptionSuccess: function() {
- this._dompc._onSetRemoteDescriptionSuccess();
- },
- onSetLocalDescriptionError: function(code, message) {
- this._localType = null;
- this._dompc._onSetLocalDescriptionFailure(this.newError(message, code));
- },
- onSetRemoteDescriptionError: function(code, message) {
- this._remoteType = null;
- this._dompc._onSetRemoteDescriptionFailure(this.newError(message, code));
- },
- onAddIceCandidateSuccess: function() {
- this._dompc._onAddIceCandidateSuccess();
- },
- onAddIceCandidateError: function(code, message) {
- this._dompc._onAddIceCandidateError(this.newError(message, code));
- },
- onIceCandidate: function(level, mid, candidate) {
- if (candidate == "") {
- this.foundIceCandidate(null);
- } else {
- this.foundIceCandidate(new this._dompc._win.RTCIceCandidate(
- {
- candidate: candidate,
- sdpMid: mid,
- sdpMLineIndex: level
- }
- ));
- }
- },
- onNegotiationNeeded: function() {
- this.dispatchEvent(new this._win.Event("negotiationneeded"));
- },
- // This method is primarily responsible for updating iceConnectionState.
- // This state is defined in the WebRTC specification as follows:
- //
- // iceConnectionState:
- // -------------------
- // new The ICE Agent is gathering addresses and/or waiting for
- // remote candidates to be supplied.
- //
- // checking The ICE Agent has received remote candidates on at least
- // one component, and is checking candidate pairs but has not
- // yet found a connection. In addition to checking, it may
- // also still be gathering.
- //
- // connected The ICE Agent has found a usable connection for all
- // components but is still checking other candidate pairs to
- // see if there is a better connection. It may also still be
- // gathering.
- //
- // completed The ICE Agent has finished gathering and checking and found
- // a connection for all components. Open issue: it is not
- // clear how the non controlling ICE side knows it is in the
- // state.
- //
- // failed The ICE Agent is finished checking all candidate pairs and
- // failed to find a connection for at least one component.
- // Connections may have been found for some components.
- //
- // disconnected Liveness checks have failed for one or more components.
- // This is more aggressive than failed, and may trigger
- // intermittently (and resolve itself without action) on a
- // flaky network.
- //
- // closed The ICE Agent has shut down and is no longer responding to
- // STUN requests.
- handleIceConnectionStateChange: function(iceConnectionState) {
- let pc = this._dompc;
- if (pc.iceConnectionState === 'new') {
- var checking_histogram = Services.telemetry.getHistogramById("WEBRTC_ICE_CHECKING_RATE");
- if (iceConnectionState === 'checking') {
- checking_histogram.add(true);
- } else if (iceConnectionState === 'failed') {
- checking_histogram.add(false);
- }
- } else if (pc.iceConnectionState === 'checking') {
- var success_histogram = Services.telemetry.getHistogramById("WEBRTC_ICE_SUCCESS_RATE");
- if (iceConnectionState === 'completed' ||
- iceConnectionState === 'connected') {
- success_histogram.add(true);
- } else if (iceConnectionState === 'failed') {
- success_histogram.add(false);
- }
- }
- if (iceConnectionState === 'failed') {
- pc.logError("ICE failed, see about:webrtc for more details");
- }
- pc.changeIceConnectionState(iceConnectionState);
- },
- // This method is responsible for updating iceGatheringState. This
- // state is defined in the WebRTC specification as follows:
- //
- // iceGatheringState:
- // ------------------
- // new The object was just created, and no networking has occurred
- // yet.
- //
- // gathering The ICE engine is in the process of gathering candidates for
- // this RTCPeerConnection.
- //
- // complete The ICE engine has completed gathering. Events such as adding
- // a new interface or a new TURN server will cause the state to
- // go back to gathering.
- //
- handleIceGatheringStateChange: function(gatheringState) {
- this._dompc.changeIceGatheringState(gatheringState);
- },
- onStateChange: function(state) {
- switch (state) {
- case "SignalingState":
- this.dispatchEvent(new this._win.Event("signalingstatechange"));
- break;
- case "IceConnectionState":
- let connState = this._dompc._pc.iceConnectionState;
- this._dompc._queueTaskWithClosedCheck(() => {
- this.handleIceConnectionStateChange(connState);
- });
- break;
- case "IceGatheringState":
- this.handleIceGatheringStateChange(this._dompc._pc.iceGatheringState);
- break;
- case "SdpState":
- // No-op
- break;
- case "ReadyState":
- // No-op
- break;
- case "SipccState":
- // No-op
- break;
- default:
- this._dompc.logWarning("Unhandled state type: " + state);
- break;
- }
- },
- onGetStatsSuccess: function(dict) {
- let pc = this._dompc;
- let chromeobj = new RTCStatsReport(pc._win, dict);
- let webidlobj = pc._win.RTCStatsReport._create(pc._win, chromeobj);
- chromeobj.makeStatsPublic(pc._warnDeprecatedStatsAccessNullable);
- pc._onGetStatsSuccess(webidlobj);
- },
- onGetStatsError: function(code, message) {
- this._dompc._onGetStatsFailure(this.newError(message, code));
- },
- onAddStream: function(stream) {
- let ev = new this._dompc._win.MediaStreamEvent("addstream",
- { stream: stream });
- this.dispatchEvent(ev);
- },
- onRemoveStream: function(stream) {
- this.dispatchEvent(new this._dompc._win.MediaStreamEvent("removestream",
- { stream: stream }));
- },
- onAddTrack: function(track, streams) {
- let pc = this._dompc;
- let receiver = pc._win.RTCRtpReceiver._create(pc._win,
- new RTCRtpReceiver(this,
- track));
- pc._receivers.push(receiver);
- let ev = new pc._win.RTCTrackEvent("track",
- { receiver: receiver,
- track: track,
- streams: streams });
- this.dispatchEvent(ev);
- // Fire legacy event as well for a little bit.
- ev = new pc._win.MediaStreamTrackEvent("addtrack", { track: track });
- this.dispatchEvent(ev);
- },
- onRemoveTrack: function(track) {
- let pc = this._dompc;
- let i = pc._receivers.findIndex(receiver => receiver.track == track);
- if (i >= 0) {
- pc._receivers.splice(i, 1);
- }
- },
- onReplaceTrackSuccess: function() {
- var pc = this._dompc;
- pc._onReplaceTrackSender.track = pc._onReplaceTrackWithTrack;
- pc._onReplaceTrackWithTrack = null;
- pc._onReplaceTrackSender = null;
- pc._onReplaceTrackSuccess();
- },
- onReplaceTrackError: function(code, message) {
- var pc = this._dompc;
- pc._onReplaceTrackWithTrack = null;
- pc._onReplaceTrackSender = null;
- pc._onReplaceTrackFailure(this.newError(message, code));
- },
- foundIceCandidate: function(cand) {
- this.dispatchEvent(new this._dompc._win.RTCPeerConnectionIceEvent("icecandidate",
- { candidate: cand } ));
- },
- notifyDataChannel: function(channel) {
- this.dispatchEvent(new this._dompc._win.RTCDataChannelEvent("datachannel",
- { channel: channel }));
- },
- onDTMFToneChange: function(trackId, tone) {
- var pc = this._dompc;
- var sender = pc._senders.find(sender => sender.track.id == trackId)
- sender.dtmf.dispatchEvent(new pc._win.RTCDTMFToneChangeEvent("tonechange",
- { tone: tone }));
- }
- };
- function RTCPeerConnectionStatic() {
- }
- RTCPeerConnectionStatic.prototype = {
- classDescription: "RTCPeerConnectionStatic",
- QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
- Ci.nsIDOMGlobalPropertyInitializer]),
- classID: PC_STATIC_CID,
- contractID: PC_STATIC_CONTRACT,
- init: function(win) {
- this._winID = win.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
- },
- registerPeerConnectionLifecycleCallback: function(cb) {
- _globalPCList._registerPeerConnectionLifecycleCallback(this._winID, cb);
- },
- };
- function RTCDTMFSender(sender) {
- this._sender = sender;
- }
- RTCDTMFSender.prototype = {
- classDescription: "RTCDTMFSender",
- classID: PC_DTMF_SENDER_CID,
- contractID: PC_DTMF_SENDER_CONTRACT,
- QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
- get toneBuffer() {
- return this._sender._pc._getDTMFToneBuffer(this._sender);
- },
- get ontonechange() {
- return this.__DOM_IMPL__.getEventHandler("ontonechange");
- },
- set ontonechange(handler) {
- this.__DOM_IMPL__.setEventHandler("ontonechange", handler);
- },
- insertDTMF: function(tones, duration, interToneGap) {
- this._sender._pc._checkClosed();
- if (this._sender._pc._senders.indexOf(this._sender.__DOM_IMPL__) == -1) {
- throw new this._sender._pc._win.DOMException("RTCRtpSender is stopped",
- "InvalidStateError");
- }
- duration = Math.max(40, Math.min(duration, 6000));
- if (interToneGap < 30) interToneGap = 30;
- tones = tones.toUpperCase();
- if (tones.match(/[^0-9A-D#*,]/)) {
- throw new this._sender._pc._win.DOMException("Invalid DTMF characters",
- "InvalidCharacterError");
- }
- this._sender._pc._insertDTMF(this._sender, tones, duration, interToneGap);
- },
- };
- function RTCRtpSender(pc, track, stream) {
- this._pc = pc;
- this.track = track;
- this._stream = stream;
- this.dtmf = pc._win.RTCDTMFSender._create(pc._win, new RTCDTMFSender(this));
- }
- RTCRtpSender.prototype = {
- classDescription: "RTCRtpSender",
- classID: PC_SENDER_CID,
- contractID: PC_SENDER_CONTRACT,
- QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
- replaceTrack: function(withTrack) {
- return this._pc._chain(() => this._pc._replaceTrack(this, withTrack));
- },
- setParameters: function(parameters) {
- return this._pc._win.Promise.resolve()
- .then(() => this._pc._setParameters(this, parameters));
- },
- getParameters: function() {
- return this._pc._getParameters(this);
- }
- };
- function RTCRtpReceiver(pc, track) {
- this._pc = pc;
- this.track = track;
- }
- RTCRtpReceiver.prototype = {
- classDescription: "RTCRtpReceiver",
- classID: PC_RECEIVER_CID,
- contractID: PC_RECEIVER_CONTRACT,
- QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
- };
- function CreateOfferRequest(windowID, innerWindowID, callID, isSecure) {
- this.windowID = windowID;
- this.innerWindowID = innerWindowID;
- this.callID = callID;
- this.isSecure = isSecure;
- }
- CreateOfferRequest.prototype = {
- classDescription: "CreateOfferRequest",
- classID: PC_COREQUEST_CID,
- contractID: PC_COREQUEST_CONTRACT,
- QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
- };
- this.NSGetFactory = XPCOMUtils.generateNSGetFactory(
- [GlobalPCList,
- RTCDTMFSender,
- RTCIceCandidate,
- RTCSessionDescription,
- RTCPeerConnection,
- RTCPeerConnectionStatic,
- RTCRtpReceiver,
- RTCRtpSender,
- RTCStatsReport,
- PeerConnectionObserver,
- CreateOfferRequest]
- );
|