diff options
Diffstat (limited to 'dom/presentation/tests/xpcshell')
6 files changed, 2886 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 0000000000..4ccc0a54ef --- /dev/null +++ b/dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js @@ -0,0 +1,1465 @@ +/* -*- 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 Cm = Components.manager; + +const { Promise } = ChromeUtils.import("resource://gre/modules/Promise.jsm"); +const { Services } = ChromeUtils.import("resource://gre/modules/Services.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) { + return new Promise(resolve => { + let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + + timer.initWithCallback( + { + notify() { + resolve(); + }, + }, + aMs, + timer.TYPE_ONE_SHOT + ); + }); +} + +function MockFactory(aClass) { + this._cls = aClass; +} +MockFactory.prototype = { + createInstance(aOuter, aIID) { + if (aOuter) { + throw Components.Exception("", 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(aLock) { + throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); + }, + QueryInterface: ChromeUtils.generateQI(["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() { + this.reset(); + + let oldContract = this.unregister(); + this.hookedMap.get(this._contractID).push(oldContract); + registrar.registerFactory( + this.classID, + "", + this._contractID, + this._newFactory + ); + + registerCleanupFunction(() => { + this.cleanup.apply(this); + }); + }, + + reset() {}, + + cleanup() { + this.reset(); + + this.unregister(); + let prevContract = this.hookedMap.get(this._contractID).pop(); + + if (prevContract.classID) { + registrar.registerFactory( + prevContract.classID, + "", + this._contractID, + prevContract.factory + ); + } + }, + + unregister() { + var classID, factory; + + try { + classID = registrar.contractIDToCID(this._contractID); + factory = Cm.getClassObject(Cc[this._contractID], Ci.nsIFactory); + } catch (ex) { + classID = ""; + factory = null; + } + + if (factory) { + try { + registrar.unregisterFactory(classID, factory); + } catch (e) { + factory = null; + } + } + + return { classID, factory }; + }, +}; + +function MockDNSServiceInfo() {} +MockDNSServiceInfo.prototype = { + QueryInterface: ChromeUtils.generateQI(["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: ChromeUtils.generateQI([ + "nsIPresentationDeviceListener", + "nsISupportsWeakReference", + ]), + + addDevice(device) { + this.devices[device.id] = device; + }, + removeDevice(device) { + delete this.devices[device.id]; + }, + updateDevice(device) { + this.devices[device.id] = device; + }, + onSessionRequest(device, url, presentationId, controlChannel) {}, + + count() { + 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: ChromeUtils.generateQI(["nsIDNSServiceDiscovery"]), + startDiscovery(serviceType, listener) {}, + registerService(serviceInfo, listener) { + deferred.resolve(); + this.serviceRegistered++; + return { + QueryInterface: ChromeUtils.generateQI(["nsICancelable"]), + cancel: () => { + this.serviceUnregistered++; + }, + }; + }, + resolveService(serviceInfo, listener) {}, + serviceRegistered: 0, + serviceUnregistered: 0, + }; + 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: ChromeUtils.generateQI([ + "nsIPresentationDeviceListener", + "nsISupportsWeakReference", + ]), + addDevice(device) {}, + removeDevice(device) {}, + updateDevice(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: ChromeUtils.generateQI(["nsIDNSServiceDiscovery"]), + startDiscovery(serviceType, listener) {}, + registerService(serviceInfo, listener) { + deferred.resolve(); + Assert.ok(false, "should not register service if not discoverable"); + }, + resolveService(serviceInfo, listener) {}, + }; + + new ContractHook(SD_CONTRACT_ID, mockObj); + let provider = Cc[PROVIDER_CONTRACT_ID].createInstance( + Ci.nsIPresentationDeviceProvider + ); + + // Try register + provider.listener = { + QueryInterface: ChromeUtils.generateQI([ + "nsIPresentationDeviceListener", + "nsISupportsWeakReference", + ]), + addDevice(device) {}, + removeDevice(device) {}, + updateDevice(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: ChromeUtils.generateQI(["nsIDNSServiceDiscovery"]), + startDiscovery(serviceType, listener) {}, + registerService(serviceInfo, listener) { + deferred.resolve(); + this.serviceRegistered++; + return { + QueryInterface: ChromeUtils.generateQI(["nsICancelable"]), + cancel: () => { + this.serviceUnregistered++; + }, + }; + }, + resolveService(serviceInfo, listener) {}, + serviceRegistered: 0, + serviceUnregistered: 0, + }; + 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: ChromeUtils.generateQI([ + "nsIPresentationDeviceListener", + "nsISupportsWeakReference", + ]), + addDevice(device) {}, + removeDevice(device) {}, + updateDevice(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: ChromeUtils.generateQI(["nsIDNSServiceDiscovery"]), + startDiscovery(serviceType, listener) { + listener.onDiscoveryStarted(serviceType); + listener.onServiceFound( + createDevice("", 0, mockDevice.serviceName, mockDevice.serviceType) + ); + return { + QueryInterface: ChromeUtils.generateQI(["nsICancelable"]), + cancel() {}, + }; + }, + registerService(serviceInfo, listener) {}, + resolveService(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 + ) + ); + }, + }; + + 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: ChromeUtils.generateQI(["nsIDNSServiceDiscovery"]), + startDiscovery(serviceType, listener) { + listener.onDiscoveryStarted(serviceType); + listener.onServiceFound( + createDevice("", 0, mockDevice.serviceName, mockDevice.serviceType) + ); + return { + QueryInterface: ChromeUtils.generateQI(["nsICancelable"]), + cancel() {}, + }; + }, + registerService(serviceInfo, listener) {}, + resolveService(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 + ) + ); + }, + }; + + new ContractHook(SD_CONTRACT_ID, mockObj); + let provider = Cc[PROVIDER_CONTRACT_ID].createInstance( + Ci.nsIPresentationDeviceProvider + ); + let listener = { + QueryInterface: ChromeUtils.generateQI([ + "nsIPresentationDeviceListener", + "nsISupportsWeakReference", + ]), + addDevice(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; + provider = null; + run_next_test(); + }, + updateDevice() {}, + removeDevice() {}, + onSessionRequest() {}, + }; + + provider.listener = listener; +} + +function handleSessionRequest() { + Services.prefs.setBoolPref(PREF_DISCOVERY, true); + Services.prefs.setBoolPref(PREF_DISCOVERABLE, false); + + const testDeviceName = "test-device-name"; + + Services.prefs.setCharPref(PREF_DEVICENAME, testDeviceName); + + let mockDevice = createDevice( + "device.local", + 12345, + "service.name", + SERVICE_TYPE + ); + let mockSDObj = { + QueryInterface: ChromeUtils.generateQI(["nsIDNSServiceDiscovery"]), + startDiscovery(serviceType, listener) { + listener.onDiscoveryStarted(serviceType); + listener.onServiceFound( + createDevice("", 0, mockDevice.serviceName, mockDevice.serviceType) + ); + return { + QueryInterface: ChromeUtils.generateQI(["nsICancelable"]), + cancel() {}, + }; + }, + registerService(serviceInfo, listener) {}, + resolveService(serviceInfo, listener) { + listener.onServiceResolved( + createDevice( + mockDevice.host, + mockDevice.port, + mockDevice.serviceName, + mockDevice.serviceType + ) + ); + }, + }; + + let mockServerObj = { + QueryInterface: ChromeUtils.generateQI(["nsIPresentationControlService"]), + connect(deviceInfo) { + this.request = { + deviceInfo, + }; + return { + QueryInterface: ChromeUtils.generateQI([ + "nsIPresentationControlChannel", + ]), + }; + }, + id: "", + version: LATEST_VERSION, + isCompatibleServer(version) { + return this.version === version; + }, + }; + + new ContractHook(SD_CONTRACT_ID, mockSDObj); + new ContractHook(SERVER_CONTRACT_ID, mockServerObj); + let provider = Cc[PROVIDER_CONTRACT_ID].createInstance( + Ci.nsIPresentationDeviceProvider + ); + let listener = { + QueryInterface: ChromeUtils.generateQI([ + "nsIPresentationDeviceListener", + "nsISupportsWeakReference", + ]), + addDevice(device) { + this.device = device; + }, + }; + + provider.listener = listener; + + 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: ChromeUtils.generateQI(["nsIDNSServiceDiscovery"]), + startDiscovery(serviceType, listener) { + listener.onDiscoveryStarted(serviceType); + listener.onServiceFound( + createDevice("", 0, mockDevice.serviceName, mockDevice.serviceType) + ); + return { + QueryInterface: ChromeUtils.generateQI(["nsICancelable"]), + cancel() {}, + }; + }, + registerService(serviceInfo, listener) {}, + resolveService(serviceInfo, listener) { + listener.onServiceResolved( + createDevice( + mockDevice.host, + mockDevice.port, + mockDevice.serviceName, + mockDevice.serviceType + ) + ); + }, + }; + + let mockServerObj = { + QueryInterface: ChromeUtils.generateQI(["nsIPresentationControlService"]), + startServer() {}, + sessionRequest() {}, + close() {}, + id: "", + version: LATEST_VERSION, + port: 0, + listener: null, + }; + + new ContractHook(SD_CONTRACT_ID, mockSDObj); + new ContractHook(SERVER_CONTRACT_ID, mockServerObj); + let provider = Cc[PROVIDER_CONTRACT_ID].createInstance( + Ci.nsIPresentationDeviceProvider + ); + let listener = { + QueryInterface: ChromeUtils.generateQI([ + "nsIPresentationDeviceListener", + "nsISupportsWeakReference", + ]), + addDevice(device) {}, + removeDevice(device) {}, + updateDevice(device) {}, + onSessionRequest(device, url, presentationId, controlChannel) { + Assert.ok(true, "receive onSessionRequest event"); + this.request = { + deviceId: device.id, + url, + presentationId, + }; + }, + }; + + provider.listener = listener; + + const deviceInfo = { + QueryInterface: ChromeUtils.generateQI(["nsITCPDeviceInfo"]), + id: mockDevice.host, + address: mockDevice.host, + port: 54321, + }; + + const testUrl = "http://example.com"; + const testPresentationId = "test-presentation-id"; + const testControlChannel = { + QueryInterface: ChromeUtils.generateQI(["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: ChromeUtils.generateQI(["nsIDNSServiceDiscovery"]), + startDiscovery(serviceType, listener) {}, + registerService(serviceInfo, listener) {}, + resolveService(serviceInfo, listener) {}, + }; + + let mockServerObj = { + QueryInterface: ChromeUtils.generateQI(["nsIPresentationControlService"]), + startServer() {}, + sessionRequest() {}, + close() {}, + id: "", + version: LATEST_VERSION, + port: 0, + listener: null, + }; + + new ContractHook(SD_CONTRACT_ID, mockSDObj); + new ContractHook(SERVER_CONTRACT_ID, mockServerObj); + let provider = Cc[PROVIDER_CONTRACT_ID].createInstance( + Ci.nsIPresentationDeviceProvider + ); + let listener = { + QueryInterface: ChromeUtils.generateQI([ + "nsIPresentationDeviceListener", + "nsISupportsWeakReference", + ]), + addDevice(device) { + Assert.ok(false, "shouldn't create any new device"); + }, + removeDevice(device) { + Assert.ok(false, "shouldn't remote any device"); + }, + updateDevice(device) { + Assert.ok(false, "shouldn't update any device"); + }, + onSessionRequest(device, url, presentationId, controlChannel) { + Assert.ok(true, "receive onSessionRequest event"); + this.request = { + deviceId: device.id, + url, + presentationId, + }; + }, + }; + + provider.listener = listener; + + const deviceInfo = { + QueryInterface: ChromeUtils.generateQI(["nsITCPDeviceInfo"]), + id: "unknown-device.local", + address: "unknown-device.local", + port: 12345, + }; + + const testUrl = "http://example.com"; + const testPresentationId = "test-presentation-id"; + const testControlChannel = { + QueryInterface: ChromeUtils.generateQI(["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 mockObj = { + QueryInterface: ChromeUtils.generateQI(["nsIDNSServiceDiscovery"]), + startDiscovery(serviceType, listener) { + Assert.ok(false, "shouldn't perform any device discovery"); + }, + registerService(serviceInfo, listener) {}, + resolveService(serviceInfo, listener) {}, + }; + new ContractHook(SD_CONTRACT_ID, mockObj); + + let provider = Cc[PROVIDER_CONTRACT_ID].createInstance( + Ci.nsIPresentationDeviceProvider + ); + let listener = { + QueryInterface: ChromeUtils.generateQI([ + "nsIPresentationDeviceListener", + "nsISupportsWeakReference", + ]), + addDevice(device) {}, + removeDevice(device) {}, + updateDevice(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: ChromeUtils.generateQI(["nsIDNSServiceDiscovery"]), + startDiscovery(serviceType, listener) { + listener.onDiscoveryStarted(serviceType); + listener.onServiceFound( + createDevice("", 0, mockDevice.serviceName, mockDevice.serviceType) + ); + return { + QueryInterface: ChromeUtils.generateQI(["nsICancelable"]), + cancel() {}, + }; + }, + registerService(serviceInfo, listener) { + deferred.resolve(); + listener.onServiceRegistered( + createDevice("", 54321, mockDevice.serviceName, mockDevice.serviceType) + ); + return { + QueryInterface: ChromeUtils.generateQI(["nsICancelable"]), + cancel() {}, + }; + }, + resolveService(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: ChromeUtils.generateQI(["nsIPresentationControlService"]), + startServer() { + Services.tm.dispatchToMainThread(() => { + this.listener.onServerReady(this.port, this.certFingerprint); + }); + }, + sessionRequest() {}, + close() {}, + id: "", + version: LATEST_VERSION, + isCompatibleServer(version) { + return false; + }, + port: 54321, + certFingerprint: "mock-cert-fingerprint", + listener: null, + }; + + new ContractHook(SD_CONTRACT_ID, mockSDObj); + 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; + provider = 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: ChromeUtils.generateQI(["nsIDNSServiceDiscovery"]), + startDiscovery(serviceType, listener) { + listener.onDiscoveryStarted(serviceType); + listener.onServiceFound( + createDevice("", 0, mockDevice.serviceName, mockDevice.serviceType) + ); + return { + QueryInterface: ChromeUtils.generateQI(["nsICancelable"]), + cancel() {}, + }; + }, + registerService(serviceInfo, listener) { + deferred.resolve(); + listener.onServiceRegistered( + createDevice("", 0, mockDevice.serviceName, mockDevice.serviceType) + ); + return { + QueryInterface: ChromeUtils.generateQI(["nsICancelable"]), + cancel() {}, + }; + }, + resolveService(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: ChromeUtils.generateQI(["nsIPresentationControlService"]), + startServer() { + Services.tm.dispatchToMainThread(() => { + this.listener.onServerReady(this.port, this.certFingerprint); + }); + }, + sessionRequest() {}, + close() {}, + id: "", + version: LATEST_VERSION, + isCompatibleServer(version) { + return this.version === version; + }, + port: 54321, + certFingerprint: "mock-cert-fingerprint", + listener: null, + }; + + new ContractHook(SD_CONTRACT_ID, mockSDObj); + 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; + provider = 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: ChromeUtils.generateQI(["nsIDNSServiceDiscovery"]), + startDiscovery(serviceType, listener) { + listener.onDiscoveryStarted(serviceType); + listener.onServiceFound( + createDevice("", 0, mockDevice.serviceName, mockDevice.serviceType) + ); + return { + QueryInterface: ChromeUtils.generateQI(["nsICancelable"]), + cancel() {}, + }; + }, + registerService(serviceInfo, listener) {}, + resolveService(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 + ) + ); + }, + }; + + 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: ChromeUtils.generateQI(["nsIDNSServiceDiscovery"]), + startDiscovery(serviceType, listener) { + listener.onDiscoveryStarted(serviceType); + + if (!this.discovered) { + listener.onServiceFound(mockDevice1); + } else { + listener.onServiceFound(mockDevice2); + } + this.discovered = true; + + return { + QueryInterface: ChromeUtils.generateQI(["nsICancelable"]), + cancel() { + listener.onDiscoveryStopped(serviceType); + }, + }; + }, + registerService(serviceInfo, listener) {}, + resolveService(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); + } + }, + }; + + new ContractHook(SD_CONTRACT_ID, mockObj); + let provider = Cc[PROVIDER_CONTRACT_ID].createInstance( + Ci.nsIPresentationDeviceProvider + ); + let listener = { + QueryInterface: ChromeUtils.generateQI([ + "nsIPresentationDeviceListener", + "nsISupportsWeakReference", + ]), + + addDevice(device) { + Assert.ok(!this.isDeviceAdded); + Assert.equal(device.id, mockDevice1.host); + Assert.equal(device.name, mockDevice1.serviceName); + this.isDeviceAdded = true; + }, + removeDevice(device) { + Assert.ok(false); + }, + updateDevice(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: ChromeUtils.generateQI(["nsIDNSServiceDiscovery"]), + startDiscovery(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: ChromeUtils.generateQI(["nsICancelable"]), + cancel() { + listener.onDiscoveryStopped(serviceType); + }, + }; + }, + registerService(serviceInfo, listener) {}, + resolveService(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); + } + }, + }; + + 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: ChromeUtils.generateQI(["nsIDNSServiceDiscovery"]), + startDiscovery(serviceType, listener) { + listener.onDiscoveryStarted(serviceType); + listener.onServiceFound( + createDevice("", 0, mockDevice.serviceName, mockDevice.serviceType) + ); + return { + QueryInterface: ChromeUtils.generateQI(["nsICancelable"]), + cancel() {}, + }; + }, + registerService(serviceInfo, listener) { + this.serviceRegistered++; + return { + QueryInterface: ChromeUtils.generateQI(["nsICancelable"]), + cancel: () => { + this.serviceUnregistered++; + }, + }; + }, + resolveService(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, + }; + 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: ChromeUtils.generateQI([ + "nsIPresentationDeviceListener", + "nsISupportsWeakReference", + ]), + addDevice(device) { + this.devices.push(device); + }, + removeDevice(device) {}, + updateDevice(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: ChromeUtils.generateQI(["nsIDNSServiceDiscovery"]), + startDiscovery(serviceType, listener) {}, + registerService(serviceInfo, listener) { + Assert.ok(isRetrying, "register service after retrying startServer"); + provider.listener = null; + run_next_test(); + }, + resolveService(serviceInfo, listener) {}, + }; + + let mockServerObj = { + QueryInterface: ChromeUtils.generateQI(["nsIPresentationControlService"]), + startServer(encrypted, port) { + if (!isRetrying) { + isRetrying = true; + Services.tm.dispatchToMainThread(() => { + this.listener.onServerStopped(Cr.NS_ERROR_FAILURE); + }); + } else { + this.port = 54321; + Services.tm.dispatchToMainThread(() => { + this.listener.onServerReady(this.port, this.certFingerprint); + }); + } + }, + sessionRequest() {}, + close() {}, + id: "", + version: LATEST_VERSION, + port: 0, + certFingerprint: "mock-cert-fingerprint", + listener: null, + }; + + new ContractHook(SD_CONTRACT_ID, mockSDObj); + new ContractHook(SERVER_CONTRACT_ID, mockServerObj); + let provider = Cc[PROVIDER_CONTRACT_ID].createInstance( + Ci.nsIPresentationDeviceProvider + ); + let listener = { + QueryInterface: ChromeUtils.generateQI([ + "nsIPresentationDeviceListener", + "nsISupportsWeakReference", + ]), + addDevice(device) {}, + removeDevice(device) {}, + updateDevice(device) {}, + onSessionRequest(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); + + new ContractHook(INFO_CONTRACT_ID, MockDNSServiceInfo); + + registerCleanupFunction(() => { + 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 0000000000..e49a794ebc --- /dev/null +++ b/dom/presentation/tests/xpcshell/test_presentation_device_manager.js @@ -0,0 +1,288 @@ +/* -*- 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 { Services } = ChromeUtils.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: ChromeUtils.generateQI(["nsIPresentationControlChannel"]), + sendOffer(offer) {}, + sendAnswer(answer) {}, + disconnect() {}, + launch() {}, + terminate() {}, + reconnect() {}, + set listener(listener) {}, + get listener() {}, +}; + +var testProvider = { + QueryInterface: ChromeUtils.generateQI(["nsIPresentationDeviceProvider"]), + + forceDiscovery() {}, + set listener(listener) {}, + get listener() {}, +}; + +const forbiddenRequestedUrl = "http://example.com"; +var testDevice = { + QueryInterface: ChromeUtils.generateQI(["nsIPresentationDevice"]), + id: "id", + name: "name", + type: "type", + establishControlChannel(url, presentationId) { + return null; + }, + disconnect() {}, + isRequestedUrlSupported(requestedUrl) { + return forbiddenRequestedUrl !== requestedUrl; + }, +}; + +function addProvider() { + Object.defineProperty(testProvider, "listener", { + configurable: true, + set(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"); + 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"); + 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); + 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"); + manager + .QueryInterface(Ci.nsIPresentationDeviceListener) + .onSessionRequest( + testDevice, + testUrl, + testPresentationId, + testControlChannel + ); +} + +function terminateRequest() { + 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"); + 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"); + 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"); + manager + .QueryInterface(Ci.nsIPresentationDeviceListener) + .removeDevice(testDevice); +} + +function removeProvider() { + Object.defineProperty(testProvider, "listener", { + configurable: true, + set(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); 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 0000000000..6f5f267c4c --- /dev/null +++ b/dom/presentation/tests/xpcshell/test_presentation_session_transport.js @@ -0,0 +1,225 @@ +/* -*- 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 CC = Components.Constructor; +const ServerSocket = CC( + "@mozilla.org/network/server-socket;1", + "nsIServerSocket", + "init" +); + +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); + +const serverChannelDescription = { + QueryInterface: ChromeUtils.generateQI(["nsIPresentationChannelDescription"]), + type: 1, + tcpAddress: addresses, +}; + +var isClientReady = false; +var isServerReady = false; +var isClientClosed = false; +var isServerClosed = false; + +const clientCallback = { + QueryInterface: ChromeUtils.generateQI([ + "nsIPresentationSessionTransportCallback", + ]), + notifyTransportReady() { + Assert.ok(true, "Client transport ready."); + + isClientReady = true; + if (isClientReady && isServerReady) { + run_next_test(); + } + }, + notifyTransportClosed(aReason) { + Assert.ok(true, "Client transport is closed."); + + isClientClosed = true; + if (isClientClosed && isServerClosed) { + run_next_test(); + } + }, + notifyData(aData) { + Assert.equal(aData, serverMessage, "Client transport receives data."); + run_next_test(); + }, +}; + +const serverCallback = { + QueryInterface: ChromeUtils.generateQI([ + "nsIPresentationSessionTransportCallback", + ]), + notifyTransportReady() { + Assert.ok(true, "Server transport ready."); + + isServerReady = true; + if (isClientReady && isServerReady) { + run_next_test(); + } + }, + notifyTransportClosed(aReason) { + Assert.ok(true, "Server transport is closed."); + + isServerClosed = true; + if (isClientClosed && isServerClosed) { + run_next_test(); + } + }, + notifyData(aData) { + Assert.equal(aData, clientMessage, "Server transport receives data."); + run_next_test(); + }, +}; + +const clientListener = { + QueryInterface: ChromeUtils.generateQI([ + "nsIPresentationSessionTransportBuilderListener", + ]), + onSessionTransport(aTransport) { + Assert.ok(true, "Client Transport is built."); + clientTransport = aTransport; + clientTransport.callback = clientCallback; + + if (serverTransport) { + run_next_test(); + } + }, +}; + +const serverListener = { + QueryInterface: ChromeUtils.generateQI([ + "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(aSocket, aTransport) { + print("Test server gets a client connection."); + serverBuilder = Cc[ + "@mozilla.org/presentation/presentationtcpsessiontransport;1" + ].createInstance(Ci.nsIPresentationTCPSessionTransportBuilder); + serverBuilder.buildTCPSenderTransport(aTransport, serverListener); + }, + onStopListening(aSocket) { + print("Test server stops listening."); + }, + close() { + 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 0000000000..05726ab4b1 --- /dev/null +++ b/dom/presentation/tests/xpcshell/test_presentation_state_machine.js @@ -0,0 +1,376 @@ +/* -*- 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 { ControllerStateMachine } = ChromeUtils.import( + "resource://gre/modules/presentation/ControllerStateMachine.jsm" +); +const { ReceiverStateMachine } = ChromeUtils.import( + "resource://gre/modules/presentation/ReceiverStateMachine.jsm" +); +const { State } = ChromeUtils.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) { + executeSoon(function() { + receiverState.onCommand(command); + }); +}; + +mockReceiverChannel.sendCommand = function(command) { + executeSoon(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(presId) { + Assert.equal( + controllerState.state, + State.CONNECTED, + "controller in connected state" + ); + Assert.equal( + presId, + 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(presId) { + Assert.equal( + controllerState.state, + State.CONNECTED, + "controller in connected state" + ); + Assert.equal( + presId, + 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(presId) { + Assert.equal( + receiverState.state, + State.CONNECTED, + "receiver in connected state" + ); + Assert.equal( + presId, + 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( + controllerCandidate + ) { + Assert.equal( + controllerCandidate, + 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(disconnectReason) { + Assert.equal(disconnectReason, 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(disconnectReason) { + Assert.equal(disconnectReason, 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(disconnectReason) { + Assert.equal( + disconnectReason, + 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); 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 0000000000..ee0b3aa074 --- /dev/null +++ b/dom/presentation/tests/xpcshell/test_tcp_control_channel.js @@ -0,0 +1,523 @@ +/* -*- 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 { Services } = ChromeUtils.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() { + info("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); + } + this.tcpPort = aTcpPort; +} + +TestDescription.prototype = { + QueryInterface: ChromeUtils.generateQI(["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() { + 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(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(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(aAnswer) { + Assert.ok(false, "get answer"); + }, + onIceCandidate(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() { + Assert.equal( + this.status, + "created", + "0. presenterControlChannel: opened" + ); + this.status = "opened"; + }, + notifyDisconnected(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: ChromeUtils.generateQI([ + "nsIPresentationControlChannelListener", + ]), + }; + }, + onReconnectRequest(deviceInfo, url, presentationId, controlChannel) { + Assert.equal(url, "http://example.com", "expected url"); + Assert.equal( + presentationId, + "testPresentationId", + "expected presentation id" + ); + yayFuncs.presenterControlChannelReconnect(); + }, + + QueryInterface: ChromeUtils.generateQI([ + "nsIPresentationControlServerListener", + ]), + }; + + let presenterDeviceInfo = { + id: "presentatorID", + address: "127.0.0.1", + port: PRESENTER_CONTROL_CHANNEL_PORT, + certFingerprint: pcs.certFingerprint, + QueryInterface: ChromeUtils.generateQI(["nsITCPDeviceInfo"]), + }; + + let controllerControlChannel = pcs.connect(presenterDeviceInfo); + + controllerControlChannel.listener = { + status: "created", + onOffer(offer) { + Assert.ok(false, "get offer"); + }, + onAnswer(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(aCandidate) { + Assert.ok(false, "get ICE candidate"); + }, + notifyConnected() { + 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(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() { + reconnectControllerControlChannel.reconnect( + "testPresentationId", + "http://example.com" + ); + }, + notifyReconnected() { + yayFuncs.controllerControlChannelReconnect(); + }, + QueryInterface: ChromeUtils.generateQI([ + "nsIPresentationControlChannelListener", + ]), + }; + }, + QueryInterface: ChromeUtils.generateQI([ + "nsIPresentationControlChannelListener", + ]), + }; +} + +function terminateRequest() { + let yayFuncs = makeJointSuccess([ + "controllerControlChannelConnected", + "controllerControlChannelDisconnected", + "presenterControlChannelDisconnected", + "terminatedByController", + "terminatedByReceiver", + ]); + let controllerControlChannel; + let terminatePhase = "controller"; + + pcs.listener = { + onTerminateRequest( + 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() { + Assert.ok(true, "control channel notify connected"); + yayFuncs.controllerControlChannelConnected(); + + terminatePhase = "receiver"; + controllerControlChannel.terminate("testPresentationId"); + }, + notifyDisconnected(aReason) { + Assert.equal( + aReason, + CLOSE_CONTROL_CHANNEL_REASON, + "controllerControlChannel notify disconncted" + ); + yayFuncs.controllerControlChannelDisconnected(); + }, + QueryInterface: ChromeUtils.generateQI([ + "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: ChromeUtils.generateQI([ + "nsITCPPresentationServerListener", + ]), + }; + + let presenterDeviceInfo = { + id: "presentatorID", + address: "127.0.0.1", + port: PRESENTER_CONTROL_CHANNEL_PORT, + certFingerprint: pcs.certFingerprint, + QueryInterface: ChromeUtils.generateQI(["nsITCPDeviceInfo"]), + }; + + let presenterControlChannel = pcs.connect(presenterDeviceInfo); + + presenterControlChannel.listener = { + notifyConnected() { + presenterControlChannel.terminate("testPresentationId"); + }, + notifyDisconnected(aReason) { + Assert.equal( + aReason, + CLOSE_CONTROL_CHANNEL_REASON, + "4. presenterControlChannel notify disconnected" + ); + yayFuncs.presenterControlChannelDisconnected(); + }, + QueryInterface: ChromeUtils.generateQI([ + "nsIPresentationControlChannelListener", + ]), + }; +} + +function terminateRequestAbnormal() { + let yayFuncs = makeJointSuccess([ + "controllerControlChannelConnected", + "controllerControlChannelDisconnected", + "presenterControlChannelDisconnected", + ]); + let controllerControlChannel; + + pcs.listener = { + onTerminateRequest( + 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() { + Assert.ok(true, "control channel notify connected"); + yayFuncs.controllerControlChannelConnected(); + }, + notifyDisconnected(aReason) { + Assert.equal( + aReason, + Cr.NS_ERROR_FAILURE, + "controllerControlChannel notify disconncted with error" + ); + yayFuncs.controllerControlChannelDisconnected(); + }, + QueryInterface: ChromeUtils.generateQI([ + "nsIPresentationControlChannelListener", + ]), + }; + }, + QueryInterface: ChromeUtils.generateQI([ + "nsITCPPresentationServerListener", + ]), + }; + + let presenterDeviceInfo = { + id: "presentatorID", + address: "127.0.0.1", + port: PRESENTER_CONTROL_CHANNEL_PORT, + certFingerprint: pcs.certFingerprint, + QueryInterface: ChromeUtils.generateQI(["nsITCPDeviceInfo"]), + }; + + let presenterControlChannel = pcs.connect(presenterDeviceInfo); + + presenterControlChannel.listener = { + notifyConnected() { + presenterControlChannel.terminate("testPresentationId"); + }, + notifyDisconnected(aReason) { + Assert.equal( + aReason, + Cr.NS_ERROR_FAILURE, + "4. presenterControlChannel notify disconnected with error" + ); + yayFuncs.presenterControlChannelDisconnected(); + }, + QueryInterface: ChromeUtils.generateQI([ + "nsIPresentationControlChannelListener", + ]), + }; +} + +function setOffline() { + pcs.listener = { + onServerReady(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() { + 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(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); + + registerCleanupFunction(() => { + 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 0000000000..635cabe5cd --- /dev/null +++ b/dom/presentation/tests/xpcshell/xpcshell.ini @@ -0,0 +1,9 @@ +[DEFAULT] +head = + +[test_multicast_dns_device_provider.js] +[test_presentation_device_manager.js] +[test_presentation_session_transport.js] +[test_tcp_control_channel.js] +skip-if = os == "android" || tsan || socketprocess_networking # Bugs 1422582, 1580136, 1450502, 1584360, 1606813 +[test_presentation_state_machine.js] |