Bug 1197690 - Part1: Support reconnect command, r=junior

--HG--
extra : rebase_source : 4ccac7c28201562848c18b60b18614f450bdd19f
This commit is contained in:
Kershaw Chang 2016-08-02 19:10:00 +02:00
Родитель 2c0b817322
Коммит 165708a330
15 изменённых файлов: 324 добавлений и 77 удалений

Просмотреть файл

@ -262,6 +262,27 @@ PresentationDeviceManager::OnTerminateRequest(nsIPresentationDevice* aDevice,
return NS_OK;
}
NS_IMETHODIMP
PresentationDeviceManager::OnReconnectRequest(nsIPresentationDevice* aDevice,
const nsAString& aUrl,
const nsAString& aPresentationId,
nsIPresentationControlChannel* aControlChannel)
{
NS_ENSURE_ARG(aDevice);
NS_ENSURE_ARG(aControlChannel);
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE);
RefPtr<PresentationSessionRequest> request =
new PresentationSessionRequest(aDevice, aUrl, aPresentationId, aControlChannel);
obs->NotifyObservers(request,
PRESENTATION_RECONNECT_REQUEST_TOPIC,
nullptr);
return NS_OK;
}
// nsIObserver
NS_IMETHODIMP
PresentationDeviceManager::Observe(nsISupports *aSubject,

Просмотреть файл

@ -64,6 +64,11 @@ interface nsIPresentationControlChannelListener: nsISupports
* @param reason The reason of channel close, NS_OK represents normal close.
*/
void notifyDisconnected(in nsresult reason);
/*
* The callback for notifying the reconnect command is acknowledged.
*/
void notifyReconnected();
};
/*
@ -122,4 +127,13 @@ interface nsIPresentationControlChannel: nsISupports
* @param reason The reason of disconnecting channel; NS_OK represents normal.
*/
void disconnect(in nsresult reason);
/*
* Reconnect a presentation on remote endpoint.
* Note that only controller is allowed to reconnect a session.
* @param presentationId The Id for representing this session.
* @param url The URL requested to open by remote device.
* @throws NS_ERROR_FAILURE on failure
*/
void reconnect(in DOMString presentationId, in DOMString url);
};

Просмотреть файл

@ -57,6 +57,18 @@ interface nsIPresentationControlServerListener: nsISupports
in DOMString aPresentationId,
in nsIPresentationControlChannel aControlChannel,
in boolean aIsFromReceiver);
/**
* Callback while the remote host is requesting to reconnect a presentation session.
* @param aDeviceInfo The device information related to the remote host.
* @param aUrl The URL requested to open by remote device.
* @param aPresentationId The Id for representing this session.
* @param aControlChannel The control channel for this session.
*/
void onReconnectRequest(in nsITCPDeviceInfo aDeviceInfo,
in DOMString url,
in DOMString aPresentationId,
in nsIPresentationControlChannel aControlChannel);
};
/**

Просмотреть файл

@ -44,6 +44,18 @@ interface nsIPresentationDeviceListener: nsISupports
in DOMString presentationId,
in nsIPresentationControlChannel controlChannel,
in boolean aIsFromReceiver);
/*
* Callback while the remote device is requesting to reconnect a presentation session.
* @param device The remote device that sent session request.
* @param aUrl The URL requested to open by remote device.
* @param presentationId The Id for representing this session.
* @param controlChannel The control channel for this session.
*/
void onReconnectRequest(in nsIPresentationDevice device,
in DOMString url,
in DOMString presentationId,
in nsIPresentationControlChannel controlChannel);
};
/*

Просмотреть файл

@ -12,8 +12,10 @@ interface nsIPresentationControlChannel;
%}
/*
* The event of a device requesting for a presentation session. User can
* monitor the session request on every device by observing "presentation-sesion-request".
* The event of a device requesting for starting or reconnecting
* a presentation session. User can monitor the session request
* on every device by observing "presentation-sesion-request" for a
* new session and "presentation-reconnect-request" for reconnecting.
*/
[scriptable, uuid(d808a084-d0f8-455a-a8df-5879e05a755b)]
interface nsIPresentationSessionRequest: nsISupports

