summaryrefslogtreecommitdiffstats
path: root/dom/presentation/tests/xpcshell
diff options
context:
space:
mode:
Diffstat (limited to 'dom/presentation/tests/xpcshell')
-rw-r--r--dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js1318
-rw-r--r--dom/presentation/tests/xpcshell/test_presentation_device_manager.js244
-rw-r--r--dom/presentation/tests/xpcshell/test_presentation_session_transport.js198
-rw-r--r--dom/presentation/tests/xpcshell/test_presentation_state_machine.js236
-rw-r--r--dom/presentation/tests/xpcshell/test_tcp_control_channel.js398
-rw-r--r--dom/presentation/tests/xpcshell/xpcshell.ini9
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]