Bug 857765: Log errors thrown by user-defined PeerConnection callbacks r=msucan

This commit is contained in:
Jan-Ivar Bruaroey 2013-04-16 19:23:07 -04:00
parent 31c06a0c04
commit 035532f753
3 changed files with 188 additions and 123 deletions

View File

@ -727,14 +727,12 @@ PeerConnection.prototype = {
}
};
// This is a separate object because we don't want to expose it to DOM.
function PeerConnectionObserver(dompc) {
this._dompc = dompc;
function RTCError(code, message) {
this.name = this.reasonName[Math.min(code, this.reasonName.length - 1)];
this.message = (typeof message === "string")? message : this.name;
this.__exposedProps__ = { name: "rw", message: "rw" };
}
PeerConnectionObserver.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.IPeerConnectionObserver,
Ci.nsISupportsWeakReference]),
RTCError.prototype = {
// These strings must match those defined in the WebRTC spec.
reasonName: [
"NO_ERROR", // Should never happen -- only used for testing
@ -747,69 +745,106 @@ PeerConnectionObserver.prototype = {
"INCOMPATIBLE_CONSTRAINTS",
"INCOMPATIBLE_MEDIASTREAMTRACK",
"INTERNAL_ERROR"
],
]
};
callErrorCallback: function(callback, code, message) {
if (code > Ci.IPeerConnection.kMaxErrorType) {
code = Ci.IPeerConnection.kInternalError;
}
if (typeof message !== "string") {
message = this.reasonName[code];
}
// This is a separate object because we don't want to expose it to DOM.
function PeerConnectionObserver(dompc) {
this._dompc = dompc;
}
PeerConnectionObserver.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.IPeerConnectionObserver,
Ci.nsISupportsWeakReference]),
callCB: function(callback, arg) {
if (callback) {
try {
callback.onCallback({
name: this.reasonName[code], message: message,
__exposedProps__: { name: "rw", message: "rw" }
});
} catch(e) {}
callback.onCallback(arg);
} catch(e) {
// A content script (user-provided) callback threw an error. We don't
// want this to take down peerconnection, but we still want the user
// to see it, so we catch it, report it, and move on.
//
// We do stack parsing in two different places for different reasons:
var msg;
if (e.result == Cr.NS_ERROR_XPC_JS_THREW_JS_OBJECT) {
// TODO(jib@mozilla.com): Revisit once bug 862153 is fixed.
//
// The actual content script frame is unavailable due to bug 862153,
// so users see file and line # into this file, which is not helpful.
//
// 1) Fix up the error message itself to differentiate between the
// 22 places we call callCB() in this file, using plain JS stack.
//
// Tweak the existing NS_ERROR_XPC_JS_THREW_JS_OBJECT message:
// -'Error: x' when calling method: [RTCPeerConCallback::onCallback]
// +'Error: x' when calling method: [RTCPeerConCallback::onCreateOfferError]
let caller = Error().stack.split("\n")[1].split("@")[0];
// caller ~= "PeerConnectionObserver.prototype.onCreateOfferError"
msg = e.message.replace("::onCallback", "::" + caller.split(".")[2]);
} else {
msg = e.message;
}
// Log error message to web console and window.onerror, if present.
//
// 2) nsIScriptError doesn't understand the nsIStackFrame format, so
// do the translation by extracting file and line from XPCOM stack:
//
// e.location ~= "JS frame :: file://.js :: RTCPCCb::onCallback :: line 1"
let stack = e.location.toString().split(" :: ");
let file = stack[1];
let line = parseInt(stack[3].split(" ")[1]);
let scriptError = Cc["@mozilla.org/scripterror;1"].
createInstance(Ci.nsIScriptError);
scriptError.initWithWindowID(msg, file, null, line, 0,
Ci.nsIScriptError.exceptionFlag,
"content javascript",
this._dompc._winID);
Cc["@mozilla.org/consoleservice;1"].
getService(Ci.nsIConsoleService).logMessage(scriptError);
// Call onerror directly if present (necessary for testing)
if (typeof this._dompc._win.onerror === "function") {
this._dompc._win.onerror(msg, file, line);
}
}
}
},
onCreateOfferSuccess: function(offer) {
if (this._dompc._onCreateOfferSuccess) {
try {
this._dompc._onCreateOfferSuccess.onCallback({
type: "offer", sdp: offer,
__exposedProps__: { type: "rw", sdp: "rw" }
});
} catch(e) {}
}
this.callCB(this._dompc._onCreateOfferSuccess,
{ type: "offer", sdp: offer,
__exposedProps__: { type: "rw", sdp: "rw" } });
this._dompc._executeNext();
},
onCreateOfferError: function(code, message) {
this.callErrorCallback (this._dompc._onCreateOfferFailure, code, message);
this.callCB(this._dompc._onCreateOfferFailure, new RTCError(code, message));
this._dompc._executeNext();
},
onCreateAnswerSuccess: function(answer) {
if (this._dompc._onCreateAnswerSuccess) {
try {
this._dompc._onCreateAnswerSuccess.onCallback({
type: "answer", sdp: answer,
__exposedProps__: { type: "rw", sdp: "rw" }
});
} catch(e) {}
}
this.callCB (this._dompc._onCreateAnswerSuccess,
{ type: "answer", sdp: answer,
__exposedProps__: { type: "rw", sdp: "rw" } });
this._dompc._executeNext();
},
onCreateAnswerError: function(code, message) {
this.callErrorCallback (this._dompc._onCreateAnswerFailure, code, message);
this.callCB(this._dompc._onCreateAnswerFailure, new RTCError(code, message));
this._dompc._executeNext();
},
onSetLocalDescriptionSuccess: function() {
this._dompc._localType = this._dompc._pendingType;
this._dompc._pendingType = null;
if (this._dompc._onSetLocalDescriptionSuccess) {
try {
this._dompc._onSetLocalDescriptionSuccess.onCallback();
} catch(e) {}
}
this.callCB(this._dompc._onSetLocalDescriptionSuccess);
// Until we support generating trickle ICE candidates,
// we go ahead and trigger a call of onicecandidate here.
@ -825,39 +860,33 @@ PeerConnectionObserver.prototype = {
onSetRemoteDescriptionSuccess: function() {
this._dompc._remoteType = this._dompc._pendingType;
this._dompc._pendingType = null;
if (this._dompc._onSetRemoteDescriptionSuccess) {
try {
this._dompc._onSetRemoteDescriptionSuccess.onCallback();
} catch(e) {}
}
this.callCB(this._dompc._onSetRemoteDescriptionSuccess);
this._dompc._executeNext();
},
onSetLocalDescriptionError: function(code, message) {
this._dompc._pendingType = null;
this.callErrorCallback (this._dompc._onSetLocalDescriptionFailure, code,
message);
this.callCB(this._dompc._onSetLocalDescriptionFailure,
new RTCError(code, message));
this._dompc._executeNext();
},
onSetRemoteDescriptionError: function(code, message) {
this._dompc._pendingType = null;
this.callErrorCallback (this._dompc._onSetRemoteDescriptionFailure, code,
message);
this.callCB(this._dompc._onSetRemoteDescriptionFailure,
new RTCError(code, message));
this._dompc._executeNext();
},
onAddIceCandidateSuccess: function() {
this._dompc._pendingType = null;
if (this._dompc._onAddIceCandidateSuccess) {
this._dompc._onAddIceCandidateSuccess.onCallback();
}
this.callCB(this._dompc._onAddIceCandidateSuccess);
this._dompc._executeNext();
},
onAddIceCandidateError: function(code, message) {
this._dompc._pendingType = null;
this.callErrorCallback (this._dompc._onAddIceCandidateError, code, message);
this.callCB(this._dompc._onAddIceCandidateError, new RTCError(code, message));
this._dompc._executeNext();
},
@ -866,42 +895,24 @@ PeerConnectionObserver.prototype = {
return;
}
let self = this;
let iceCb = function() {};
let iceGatherCb = function() {};
if (this._dompc.onicechange) {
iceCb = function(args) {
try {
self._dompc.onicechange(args);
} catch(e) {}
};
}
if (this._dompc.ongatheringchange) {
iceGatherCb = function(args) {
try {
self._dompc.ongatheringchange(args);
} catch(e) {}
};
}
switch (this._dompc._pc.iceState) {
case Ci.IPeerConnection.kIceGathering:
iceGatherCb("gathering");
this.callCB(this._dompc.ongatheringchange, "gathering");
break;
case Ci.IPeerConnection.kIceWaiting:
iceCb("starting");
this.callCB(this._dompc.onicechange, "starting");
this._dompc._executeNext();
break;
case Ci.IPeerConnection.kIceChecking:
iceCb("checking");
this.callCB(this._dompc.onicechange, "checking");
break;
case Ci.IPeerConnection.kIceConnected:
// ICE gathering complete.
iceCb("connected");
iceGatherCb("complete");
this.callCB(this._dompc.onicechange, "connected");
this.callCB(this._dompc.ongatheringchange, "complete");
break;
case Ci.IPeerConnection.kIceFailed:
iceCb("failed");
this.callCB(this._dompc.onicechange, "failed");
break;
default:
// Unknown state!
@ -910,63 +921,33 @@ PeerConnectionObserver.prototype = {
},
onAddStream: function(stream, type) {
if (this._dompc.onaddstream) {
try {
this._dompc.onaddstream.onCallback({
stream: stream, type: type,
__exposedProps__: { stream: "r", type: "r" }
});
} catch(e) {}
}
this.callCB(this._dompc.onaddstream,
{ stream: stream, type: type,
__exposedProps__: { stream: "r", type: "r" } });
},
onRemoveStream: function(stream, type) {
if (this._dompc.onremovestream) {
try {
this._dompc.onremovestream.onCallback({
stream: stream, type: type,
__exposedProps__: { stream: "r", type: "r" }
});
} catch(e) {}
}
this.callCB(this._dompc.onremovestream,
{ stream: stream, type: type,
__exposedProps__: { stream: "r", type: "r" } });
},
foundIceCandidate: function(cand) {
if (this._dompc.onicecandidate) {
try {
this._dompc.onicecandidate.onCallback({
candidate: cand,
__exposedProps__: { candidate: "rw" }
});
} catch(e) {}
}
this.callCB(this._dompc.onicecandidate,
{candidate: cand, __exposedProps__: { candidate: "rw" } });
},
notifyDataChannel: function(channel) {
if (this._dompc.ondatachannel) {
try {
this._dompc.ondatachannel.onCallback({
channel: channel,
__exposedProps__: { channel: "r" }
});
} catch(e) {}
}
this.callCB(this._dompc.ondatachannel,
{ channel: channel, __exposedProps__: { channel: "r" } });
},
notifyConnection: function() {
if (this._dompc.onconnection) {
try {
this._dompc.onconnection.onCallback();
} catch(e) {}
}
this.callCB (this._dompc.onconnection);
},
notifyClosedConnection: function() {
if (this._dompc.onclosedconnection) {
try {
this._dompc.onclosedconnection.onCallback();
} catch(e) {}
}
this.callCB (this._dompc.onclosedconnection);
}
};

View File

@ -33,6 +33,7 @@ MOCHITEST_FILES = \
test_peerConnection_offerRequiresReceiveAudio.html \
test_peerConnection_offerRequiresReceiveVideo.html \
test_peerConnection_offerRequiresReceiveVideoAudio.html \
test_peerConnection_throwInCallbacks.html \
test_peerConnection_bug822674.html \
test_peerConnection_bug825703.html \
test_peerConnection_bug827843.html \

View File

@ -0,0 +1,83 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="head.js"></script>
</head>
<body>
<pre id="test">
<script type="application/javascript;version=1.8">
createHTML({
bug: "857765",
title: "Throw in PeerConnection callbacks"
});
let error_count = 0;
let oldOnError = window.onerror;
window.onerror = function (errorMsg, url, lineNumber) {
if (errorMsg.indexOf("Expected") == -1) {
getFail(new Error)(errorMsg);
}
error_count += 1;
info("onerror " + error_count + ": " + errorMsg);
if (error_count == 7) {
finish();
}
return false;
}
let pc0, pc1, pc2;
runTest(function () {
error_count = 0;
// Test failure callbacks (limited to 1 for now)
pc0 = new mozRTCPeerConnection();
pc0.createOffer(getFail(new Error), function(err) {
pc1 = new mozRTCPeerConnection();
pc2 = new mozRTCPeerConnection();
// Test success callbacks (happy path)
navigator.mozGetUserMedia({video:true, fake: true}, function(video1) {
pc1.addStream(video1);
pc1.createOffer(function(offer) {
pc1.setLocalDescription(offer, function() {
pc2.setRemoteDescription(offer, function() {
pc2.createAnswer(function(answer) {
pc2.setLocalDescription(answer, function() {
pc1.setRemoteDescription(answer, function() {
throw new Error("Expected");
}, getFail(new Error));
throw new Error("Expected");
}, getFail(new Error));
throw new Error("Expected");
}, getFail(new Error));
throw new Error("Expected");
}, getFail(new Error));
throw new Error("Expected");
}, getFail(new Error));
throw new Error("Expected");
});
}, getFail(new Error));
throw new Error("Expected");
});
}, 1);
function finish() {
window.onerror = oldOnError;
ok(error_count == 7, "Seven expected errors verified.");
SimpleTest.finish();
}
function getFail(where) {
return function (err) {
window.onerror = onOldError;
unexpectedCallbackAndFinish(where)(err);
};
}
</script>
</pre>
</body>
</html>