--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -524,51 +524,66 @@ RTCPeerConnection.prototype = {
}
return await func();
})();
// don't propagate errors in the operations chain (this is a fork of p).
this._operationsChain = p.catch(() => {});
return await p;
},
- // This wrapper helps implement legacy callbacks in a manner that produces
+ // These wrappers help 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) {
- let operation = () => {
- this._checkClosed();
- return func();
- };
+ _async: function(func) {
+ return this._win.Promise.resolve(this._closeWrapper(func));
+ },
+
+ _legacy: function(...args) {
+ return this._win.Promise.resolve(this._legacyCloseWrapper(...args));
+ },
+
+ _auto: function(onSucc, onErr, func) {
+ return (typeof onSucc == "function") ? this._legacy(onSucc, onErr, func)
+ : this._async(func);
+ },
- if (!onSuccess) {
- return this._win.Promise.resolve(operation())
- .then(v => (this._closed ? new Promise(() => {}) : v),
- e => (this._closed ? new Promise(() => {}) : Promise.reject(e)));
- }
+ _closeWrapper: async function(func) {
+ let closed = this._closed;
try {
- return this._win.Promise.resolve(operation())
- .then(this._wrapLegacyCallback(onSuccess),
- this._wrapLegacyCallback(onError));
+ let result = await func();
+ if (!closed && this._closed) {
+ await new Promise(() => {});
+ }
+ return result;
} catch (e) {
- this._wrapLegacyCallback(onError)(e);
- return this._win.Promise.resolve(); // avoid webidl TypeError
+ if (!closed && this._closed) {
+ await new Promise(() => {});
+ }
+ throw e;
}
},
- _wrapLegacyCallback: function(func) {
- return result => {
+ _legacyCloseWrapper: async function(onSucc, onErr, func) {
+
+ let wrapCallback = cb => result => {
try {
- func && func(result);
+ cb && cb(result);
} catch (e) {
this.logErrorAndCallOnError(e);
}
};
+
+ try {
+ wrapCallback(onSucc)(await func());
+ } catch (e) {
+ wrapCallback(onErr)(e);
+ }
},
/**
* 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"],
@@ -720,86 +735,86 @@ RTCPeerConnection.prototype = {
get:function() { return this.getEH(name); },
set:function(h) {
this.logWarning(name + " is deprecated! " + msg);
return this.setEH(name, h);
}
});
},
- createOffer: function(optionsOrOnSuccess, onError, options) {
+ createOffer: function(optionsOrOnSucc, onErr, 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;
+ if (typeof optionsOrOnSucc == "function") {
+ return this._legacy(optionsOrOnSucc, onErr, () => this._createOffer(options));
}
- return this._legacyCatchAndCloseGuard(onSuccess, onError, async () => {
- let origin = Cu.getWebIDLCallerPrincipal().origin;
- return await this._chain(async () => {
- let haveAssertion;
- if (this._localIdp.enabled) {
- haveAssertion = this._getIdentityAssertion(origin);
- }
- await this._getPermission();
- await this._certificateReady;
- let sdp = await new Promise((resolve, reject) => {
- this._onCreateOfferSuccess = resolve;
- this._onCreateOfferFailure = reject;
- this._impl.createOffer(options);
- });
- if (haveAssertion) {
- await haveAssertion;
- sdp = this._localIdp.addIdentityAttribute(sdp);
- }
- return Cu.cloneInto({ type: "offer", sdp }, this._win);
+ return this._async(() => this._createOffer(optionsOrOnSucc));
+ },
+
+ _createOffer: async function(options) {
+ this._checkClosed();
+ let origin = Cu.getWebIDLCallerPrincipal().origin;
+ return await this._chain(async () => {
+ let haveAssertion;
+ if (this._localIdp.enabled) {
+ haveAssertion = this._getIdentityAssertion(origin);
+ }
+ await this._getPermission();
+ await this._certificateReady;
+ let sdp = await new Promise((resolve, reject) => {
+ this._onCreateOfferSuccess = resolve;
+ this._onCreateOfferFailure = reject;
+ this._impl.createOffer(options);
});
+ if (haveAssertion) {
+ await haveAssertion;
+ sdp = this._localIdp.addIdentityAttribute(sdp);
+ }
+ return Cu.cloneInto({ type: "offer", sdp }, this._win);
});
},
- createAnswer: function(optionsOrOnSuccess, onError) {
+ createAnswer: function(optionsOrOnSucc, onErr) {
// 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;
+ if (typeof optionsOrOnSucc == "function") {
+ return this._legacy(optionsOrOnSucc, onErr, () => this._createAnswer({}));
}
- return this._legacyCatchAndCloseGuard(onSuccess, onError, async () => {
- let origin = Cu.getWebIDLCallerPrincipal().origin;
- return await this._chain(async () => {
- // 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");
- }
- let haveAssertion;
- if (this._localIdp.enabled) {
- haveAssertion = this._getIdentityAssertion(origin);
- }
- await this._getPermission();
- await this._certificateReady;
- let sdp = await new Promise((resolve, reject) => {
- this._onCreateAnswerSuccess = resolve;
- this._onCreateAnswerFailure = reject;
- this._impl.createAnswer();
- });
- if (haveAssertion) {
- await haveAssertion;
- sdp = this._localIdp.addIdentityAttribute(sdp);
- }
- return Cu.cloneInto({ type: "answer", sdp }, this._win);
+ return this._async(() => this._createAnswer(optionsOrOnSucc));
+ },
+
+ _createAnswer: async function(options) {
+ this._checkClosed();
+ let origin = Cu.getWebIDLCallerPrincipal().origin;
+ return await this._chain(async () => {
+ // 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");
+ }
+ let haveAssertion;
+ if (this._localIdp.enabled) {
+ haveAssertion = this._getIdentityAssertion(origin);
+ }
+ await this._getPermission();
+ await this._certificateReady;
+ let sdp = await new Promise((resolve, reject) => {
+ this._onCreateAnswerSuccess = resolve;
+ this._onCreateAnswerFailure = reject;
+ this._impl.createAnswer();
});
+ if (haveAssertion) {
+ await haveAssertion;
+ sdp = this._localIdp.addIdentityAttribute(sdp);
+ }
+ return Cu.cloneInto({ type: "answer", sdp }, this._win);
});
},
_getPermission: async function() {
if (!this._havePermission) {
let privileged = this._isChrome || AppConstants.MOZ_B2G ||
Services.prefs.getBoolPref("media.navigator.permission.disabled");
@@ -824,44 +839,48 @@ RTCPeerConnection.prototype = {
_actions: {
offer: Ci.IPeerConnection.kActionOffer,
answer: Ci.IPeerConnection.kActionAnswer,
pranswer: Ci.IPeerConnection.kActionPRAnswer,
rollback: Ci.IPeerConnection.kActionRollback,
answer: Ci.IPeerConnection.kActionAnswer,
},
- setLocalDescription: function({ type, sdp }, onSuccess, onError) {
- return this._legacyCatchAndCloseGuard(onSuccess, onError, async () => {
- this._localType = type;
+ setLocalDescription: function(desc, onSucc, onErr) {
+ return this._auto(onSucc, onErr, () => this._setLocalDescription(desc));
+ },
+
+ _setLocalDescription: async function({ type, sdp }) {
+ this._checkClosed();
+
+ this._localType = type;
- let action = this._actions[type];
- if (action === undefined) {
- throw new this._win.DOMException(
- "Invalid type " + type + " provided to setLocalDescription",
- "InvalidParameterError");
- }
- if (action == Ci.IPeerConnection.kActionPRAnswer) {
- throw new this._win.DOMException("pranswer not yet implemented",
- "NotSupportedError");
- }
+ let action = this._actions[type];
+ if (action === undefined) {
+ throw new this._win.DOMException(
+ "Invalid type " + type + " provided to setLocalDescription",
+ "InvalidParameterError");
+ }
+ if (action == Ci.IPeerConnection.kActionPRAnswer) {
+ throw new this._win.DOMException("pranswer not yet implemented",
+ "NotSupportedError");
+ }
- if (!sdp && action != Ci.IPeerConnection.kActionRollback) {
- throw new this._win.DOMException(
- "Empty or null SDP provided to setLocalDescription",
- "InvalidParameterError");
- }
+ if (!sdp && action != Ci.IPeerConnection.kActionRollback) {
+ throw new this._win.DOMException(
+ "Empty or null SDP provided to setLocalDescription",
+ "InvalidParameterError");
+ }
- return await this._chain(async () => {
- await this._getPermission();
- await new Promise((resolve, reject) => {
- this._onSetLocalDescriptionSuccess = resolve;
- this._onSetLocalDescriptionFailure = reject;
- this._impl.setLocalDescription(action, sdp);
- });
+ return await this._chain(async () => {
+ await this._getPermission();
+ await new Promise((resolve, reject) => {
+ this._onSetLocalDescriptionSuccess = resolve;
+ this._onSetLocalDescriptionFailure = reject;
+ this._impl.setLocalDescription(action, sdp);
});
});
},
_validateIdentity: async function(sdp, origin) {
let expectedIdentity;
// Only run a single identity verification at a time. We have to do this to
@@ -901,57 +920,60 @@ RTCPeerConnection.prototype = {
this._lastIdentityValidation = p.catch(() => {});
// Only wait for IdP validation if we need identity matching
if (expectedIdentity) {
await p;
}
},
- setRemoteDescription: function({ type, sdp }, onSuccess, onError) {
- return this._legacyCatchAndCloseGuard(onSuccess, onError, async () => {
- this._remoteType = type;
+ setRemoteDescription: function(desc, onSucc, onErr) {
+ return this._auto(onSucc, onErr, () => this._setRemoteDescription(desc));
+ },
+
+ _setRemoteDescription: async function({ type, sdp }) {
+ this._checkClosed();
+ this._remoteType = type;
- let action = this._actions[type];
- if (action === undefined) {
- throw new this._win.DOMException(
- "Invalid type " + type + " provided to setRemoteDescription",
- "InvalidParameterError");
- }
- if (action == Ci.IPeerConnection.kActionPRAnswer) {
- throw new this._win.DOMException("pranswer not yet implemented",
- "NotSupportedError");
- }
-
- if (!sdp && action != Ci.IPeerConnection.kActionRollback) {
- throw new this._win.DOMException(
- "Empty or null SDP provided to setRemoteDescription",
- "InvalidParameterError");
- }
+ let action = this._actions[type];
+ if (action === undefined) {
+ throw new this._win.DOMException(
+ "Invalid type " + type + " provided to setRemoteDescription",
+ "InvalidParameterError");
+ }
+ if (action == Ci.IPeerConnection.kActionPRAnswer) {
+ throw new this._win.DOMException("pranswer not yet implemented",
+ "NotSupportedError");
+ }
- // Get caller's origin before hitting the promise chain
- let origin = Cu.getWebIDLCallerPrincipal().origin;
+ if (!sdp && 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 await this._chain(async () => {
- let haveSetRemote = (async () => {
- await this._getPermission();
- await new Promise((resolve, reject) => {
- this._onSetRemoteDescriptionSuccess = resolve;
- this._onSetRemoteDescriptionFailure = reject;
- this._impl.setRemoteDescription(action, sdp);
- });
- this._updateCanTrickle();
- })();
+ return await this._chain(async () => {
+ let haveSetRemote = (async () => {
+ await this._getPermission();
+ await new Promise((resolve, reject) => {
+ this._onSetRemoteDescriptionSuccess = resolve;
+ this._onSetRemoteDescriptionFailure = reject;
+ this._impl.setRemoteDescription(action, sdp);
+ });
+ this._updateCanTrickle();
+ })();
- if (action != Ci.IPeerConnection.kActionRollback) {
- // Do setRemoteDescription and identity validation in parallel
- await this._validateIdentity(sdp, origin);
- }
- await haveSetRemote;
- });
+ if (action != Ci.IPeerConnection.kActionRollback) {
+ // Do setRemoteDescription and identity validation in parallel
+ await this._validateIdentity(sdp, origin);
+ }
+ await haveSetRemote;
});
},
setIdentityProvider: function(provider, protocol, username) {
this._checkClosed();
this._localIdp.setIdentityProvider(provider, protocol, username);
},
@@ -996,31 +1018,32 @@ RTCPeerConnection.prototype = {
let sections = desc.sdp.split(/(?:\r\n?|\n)m=/);
let topSection = sections.shift();
this._canTrickle =
containsTrickle(topSection) || sections.every(containsTrickle);
},
// TODO: Implement processing for end-of-candidates (bug 1318167)
- addIceCandidate: function(candidate, onSuccess, onError) {
- let add = async ({ candidate, sdpMid, sdpMLineIndex }) => {
- if (sdpMid === null && sdpMLineIndex === null) {
- throw new this._win.DOMException(
- "Invalid candidate (both sdpMid and sdpMLineIndex are null).",
- "TypeError");
- }
- return await this._chain(() => new Promise((resolve, reject) => {
- this._onAddIceCandidateSuccess = resolve;
- this._onAddIceCandidateError = reject;
- this._impl.addIceCandidate(candidate, sdpMid || "", sdpMLineIndex);
- }));
- };
- return this._legacyCatchAndCloseGuard(onSuccess, onError,
- () => candidate ? add(candidate) : Promise.resolve());
+ addIceCandidate: function(cand, onSucc, onErr) {
+ return this._auto(onSucc, onErr, () => cand && this._addIceCandidate(cand));
+ },
+
+ _addIceCandidate: async function({ candidate, sdpMid, sdpMLineIndex }) {
+ this._checkClosed();
+ if (sdpMid === null && sdpMLineIndex === null) {
+ throw new this._win.DOMException(
+ "Invalid candidate (both sdpMid and sdpMLineIndex are null).",
+ "TypeError");
+ }
+ return await this._chain(() => new Promise((resolve, reject) => {
+ this._onAddIceCandidateSuccess = resolve;
+ this._onAddIceCandidateError = reject;
+ this._impl.addIceCandidate(candidate, sdpMid || "", sdpMLineIndex);
+ }));
},
addStream: function(stream) {
stream.getTracks().forEach(track => this.addTrack(track, stream));
},
addTrack: function(track, stream) {
if (stream.currentTime === undefined) {
@@ -1053,24 +1076,25 @@ RTCPeerConnection.prototype = {
_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) {
- return new this._win.Promise((resolve, reject) => {
+ _replaceTrack: async function(sender, withTrack) {
+ this._checkClosed();
+ return await this._chain(() => new Promise((resolve, reject) => {
this._onReplaceTrackSender = sender;
this._onReplaceTrackWithTrack = withTrack;
this._onReplaceTrackSuccess = resolve;
this._onReplaceTrackFailure = reject;
this._impl.replaceTrack(sender.track, withTrack);
- });
+ }));
},
_setParameters: function({ track }, parameters) {
if (!Services.prefs.getBoolPref("media.peerconnection.simulcast")) {
return;
}
// validate parameters input
var encodings = parameters.encodings || [];
@@ -1183,24 +1207,27 @@ RTCPeerConnection.prototype = {
},
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 Promise((resolve, reject) => {
- this._onGetStatsSuccess = resolve;
- this._onGetStatsFailure = reject;
- this._impl.getStats(selector);
- }));
- });
+ getStats: function(selector, onSucc, onErr) {
+ return this._auto(onSucc, onErr, () => this._getStats(selector));
+ },
+
+ _getStats: async function(selector) {
+ // getStats is allowed even in closed state.
+ return await this._chain(() => new Promise((resolve, reject) => {
+ this._onGetStatsSuccess = resolve;
+ this._onGetStatsFailure = reject;
+ this._impl.getStats(selector);
+ }));
},
createDataChannel: function(label, {
maxRetransmits, ordered, negotiated,
id = 0xFFFF,
maxRetransmitTime,
maxPacketLifeTime = maxRetransmitTime,
protocol,
@@ -1600,18 +1627,17 @@ function RTCRtpSender(pc, track, stream)
}
RTCRtpSender.prototype = {
classDescription: "RTCRtpSender",
classID: PC_SENDER_CID,
contractID: PC_SENDER_CONTRACT,
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
replaceTrack: function(withTrack) {
- this._pc._checkClosed();
- return this._pc._chain(() => this._pc._replaceTrack(this, withTrack));
+ return this._pc._async(() => this._pc._replaceTrack(this, withTrack));
},
setParameters: function(parameters) {
return this._pc._win.Promise.resolve()
.then(() => this._pc._setParameters(this, parameters));
},
getParameters: function() {