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.js1465
-rw-r--r--dom/presentation/tests/xpcshell/test_presentation_device_manager.js288
-rw-r--r--dom/presentation/tests/xpcshell/test_presentation_session_transport.js225
-rw-r--r--dom/presentation/tests/xpcshell/test_presentation_state_machine.js376
-rw-r--r--dom/presentation/tests/xpcshell/test_tcp_control_channel.js523
-rw-r--r--dom/presentation/tests/xpcshell/xpcshell.ini9
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]