Просмотреть файл

@ -60,6 +60,9 @@ var handlers = [
case CommandType.ICE_CANDIDATE:
stateMachine._notifyChannelDescriptor(command);
break;
case CommandType.RECONNECT_ACK:
stateMachine._notifyReconnect(command.presentationId);
break;
default:
debug("unexpected command: " + JSON.stringify(command));
// ignore unexpected command.
@ -111,6 +114,16 @@ ControllerStateMachine.prototype = {
}
},
reconnect: function _reconnect(presentationId, url) {
if (this.state === State.CONNECTED) {
this._sendCommand({
type: CommandType.RECONNECT,
presentationId: presentationId,
url: url,
});
}
},
sendOffer: function _sendOffer(offer) {
if (this.state === State.CONNECTED) {
this._sendCommand({
@ -200,6 +213,10 @@ ControllerStateMachine.prototype = {
this._channel.notifyTerminate(presentationId);
},
_notifyReconnect: function _notifyReconnect(presentationId) {
this._channel.notifyReconnect(presentationId);
},
_notifyChannelDescriptor: function _notifyChannelDescriptor(command) {
switch (command.type) {
case CommandType.ANSWER:

Просмотреть файл

@ -436,6 +436,37 @@ DisplayDeviceProvider::OnTerminateRequest(nsITCPDeviceInfo* aDeviceInfo,
return NS_OK;
}
NS_IMETHODIMP
DisplayDeviceProvider::OnReconnectRequest(nsITCPDeviceInfo* aDeviceInfo,
const nsAString& aUrl,
const nsAString& aPresentationId,
nsIPresentationControlChannel* aControlChannel)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aDeviceInfo);
MOZ_ASSERT(aControlChannel);
nsresult rv;
nsCOMPtr<nsIPresentationDeviceListener> listener;
rv = GetListener(getter_AddRefs(listener));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_ASSERT(!listener);
rv = listener->OnReconnectRequest(mDevice,
aUrl,
aPresentationId,
aControlChannel);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
// nsIObserver
NS_IMETHODIMP
DisplayDeviceProvider::Observe(nsISupports* aSubject,

Просмотреть файл

@ -474,6 +474,12 @@ LegacyTCPControlChannel.prototype = {
}
},
reconnect: function() {
// Legacy protocol doesn't support extra reconnect protocol.
// Trigger error handling for browser to shutdown all the resource locally.
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
classID: Components.ID("{4027ce3d-06e3-4d06-a235-df329cb0d411}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannel,
Ci.nsIStreamListener]),

Просмотреть файл

@ -873,63 +873,13 @@ MulticastDNSDeviceProvider::OnPortChange(uint16_t aPort)
return NS_OK;
}
NS_IMETHODIMP
MulticastDNSDeviceProvider::OnSessionRequest(nsITCPDeviceInfo* aDeviceInfo,
const nsAString& aUrl,
const nsAString& aPresentationId,
nsIPresentationControlChannel* aControlChannel)
// Create a new device if we were unable to find one with the address.
already_AddRefed<MulticastDNSDeviceProvider::Device>
MulticastDNSDeviceProvider::GetOrCreateDevice(nsITCPDeviceInfo* aDeviceInfo)
{
MOZ_ASSERT(NS_IsMainThread());
nsAutoCString address;
Unused << aDeviceInfo->GetAddress(address);
LOG_I("OnSessionRequest: %s", address.get());
RefPtr<Device> device;
uint32_t index;
if (FindDeviceByAddress(address, index)) {
device = mDevices[index];
} else {
// create a one-time device object for non-discoverable controller
// this device will not be listed in available device list and cannot
// be used for requesting session.
nsAutoCString id;
Unused << aDeviceInfo->GetId(id);
uint16_t port;
Unused << aDeviceInfo->GetPort(&port);
device = new Device(id,
/* aName = */ id,
/* aType = */ EmptyCString(),
address,
port,
DeviceState::eActive,
/* aProvider = */ nullptr);
}
nsCOMPtr<nsIPresentationDeviceListener> listener;
if (NS_SUCCEEDED(GetListener(getter_AddRefs(listener))) && listener) {
Unused << listener->OnSessionRequest(device, aUrl, aPresentationId,
aControlChannel);
}
return NS_OK;
}
NS_IMETHODIMP
MulticastDNSDeviceProvider::OnTerminateRequest(nsITCPDeviceInfo* aDeviceInfo,
const nsAString& aPresentationId,
nsIPresentationControlChannel* aControlChannel,
bool aIsFromReceiver)
{
MOZ_ASSERT(NS_IsMainThread());
nsAutoCString address;
Unused << aDeviceInfo->GetAddress(address);
LOG_I("OnTerminateRequest: %s", address.get());
RefPtr<Device> device;
uint32_t index;
if (FindDeviceByAddress(address, index)) {
@ -952,6 +902,46 @@ MulticastDNSDeviceProvider::OnTerminateRequest(nsITCPDeviceInfo* aDeviceInfo,
/* aProvider = */ nullptr);
}
return device.forget();
}
NS_IMETHODIMP
MulticastDNSDeviceProvider::OnSessionRequest(nsITCPDeviceInfo* aDeviceInfo,
const nsAString& aUrl,
const nsAString& aPresentationId,
nsIPresentationControlChannel* aControlChannel)
{
MOZ_ASSERT(NS_IsMainThread());
nsAutoCString address;
Unused << aDeviceInfo->GetAddress(address);
LOG_I("OnSessionRequest: %s", address.get());
RefPtr<Device> device = GetOrCreateDevice(aDeviceInfo);
nsCOMPtr<nsIPresentationDeviceListener> listener;
if (NS_SUCCEEDED(GetListener(getter_AddRefs(listener))) && listener) {
Unused << listener->OnSessionRequest(device, aUrl, aPresentationId,
aControlChannel);
}
return NS_OK;
}
NS_IMETHODIMP
MulticastDNSDeviceProvider::OnTerminateRequest(nsITCPDeviceInfo* aDeviceInfo,
const nsAString& aPresentationId,
nsIPresentationControlChannel* aControlChannel,
bool aIsFromReceiver)
{
MOZ_ASSERT(NS_IsMainThread());
nsAutoCString address;
Unused << aDeviceInfo->GetAddress(address);
LOG_I("OnTerminateRequest: %s", address.get());
RefPtr<Device> device = GetOrCreateDevice(aDeviceInfo);
nsCOMPtr<nsIPresentationDeviceListener> listener;
if (NS_SUCCEEDED(GetListener(getter_AddRefs(listener))) && listener) {
Unused << listener->OnTerminateRequest(device, aPresentationId,
@ -961,6 +951,29 @@ MulticastDNSDeviceProvider::OnTerminateRequest(nsITCPDeviceInfo* aDeviceInfo,
return NS_OK;
}
NS_IMETHODIMP
MulticastDNSDeviceProvider::OnReconnectRequest(nsITCPDeviceInfo* aDeviceInfo,
const nsAString& aUrl,
const nsAString& aPresentationId,
nsIPresentationControlChannel* aControlChannel)
{
MOZ_ASSERT(NS_IsMainThread());
nsAutoCString address;
Unused << aDeviceInfo->GetAddress(address);
LOG_I("OnReconnectRequest: %s", address.get());
RefPtr<Device> device = GetOrCreateDevice(aDeviceInfo);
nsCOMPtr<nsIPresentationDeviceListener> listener;
if (NS_SUCCEEDED(GetListener(getter_AddRefs(listener))) && listener) {
Unused << listener->OnReconnectRequest(device, aUrl, aPresentationId,
aControlChannel);
}
return NS_OK;
}
// nsIObserver
NS_IMETHODIMP
MulticastDNSDeviceProvider::Observe(nsISupports* aSubject,

Просмотреть файл

@ -19,6 +19,8 @@
#include "nsTArray.h"
#include "nsWeakPtr.h"
class nsITCPDeviceInfo;
namespace mozilla {
namespace dom {
namespace presentation {
@ -162,6 +164,9 @@ private:
bool FindDeviceByAddress(const nsACString& aAddress,
uint32_t& aIndex);
already_AddRefed<Device>
GetOrCreateDevice(nsITCPDeviceInfo* aDeviceInfo);
void MarkAllDevicesUnknown();
void ClearUnknownDevices();
void ClearDevices();

Просмотреть файл

@ -193,6 +193,21 @@ PresentationControlService.prototype = {
this.releaseControlChannel(aControlChannel);
},
onSessionReconnect: function(aDeviceInfo, aUrl, aPresentationId, aControlChannel) {
DEBUG && log("TCPPresentationServer - onSessionReconnect: " +
aDeviceInfo.address + ":" + aDeviceInfo.port); // jshint ignore:line
if (!this.listener) {
this.releaseControlChannel(aControlChannel);
return;
}
this.listener.onReconnectRequest(aDeviceInfo,
aUrl,
aPresentationId,
aControlChannel);
this.releaseControlChannel(aControlChannel);
},
// nsIServerSocketListener (Triggered by nsIServerSocket.init)
onSocketAccepted: function(aServerSocket, aClientSocket) {
DEBUG && log("PresentationControlService - onSocketAccepted: " +
@ -393,6 +408,7 @@ TCPControlChannel.prototype = {
_pendingAnswer: null,
_pendingClose: null,
_pendingCloseReason: null,
_pendingReconnect: false,
sendOffer: function(aOffer) {
this._stateMachine.sendOffer(discriptionAsJson(aOffer));
@ -555,6 +571,12 @@ TCPControlChannel.prototype = {
this._notifyDisconnected(this._pendingCloseReason);
this._pendingClose = null;
}
if (this._pendingReconnect) {
DEBUG && log("TCPControlChannel - notify pending reconnected"); // jshint ignore:line
this._notifyReconnected();
this._pendingReconnect = false;
}
},
/**
@ -624,6 +646,17 @@ TCPControlChannel.prototype = {
this._listener.notifyDisconnected(aReason);
},
_notifyReconnected: function() {
if (!this._listener) {
this._pendingReconnect = true;
return;
}
DEBUG && log("TCPControlChannel - notify reconnected with role: " +
this._direction); // jshint ignore:line
this._listener.notifyReconnected();
},
_closeTransport: function() {
if (this._connected) {
this._transport.setEventSink(null, null);
@ -649,6 +682,16 @@ TCPControlChannel.prototype = {
}
},
reconnect: function(aPresentationId, aUrl) {
DEBUG && log("TCPControlChannel - reconnect with role: " +
this._direction); // jshint ignore:line
if (this._direction != "sender") {
return Cr.NS_ERROR_FAILURE;
}
this._stateMachine.reconnect(aPresentationId, aUrl);
},
// callback from state machine
sendCommand: function(command) {
this._send(command);
@ -684,9 +727,9 @@ TCPControlChannel.prototype = {
if (!this._terminatingId) {
this._terminatingId = presentationId;
this._presentationService.onSessionTerminate(this._deviceInfo,
presentationId,
this,
this._direction === "sender");
presentationId,
this,
this._direction === "sender");
return;
}
@ -700,6 +743,20 @@ TCPControlChannel.prototype = {
delete this._terminatingId;
},
notifyReconnect: function(presentationId, url) {
switch (this._direction) {
case "receiver":
this._presentationService.onSessionReconnect(this._deviceInfo,
url,
presentationId,
this);
break;
case "sender":
this._notifyReconnected();
break;
}
},
notifyOffer: function(offer) {
this._onOffer(offer);
},

Просмотреть файл

@ -68,6 +68,14 @@ var handlers = [
case CommandType.ICE_CANDIDATE:
stateMachine._notifyChannelDescriptor(command);
break;
case CommandType.RECONNECT:
stateMachine._notifyReconnect(command.presentationId,
command.url);
stateMachine._sendCommand({
type: CommandType.RECONNECT_ACK,
presentationId: command.presentationId
});
break;
default:
debug("unexpected command: " + JSON.stringify(command));
// ignore unexpected command
@ -113,6 +121,10 @@ ReceiverStateMachine.prototype = {
}
},
reconnect: function _reconnect() {
debug("receiver shouldn't trigger reconnect");
},
sendOffer: function _sendOffer() {
// offer can only be sent by controlling UA.
debug("receiver shouldn't generate offer");
@ -199,6 +211,10 @@ ReceiverStateMachine.prototype = {
this._channel.notifyTerminate(presentationId);
},
_notifyReconnect: function _notifyReconnect(presentationId, url) {
this._channel.notifyReconnect(presentationId, url);
},
_notifyChannelDescriptor: function _notifyChannelDescriptor(command) {
switch (command.type) {
case CommandType.OFFER:

Просмотреть файл

@ -27,6 +27,8 @@ const CommandType = Object.freeze({
LAUNCH_ACK: "launch-ack", // { presentationId: <string> }
TERMINATE: "terminate", // { presentationId: <string> }
TERMINATE_ACK: "terminate-ack", // { presentationId: <string> }
RECONNECT: "reconnect", // { presentationId: <string> }
RECONNECT_ACK: "reconnect-ack", // { presentationId: <string> }
// session transport establishment
OFFER: "offer", // { offer: <json> }
ANSWER: "answer", // { answer: <json> }

Просмотреть файл

@ -24,6 +24,7 @@ TestPresentationControlChannel.prototype = {
disconnect: function() {},
launch: function() {},
terminate: function() {},
reconnect: function() {},
set listener(listener) {},
get listener() {},
};
@ -161,6 +162,25 @@ function terminateRequest() {
testControlChannel, testIsFromReceiver);
}
function reconnectRequest() {
let testUrl = 'http://www.example.org/';
let testPresentationId = 'test-presentation-id';
let testControlChannel = new TestPresentationControlChannel();
Services.obs.addObserver(function observer(subject, topic, data) {
Services.obs.removeObserver(observer, topic);
let request = subject.QueryInterface(Ci.nsIPresentationSessionRequest);
Assert.equal(request.device.id, testDevice.id, 'expected device');
Assert.equal(request.url, testUrl, 'expected requesting URL');
Assert.equal(request.presentationId, testPresentationId, 'expected presentation Id');
run_next_test();
}, 'presentation-reconnect-request', false);
manager.QueryInterface(Ci.nsIPresentationDeviceListener)
.onReconnectRequest(testDevice, testUrl, testPresentationId, testControlChannel);
}
function removeDevice() {
Services.obs.addObserver(function observer(subject, topic, data) {
Services.obs.removeObserver(observer, topic);
@ -199,6 +219,7 @@ add_test(addDevice);
add_test(updateDevice);
add_test(sessionRequest);
add_test(terminateRequest);
add_test(reconnectRequest);
add_test(removeDevice);
add_test(removeProvider);

Просмотреть файл

@ -67,22 +67,24 @@ function loopOfferAnser() {
function testPresentationServer() {
let yayFuncs = makeJointSuccess(['controllerControlChannelClose',
'presenterControlChannelClose']);
let controllerControlChannel;
'presenterControlChannelClose',
'controllerControlChannelReconnect',
'presenterControlChannelReconnect']);
let presenterControlChannel;
pcs.listener = {
onSessionRequest: function(deviceInfo, url, presentationId, controlChannel) {
controllerControlChannel = controlChannel;
presenterControlChannel = controlChannel;
Assert.equal(deviceInfo.id, pcs.id, 'expected device id');
Assert.equal(deviceInfo.address, '127.0.0.1', 'expected device address');
Assert.equal(url, 'http://example.com', 'expected url');
Assert.equal(presentationId, 'testPresentationId', 'expected presentation id');
controllerControlChannel.listener = {
presenterControlChannel.listener = {
status: 'created',
onOffer: function(aOffer) {
Assert.equal(this.status, 'opened', '1. controllerControlChannel: get offer, send answer');
Assert.equal(this.status, 'opened', '1. presenterControlChannel: get offer, send answer');
this.status = 'onOffer';
let offer = aOffer.QueryInterface(Ci.nsIPresentationChannelDescription);
@ -93,7 +95,7 @@ function testPresentationServer() {
try {
let tcpType = Ci.nsIPresentationChannelDescription.TYPE_TCP;
let answer = new TestDescription(tcpType, [ANSWER_ADDRESS], ANSWER_PORT);
controllerControlChannel.sendAnswer(answer);
presenterControlChannel.sendAnswer(answer);
} catch (e) {
Assert.ok(false, 'sending answer fails' + e);
}
@ -102,28 +104,33 @@ function testPresentationServer() {
Assert.ok(false, 'get answer');
},
onIceCandidate: function(aCandidate) {
Assert.ok(true, '3. controllerControlChannel: get ice candidate, close channel');
Assert.ok(true, '3. presenterControlChannel: 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.disconnect(CLOSE_CONTROL_CHANNEL_REASON);
presenterControlChannel.disconnect(CLOSE_CONTROL_CHANNEL_REASON);
},
notifyConnected: function() {
Assert.equal(this.status, 'created', '0. controllerControlChannel: opened');
Assert.equal(this.status, 'created', '0. presenterControlChannel: opened');
this.status = 'opened';
},
notifyDisconnected: function(aReason) {
Assert.equal(this.status, 'onOffer', '4. controllerControlChannel: closed');
Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, 'controllerControlChannel notify closed');
Assert.equal(this.status, 'onOffer', '4. presenterControlChannel: closed');
Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, 'presenterControlChannel notify closed');
this.status = 'closed';
yayFuncs.controllerControlChannelClose();
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]),
};
},
onReconnectRequest: function(deviceInfo, url, presentationId, controlChannel) {
Assert.equal(url, 'http://example.com', 'expected url');
Assert.equal(presentationId, 'testPresentationId', 'expected presentation id');
yayFuncs.presenterControlChannelReconnect();
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlServerListener]),
};
@ -135,15 +142,15 @@ function testPresentationServer() {
QueryInterface: XPCOMUtils.generateQI([Ci.nsITCPDeviceInfo]),
};
let presenterControlChannel = pcs.connect(presenterDeviceInfo);
let controllerControlChannel = pcs.connect(presenterDeviceInfo);
presenterControlChannel.listener = {
controllerControlChannel.listener = {
status: 'created',
onOffer: function(offer) {
Assert.ok(false, 'get offer');
},
onAnswer: function(aAnswer) {
Assert.equal(this.status, 'opened', '2. presenterControlChannel: get answer, send ICE candidate');
Assert.equal(this.status, 'opened', '2. controllerControlChannel: get answer, send ICE candidate');
let answer = aAnswer.QueryInterface(Ci.nsIPresentationChannelDescription);
Assert.strictEqual(answer.tcpAddress.queryElementAt(0,Ci.nsISupportsCString).data,
@ -155,27 +162,38 @@ function testPresentationServer() {
sdpMid: "helloworld",
sdpMLineIndex: 1
};
presenterControlChannel.sendIceCandidate(JSON.stringify(candidate));
controllerControlChannel.sendIceCandidate(JSON.stringify(candidate));
},
onIceCandidate: function(aCandidate) {
Assert.ok(false, 'get ICE candidate');
},
notifyConnected: function() {
Assert.equal(this.status, 'created', '0. presenterControlChannel: opened, send offer');
presenterControlChannel.launch('testPresentationId', 'http://example.com');
Assert.equal(this.status, 'created', '0. controllerControlChannel: opened, send offer');
controllerControlChannel.launch('testPresentationId', 'http://example.com');
this.status = 'opened';
try {
let tcpType = Ci.nsIPresentationChannelDescription.TYPE_TCP;
let offer = new TestDescription(tcpType, [OFFER_ADDRESS], OFFER_PORT)
presenterControlChannel.sendOffer(offer);
controllerControlChannel.sendOffer(offer);
} catch (e) {
Assert.ok(false, 'sending offer fails:' + e);
}
},
notifyDisconnected: function(aReason) {
this.status = 'closed';
Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, '4. presenterControlChannel notify closed');
Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, '4. controllerControlChannel notify closed');
yayFuncs.presenterControlChannelClose();
let reconnectControllerControlChannel = pcs.connect(presenterDeviceInfo);
reconnectControllerControlChannel.listener = {
notifyConnected: function() {
reconnectControllerControlChannel.reconnect('testPresentationId', 'http://example.com');
},
notifyReconnected: function() {
yayFuncs.controllerControlChannelReconnect();
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]),
};
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]),
};