diff --git a/dom/presentation/PresentationSessionInfo.cpp b/dom/presentation/PresentationSessionInfo.cpp index af658555c230..081a015b2b0e 100644 --- a/dom/presentation/PresentationSessionInfo.cpp +++ b/dom/presentation/PresentationSessionInfo.cpp @@ -546,6 +546,13 @@ PresentationControllingInfo::GetAddress() return NS_OK; } +NS_IMETHODIMP +PresentationControllingInfo::OnIceCandidate(const nsAString& aCandidate) +{ + MOZ_ASSERT(false, "Should not receive ICE candidates."); + return NS_ERROR_FAILURE; +} + nsresult PresentationControllingInfo::OnGetAddress(const nsACString& aAddress) { @@ -851,6 +858,13 @@ PresentationPresentingInfo::OnAnswer(nsIPresentationChannelDescription* aDescrip return NS_ERROR_FAILURE; } +NS_IMETHODIMP +PresentationPresentingInfo::OnIceCandidate(const nsAString& aCandidate) +{ + MOZ_ASSERT(false, "Should not receive ICE candidates."); + return NS_ERROR_FAILURE; +} + NS_IMETHODIMP PresentationPresentingInfo::NotifyOpened() { diff --git a/dom/presentation/interfaces/nsIPresentationControlChannel.idl b/dom/presentation/interfaces/nsIPresentationControlChannel.idl index 5952cc3ce49b..b6f3ac16e97d 100644 --- a/dom/presentation/interfaces/nsIPresentationControlChannel.idl +++ b/dom/presentation/interfaces/nsIPresentationControlChannel.idl @@ -32,7 +32,7 @@ interface nsIPresentationChannelDescription: nsISupports /* * The callbacks for events on control channel. */ -[scriptable, uuid(d0cdc638-a9d5-4bcd-838c-3aed7c3f2a6b)] +[scriptable, uuid(96dd548f-7d0f-43c1-b1ad-28e666cf1e82)] interface nsIPresentationControlChannelListener: nsISupports { /* @@ -47,6 +47,12 @@ interface nsIPresentationControlChannelListener: nsISupports */ void onAnswer(in nsIPresentationChannelDescription answer); + /* + * Callback for receiving ICE candidate from remote endpoint. + * @param answer The received answer. + */ + void onIceCandidate(in DOMString candidate); + /* * The callback for notifying channel opened. */ @@ -63,7 +69,7 @@ interface nsIPresentationControlChannelListener: nsISupports * The control channel for establishing RTCPeerConnection for a presentation * session. SDP Offer/Answer will be exchanged through this interface. */ -[scriptable, uuid(2c8ec493-4e5b-4df7-bedc-7ab25af323f0)] +[scriptable, uuid(e60e208c-a9f5-4bc6-9a3e-47f3e4ae9c57)] interface nsIPresentationControlChannel: nsISupports { // The listener for handling events of this control channel. @@ -71,26 +77,28 @@ interface nsIPresentationControlChannel: nsISupports attribute nsIPresentationControlChannelListener listener; /* - * Send offer to remote endpiont. |onOffer| should be invoked - * on remote endpoint. + * Send offer to remote endpoint. |onOffer| should be invoked on remote + * endpoint. * @param offer The offer to send. * @throws NS_ERROR_FAILURE on failure */ void sendOffer(in nsIPresentationChannelDescription offer); /* - * Send answer to remote endpiont. |onAnswer| should - * be invoked on remote endpoint. + * Send answer to remote endpoint. |onAnswer| should be invoked on remote + * endpoint. * @param answer The answer to send. * @throws NS_ERROR_FAILURE on failure */ void sendAnswer(in nsIPresentationChannelDescription answer); /* - * Notify the app-to-app connection is fully established. (Only used at the - * receiver side.) + * Send ICE candidate to remote endpoint. |onIceCandidate| should be invoked + * on remote endpoint. + * @param candidate The candidate to send + * @throws NS_ERROR_FAILURE on failure */ - void sendReceiverReady(); + void sendIceCandidate(in DOMString candidate); /* * Close the transport channel. diff --git a/dom/presentation/provider/TCPPresentationServer.js b/dom/presentation/provider/TCPPresentationServer.js index 6bbc9974961e..ec6e2a4254a8 100644 --- a/dom/presentation/provider/TCPPresentationServer.js +++ b/dom/presentation/provider/TCPPresentationServer.js @@ -374,6 +374,14 @@ TCPControlChannel.prototype = { this._sendMessage("answer", msg); }, + sendIceCandidate: function(aCandidate) { + let msg = { + type: "requestSession:IceCandidate", + presentationId: this.presentationId, + iceCandidate: aCandidate, + }; + this._sendMessage("iceCandidate", msg); + }, // may throw an exception _send: function(aMsg) { DEBUG && log("TCPControlChannel - Send: " + JSON.stringify(aMsg, null, 2)); @@ -438,7 +446,7 @@ TCPControlChannel.prototype = { onStopRequest: function(aRequest, aContext, aStatus) { DEBUG && log("TCPControlChannel - onStopRequest: " + aStatus + " with role: " + this._direction); - this.close(); + this.close(Cr.NS_OK); this._notifyClosed(aStatus); }, @@ -496,6 +504,14 @@ TCPControlChannel.prototype = { this._onAnswer(aMsg.answer); break; } + case "requestSession:IceCandidate": { + this._listener.onIceCandidate(aMsg.iceCandidate); + break; + } + case "requestSession:CloseReason": { + this._pendingCloseReason = aMsg.reason; + break; + } } }, @@ -573,7 +589,7 @@ TCPControlChannel.prototype = { _notifyOpened: function() { this._connected = true; this._pendingClose = false; - this._pendingCloseReason = null; + this._pendingCloseReason = Cr.NS_OK; if (!this._listener) { this._pendingOpen = true; @@ -591,10 +607,15 @@ TCPControlChannel.prototype = { this._pendingOffer = null; this._pendingAnswer = null; + // Remote endpoint closes the control channel with abnormal reason. + if (aReason == Cr.NS_OK && this._pendingCloseReason != Cr.NS_OK) { + aReason = this._pendingCloseReason; + } + if (!this._listener) { - this._pendingClose = true; - this._pendingCloseReason = aReason; - return; + this._pendingClose = true; + this._pendingCloseReason = aReason; + return; } DEBUG && log("TCPControlChannel - notify closed with role: " @@ -602,9 +623,21 @@ TCPControlChannel.prototype = { this._listener.notifyClosed(aReason); }, - close: function() { - DEBUG && log("TCPControlChannel - close"); + close: function(aReason) { + DEBUG && log("TCPControlChannel - close with reason: " + aReason); + if (this._connected) { + // default reason is NS_OK + if (typeof aReason !== "undefined" && aReason !== Cr.NS_OK) { + let msg = { + type: "requestSession:CloseReason", + presentationId: this.presentationId, + reason: aReason, + }; + this._sendMessage("close", msg); + this._pendingCloseReason = aReason; + } + this._transport.setEventSink(null, null); this._pump = null; diff --git a/dom/presentation/tests/xpcshell/test_tcp_control_channel.js b/dom/presentation/tests/xpcshell/test_tcp_control_channel.js index 24d5ff85e748..845c69164297 100644 --- a/dom/presentation/tests/xpcshell/test_tcp_control_channel.js +++ b/dom/presentation/tests/xpcshell/test_tcp_control_channel.js @@ -44,6 +44,9 @@ TestDescription.prototype = { const CONTROLLER_CONTROL_CHANNEL_PORT = 36777; const PRESENTER_CONTROL_CHANNEL_PORT = 36888; +var CLOSE_CONTROL_CHANNEL_REASON = Cr.NS_OK; +var candidate; + // presenter's presentation channel description const OFFER_ADDRESS = '192.168.123.123'; const OFFER_PORT = 123; @@ -98,13 +101,23 @@ function testPresentationServer() { onAnswer: function(aAnswer) { Assert.ok(false, 'get answer'); }, + onIceCandidate: function(aCandidate) { + Assert.ok(true, '3. controllerControlChannel: get ice candidate, close channel'); + let recvCandidate = JSON.parse(aCandidate); + for (let key in recvCandidate) { + if (typeof(recvCandidate[key]) !== "function") { + Assert.equal(recvCandidate[key], candidate[key], "key " + key + " should match."); + } + } + controllerControlChannel.close(CLOSE_CONTROL_CHANNEL_REASON); + }, notifyOpened: function() { Assert.equal(this.status, 'created', '0. controllerControlChannel: opened'); this.status = 'opened'; }, notifyClosed: function(aReason) { - Assert.equal(this.status, 'onOffer', '3. controllerControlChannel: closed'); - Assert.equal(aReason, Cr.NS_OK, 'presenterControlChannel notify closed NS_OK'); + Assert.equal(this.status, 'onOffer', '4. controllerControlChannel: closed'); + Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, 'presenterControlChannel notify closed'); this.status = 'closed'; yayFuncs.controllerControlChannelClose(); }, @@ -132,15 +145,22 @@ function testPresentationServer() { Assert.ok(false, 'get offer'); }, onAnswer: function(aAnswer) { - Assert.equal(this.status, 'opened', '2. presenterControlChannel: get answer, close channel'); + Assert.equal(this.status, 'opened', '2. presenterControlChannel: get answer, send ICE candidate'); let answer = aAnswer.QueryInterface(Ci.nsIPresentationChannelDescription); Assert.strictEqual(answer.tcpAddress.queryElementAt(0,Ci.nsISupportsCString).data, ANSWER_ADDRESS, 'expected answer address array'); Assert.equal(answer.tcpPort, ANSWER_PORT, 'expected answer port'); - - presenterControlChannel.close(Cr.NS_OK); + candidate = { + candidate: "1 1 UDP 1 127.0.0.1 34567 type host", + sdpMid: "helloworld", + sdpMLineIndex: 1 + }; + presenterControlChannel.sendIceCandidate(JSON.stringify(candidate)); + }, + onIceCandidate: function(aCandidate) { + Assert.ok(false, 'get ICE candidate'); }, notifyOpened: function() { Assert.equal(this.status, 'created', '0. presenterControlChannel: opened, send offer'); @@ -155,7 +175,7 @@ function testPresentationServer() { }, notifyClosed: function(aReason) { this.status = 'closed'; - Assert.equal(aReason, Cr.NS_OK, '3. presenterControlChannel notify closed NS_OK'); + Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, '4. presenterControlChannel notify closed'); yayFuncs.presenterControlChannelClose(); }, QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]), @@ -198,8 +218,15 @@ function shutdown() tps.close(); } +// Test manually close control channel with NS_ERROR_FAILURE +function changeCloseReason() { + CLOSE_CONTROL_CHANNEL_REASON = Cr.NS_ERROR_FAILURE; + run_next_test(); +} + add_test(loopOfferAnser); add_test(setOffline); +add_test(changeCloseReason); add_test(oneMoreLoop); add_test(shutdown);