diff options
Diffstat (limited to 'dom/presentation/tests/xpcshell')
6 files changed, 2403 insertions, 0 deletions
diff --git a/dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js b/dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js new file mode 100644 index 000000000..137a5609a --- /dev/null +++ b/dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js @@ -0,0 +1,1318 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +/* global Services, do_register_cleanup, do_test_pending */ + +"use strict"; + +const { classes: Cc, interfaces: Ci, manager: Cm, results: Cr, utils: Cu } = Components; + +Cu.import("resource://gre/modules/Promise.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +const INFO_CONTRACT_ID = "@mozilla.org/toolkit/components/mdnsresponder/dns-info;1"; +const PROVIDER_CONTRACT_ID = "@mozilla.org/presentation-device/multicastdns-provider;1"; +const SD_CONTRACT_ID = "@mozilla.org/toolkit/components/mdnsresponder/dns-sd;1"; +const UUID_CONTRACT_ID = "@mozilla.org/uuid-generator;1"; +const SERVER_CONTRACT_ID = "@mozilla.org/presentation/control-service;1"; + +const PREF_DISCOVERY = "dom.presentation.discovery.enabled"; +const PREF_DISCOVERABLE = "dom.presentation.discoverable"; +const PREF_DEVICENAME= "dom.presentation.device.name"; + +const LATEST_VERSION = 1; +const SERVICE_TYPE = "_presentation-ctrl._tcp"; +const versionAttr = Cc["@mozilla.org/hash-property-bag;1"] + .createInstance(Ci.nsIWritablePropertyBag2); +versionAttr.setPropertyAsUint32("version", LATEST_VERSION); + +var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar); + +function sleep(aMs) { + let deferred = Promise.defer(); + + let timer = Cc["@mozilla.org/timer;1"] + .createInstance(Ci.nsITimer); + + timer.initWithCallback({ + notify: function () { + deferred.resolve(); + }, + }, aMs, timer.TYPE_ONE_SHOT); + + return deferred.promise; +} + +function MockFactory(aClass) { + this._cls = aClass; +} +MockFactory.prototype = { + createInstance: function(aOuter, aIID) { + if (aOuter) { + throw Cr.NS_ERROR_NO_AGGREGATION; + } + switch(typeof(this._cls)) { + case "function": + return new this._cls().QueryInterface(aIID); + case "object": + return this._cls.QueryInterface(aIID); + default: + return null; + } + }, + lockFactory: function(aLock) { + throw Cr.NS_ERROR_NOT_IMPLEMENTED; + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory]) +}; + +function ContractHook(aContractID, aClass) { + this._contractID = aContractID; + this.classID = Cc[UUID_CONTRACT_ID].getService(Ci.nsIUUIDGenerator).generateUUID(); + this._newFactory = new MockFactory(aClass); + + if (!this.hookedMap.has(this._contractID)) { + this.hookedMap.set(this._contractID, []); + } + + this.init(); +} + +ContractHook.prototype = { + hookedMap: new Map(), // remember only the most original factory. + + init: function() { + this.reset(); + + let oldContract = this.unregister(); + this.hookedMap.get(this._contractID).push(oldContract); + registrar.registerFactory(this.classID, + "", + this._contractID, + this._newFactory); + + do_register_cleanup(() => { this.cleanup.apply(this); }); + }, + + reset: function() {}, + + cleanup: function() { + this.reset(); + + this.unregister(); + let prevContract = this.hookedMap.get(this._contractID).pop(); + + if (prevContract.factory) { + registrar.registerFactory(prevContract.classID, + "", + this._contractID, + prevContract.factory); + } + }, + + unregister: function() { + var classID, factory; + + try { + classID = registrar.contractIDToCID(this._contractID); + factory = Cm.getClassObject(Cc[this._contractID], Ci.nsIFactory); + } catch (ex) { + classID = ""; + factory = null; + } + + if (factory) { + registrar.unregisterFactory(classID, factory); + } + + return { classID: classID, factory: factory }; + } +}; + +function MockDNSServiceInfo() {} +MockDNSServiceInfo.prototype = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceInfo]), + + set host(aHost) { + this._host = aHost; + }, + + get host() { + return this._host; + }, + + set address(aAddress) { + this._address = aAddress; + }, + + get address() { + return this._address; + }, + + set port(aPort) { + this._port = aPort; + }, + + get port() { + return this._port; + }, + + set serviceName(aServiceName) { + this._serviceName = aServiceName; + }, + + get serviceName() { + return this._serviceName; + }, + + set serviceType(aServiceType) { + this._serviceType = aServiceType; + }, + + get serviceType() { + return this._serviceType; + }, + + set domainName(aDomainName) { + this._domainName = aDomainName; + }, + + get domainName() { + return this._domainName; + }, + + set attributes(aAttributes) { + this._attributes = aAttributes; + }, + + get attributes() { + return this._attributes; + } +}; + +function TestPresentationDeviceListener() { + this.devices = {}; +} +TestPresentationDeviceListener.prototype = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener, + Ci.nsISupportsWeakReference]), + + addDevice: function(device) { this.devices[device.id] = device; }, + removeDevice: function(device) { delete this.devices[device.id]; }, + updateDevice: function(device) { this.devices[device.id] = device; }, + onSessionRequest: function(device, url, presentationId, controlChannel) {}, + + count: function() { + var size = 0, key; + for (key in this.devices) { + if (this.devices.hasOwnProperty(key)) { + ++size; + } + } + return size; + } +}; + +function createDevice(host, port, serviceName, serviceType, domainName, attributes) { + let device = new MockDNSServiceInfo(); + device.host = host || ""; + device.port = port || 0; + device.address = host || ""; + device.serviceName = serviceName || ""; + device.serviceType = serviceType || ""; + device.domainName = domainName || ""; + device.attributes = attributes || versionAttr; + return device; +} + +function registerService() { + Services.prefs.setBoolPref(PREF_DISCOVERABLE, true); + + let deferred = Promise.defer(); + + let mockObj = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]), + startDiscovery: function(serviceType, listener) {}, + registerService: function(serviceInfo, listener) { + deferred.resolve(); + this.serviceRegistered++; + return { + QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]), + cancel: function() { + this.serviceUnregistered++; + }.bind(this) + }; + }, + resolveService: function(serviceInfo, listener) {}, + serviceRegistered: 0, + serviceUnregistered: 0 + }; + let contractHook = new ContractHook(SD_CONTRACT_ID, mockObj); + let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider); + + Assert.equal(mockObj.serviceRegistered, 0); + Assert.equal(mockObj.serviceUnregistered, 0); + + // Register + provider.listener = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener, + Ci.nsISupportsWeakReference]), + addDevice: function(device) {}, + removeDevice: function(device) {}, + updateDevice: function(device) {}, + }; + + deferred.promise.then(function() { + Assert.equal(mockObj.serviceRegistered, 1); + Assert.equal(mockObj.serviceUnregistered, 0); + + // Unregister + provider.listener = null; + Assert.equal(mockObj.serviceRegistered, 1); + Assert.equal(mockObj.serviceUnregistered, 1); + + run_next_test(); + }); +} + +function noRegisterService() { + Services.prefs.setBoolPref(PREF_DISCOVERABLE, false); + + let deferred = Promise.defer(); + + let mockObj = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]), + startDiscovery: function(serviceType, listener) {}, + registerService: function(serviceInfo, listener) { + deferred.resolve(); + Assert.ok(false, "should not register service if not discoverable"); + }, + resolveService: function(serviceInfo, listener) {}, + }; + + let contractHook = new ContractHook(SD_CONTRACT_ID, mockObj); + let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider); + + // Try register + provider.listener = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener, + Ci.nsISupportsWeakReference]), + addDevice: function(device) {}, + removeDevice: function(device) {}, + updateDevice: function(device) {}, + }; + + let race = Promise.race([ + deferred.promise, + sleep(1000), + ]); + + race.then(() => { + provider.listener = null; + + run_next_test(); + }); +} + +function registerServiceDynamically() { + Services.prefs.setBoolPref(PREF_DISCOVERABLE, false); + + let deferred = Promise.defer(); + + let mockObj = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]), + startDiscovery: function(serviceType, listener) {}, + registerService: function(serviceInfo, listener) { + deferred.resolve(); + this.serviceRegistered++; + return { + QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]), + cancel: function() { + this.serviceUnregistered++; + }.bind(this) + }; + }, + resolveService: function(serviceInfo, listener) {}, + serviceRegistered: 0, + serviceUnregistered: 0 + }; + let contractHook = new ContractHook(SD_CONTRACT_ID, mockObj); + let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider); + + Assert.equal(mockObj.serviceRegistered, 0); + Assert.equal(mockObj.serviceRegistered, 0); + + // Try Register + provider.listener = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener, + Ci.nsISupportsWeakReference]), + addDevice: function(device) {}, + removeDevice: function(device) {}, + updateDevice: function(device) {}, + }; + + Assert.equal(mockObj.serviceRegistered, 0); + Assert.equal(mockObj.serviceUnregistered, 0); + + // Enable registration + Services.prefs.setBoolPref(PREF_DISCOVERABLE, true); + + deferred.promise.then(function() { + Assert.equal(mockObj.serviceRegistered, 1); + Assert.equal(mockObj.serviceUnregistered, 0); + + // Disable registration + Services.prefs.setBoolPref(PREF_DISCOVERABLE, false); + Assert.equal(mockObj.serviceRegistered, 1); + Assert.equal(mockObj.serviceUnregistered, 1); + + // Try unregister + provider.listener = null; + Assert.equal(mockObj.serviceRegistered, 1); + Assert.equal(mockObj.serviceUnregistered, 1); + + run_next_test(); + }); +} + +function addDevice() { + Services.prefs.setBoolPref(PREF_DISCOVERY, true); + + let mockDevice = createDevice("device.local", + 12345, + "service.name", + SERVICE_TYPE); + let mockObj = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]), + startDiscovery: function(serviceType, listener) { + listener.onDiscoveryStarted(serviceType); + listener.onServiceFound(createDevice("", + 0, + mockDevice.serviceName, + mockDevice.serviceType)); + return { + QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]), + cancel: function() {} + }; + }, + registerService: function(serviceInfo, listener) {}, + resolveService: function(serviceInfo, listener) { + Assert.equal(serviceInfo.serviceName, mockDevice.serviceName); + Assert.equal(serviceInfo.serviceType, mockDevice.serviceType); + listener.onServiceResolved(createDevice(mockDevice.host, + mockDevice.port, + mockDevice.serviceName, + mockDevice.serviceType)); + } + }; + + let contractHook = new ContractHook(SD_CONTRACT_ID, mockObj); + let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider); + let listener = new TestPresentationDeviceListener(); + Assert.equal(listener.count(), 0); + + // Start discovery + provider.listener = listener; + Assert.equal(listener.count(), 1); + + // Force discovery again + provider.forceDiscovery(); + Assert.equal(listener.count(), 1); + + provider.listener = null; + Assert.equal(listener.count(), 1); + + run_next_test(); +} + +function filterDevice() { + Services.prefs.setBoolPref(PREF_DISCOVERY, true); + + let mockDevice = createDevice("device.local", + 12345, + "service.name", + SERVICE_TYPE); + let mockObj = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]), + startDiscovery: function(serviceType, listener) { + listener.onDiscoveryStarted(serviceType); + listener.onServiceFound(createDevice("", + 0, + mockDevice.serviceName, + mockDevice.serviceType)); + return { + QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]), + cancel: function() {} + }; + }, + registerService: function(serviceInfo, listener) {}, + resolveService: function(serviceInfo, listener) { + Assert.equal(serviceInfo.serviceName, mockDevice.serviceName); + Assert.equal(serviceInfo.serviceType, mockDevice.serviceType); + listener.onServiceResolved(createDevice(mockDevice.host, + mockDevice.port, + mockDevice.serviceName, + mockDevice.serviceType)); + } + }; + + let contractHook = new ContractHook(SD_CONTRACT_ID, mockObj); + let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider); + let listener = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener, + Ci.nsISupportsWeakReference]), + addDevice: function(device) { + let tests = [ + { requestedUrl: "app://fling-player.gaiamobile.org/index.html", supported: true }, + { requestedUrl: "app://notification-receiver.gaiamobile.org/index.html", supported: true }, + { requestedUrl: "http://example.com", supported: true }, + { requestedUrl: "https://example.com", supported: true }, + { requestedUrl: "ftp://example.com", supported: false }, + { requestedUrl: "app://unknown-app-id", supported: false }, + { requestedUrl: "unknowSchem://example.com", supported: false }, + ]; + + for (let test of tests) { + Assert.equal(device.isRequestedUrlSupported(test.requestedUrl), test.supported); + } + + provider.listener = null; + run_next_test(); + }, + updateDevice: function() {}, + removeDevice: function() {}, + onSessionRequest: function() {}, + }; + + provider.listener = listener; +} + +function handleSessionRequest() { + Services.prefs.setBoolPref(PREF_DISCOVERY, true); + Services.prefs.setBoolPref(PREF_DISCOVERABLE, false); + + const testUrl = "http://example.com"; + const testPresentationId = "test-presentation-id"; + const testDeviceName = "test-device-name"; + + Services.prefs.setCharPref(PREF_DEVICENAME, testDeviceName); + + let mockDevice = createDevice("device.local", + 12345, + "service.name", + SERVICE_TYPE); + let mockSDObj = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]), + startDiscovery: function(serviceType, listener) { + listener.onDiscoveryStarted(serviceType); + listener.onServiceFound(createDevice("", + 0, + mockDevice.serviceName, + mockDevice.serviceType)); + return { + QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]), + cancel: function() {} + }; + }, + registerService: function(serviceInfo, listener) {}, + resolveService: function(serviceInfo, listener) { + listener.onServiceResolved(createDevice(mockDevice.host, + mockDevice.port, + mockDevice.serviceName, + mockDevice.serviceType)); + } + }; + + let mockServerObj = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlService]), + connect: function(deviceInfo) { + this.request = { + deviceInfo: deviceInfo, + }; + return { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannel]), + }; + }, + id: "", + version: LATEST_VERSION, + isCompatibleServer: function(version) { + return this.version === version; + } + }; + + let contractHookSD = new ContractHook(SD_CONTRACT_ID, mockSDObj); + let contractHookServer = new ContractHook(SERVER_CONTRACT_ID, mockServerObj); + let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider); + let listener = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener, + Ci.nsISupportsWeakReference]), + addDevice: function(device) { + this.device = device; + }, + }; + + provider.listener = listener; + + let controlChannel = listener.device.establishControlChannel(); + + Assert.equal(mockServerObj.request.deviceInfo.id, mockDevice.host); + Assert.equal(mockServerObj.request.deviceInfo.address, mockDevice.host); + Assert.equal(mockServerObj.request.deviceInfo.port, mockDevice.port); + Assert.equal(mockServerObj.id, testDeviceName); + + provider.listener = null; + + run_next_test(); +} + +function handleOnSessionRequest() { + Services.prefs.setBoolPref(PREF_DISCOVERY, true); + Services.prefs.setBoolPref(PREF_DISCOVERABLE, true); + + let mockDevice = createDevice("device.local", + 12345, + "service.name", + SERVICE_TYPE); + let mockSDObj = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]), + startDiscovery: function(serviceType, listener) { + listener.onDiscoveryStarted(serviceType); + listener.onServiceFound(createDevice("", + 0, + mockDevice.serviceName, + mockDevice.serviceType)); + return { + QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]), + cancel: function() {} + }; + }, + registerService: function(serviceInfo, listener) {}, + resolveService: function(serviceInfo, listener) { + listener.onServiceResolved(createDevice(mockDevice.host, + mockDevice.port, + mockDevice.serviceName, + mockDevice.serviceType)); + } + }; + + let mockServerObj = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlService]), + startServer: function() {}, + sessionRequest: function() {}, + close: function() {}, + id: '', + version: LATEST_VERSION, + port: 0, + listener: null, + }; + + let contractHookSD = new ContractHook(SD_CONTRACT_ID, mockSDObj); + let contractHookServer = new ContractHook(SERVER_CONTRACT_ID, mockServerObj); + let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider); + let listener = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener, + Ci.nsISupportsWeakReference]), + addDevice: function(device) {}, + removeDevice: function(device) {}, + updateDevice: function(device) {}, + onSessionRequest: function(device, url, presentationId, controlChannel) { + Assert.ok(true, "receive onSessionRequest event"); + this.request = { + deviceId: device.id, + url: url, + presentationId: presentationId, + }; + } + }; + + provider.listener = listener; + + const deviceInfo = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsITCPDeviceInfo]), + id: mockDevice.host, + address: mockDevice.host, + port: 54321, + }; + + const testUrl = "http://example.com"; + const testPresentationId = "test-presentation-id"; + const testControlChannel = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannel]), + }; + provider.QueryInterface(Ci.nsIPresentationControlServerListener) + .onSessionRequest(deviceInfo, testUrl, testPresentationId, testControlChannel); + + Assert.equal(listener.request.deviceId, deviceInfo.id); + Assert.equal(listener.request.url, testUrl); + Assert.equal(listener.request.presentationId, testPresentationId); + + provider.listener = null; + + run_next_test(); +} + +function handleOnSessionRequestFromUnknownDevice() { + Services.prefs.setBoolPref(PREF_DISCOVERY, false); + Services.prefs.setBoolPref(PREF_DISCOVERABLE, true); + + let mockSDObj = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]), + startDiscovery: function(serviceType, listener) {}, + registerService: function(serviceInfo, listener) {}, + resolveService: function(serviceInfo, listener) {} + }; + + let mockServerObj = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlService]), + startServer: function() {}, + sessionRequest: function() {}, + close: function() {}, + id: '', + version: LATEST_VERSION, + port: 0, + listener: null, + }; + + let contractHookSD = new ContractHook(SD_CONTRACT_ID, mockSDObj); + let contractHookServer = new ContractHook(SERVER_CONTRACT_ID, mockServerObj); + let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider); + let listener = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener, + Ci.nsISupportsWeakReference]), + addDevice: function(device) { + Assert.ok(false, "shouldn't create any new device"); + }, + removeDevice: function(device) { + Assert.ok(false, "shouldn't remote any device"); + }, + updateDevice: function(device) { + Assert.ok(false, "shouldn't update any device"); + }, + onSessionRequest: function(device, url, presentationId, controlChannel) { + Assert.ok(true, "receive onSessionRequest event"); + this.request = { + deviceId: device.id, + url: url, + presentationId: presentationId, + }; + } + }; + + provider.listener = listener; + + const deviceInfo = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsITCPDeviceInfo]), + id: "unknown-device.local", + address: "unknown-device.local", + port: 12345, + }; + + const testUrl = "http://example.com"; + const testPresentationId = "test-presentation-id"; + const testControlChannel = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannel]), + }; + provider.QueryInterface(Ci.nsIPresentationControlServerListener) + .onSessionRequest(deviceInfo, testUrl, testPresentationId, testControlChannel); + + Assert.equal(listener.request.deviceId, deviceInfo.id); + Assert.equal(listener.request.url, testUrl); + Assert.equal(listener.request.presentationId, testPresentationId); + + provider.listener = null; + + run_next_test(); +} + +function noAddDevice() { + Services.prefs.setBoolPref(PREF_DISCOVERY, false); + + let mockDevice = createDevice("device.local", 12345, "service.name", SERVICE_TYPE); + let mockObj = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]), + startDiscovery: function(serviceType, listener) { + Assert.ok(false, "shouldn't perform any device discovery"); + }, + registerService: function(serviceInfo, listener) {}, + resolveService: function(serviceInfo, listener) { + } + }; + let contractHook = new ContractHook(SD_CONTRACT_ID, mockObj); + + let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider); + let listener = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener, + Ci.nsISupportsWeakReference]), + addDevice: function(device) {}, + removeDevice: function(device) {}, + updateDevice: function(device) {}, + }; + provider.listener = listener; + provider.forceDiscovery(); + provider.listener = null; + + run_next_test(); +} + +function ignoreIncompatibleDevice() { + Services.prefs.setBoolPref(PREF_DISCOVERY, false); + Services.prefs.setBoolPref(PREF_DISCOVERABLE, true); + + let mockDevice = createDevice("device.local", + 12345, + "service.name", + SERVICE_TYPE); + + let deferred = Promise.defer(); + + let mockSDObj = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]), + startDiscovery: function(serviceType, listener) { + listener.onDiscoveryStarted(serviceType); + listener.onServiceFound(createDevice("", + 0, + mockDevice.serviceName, + mockDevice.serviceType)); + return { + QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]), + cancel: function() {} + }; + }, + registerService: function(serviceInfo, listener) { + deferred.resolve(); + listener.onServiceRegistered(createDevice("", + 54321, + mockDevice.serviceName, + mockDevice.serviceType)); + return { + QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]), + cancel: function() {} + }; + }, + resolveService: function(serviceInfo, listener) { + Assert.equal(serviceInfo.serviceName, mockDevice.serviceName); + Assert.equal(serviceInfo.serviceType, mockDevice.serviceType); + listener.onServiceResolved(createDevice(mockDevice.host, + mockDevice.port, + mockDevice.serviceName, + mockDevice.serviceType)); + } + }; + + let mockServerObj = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlService]), + startServer: function() { + Services.tm.currentThread.dispatch(() => { + this.listener.onServerReady(this.port, this.certFingerprint); + }, Ci.nsIThread.DISPATCH_NORMAL); + }, + sessionRequest: function() {}, + close: function() {}, + id: '', + version: LATEST_VERSION, + isCompatibleServer: function(version) { + return false; + }, + port: 54321, + certFingerprint: 'mock-cert-fingerprint', + listener: null, + }; + + let contractHookSD = new ContractHook(SD_CONTRACT_ID, mockSDObj); + let contractHookServer = new ContractHook(SERVER_CONTRACT_ID, mockServerObj); + let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider); + let listener = new TestPresentationDeviceListener(); + + // Register service + provider.listener = listener; + + deferred.promise.then(function() { + Assert.equal(mockServerObj.id, mockDevice.host); + + // Start discovery + Services.prefs.setBoolPref(PREF_DISCOVERY, true); + Assert.equal(listener.count(), 0); + + provider.listener = null; + + run_next_test(); + }); +} + +function ignoreSelfDevice() { + Services.prefs.setBoolPref(PREF_DISCOVERY, false); + Services.prefs.setBoolPref(PREF_DISCOVERABLE, true); + + let mockDevice = createDevice("device.local", + 12345, + "service.name", + SERVICE_TYPE); + + let deferred = Promise.defer(); + let mockSDObj = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]), + startDiscovery: function(serviceType, listener) { + listener.onDiscoveryStarted(serviceType); + listener.onServiceFound(createDevice("", + 0, + mockDevice.serviceName, + mockDevice.serviceType)); + return { + QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]), + cancel: function() {} + }; + }, + registerService: function(serviceInfo, listener) { + deferred.resolve(); + listener.onServiceRegistered(createDevice("", + 0, + mockDevice.serviceName, + mockDevice.serviceType)); + return { + QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]), + cancel: function() {} + }; + }, + resolveService: function(serviceInfo, listener) { + Assert.equal(serviceInfo.serviceName, mockDevice.serviceName); + Assert.equal(serviceInfo.serviceType, mockDevice.serviceType); + listener.onServiceResolved(createDevice(mockDevice.host, + mockDevice.port, + mockDevice.serviceName, + mockDevice.serviceType)); + } + }; + + let mockServerObj = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlService]), + startServer: function() { + Services.tm.currentThread.dispatch(() => { + this.listener.onServerReady(this.port, this.certFingerprint); + }, Ci.nsIThread.DISPATCH_NORMAL); + }, + sessionRequest: function() {}, + close: function() {}, + id: '', + version: LATEST_VERSION, + isCompatibleServer: function(version) { + return this.version === version; + }, + port: 54321, + certFingerprint: 'mock-cert-fingerprint', + listener: null, + }; + + let contractHookSD = new ContractHook(SD_CONTRACT_ID, mockSDObj); + let contractHookServer = new ContractHook(SERVER_CONTRACT_ID, mockServerObj); + let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider); + let listener = new TestPresentationDeviceListener(); + + // Register service + provider.listener = listener; + deferred.promise.then(() => { + Assert.equal(mockServerObj.id, mockDevice.host); + + // Start discovery + Services.prefs.setBoolPref(PREF_DISCOVERY, true); + Assert.equal(listener.count(), 0); + + provider.listener = null; + + run_next_test(); + }); +} + +function addDeviceDynamically() { + Services.prefs.setBoolPref(PREF_DISCOVERY, false); + + let mockDevice = createDevice("device.local", + 12345, + "service.name", + SERVICE_TYPE); + let mockObj = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]), + startDiscovery: function(serviceType, listener) { + listener.onDiscoveryStarted(serviceType); + listener.onServiceFound(createDevice("", + 0, + mockDevice.serviceName, + mockDevice.serviceType)); + return { + QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]), + cancel: function() {} + }; + }, + registerService: function(serviceInfo, listener) {}, + resolveService: function(serviceInfo, listener) { + Assert.equal(serviceInfo.serviceName, mockDevice.serviceName); + Assert.equal(serviceInfo.serviceType, mockDevice.serviceType); + listener.onServiceResolved(createDevice(mockDevice.host, + mockDevice.port, + mockDevice.serviceName, + mockDevice.serviceType)); + } + }; + + let contractHook = new ContractHook(SD_CONTRACT_ID, mockObj); + let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider); + let listener = new TestPresentationDeviceListener(); + provider.listener = listener; + Assert.equal(listener.count(), 0); + + // Enable discovery + Services.prefs.setBoolPref(PREF_DISCOVERY, true); + Assert.equal(listener.count(), 1); + + // Try discovery again + provider.forceDiscovery(); + Assert.equal(listener.count(), 1); + + // Try discovery once more + Services.prefs.setBoolPref(PREF_DISCOVERY, false); + Services.prefs.setBoolPref(PREF_DISCOVERY, true); + provider.forceDiscovery(); + Assert.equal(listener.count(), 1); + + provider.listener = null; + + run_next_test(); +} + +function updateDevice() { + Services.prefs.setBoolPref(PREF_DISCOVERY, true); + + let mockDevice1 = createDevice("A.local", 12345, "N1", SERVICE_TYPE); + let mockDevice2 = createDevice("A.local", 23456, "N2", SERVICE_TYPE); + + let mockObj = { + discovered: false, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]), + startDiscovery: function(serviceType, listener) { + listener.onDiscoveryStarted(serviceType); + + if (!this.discovered) { + listener.onServiceFound(mockDevice1); + } else { + listener.onServiceFound(mockDevice2); + } + this.discovered = true; + + return { + QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]), + cancel: function() { + listener.onDiscoveryStopped(serviceType); + } + }; + }, + registerService: function(serviceInfo, listener) {}, + resolveService: function(serviceInfo, listener) { + Assert.equal(serviceInfo.serviceType, SERVICE_TYPE); + if (serviceInfo.serviceName == "N1") { + listener.onServiceResolved(mockDevice1); + } else if (serviceInfo.serviceName == "N2") { + listener.onServiceResolved(mockDevice2); + } else { + Assert.ok(false); + } + } + }; + + let contractHook = new ContractHook(SD_CONTRACT_ID, mockObj); + let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider); + let listener = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener, + Ci.nsISupportsWeakReference]), + + addDevice: function(device) { + Assert.ok(!this.isDeviceAdded); + Assert.equal(device.id, mockDevice1.host); + Assert.equal(device.name, mockDevice1.serviceName); + this.isDeviceAdded = true; + }, + removeDevice: function(device) { Assert.ok(false); }, + updateDevice: function(device) { + Assert.ok(!this.isDeviceUpdated); + Assert.equal(device.id, mockDevice2.host); + Assert.equal(device.name, mockDevice2.serviceName); + this.isDeviceUpdated = true; + }, + + isDeviceAdded: false, + isDeviceUpdated: false + }; + Assert.equal(listener.isDeviceAdded, false); + Assert.equal(listener.isDeviceUpdated, false); + + // Start discovery + provider.listener = listener; // discover: N1 + + Assert.equal(listener.isDeviceAdded, true); + Assert.equal(listener.isDeviceUpdated, false); + + // temporarily disable to stop discovery and re-enable + Services.prefs.setBoolPref(PREF_DISCOVERY, false); + Services.prefs.setBoolPref(PREF_DISCOVERY, true); + + provider.forceDiscovery(); // discover: N2 + + Assert.equal(listener.isDeviceAdded, true); + Assert.equal(listener.isDeviceUpdated, true); + + provider.listener = null; + + run_next_test(); +} + +function diffDiscovery() { + Services.prefs.setBoolPref(PREF_DISCOVERY, true); + + let mockDevice1 = createDevice("A.local", 12345, "N1", SERVICE_TYPE); + let mockDevice2 = createDevice("B.local", 23456, "N2", SERVICE_TYPE); + let mockDevice3 = createDevice("C.local", 45678, "N3", SERVICE_TYPE); + + let mockObj = { + discovered: false, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]), + startDiscovery: function(serviceType, listener) { + listener.onDiscoveryStarted(serviceType); + + if (!this.discovered) { + listener.onServiceFound(mockDevice1); + listener.onServiceFound(mockDevice2); + } else { + listener.onServiceFound(mockDevice1); + listener.onServiceFound(mockDevice3); + } + this.discovered = true; + + return { + QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]), + cancel: function() { + listener.onDiscoveryStopped(serviceType); + } + }; + }, + registerService: function(serviceInfo, listener) {}, + resolveService: function(serviceInfo, listener) { + Assert.equal(serviceInfo.serviceType, SERVICE_TYPE); + if (serviceInfo.serviceName == "N1") { + listener.onServiceResolved(mockDevice1); + } else if (serviceInfo.serviceName == "N2") { + listener.onServiceResolved(mockDevice2); + } else if (serviceInfo.serviceName == "N3") { + listener.onServiceResolved(mockDevice3); + } else { + Assert.ok(false); + } + } + }; + + let contractHook = new ContractHook(SD_CONTRACT_ID, mockObj); + let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider); + let listener = new TestPresentationDeviceListener(); + Assert.equal(listener.count(), 0); + + // Start discovery + provider.listener = listener; // discover: N1, N2 + Assert.equal(listener.count(), 2); + Assert.equal(listener.devices['A.local'].name, mockDevice1.serviceName); + Assert.equal(listener.devices['B.local'].name, mockDevice2.serviceName); + Assert.ok(!listener.devices['C.local']); + + // temporarily disable to stop discovery and re-enable + Services.prefs.setBoolPref(PREF_DISCOVERY, false); + Services.prefs.setBoolPref(PREF_DISCOVERY, true); + + provider.forceDiscovery(); // discover: N1, N3, going to remove: N2 + Assert.equal(listener.count(), 3); + Assert.equal(listener.devices['A.local'].name, mockDevice1.serviceName); + Assert.equal(listener.devices['B.local'].name, mockDevice2.serviceName); + Assert.equal(listener.devices['C.local'].name, mockDevice3.serviceName); + + // temporarily disable to stop discovery and re-enable + Services.prefs.setBoolPref(PREF_DISCOVERY, false); + Services.prefs.setBoolPref(PREF_DISCOVERY, true); + + provider.forceDiscovery(); // discover: N1, N3, remove: N2 + Assert.equal(listener.count(), 2); + Assert.equal(listener.devices['A.local'].name, mockDevice1.serviceName); + Assert.ok(!listener.devices['B.local']); + Assert.equal(listener.devices['C.local'].name, mockDevice3.serviceName); + + provider.listener = null; + + run_next_test(); +} + +function serverClosed() { + Services.prefs.setBoolPref(PREF_DISCOVERABLE, true); + Services.prefs.setBoolPref(PREF_DISCOVERY, true); + + let mockDevice = createDevice("device.local", + 12345, + "service.name", + SERVICE_TYPE); + + let mockObj = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]), + startDiscovery: function(serviceType, listener) { + listener.onDiscoveryStarted(serviceType); + listener.onServiceFound(createDevice("", + 0, + mockDevice.serviceName, + mockDevice.serviceType)); + return { + QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]), + cancel: function() {} + }; + }, + registerService: function(serviceInfo, listener) { + this.serviceRegistered++; + return { + QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]), + cancel: function() { + this.serviceUnregistered++; + }.bind(this) + }; + }, + resolveService: function(serviceInfo, listener) { + Assert.equal(serviceInfo.serviceName, mockDevice.serviceName); + Assert.equal(serviceInfo.serviceType, mockDevice.serviceType); + listener.onServiceResolved(createDevice(mockDevice.host, + mockDevice.port, + mockDevice.serviceName, + mockDevice.serviceType)); + }, + serviceRegistered: 0, + serviceUnregistered: 0 + }; + let contractHook = new ContractHook(SD_CONTRACT_ID, mockObj); + let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider); + + Assert.equal(mockObj.serviceRegistered, 0); + Assert.equal(mockObj.serviceUnregistered, 0); + + // Register + let listener = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener, + Ci.nsISupportsWeakReference]), + addDevice: function(device) { this.devices.push(device); }, + removeDevice: function(device) {}, + updateDevice: function(device) {}, + devices: [] + }; + Assert.equal(listener.devices.length, 0); + + provider.listener = listener; + Assert.equal(mockObj.serviceRegistered, 1); + Assert.equal(mockObj.serviceUnregistered, 0); + Assert.equal(listener.devices.length, 1); + + let serverListener = provider.QueryInterface(Ci.nsIPresentationControlServerListener); + let randomPort = 9527; + serverListener.onServerReady(randomPort, ''); + + Assert.equal(mockObj.serviceRegistered, 2); + Assert.equal(mockObj.serviceUnregistered, 1); + Assert.equal(listener.devices.length, 1); + + // Unregister + provider.listener = null; + Assert.equal(mockObj.serviceRegistered, 2); + Assert.equal(mockObj.serviceUnregistered, 2); + Assert.equal(listener.devices.length, 1); + + run_next_test(); +} + +function serverRetry() { + Services.prefs.setBoolPref(PREF_DISCOVERY, false); + Services.prefs.setBoolPref(PREF_DISCOVERABLE, true); + + let isRetrying = false; + + let mockSDObj = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]), + startDiscovery: function(serviceType, listener) {}, + registerService: function(serviceInfo, listener) { + Assert.ok(isRetrying, "register service after retrying startServer"); + provider.listener = null; + run_next_test(); + }, + resolveService: function(serviceInfo, listener) {} + }; + + let mockServerObj = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlService]), + startServer: function(encrypted, port) { + if (!isRetrying) { + isRetrying = true; + Services.tm.currentThread.dispatch(() => { + this.listener.onServerStopped(Cr.NS_ERROR_FAILURE); + }, Ci.nsIThread.DISPATCH_NORMAL); + } else { + this.port = 54321; + Services.tm.currentThread.dispatch(() => { + this.listener.onServerReady(this.port, this.certFingerprint); + }, Ci.nsIThread.DISPATCH_NORMAL); + } + }, + sessionRequest: function() {}, + close: function() {}, + id: '', + version: LATEST_VERSION, + port: 0, + certFingerprint: 'mock-cert-fingerprint', + listener: null, + }; + + let contractHookSD = new ContractHook(SD_CONTRACT_ID, mockSDObj); + let contractHookServer = new ContractHook(SERVER_CONTRACT_ID, mockServerObj); + let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider); + let listener = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener, + Ci.nsISupportsWeakReference]), + addDevice: function(device) {}, + removeDevice: function(device) {}, + updateDevice: function(device) {}, + onSessionRequest: function(device, url, presentationId, controlChannel) {} + }; + + provider.listener = listener; +} + +function run_test() { + // Need profile dir to store the key / cert + do_get_profile(); + // Ensure PSM is initialized + Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports); + + let infoHook = new ContractHook(INFO_CONTRACT_ID, MockDNSServiceInfo); + + do_register_cleanup(() => { + Services.prefs.clearUserPref(PREF_DISCOVERY); + Services.prefs.clearUserPref(PREF_DISCOVERABLE); + }); + + add_test(registerService); + add_test(noRegisterService); + add_test(registerServiceDynamically); + add_test(addDevice); + add_test(filterDevice); + add_test(handleSessionRequest); + add_test(handleOnSessionRequest); + add_test(handleOnSessionRequestFromUnknownDevice); + add_test(noAddDevice); + add_test(ignoreIncompatibleDevice); + add_test(ignoreSelfDevice); + add_test(addDeviceDynamically); + add_test(updateDevice); + add_test(diffDiscovery); + add_test(serverClosed); + add_test(serverRetry); + + run_next_test(); +} diff --git a/dom/presentation/tests/xpcshell/test_presentation_device_manager.js b/dom/presentation/tests/xpcshell/test_presentation_device_manager.js new file mode 100644 index 000000000..68f07df65 --- /dev/null +++ b/dom/presentation/tests/xpcshell/test_presentation_device_manager.js @@ -0,0 +1,244 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +'use strict'; + +const { classes: Cc, interfaces: Ci, utils: Cu } = Components; + +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); +Cu.import('resource://gre/modules/Services.jsm'); + +const manager = Cc['@mozilla.org/presentation-device/manager;1'] + .getService(Ci.nsIPresentationDeviceManager); + +function TestPresentationDevice() {} + + +function TestPresentationControlChannel() {} + +TestPresentationControlChannel.prototype = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannel]), + sendOffer: function(offer) {}, + sendAnswer: function(answer) {}, + disconnect: function() {}, + launch: function() {}, + terminate: function() {}, + reconnect: function() {}, + set listener(listener) {}, + get listener() {}, +}; + +var testProvider = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceProvider]), + + forceDiscovery: function() { + }, + set listener(listener) { + }, + get listener() { + }, +}; + +const forbiddenRequestedUrl = 'http://example.com'; +var testDevice = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDevice]), + id: 'id', + name: 'name', + type: 'type', + establishControlChannel: function(url, presentationId) { + return null; + }, + disconnect: function() {}, + isRequestedUrlSupported: function(requestedUrl) { + return forbiddenRequestedUrl !== requestedUrl; + }, +}; + +function addProvider() { + Object.defineProperty(testProvider, 'listener', { + configurable: true, + set: function(listener) { + Assert.strictEqual(listener, manager, 'listener setter is invoked by PresentationDeviceManager'); + delete testProvider.listener; + run_next_test(); + }, + }); + manager.addDeviceProvider(testProvider); +} + +function forceDiscovery() { + testProvider.forceDiscovery = function() { + testProvider.forceDiscovery = function() {}; + Assert.ok(true, 'forceDiscovery is invoked by PresentationDeviceManager'); + run_next_test(); + }; + manager.forceDiscovery(); +} + +function addDevice() { + Services.obs.addObserver(function observer(subject, topic, data) { + Services.obs.removeObserver(observer, topic); + + let updatedDevice = subject.QueryInterface(Ci.nsIPresentationDevice); + Assert.equal(updatedDevice.id, testDevice.id, 'expected device id'); + Assert.equal(updatedDevice.name, testDevice.name, 'expected device name'); + Assert.equal(updatedDevice.type, testDevice.type, 'expected device type'); + Assert.equal(data, 'add', 'expected update type'); + + Assert.ok(manager.deviceAvailable, 'device is available'); + + let devices = manager.getAvailableDevices(); + Assert.equal(devices.length, 1, 'expect 1 available device'); + + let device = devices.queryElementAt(0, Ci.nsIPresentationDevice); + Assert.equal(device.id, testDevice.id, 'expected device id'); + Assert.equal(device.name, testDevice.name, 'expected device name'); + Assert.equal(device.type, testDevice.type, 'expected device type'); + + run_next_test(); + }, 'presentation-device-change', false); + manager.QueryInterface(Ci.nsIPresentationDeviceListener).addDevice(testDevice); +} + +function updateDevice() { + Services.obs.addObserver(function observer(subject, topic, data) { + Services.obs.removeObserver(observer, topic); + + let updatedDevice = subject.QueryInterface(Ci.nsIPresentationDevice); + Assert.equal(updatedDevice.id, testDevice.id, 'expected device id'); + Assert.equal(updatedDevice.name, testDevice.name, 'expected device name'); + Assert.equal(updatedDevice.type, testDevice.type, 'expected device type'); + Assert.equal(data, 'update', 'expected update type'); + + Assert.ok(manager.deviceAvailable, 'device is available'); + + let devices = manager.getAvailableDevices(); + Assert.equal(devices.length, 1, 'expect 1 available device'); + + let device = devices.queryElementAt(0, Ci.nsIPresentationDevice); + Assert.equal(device.id, testDevice.id, 'expected device id'); + Assert.equal(device.name, testDevice.name, 'expected name after device update'); + Assert.equal(device.type, testDevice.type, 'expected device type'); + + run_next_test(); + }, 'presentation-device-change', false); + testDevice.name = 'updated-name'; + manager.QueryInterface(Ci.nsIPresentationDeviceListener).updateDevice(testDevice); +} + +function filterDevice() { + let presentationUrls = Cc['@mozilla.org/array;1'].createInstance(Ci.nsIMutableArray); + let url = Cc['@mozilla.org/supports-string;1'].createInstance(Ci.nsISupportsString); + url.data = forbiddenRequestedUrl; + presentationUrls.appendElement(url, false); + let devices = manager.getAvailableDevices(presentationUrls); + Assert.equal(devices.length, 0, 'expect 0 available device for example.com'); + run_next_test(); +} + +function sessionRequest() { + 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-session-request', false); + manager.QueryInterface(Ci.nsIPresentationDeviceListener) + .onSessionRequest(testDevice, testUrl, testPresentationId, testControlChannel); +} + +function terminateRequest() { + let testUrl = 'http://www.example.org/'; + let testPresentationId = 'test-presentation-id'; + let testControlChannel = new TestPresentationControlChannel(); + let testIsFromReceiver = true; + Services.obs.addObserver(function observer(subject, topic, data) { + Services.obs.removeObserver(observer, topic); + + let request = subject.QueryInterface(Ci.nsIPresentationTerminateRequest); + + Assert.equal(request.device.id, testDevice.id, 'expected device'); + Assert.equal(request.presentationId, testPresentationId, 'expected presentation Id'); + Assert.equal(request.isFromReceiver, testIsFromReceiver, 'expected isFromReceiver'); + + run_next_test(); + }, 'presentation-terminate-request', false); + manager.QueryInterface(Ci.nsIPresentationDeviceListener) + .onTerminateRequest(testDevice, testPresentationId, + 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); + + let updatedDevice = subject.QueryInterface(Ci.nsIPresentationDevice); + Assert.equal(updatedDevice.id, testDevice.id, 'expected device id'); + Assert.equal(updatedDevice.name, testDevice.name, 'expected device name'); + Assert.equal(updatedDevice.type, testDevice.type, 'expected device type'); + Assert.equal(data, 'remove', 'expected update type'); + + Assert.ok(!manager.deviceAvailable, 'device is not available'); + + let devices = manager.getAvailableDevices(); + Assert.equal(devices.length, 0, 'expect 0 available device'); + + run_next_test(); + }, 'presentation-device-change', false); + manager.QueryInterface(Ci.nsIPresentationDeviceListener).removeDevice(testDevice); +} + +function removeProvider() { + Object.defineProperty(testProvider, 'listener', { + configurable: true, + set: function(listener) { + Assert.strictEqual(listener, null, 'unsetListener is invoked by PresentationDeviceManager'); + delete testProvider.listener; + run_next_test(); + }, + }); + manager.removeDeviceProvider(testProvider); +} + +add_test(addProvider); +add_test(forceDiscovery); +add_test(addDevice); +add_test(updateDevice); +add_test(filterDevice); +add_test(sessionRequest); +add_test(terminateRequest); +add_test(reconnectRequest); +add_test(removeDevice); +add_test(removeProvider); + +function run_test() { + run_next_test(); +} diff --git a/dom/presentation/tests/xpcshell/test_presentation_session_transport.js b/dom/presentation/tests/xpcshell/test_presentation_session_transport.js new file mode 100644 index 000000000..8e207bc22 --- /dev/null +++ b/dom/presentation/tests/xpcshell/test_presentation_session_transport.js @@ -0,0 +1,198 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +'use strict'; + +const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr, Constructor: CC } = Components; +const ServerSocket = CC("@mozilla.org/network/server-socket;1", + "nsIServerSocket", + "init"); + +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); +Cu.import('resource://gre/modules/Services.jsm'); + +var testServer = null; +var clientTransport = null; +var serverTransport = null; + +var clientBuilder = null; +var serverBuilder = null; + +const clientMessage = "Client Message"; +const serverMessage = "Server Message"; + +const address = Cc["@mozilla.org/supports-cstring;1"] + .createInstance(Ci.nsISupportsCString); +address.data = "127.0.0.1"; +const addresses = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray); +addresses.appendElement(address, false); + +const serverChannelDescription = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationChannelDescription]), + type: 1, + tcpAddress: addresses, +}; + +var isClientReady = false; +var isServerReady = false; +var isClientClosed = false; +var isServerClosed = false; + +const clientCallback = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransportCallback]), + notifyTransportReady: function () { + Assert.ok(true, "Client transport ready."); + + isClientReady = true; + if (isClientReady && isServerReady) { + run_next_test(); + } + }, + notifyTransportClosed: function (aReason) { + Assert.ok(true, "Client transport is closed."); + + isClientClosed = true; + if (isClientClosed && isServerClosed) { + run_next_test(); + } + }, + notifyData: function(aData) { + Assert.equal(aData, serverMessage, "Client transport receives data."); + run_next_test(); + }, +}; + +const serverCallback = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransportCallback]), + notifyTransportReady: function () { + Assert.ok(true, "Server transport ready."); + + isServerReady = true; + if (isClientReady && isServerReady) { + run_next_test(); + } + }, + notifyTransportClosed: function (aReason) { + Assert.ok(true, "Server transport is closed."); + + isServerClosed = true; + if (isClientClosed && isServerClosed) { + run_next_test(); + } + }, + notifyData: function(aData) { + Assert.equal(aData, clientMessage, "Server transport receives data."); + run_next_test(); + }, +}; + +const clientListener = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransportBuilderListener]), + onSessionTransport(aTransport) { + Assert.ok(true, "Client Transport is built."); + clientTransport = aTransport; + clientTransport.callback = clientCallback; + + if (serverTransport) { + run_next_test(); + } + } +} + +const serverListener = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransportBuilderListener]), + onSessionTransport(aTransport) { + Assert.ok(true, "Server Transport is built."); + serverTransport = aTransport; + serverTransport.callback = serverCallback; + serverTransport.enableDataNotification(); + + if (clientTransport) { + run_next_test(); + } + } +} + +function TestServer() { + this.serverSocket = ServerSocket(-1, true, -1); + this.serverSocket.asyncListen(this) +} + +TestServer.prototype = { + onSocketAccepted: function(aSocket, aTransport) { + print("Test server gets a client connection."); + serverBuilder = Cc["@mozilla.org/presentation/presentationtcpsessiontransport;1"] + .createInstance(Ci.nsIPresentationTCPSessionTransportBuilder); + serverBuilder.buildTCPSenderTransport(aTransport, serverListener); + }, + onStopListening: function(aSocket) { + print("Test server stops listening."); + }, + close: function() { + if (this.serverSocket) { + this.serverSocket.close(); + this.serverSocket = null; + } + } +}; + +// Set up the transport connection and ensure |notifyTransportReady| triggered +// at both sides. +function setup() { + clientBuilder = Cc["@mozilla.org/presentation/presentationtcpsessiontransport;1"] + .createInstance(Ci.nsIPresentationTCPSessionTransportBuilder); + clientBuilder.buildTCPReceiverTransport(serverChannelDescription, clientListener); +} + +// Test |selfAddress| attribute of |nsIPresentationSessionTransport|. +function selfAddress() { + var serverSelfAddress = serverTransport.selfAddress; + Assert.equal(serverSelfAddress.address, address.data, "The self address of server transport should be set."); + Assert.equal(serverSelfAddress.port, testServer.serverSocket.port, "The port of server transport should be set."); + + var clientSelfAddress = clientTransport.selfAddress; + Assert.ok(clientSelfAddress.address, "The self address of client transport should be set."); + Assert.ok(clientSelfAddress.port, "The port of client transport should be set."); + + run_next_test(); +} + +// Test the client sends a message and then a corresponding notification gets +// triggered at the server side. +function clientSendMessage() { + clientTransport.send(clientMessage); +} + +// Test the server sends a message an then a corresponding notification gets +// triggered at the client side. +function serverSendMessage() { + serverTransport.send(serverMessage); + // The client enables data notification even after the incoming message has + // been sent, and should still be able to consume it. + clientTransport.enableDataNotification(); +} + +function transportClose() { + clientTransport.close(Cr.NS_OK); +} + +function shutdown() { + testServer.close(); + run_next_test(); +} + +add_test(setup); +add_test(selfAddress); +add_test(clientSendMessage); +add_test(serverSendMessage); +add_test(transportClose); +add_test(shutdown); + +function run_test() { + testServer = new TestServer(); + // Get the port of the test server. + serverChannelDescription.tcpPort = testServer.serverSocket.port; + + run_next_test(); +} diff --git a/dom/presentation/tests/xpcshell/test_presentation_state_machine.js b/dom/presentation/tests/xpcshell/test_presentation_state_machine.js new file mode 100644 index 000000000..fcaf34da6 --- /dev/null +++ b/dom/presentation/tests/xpcshell/test_presentation_state_machine.js @@ -0,0 +1,236 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +/* jshint esnext:true, globalstrict:true, moz:true, undef:true, unused:true */ +/* globals Components,Assert,run_next_test,add_test,do_execute_soon */ + +'use strict'; + +const { utils: Cu, results: Cr } = Components; + +/* globals ControllerStateMachine */ +Cu.import('resource://gre/modules/presentation/ControllerStateMachine.jsm'); +/* globals ReceiverStateMachine */ +Cu.import('resource://gre/modules/presentation/ReceiverStateMachine.jsm'); +/* globals State */ +Cu.import('resource://gre/modules/presentation/StateMachineHelper.jsm'); + +const testControllerId = 'test-controller-id'; +const testPresentationId = 'test-presentation-id'; +const testUrl = 'http://example.org'; + +let mockControllerChannel = {}; +let mockReceiverChannel = {}; + +let controllerState = new ControllerStateMachine(mockControllerChannel, testControllerId); +let receiverState = new ReceiverStateMachine(mockReceiverChannel); + +mockControllerChannel.sendCommand = function(command) { + do_execute_soon(function() { + receiverState.onCommand(command); + }); +}; + +mockReceiverChannel.sendCommand = function(command) { + do_execute_soon(function() { + controllerState.onCommand(command); + }); +}; + +function connect() { + Assert.equal(controllerState.state, State.INIT, 'controller in init state'); + Assert.equal(receiverState.state, State.INIT, 'receiver in init state'); + // step 1: underlying connection is ready + controllerState.onChannelReady(); + Assert.equal(controllerState.state, State.CONNECTING, 'controller in connecting state'); + receiverState.onChannelReady(); + Assert.equal(receiverState.state, State.CONNECTING, 'receiver in connecting state'); + + // step 2: receiver reply to connect command + mockReceiverChannel.notifyDeviceConnected = function(deviceId) { + Assert.equal(deviceId, testControllerId, 'receiver connect to mock controller'); + Assert.equal(receiverState.state, State.CONNECTED, 'receiver in connected state'); + + // step 3: controller receive connect-ack command + mockControllerChannel.notifyDeviceConnected = function() { + Assert.equal(controllerState.state, State.CONNECTED, 'controller in connected state'); + run_next_test(); + }; + }; +} + +function launch() { + Assert.equal(controllerState.state, State.CONNECTED, 'controller in connected state'); + Assert.equal(receiverState.state, State.CONNECTED, 'receiver in connected state'); + + controllerState.launch(testPresentationId, testUrl); + mockReceiverChannel.notifyLaunch = function(presentationId, url) { + Assert.equal(receiverState.state, State.CONNECTED, 'receiver in connected state'); + Assert.equal(presentationId, testPresentationId, 'expected presentationId received'); + Assert.equal(url, testUrl, 'expected url received'); + + mockControllerChannel.notifyLaunch = function(presentationId) { + Assert.equal(controllerState.state, State.CONNECTED, 'controller in connected state'); + Assert.equal(presentationId, testPresentationId, 'expected presentationId received from ack'); + + run_next_test(); + }; + }; +} + +function terminateByController() { + Assert.equal(controllerState.state, State.CONNECTED, 'controller in connected state'); + Assert.equal(receiverState.state, State.CONNECTED, 'receiver in connected state'); + + controllerState.terminate(testPresentationId); + mockReceiverChannel.notifyTerminate = function(presentationId) { + Assert.equal(receiverState.state, State.CONNECTED, 'receiver in connected state'); + Assert.equal(presentationId, testPresentationId, 'expected presentationId received'); + + mockControllerChannel.notifyTerminate = function(presentationId) { + Assert.equal(controllerState.state, State.CONNECTED, 'controller in connected state'); + Assert.equal(presentationId, testPresentationId, 'expected presentationId received from ack'); + + run_next_test(); + }; + + receiverState.terminateAck(presentationId); + }; +} + +function terminateByReceiver() { + Assert.equal(controllerState.state, State.CONNECTED, 'controller in connected state'); + Assert.equal(receiverState.state, State.CONNECTED, 'receiver in connected state'); + + receiverState.terminate(testPresentationId); + mockControllerChannel.notifyTerminate = function(presentationId) { + Assert.equal(controllerState.state, State.CONNECTED, 'controller in connected state'); + Assert.equal(presentationId, testPresentationId, 'expected presentationId received'); + + mockReceiverChannel.notifyTerminate = function(presentationId) { + Assert.equal(receiverState.state, State.CONNECTED, 'receiver in connected state'); + Assert.equal(presentationId, testPresentationId, 'expected presentationId received from ack'); + run_next_test(); + }; + + controllerState.terminateAck(presentationId); + }; +} + +function exchangeSDP() { + Assert.equal(controllerState.state, State.CONNECTED, 'controller in connected state'); + Assert.equal(receiverState.state, State.CONNECTED, 'receiver in connected state'); + + const testOffer = 'test-offer'; + const testAnswer = 'test-answer'; + const testIceCandidate = 'test-ice-candidate'; + controllerState.sendOffer(testOffer); + mockReceiverChannel.notifyOffer = function(offer) { + Assert.equal(offer, testOffer, 'expected offer received'); + + receiverState.sendAnswer(testAnswer); + mockControllerChannel.notifyAnswer = function(answer) { + Assert.equal(answer, testAnswer, 'expected answer received'); + + controllerState.updateIceCandidate(testIceCandidate); + mockReceiverChannel.notifyIceCandidate = function(candidate) { + Assert.equal(candidate, testIceCandidate, 'expected ice candidate received in receiver'); + + receiverState.updateIceCandidate(testIceCandidate); + mockControllerChannel.notifyIceCandidate = function(candidate) { + Assert.equal(candidate, testIceCandidate, 'expected ice candidate received in controller'); + + run_next_test(); + }; + }; + }; + }; +} + +function disconnect() { + // step 1: controller send disconnect command + controllerState.onChannelClosed(Cr.NS_OK, false); + Assert.equal(controllerState.state, State.CLOSING, 'controller in closing state'); + + mockReceiverChannel.notifyDisconnected = function(reason) { + Assert.equal(reason, Cr.NS_OK, 'receive close reason'); + Assert.equal(receiverState.state, State.CLOSED, 'receiver in closed state'); + + receiverState.onChannelClosed(Cr.NS_OK, true); + Assert.equal(receiverState.state, State.CLOSED, 'receiver in closed state'); + + mockControllerChannel.notifyDisconnected = function(reason) { + Assert.equal(reason, Cr.NS_OK, 'receive close reason'); + Assert.equal(controllerState.state, State.CLOSED, 'controller in closed state'); + + run_next_test(); + }; + controllerState.onChannelClosed(Cr.NS_OK, true); + }; +} + +function receiverDisconnect() { + // initial state: controller and receiver are connected + controllerState.state = State.CONNECTED; + receiverState.state = State.CONNECTED; + + // step 1: controller send disconnect command + receiverState.onChannelClosed(Cr.NS_OK, false); + Assert.equal(receiverState.state, State.CLOSING, 'receiver in closing state'); + + mockControllerChannel.notifyDisconnected = function(reason) { + Assert.equal(reason, Cr.NS_OK, 'receive close reason'); + Assert.equal(controllerState.state, State.CLOSED, 'controller in closed state'); + + controllerState.onChannelClosed(Cr.NS_OK, true); + Assert.equal(controllerState.state, State.CLOSED, 'controller in closed state'); + + mockReceiverChannel.notifyDisconnected = function(reason) { + Assert.equal(reason, Cr.NS_OK, 'receive close reason'); + Assert.equal(receiverState.state, State.CLOSED, 'receiver in closed state'); + + run_next_test(); + }; + receiverState.onChannelClosed(Cr.NS_OK, true); + }; +} + +function abnormalDisconnect() { + // initial state: controller and receiver are connected + controllerState.state = State.CONNECTED; + receiverState.state = State.CONNECTED; + + const testErrorReason = Cr.NS_ERROR_FAILURE; + // step 1: controller send disconnect command + controllerState.onChannelClosed(testErrorReason, false); + Assert.equal(controllerState.state, State.CLOSING, 'controller in closing state'); + + mockReceiverChannel.notifyDisconnected = function(reason) { + Assert.equal(reason, testErrorReason, 'receive abnormal close reason'); + Assert.equal(receiverState.state, State.CLOSED, 'receiver in closed state'); + + receiverState.onChannelClosed(Cr.NS_OK, true); + Assert.equal(receiverState.state, State.CLOSED, 'receiver in closed state'); + + mockControllerChannel.notifyDisconnected = function(reason) { + Assert.equal(reason, testErrorReason, 'receive abnormal close reason'); + Assert.equal(controllerState.state, State.CLOSED, 'controller in closed state'); + + run_next_test(); + }; + controllerState.onChannelClosed(Cr.NS_OK, true); + }; +} + +add_test(connect); +add_test(launch); +add_test(terminateByController); +add_test(terminateByReceiver); +add_test(exchangeSDP); +add_test(disconnect); +add_test(receiverDisconnect); +add_test(abnormalDisconnect); + +function run_test() { // jshint ignore:line + run_next_test(); +} diff --git a/dom/presentation/tests/xpcshell/test_tcp_control_channel.js b/dom/presentation/tests/xpcshell/test_tcp_control_channel.js new file mode 100644 index 000000000..5f3df584d --- /dev/null +++ b/dom/presentation/tests/xpcshell/test_tcp_control_channel.js @@ -0,0 +1,398 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +'use strict'; + +const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; + +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); +Cu.import('resource://gre/modules/Services.jsm'); + +var pcs; + +// Call |run_next_test| if all functions in |names| are called +function makeJointSuccess(names) { + let funcs = {}, successCount = 0; + names.forEach(function(name) { + funcs[name] = function() { + do_print('got expected: ' + name); + if (++successCount === names.length) + run_next_test(); + }; + }); + return funcs; +} + +function TestDescription(aType, aTcpAddress, aTcpPort) { + this.type = aType; + this.tcpAddress = Cc["@mozilla.org/array;1"] + .createInstance(Ci.nsIMutableArray); + for (let address of aTcpAddress) { + let wrapper = Cc["@mozilla.org/supports-cstring;1"] + .createInstance(Ci.nsISupportsCString); + wrapper.data = address; + this.tcpAddress.appendElement(wrapper, false); + } + this.tcpPort = aTcpPort; +} + +TestDescription.prototype = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationChannelDescription]), +} + +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; + +// controller's presentation channel description +const ANSWER_ADDRESS = '192.168.321.321'; +const ANSWER_PORT = 321; + +function loopOfferAnser() { + pcs = Cc["@mozilla.org/presentation/control-service;1"] + .createInstance(Ci.nsIPresentationControlService); + pcs.id = 'controllerID'; + pcs.listener = { + onServerReady: function() { + testPresentationServer(); + } + }; + + // First run with TLS enabled. + pcs.startServer(true, PRESENTER_CONTROL_CHANNEL_PORT); +} + + +function testPresentationServer() { + let yayFuncs = makeJointSuccess(['controllerControlChannelClose', + 'presenterControlChannelClose', + 'controllerControlChannelReconnect', + 'presenterControlChannelReconnect']); + let presenterControlChannel; + + pcs.listener = { + + onSessionRequest: function(deviceInfo, url, presentationId, 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'); + + presenterControlChannel.listener = { + status: 'created', + onOffer: function(aOffer) { + Assert.equal(this.status, 'opened', '1. presenterControlChannel: get offer, send answer'); + this.status = 'onOffer'; + + let offer = aOffer.QueryInterface(Ci.nsIPresentationChannelDescription); + Assert.strictEqual(offer.tcpAddress.queryElementAt(0,Ci.nsISupportsCString).data, + OFFER_ADDRESS, + 'expected offer address array'); + Assert.equal(offer.tcpPort, OFFER_PORT, 'expected offer port'); + try { + let tcpType = Ci.nsIPresentationChannelDescription.TYPE_TCP; + let answer = new TestDescription(tcpType, [ANSWER_ADDRESS], ANSWER_PORT); + presenterControlChannel.sendAnswer(answer); + } catch (e) { + Assert.ok(false, 'sending answer fails' + e); + } + }, + onAnswer: function(aAnswer) { + Assert.ok(false, 'get answer'); + }, + onIceCandidate: function(aCandidate) { + 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."); + } + } + presenterControlChannel.disconnect(CLOSE_CONTROL_CHANNEL_REASON); + }, + notifyConnected: function() { + Assert.equal(this.status, 'created', '0. presenterControlChannel: opened'); + this.status = 'opened'; + }, + notifyDisconnected: function(aReason) { + 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]), + }; + + let presenterDeviceInfo = { + id: 'presentatorID', + address: '127.0.0.1', + port: PRESENTER_CONTROL_CHANNEL_PORT, + certFingerprint: pcs.certFingerprint, + QueryInterface: XPCOMUtils.generateQI([Ci.nsITCPDeviceInfo]), + }; + + let controllerControlChannel = pcs.connect(presenterDeviceInfo); + + controllerControlChannel.listener = { + status: 'created', + onOffer: function(offer) { + Assert.ok(false, 'get offer'); + }, + onAnswer: function(aAnswer) { + 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, + ANSWER_ADDRESS, + 'expected answer address array'); + Assert.equal(answer.tcpPort, ANSWER_PORT, 'expected answer port'); + candidate = { + candidate: "1 1 UDP 1 127.0.0.1 34567 type host", + sdpMid: "helloworld", + sdpMLineIndex: 1 + }; + controllerControlChannel.sendIceCandidate(JSON.stringify(candidate)); + }, + onIceCandidate: function(aCandidate) { + Assert.ok(false, 'get ICE candidate'); + }, + notifyConnected: function() { + 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) + 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. 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]), + }; +} + +function terminateRequest() { + let yayFuncs = makeJointSuccess(['controllerControlChannelConnected', + 'controllerControlChannelDisconnected', + 'presenterControlChannelDisconnected', + 'terminatedByController', + 'terminatedByReceiver']); + let controllerControlChannel; + let terminatePhase = 'controller'; + + pcs.listener = { + onTerminateRequest: function(deviceInfo, presentationId, controlChannel, isFromReceiver) { + Assert.equal(deviceInfo.address, '127.0.0.1', 'expected device address'); + Assert.equal(presentationId, 'testPresentationId', 'expected presentation id'); + controlChannel.terminate(presentationId); // Reply terminate ack. + + if (terminatePhase === 'controller') { + controllerControlChannel = controlChannel; + Assert.equal(deviceInfo.id, pcs.id, 'expected controller device id'); + Assert.equal(isFromReceiver, false, 'expected request from controller'); + yayFuncs.terminatedByController(); + + controllerControlChannel.listener = { + notifyConnected: function() { + Assert.ok(true, 'control channel notify connected'); + yayFuncs.controllerControlChannelConnected(); + + terminatePhase = 'receiver'; + controllerControlChannel.terminate('testPresentationId'); + }, + notifyDisconnected: function(aReason) { + Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, 'controllerControlChannel notify disconncted'); + yayFuncs.controllerControlChannelDisconnected(); + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]), + }; + } else { + Assert.equal(deviceInfo.id, presenterDeviceInfo.id, 'expected presenter device id'); + Assert.equal(isFromReceiver, true, 'expected request from receiver'); + yayFuncs.terminatedByReceiver(); + presenterControlChannel.disconnect(CLOSE_CONTROL_CHANNEL_REASON); + } + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsITCPPresentationServerListener]), + }; + + let presenterDeviceInfo = { + id: 'presentatorID', + address: '127.0.0.1', + port: PRESENTER_CONTROL_CHANNEL_PORT, + certFingerprint: pcs.certFingerprint, + QueryInterface: XPCOMUtils.generateQI([Ci.nsITCPDeviceInfo]), + }; + + let presenterControlChannel = pcs.connect(presenterDeviceInfo); + + presenterControlChannel.listener = { + notifyConnected: function() { + presenterControlChannel.terminate('testPresentationId'); + }, + notifyDisconnected: function(aReason) { + Assert.equal(aReason, CLOSE_CONTROL_CHANNEL_REASON, '4. presenterControlChannel notify disconnected'); + yayFuncs.presenterControlChannelDisconnected(); + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]), + }; +} + +function terminateRequestAbnormal() { + let yayFuncs = makeJointSuccess(['controllerControlChannelConnected', + 'controllerControlChannelDisconnected', + 'presenterControlChannelDisconnected']); + let controllerControlChannel; + + pcs.listener = { + onTerminateRequest: function(deviceInfo, presentationId, controlChannel, isFromReceiver) { + Assert.equal(deviceInfo.id, pcs.id, 'expected controller device id'); + Assert.equal(deviceInfo.address, '127.0.0.1', 'expected device address'); + Assert.equal(presentationId, 'testPresentationId', 'expected presentation id'); + Assert.equal(isFromReceiver, false, 'expected request from controller'); + controlChannel.terminate('unmatched-presentationId'); // Reply abnormal terminate ack. + + controllerControlChannel = controlChannel; + + controllerControlChannel.listener = { + notifyConnected: function() { + Assert.ok(true, 'control channel notify connected'); + yayFuncs.controllerControlChannelConnected(); + }, + notifyDisconnected: function(aReason) { + Assert.equal(aReason, Cr.NS_ERROR_FAILURE, 'controllerControlChannel notify disconncted with error'); + yayFuncs.controllerControlChannelDisconnected(); + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]), + }; + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsITCPPresentationServerListener]), + }; + + let presenterDeviceInfo = { + id: 'presentatorID', + address: '127.0.0.1', + port: PRESENTER_CONTROL_CHANNEL_PORT, + certFingerprint: pcs.certFingerprint, + QueryInterface: XPCOMUtils.generateQI([Ci.nsITCPDeviceInfo]), + }; + + let presenterControlChannel = pcs.connect(presenterDeviceInfo); + + presenterControlChannel.listener = { + notifyConnected: function() { + presenterControlChannel.terminate('testPresentationId'); + }, + notifyDisconnected: function(aReason) { + Assert.equal(aReason, Cr.NS_ERROR_FAILURE, '4. presenterControlChannel notify disconnected with error'); + yayFuncs.presenterControlChannelDisconnected(); + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannelListener]), + }; +} + +function setOffline() { + pcs.listener = { + onServerReady: function(aPort, aCertFingerprint) { + Assert.notEqual(aPort, 0, 'TCPPresentationServer port changed and the port should be valid'); + pcs.close(); + run_next_test(); + }, + }; + + // Let the server socket restart automatically. + Services.io.offline = true; + Services.io.offline = false; +} + +function oneMoreLoop() { + try { + pcs.listener = { + onServerReady: function() { + testPresentationServer(); + } + }; + + // Second run with TLS disabled. + pcs.startServer(false, PRESENTER_CONTROL_CHANNEL_PORT); + } catch (e) { + Assert.ok(false, 'TCP presentation init fail:' + e); + run_next_test(); + } +} + + +function shutdown() +{ + pcs.listener = { + onServerReady: function(aPort, aCertFingerprint) { + Assert.ok(false, 'TCPPresentationServer port changed'); + }, + }; + pcs.close(); + Assert.equal(pcs.port, 0, "TCPPresentationServer closed"); + run_next_test(); +} + +// 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(terminateRequest); +add_test(terminateRequestAbnormal); +add_test(setOffline); +add_test(changeCloseReason); +add_test(oneMoreLoop); +add_test(shutdown); + +function run_test() { + // Need profile dir to store the key / cert + do_get_profile(); + // Ensure PSM is initialized + Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports); + + Services.prefs.setBoolPref("dom.presentation.tcp_server.debug", true); + + do_register_cleanup(() => { + Services.prefs.clearUserPref("dom.presentation.tcp_server.debug"); + }); + + run_next_test(); +} diff --git a/dom/presentation/tests/xpcshell/xpcshell.ini b/dom/presentation/tests/xpcshell/xpcshell.ini new file mode 100644 index 000000000..8a9c305a0 --- /dev/null +++ b/dom/presentation/tests/xpcshell/xpcshell.ini @@ -0,0 +1,9 @@ +[DEFAULT] +head = +tail = + +[test_multicast_dns_device_provider.js] +[test_presentation_device_manager.js] +[test_presentation_session_transport.js] +[test_tcp_control_channel.js] +[test_presentation_state_machine.js] |