diff --git a/dom/media/PeerConnection.js b/dom/media/PeerConnection.js index 7862d28dc1f..476f066d55a 100644 --- a/dom/media/PeerConnection.js +++ b/dom/media/PeerConnection.js @@ -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); } }; diff --git a/dom/media/tests/mochitest/Makefile.in b/dom/media/tests/mochitest/Makefile.in index 76ab887273d..ab6454c6485 100644 --- a/dom/media/tests/mochitest/Makefile.in +++ b/dom/media/tests/mochitest/Makefile.in @@ -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 \ diff --git a/dom/media/tests/mochitest/test_peerConnection_throwInCallbacks.html b/dom/media/tests/mochitest/test_peerConnection_throwInCallbacks.html new file mode 100644 index 00000000000..dcc22e55dff --- /dev/null +++ b/dom/media/tests/mochitest/test_peerConnection_throwInCallbacks.html @@ -0,0 +1,83 @@ + + +
+ + + + + + ++ ++ +