summaryrefslogtreecommitdiffstats
path: root/dom/presentation/tests
diff options
context:
space:
mode:
Diffstat (limited to 'dom/presentation/tests')
-rw-r--r--dom/presentation/tests/mochitest/PresentationDeviceInfoChromeScript.js150
-rw-r--r--dom/presentation/tests/mochitest/PresentationSessionChromeScript.js470
-rw-r--r--dom/presentation/tests/mochitest/PresentationSessionChromeScript1UA.js366
-rw-r--r--dom/presentation/tests/mochitest/PresentationSessionFrameScript.js258
-rw-r--r--dom/presentation/tests/mochitest/chrome.ini14
-rw-r--r--dom/presentation/tests/mochitest/file_presentation_1ua_receiver.html220
-rw-r--r--dom/presentation/tests/mochitest/file_presentation_1ua_wentaway.html95
-rw-r--r--dom/presentation/tests/mochitest/file_presentation_mixed_security_contexts.html159
-rw-r--r--dom/presentation/tests/mochitest/file_presentation_non_receiver.html41
-rw-r--r--dom/presentation/tests/mochitest/file_presentation_non_receiver_inner_iframe.html26
-rw-r--r--dom/presentation/tests/mochitest/file_presentation_receiver.html140
-rw-r--r--dom/presentation/tests/mochitest/file_presentation_receiver_auxiliary_navigation.html60
-rw-r--r--dom/presentation/tests/mochitest/file_presentation_receiver_establish_connection_error.html79
-rw-r--r--dom/presentation/tests/mochitest/file_presentation_receiver_inner_iframe.html26
-rw-r--r--dom/presentation/tests/mochitest/file_presentation_reconnect.html102
-rw-r--r--dom/presentation/tests/mochitest/file_presentation_sandboxed_presentation.html114
-rw-r--r--dom/presentation/tests/mochitest/file_presentation_terminate.html104
-rw-r--r--dom/presentation/tests/mochitest/file_presentation_terminate_establish_connection_error.html114
-rw-r--r--dom/presentation/tests/mochitest/file_presentation_unknown_content_type.test1
-rw-r--r--dom/presentation/tests/mochitest/file_presentation_unknown_content_type.test^headers^1
-rw-r--r--dom/presentation/tests/mochitest/mochitest.ini77
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_1ua_connection_wentaway.js175
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_1ua_connection_wentaway_inproc.html18
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_1ua_connection_wentaway_oop.html18
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver.js370
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver_inproc.html18
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver_oop.html18
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_availability.html236
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_datachannel_sessiontransport.html245
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_dc_receiver.html141
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_dc_receiver_oop.html213
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_dc_sender.html291
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_device_info.html144
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_device_info_permission.html35
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_mixed_security_contexts.html81
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_receiver_auxiliary_navigation.js77
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_receiver_auxiliary_navigation_inproc.html18
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_receiver_auxiliary_navigation_oop.html18
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_reconnect.html379
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_sandboxed_presentation.html75
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_sender_on_terminate_request.html187
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_sender_startWithDevice.html173
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_tcp_receiver.html137
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_error.html110
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_timeout.html81
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_unknown_content_type.js88
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_unknown_content_type_inproc.html16
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_unknown_content_type_oop.html16
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_tcp_receiver_oop.html178
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_tcp_sender.html260
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_tcp_sender_default_request.html151
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_tcp_sender_disconnect.html160
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_tcp_sender_establish_connection_error.html514
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_terminate.js243
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_terminate_establish_connection_error.js197
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_terminate_establish_connection_error_inproc.html18
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_terminate_establish_connection_error_oop.html18
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_terminate_inproc.html18
-rw-r--r--dom/presentation/tests/mochitest/test_presentation_terminate_oop.html18
-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
65 files changed, 10173 insertions, 0 deletions
diff --git a/dom/presentation/tests/mochitest/PresentationDeviceInfoChromeScript.js b/dom/presentation/tests/mochitest/PresentationDeviceInfoChromeScript.js
new file mode 100644
index 000000000..2bc069f6b
--- /dev/null
+++ b/dom/presentation/tests/mochitest/PresentationDeviceInfoChromeScript.js
@@ -0,0 +1,150 @@
+/* -*- 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/PresentationDeviceInfoManager.jsm');
+
+const { XPCOMUtils } = Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+
+const manager = Cc['@mozilla.org/presentation-device/manager;1']
+ .getService(Ci.nsIPresentationDeviceManager);
+
+var testProvider = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceProvider]),
+ forceDiscovery: function() {
+ sendAsyncMessage('force-discovery');
+ },
+ listener: null,
+};
+
+var testDevice = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDevice]),
+ establishControlChannel: function() {
+ return null;
+ },
+ disconnect: function() {},
+ isRequestedUrlSupported: function(requestedUrl) {
+ return true;
+ },
+ id: null,
+ name: null,
+ type: null,
+ listener: null,
+};
+
+var testDevice1 = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDevice]),
+ id: 'dummyid',
+ name: 'dummyName',
+ type: 'dummyType',
+ establishControlChannel: function(url, presentationId) {
+ return null;
+ },
+ disconnect: function() {},
+ isRequestedUrlSupported: function(requestedUrl) {
+ return true;
+ },
+};
+
+var testDevice2 = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDevice]),
+ id: 'dummyid',
+ name: 'dummyName',
+ type: 'dummyType',
+ establishControlChannel: function(url, presentationId) {
+ return null;
+ },
+ disconnect: function() {},
+ isRequestedUrlSupported: function(requestedUrl) {
+ return true;
+ },
+};
+
+var mockedDeviceWithoutSupportedURL = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDevice]),
+ id: 'dummyid',
+ name: 'dummyName',
+ type: 'dummyType',
+ establishControlChannel: function(url, presentationId) {
+ return null;
+ },
+ disconnect: function() {},
+ isRequestedUrlSupported: function(requestedUrl) {
+ return false;
+ },
+};
+
+var mockedDeviceSupportHttpsURL = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDevice]),
+ id: 'dummyid',
+ name: 'dummyName',
+ type: 'dummyType',
+ establishControlChannel: function(url, presentationId) {
+ return null;
+ },
+ disconnect: function() {},
+ isRequestedUrlSupported: function(requestedUrl) {
+ if (requestedUrl.indexOf("https://") != -1) {
+ return true;
+ }
+ return false;
+ },
+};
+
+addMessageListener('setup', function() {
+ manager.addDeviceProvider(testProvider);
+
+ sendAsyncMessage('setup-complete');
+});
+
+addMessageListener('trigger-device-add', function(device) {
+ testDevice.id = device.id;
+ testDevice.name = device.name;
+ testDevice.type = device.type;
+ manager.addDevice(testDevice);
+});
+
+addMessageListener('trigger-add-unsupport-url-device', function() {
+ manager.addDevice(mockedDeviceWithoutSupportedURL);
+});
+
+addMessageListener('trigger-add-multiple-devices', function() {
+ manager.addDevice(testDevice1);
+ manager.addDevice(testDevice2);
+});
+
+addMessageListener('trigger-add-https-devices', function() {
+ manager.addDevice(mockedDeviceSupportHttpsURL);
+});
+
+
+addMessageListener('trigger-device-update', function(device) {
+ testDevice.id = device.id;
+ testDevice.name = device.name;
+ testDevice.type = device.type;
+ manager.updateDevice(testDevice);
+});
+
+addMessageListener('trigger-device-remove', function() {
+ manager.removeDevice(testDevice);
+});
+
+addMessageListener('trigger-remove-unsupported-device', function() {
+ manager.removeDevice(mockedDeviceWithoutSupportedURL);
+});
+
+addMessageListener('trigger-remove-multiple-devices', function() {
+ manager.removeDevice(testDevice1);
+ manager.removeDevice(testDevice2);
+});
+
+addMessageListener('trigger-remove-https-devices', function() {
+ manager.removeDevice(mockedDeviceSupportHttpsURL);
+});
+
+addMessageListener('teardown', function() {
+ manager.removeDeviceProvider(testProvider);
+});
diff --git a/dom/presentation/tests/mochitest/PresentationSessionChromeScript.js b/dom/presentation/tests/mochitest/PresentationSessionChromeScript.js
new file mode 100644
index 000000000..3052bdcb1
--- /dev/null
+++ b/dom/presentation/tests/mochitest/PresentationSessionChromeScript.js
@@ -0,0 +1,470 @@
+/* -*- 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, manager: Cm, utils: Cu, results: Cr } = Components;
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+Cu.import('resource://gre/modules/Services.jsm');
+Cu.import('resource://gre/modules/Timer.jsm');
+
+const uuidGenerator = Cc["@mozilla.org/uuid-generator;1"]
+ .getService(Ci.nsIUUIDGenerator);
+
+function registerMockedFactory(contractId, mockedClassId, mockedFactory) {
+ var originalClassId, originalFactory;
+
+ var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+ if (!registrar.isCIDRegistered(mockedClassId)) {
+ try {
+ originalClassId = registrar.contractIDToCID(contractId);
+ originalFactory = Cm.getClassObject(Cc[contractId], Ci.nsIFactory);
+ } catch (ex) {
+ originalClassId = "";
+ originalFactory = null;
+ }
+ if (originalFactory) {
+ registrar.unregisterFactory(originalClassId, originalFactory);
+ }
+ registrar.registerFactory(mockedClassId, "", contractId, mockedFactory);
+ }
+
+ return { contractId: contractId,
+ mockedClassId: mockedClassId,
+ mockedFactory: mockedFactory,
+ originalClassId: originalClassId,
+ originalFactory: originalFactory };
+}
+
+function registerOriginalFactory(contractId, mockedClassId, mockedFactory, originalClassId, originalFactory) {
+ if (originalFactory) {
+ var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+ registrar.unregisterFactory(mockedClassId, mockedFactory);
+ registrar.registerFactory(originalClassId, "", contractId, originalFactory);
+ }
+}
+
+var sessionId = 'test-session-id-' + uuidGenerator.generateUUID().toString();
+
+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 mockedChannelDescription = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationChannelDescription]),
+ get type() {
+ if (Services.prefs.getBoolPref("dom.presentation.session_transport.data_channel.enable")) {
+ return Ci.nsIPresentationChannelDescription.TYPE_DATACHANNEL;
+ }
+ return Ci.nsIPresentationChannelDescription.TYPE_TCP;
+ },
+ tcpAddress: addresses,
+ tcpPort: 1234,
+};
+
+const mockedServerSocket = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIServerSocket,
+ Ci.nsIFactory]),
+ createInstance: function(aOuter, aIID) {
+ if (aOuter) {
+ throw Components.results.NS_ERROR_NO_AGGREGATION;
+ }
+ return this.QueryInterface(aIID);
+ },
+ get port() {
+ return this._port;
+ },
+ set listener(listener) {
+ this._listener = listener;
+ },
+ init: function(port, loopbackOnly, backLog) {
+ if (port != -1) {
+ this._port = port;
+ } else {
+ this._port = 5678;
+ }
+ },
+ asyncListen: function(listener) {
+ this._listener = listener;
+ },
+ close: function() {
+ this._listener.onStopListening(this, Cr.NS_BINDING_ABORTED);
+ },
+ simulateOnSocketAccepted: function(serverSocket, socketTransport) {
+ this._listener.onSocketAccepted(serverSocket, socketTransport);
+ }
+};
+
+const mockedSocketTransport = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISocketTransport]),
+};
+
+const mockedControlChannel = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannel]),
+ set listener(listener) {
+ this._listener = listener;
+ },
+ get listener() {
+ return this._listener;
+ },
+ sendOffer: function(offer) {
+ sendAsyncMessage('offer-sent', this._isValidSDP(offer));
+ },
+ sendAnswer: function(answer) {
+ sendAsyncMessage('answer-sent', this._isValidSDP(answer));
+
+ if (answer.type == Ci.nsIPresentationChannelDescription.TYPE_TCP) {
+ this._listener.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyTransportReady();
+ }
+ },
+ _isValidSDP: function(aSDP) {
+ var isValid = false;
+ if (aSDP.type == Ci.nsIPresentationChannelDescription.TYPE_TCP) {
+ try {
+ var addresses = aSDP.tcpAddress;
+ if (addresses.length > 0) {
+ for (var i = 0; i < addresses.length; i++) {
+ // Ensure CString addresses are used. Otherwise, an error will be thrown.
+ addresses.queryElementAt(i, Ci.nsISupportsCString);
+ }
+
+ isValid = true;
+ }
+ } catch (e) {
+ isValid = false;
+ }
+ } else if (aSDP.type == Ci.nsIPresentationChannelDescription.TYPE_DATACHANNEL) {
+ isValid = (aSDP.dataChannelSDP == "test-sdp");
+ }
+ return isValid;
+ },
+ launch: function(presentationId, url) {
+ sessionId = presentationId;
+ },
+ terminate: function(presentationId) {
+ sendAsyncMessage('sender-terminate', presentationId);
+ },
+ reconnect: function(presentationId, url) {
+ sendAsyncMessage('start-reconnect', url);
+ },
+ notifyReconnected: function() {
+ this._listener
+ .QueryInterface(Ci.nsIPresentationControlChannelListener)
+ .notifyReconnected();
+ },
+ disconnect: function(reason) {
+ sendAsyncMessage('control-channel-closed', reason);
+ this._listener.QueryInterface(Ci.nsIPresentationControlChannelListener).notifyDisconnected(reason);
+ },
+ simulateReceiverReady: function() {
+ this._listener.QueryInterface(Ci.nsIPresentationControlChannelListener).notifyReceiverReady();
+ },
+ simulateOnOffer: function() {
+ sendAsyncMessage('offer-received');
+ this._listener.QueryInterface(Ci.nsIPresentationControlChannelListener).onOffer(mockedChannelDescription);
+ },
+ simulateOnAnswer: function() {
+ sendAsyncMessage('answer-received');
+ this._listener.QueryInterface(Ci.nsIPresentationControlChannelListener).onAnswer(mockedChannelDescription);
+ },
+ simulateNotifyConnected: function() {
+ sendAsyncMessage('control-channel-opened');
+ this._listener.QueryInterface(Ci.nsIPresentationControlChannelListener).notifyConnected();
+ },
+};
+
+const mockedDevice = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDevice]),
+ id: 'id',
+ name: 'name',
+ type: 'type',
+ establishControlChannel: function(url, presentationId) {
+ sendAsyncMessage('control-channel-established');
+ return mockedControlChannel;
+ },
+ disconnect: function() {},
+ isRequestedUrlSupported: function(requestedUrl) {
+ return true;
+ },
+};
+
+const mockedDevicePrompt = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDevicePrompt,
+ Ci.nsIFactory]),
+ createInstance: function(aOuter, aIID) {
+ if (aOuter) {
+ throw Components.results.NS_ERROR_NO_AGGREGATION;
+ }
+ return this.QueryInterface(aIID);
+ },
+ set request(request) {
+ this._request = request;
+ },
+ get request() {
+ return this._request;
+ },
+ promptDeviceSelection: function(request) {
+ this._request = request;
+ sendAsyncMessage('device-prompt');
+ },
+ simulateSelect: function() {
+ this._request.select(mockedDevice);
+ },
+ simulateCancel: function(result) {
+ this._request.cancel(result);
+ }
+};
+
+const mockedSessionTransport = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransport,
+ Ci.nsIPresentationSessionTransportBuilder,
+ Ci.nsIPresentationTCPSessionTransportBuilder,
+ Ci.nsIPresentationDataChannelSessionTransportBuilder,
+ Ci.nsIPresentationControlChannelListener,
+ Ci.nsIFactory]),
+ createInstance: function(aOuter, aIID) {
+ if (aOuter) {
+ throw Components.results.NS_ERROR_NO_AGGREGATION;
+ }
+ return this.QueryInterface(aIID);
+ },
+ set callback(callback) {
+ this._callback = callback;
+ },
+ get callback() {
+ return this._callback;
+ },
+ get selfAddress() {
+ return this._selfAddress;
+ },
+ buildTCPSenderTransport: function(transport, listener) {
+ this._listener = listener;
+ this._role = Ci.nsIPresentationService.ROLE_CONTROLLER;
+ this._listener.onSessionTransport(this);
+ this._listener = null;
+ sendAsyncMessage('data-transport-initialized');
+
+ setTimeout(()=>{
+ this.simulateTransportReady();
+ }, 0);
+ },
+ buildTCPReceiverTransport: function(description, listener) {
+ this._listener = listener;
+ this._role = Ci.nsIPresentationService.ROLE_RECEIVER;
+
+ var addresses = description.QueryInterface(Ci.nsIPresentationChannelDescription).tcpAddress;
+ this._selfAddress = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsINetAddr]),
+ address: (addresses.length > 0) ?
+ addresses.queryElementAt(0, Ci.nsISupportsCString).data : "",
+ port: description.QueryInterface(Ci.nsIPresentationChannelDescription).tcpPort,
+ };
+
+ setTimeout(()=>{
+ this._listener.onSessionTransport(this);
+ this._listener = null;
+ }, 0);
+ },
+ // in-process case
+ buildDataChannelTransport: function(role, window, listener) {
+ this._listener = listener;
+ this._role = role;
+
+ var hasNavigator = window ? (typeof window.navigator != "undefined") : false;
+ sendAsyncMessage('check-navigator', hasNavigator);
+
+ setTimeout(()=>{
+ this._listener.onSessionTransport(this);
+ this._listener = null;
+ this.simulateTransportReady();
+ }, 0);
+ },
+ enableDataNotification: function() {
+ sendAsyncMessage('data-transport-notification-enabled');
+ },
+ send: function(data) {
+ sendAsyncMessage('message-sent', data);
+ },
+ close: function(reason) {
+ sendAsyncMessage('data-transport-closed', reason);
+ this._callback.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyTransportClosed(reason);
+ },
+ simulateTransportReady: function() {
+ this._callback.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyTransportReady();
+ },
+ simulateIncomingMessage: function(message) {
+ this._callback.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyData(message, false);
+ },
+ onOffer: function(aOffer) {
+ },
+ onAnswer: function(aAnswer) {
+ }
+};
+
+const mockedNetworkInfo = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInfo]),
+ getAddresses: function(ips, prefixLengths) {
+ ips.value = ["127.0.0.1"];
+ prefixLengths.value = [0];
+ return 1;
+ },
+};
+
+const mockedNetworkManager = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkManager,
+ Ci.nsIFactory]),
+ createInstance: function(aOuter, aIID) {
+ if (aOuter) {
+ throw Components.results.NS_ERROR_NO_AGGREGATION;
+ }
+ return this.QueryInterface(aIID);
+ },
+ get activeNetworkInfo() {
+ return mockedNetworkInfo;
+ },
+};
+
+var requestPromise = null;
+
+const mockedRequestUIGlue = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationRequestUIGlue,
+ Ci.nsIFactory]),
+ createInstance: function(aOuter, aIID) {
+ if (aOuter) {
+ throw Components.results.NS_ERROR_NO_AGGREGATION;
+ }
+ return this.QueryInterface(aIID);
+ },
+ sendRequest: function(aUrl, aSessionId) {
+ sendAsyncMessage('receiver-launching', aSessionId);
+ return requestPromise;
+ },
+};
+
+// Register mocked factories.
+const originalFactoryData = [];
+originalFactoryData.push(registerMockedFactory("@mozilla.org/presentation-device/prompt;1",
+ uuidGenerator.generateUUID(),
+ mockedDevicePrompt));
+originalFactoryData.push(registerMockedFactory("@mozilla.org/network/server-socket;1",
+ uuidGenerator.generateUUID(),
+ mockedServerSocket));
+originalFactoryData.push(registerMockedFactory("@mozilla.org/presentation/presentationtcpsessiontransport;1",
+ uuidGenerator.generateUUID(),
+ mockedSessionTransport));
+originalFactoryData.push(registerMockedFactory("@mozilla.org/presentation/datachanneltransportbuilder;1",
+ uuidGenerator.generateUUID(),
+ mockedSessionTransport));
+originalFactoryData.push(registerMockedFactory("@mozilla.org/network/manager;1",
+ uuidGenerator.generateUUID(),
+ mockedNetworkManager));
+originalFactoryData.push(registerMockedFactory("@mozilla.org/presentation/requestuiglue;1",
+ uuidGenerator.generateUUID(),
+ mockedRequestUIGlue));
+
+function tearDown() {
+ requestPromise = null;
+ mockedServerSocket.listener = null;
+ mockedControlChannel.listener = null;
+ mockedDevice.listener = null;
+ mockedDevicePrompt.request = null;
+ mockedSessionTransport.callback = null;
+
+ var deviceManager = Cc['@mozilla.org/presentation-device/manager;1']
+ .getService(Ci.nsIPresentationDeviceManager);
+ deviceManager.QueryInterface(Ci.nsIPresentationDeviceListener).removeDevice(mockedDevice);
+
+ // Register original factories.
+ for (var data of originalFactoryData) {
+ registerOriginalFactory(data.contractId, data.mockedClassId,
+ data.mockedFactory, data.originalClassId,
+ data.originalFactory);
+ }
+
+ sendAsyncMessage('teardown-complete');
+}
+
+addMessageListener('trigger-device-add', function() {
+ var deviceManager = Cc['@mozilla.org/presentation-device/manager;1']
+ .getService(Ci.nsIPresentationDeviceManager);
+ deviceManager.QueryInterface(Ci.nsIPresentationDeviceListener).addDevice(mockedDevice);
+});
+
+addMessageListener('trigger-device-prompt-select', function() {
+ mockedDevicePrompt.simulateSelect();
+});
+
+addMessageListener('trigger-device-prompt-cancel', function(result) {
+ mockedDevicePrompt.simulateCancel(result);
+});
+
+addMessageListener('trigger-incoming-session-request', function(url) {
+ var deviceManager = Cc['@mozilla.org/presentation-device/manager;1']
+ .getService(Ci.nsIPresentationDeviceManager);
+ deviceManager.QueryInterface(Ci.nsIPresentationDeviceListener)
+ .onSessionRequest(mockedDevice, url, sessionId, mockedControlChannel);
+});
+
+addMessageListener('trigger-incoming-terminate-request', function() {
+ var deviceManager = Cc['@mozilla.org/presentation-device/manager;1']
+ .getService(Ci.nsIPresentationDeviceManager);
+ deviceManager.QueryInterface(Ci.nsIPresentationDeviceListener)
+ .onTerminateRequest(mockedDevice, sessionId, mockedControlChannel, true);
+});
+
+addMessageListener('trigger-reconnected-acked', function(url) {
+ mockedControlChannel.notifyReconnected();
+});
+
+addMessageListener('trigger-incoming-offer', function() {
+ mockedControlChannel.simulateOnOffer();
+});
+
+addMessageListener('trigger-incoming-answer', function() {
+ mockedControlChannel.simulateOnAnswer();
+});
+
+addMessageListener('trigger-incoming-transport', function() {
+ mockedServerSocket.simulateOnSocketAccepted(mockedServerSocket, mockedSocketTransport);
+});
+
+addMessageListener('trigger-control-channel-open', function(reason) {
+ mockedControlChannel.simulateNotifyConnected();
+});
+
+addMessageListener('trigger-control-channel-close', function(reason) {
+ mockedControlChannel.disconnect(reason);
+});
+
+addMessageListener('trigger-data-transport-close', function(reason) {
+ mockedSessionTransport.close(reason);
+});
+
+addMessageListener('trigger-incoming-message', function(message) {
+ mockedSessionTransport.simulateIncomingMessage(message);
+});
+
+addMessageListener('teardown', function() {
+ tearDown();
+});
+
+var controlChannelListener;
+addMessageListener('save-control-channel-listener', function() {
+ controlChannelListener = mockedControlChannel.listener;
+});
+
+addMessageListener('restore-control-channel-listener', function(message) {
+ mockedControlChannel.listener = controlChannelListener;
+ controlChannelListener = null;
+});
+
+var obs = Cc["@mozilla.org/observer-service;1"]
+ .getService(Ci.nsIObserverService);
+obs.addObserver(function observer(aSubject, aTopic, aData) {
+ obs.removeObserver(observer, aTopic);
+
+ requestPromise = aSubject;
+}, 'setup-request-promise', false);
diff --git a/dom/presentation/tests/mochitest/PresentationSessionChromeScript1UA.js b/dom/presentation/tests/mochitest/PresentationSessionChromeScript1UA.js
new file mode 100644
index 000000000..82d7362b2
--- /dev/null
+++ b/dom/presentation/tests/mochitest/PresentationSessionChromeScript1UA.js
@@ -0,0 +1,366 @@
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+* You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+'use strict';
+
+const { classes: Cc, interfaces: Ci, manager: Cm, utils: Cu, results: Cr } = Components;
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+Cu.import('resource://gre/modules/Services.jsm');
+
+const uuidGenerator = Cc["@mozilla.org/uuid-generator;1"]
+ .getService(Ci.nsIUUIDGenerator);
+
+function debug(str) {
+ // dump('DEBUG -*- PresentationSessionChromeScript1UA -*-: ' + str + '\n');
+}
+
+const originalFactoryData = [];
+var sessionId; // Store the uuid generated by PresentationRequest.
+var triggerControlChannelError = false; // For simulating error during control channel establishment.
+
+// control channel of sender
+const mockControlChannelOfSender = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannel]),
+ set listener(listener) {
+ // PresentationControllingInfo::SetControlChannel
+ if (listener) {
+ debug('set listener for mockControlChannelOfSender without null');
+ } else {
+ debug('set listener for mockControlChannelOfSender with null');
+ }
+ this._listener = listener;
+ },
+ get listener() {
+ return this._listener;
+ },
+ notifyConnected: function() {
+ // send offer after notifyConnected immediately
+ this._listener
+ .QueryInterface(Ci.nsIPresentationControlChannelListener)
+ .notifyConnected();
+ },
+ notifyReconnected: function() {
+ // send offer after notifyOpened immediately
+ this._listener
+ .QueryInterface(Ci.nsIPresentationControlChannelListener)
+ .notifyReconnected();
+ },
+ sendOffer: function(offer) {
+ Services.tm.mainThread.dispatch(() => {
+ mockControlChannelOfReceiver.onOffer(offer);
+ }, Ci.nsIThread.DISPATCH_NORMAL);
+ },
+ onAnswer: function(answer) {
+ this._listener
+ .QueryInterface(Ci.nsIPresentationControlChannelListener)
+ .onAnswer(answer);
+ },
+ launch: function(presentationId, url) {
+ sessionId = presentationId;
+ sendAsyncMessage('sender-launch', url);
+ },
+ disconnect: function(reason) {
+ if (!this._listener) {
+ return;
+ }
+ this._listener
+ .QueryInterface(Ci.nsIPresentationControlChannelListener)
+ .notifyDisconnected(reason);
+ mockControlChannelOfReceiver.disconnect();
+ },
+ terminate: function(presentationId) {
+ sendAsyncMessage('sender-terminate');
+ },
+ reconnect: function(presentationId, url) {
+ sendAsyncMessage('start-reconnect', url);
+ },
+ sendIceCandidate: function(candidate) {
+ mockControlChannelOfReceiver.notifyIceCandidate(candidate);
+ },
+ notifyIceCandidate: function(candidate) {
+ if (!this._listener) {
+ return;
+ }
+
+ this._listener
+ .QueryInterface(Ci.nsIPresentationControlChannelListener)
+ .onIceCandidate(candidate);
+ },
+};
+
+// control channel of receiver
+const mockControlChannelOfReceiver = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannel]),
+ set listener(listener) {
+ // PresentationPresentingInfo::SetControlChannel
+ if (listener) {
+ debug('set listener for mockControlChannelOfReceiver without null');
+ } else {
+ debug('set listener for mockControlChannelOfReceiver with null');
+ }
+ this._listener = listener;
+
+ if (this._pendingOpened) {
+ this._pendingOpened = false;
+ this.notifyConnected();
+ }
+ },
+ get listener() {
+ return this._listener;
+ },
+ notifyConnected: function() {
+ // do nothing
+ if (!this._listener) {
+ this._pendingOpened = true;
+ return;
+ }
+ this._listener
+ .QueryInterface(Ci.nsIPresentationControlChannelListener)
+ .notifyConnected();
+ },
+ onOffer: function(offer) {
+ this._listener
+ .QueryInterface(Ci.nsIPresentationControlChannelListener)
+ .onOffer(offer);
+ },
+ sendAnswer: function(answer) {
+ Services.tm.mainThread.dispatch(() => {
+ mockControlChannelOfSender.onAnswer(answer);
+ }, Ci.nsIThread.DISPATCH_NORMAL);
+ },
+ disconnect: function(reason) {
+ if (!this._listener) {
+ return;
+ }
+
+ this._listener
+ .QueryInterface(Ci.nsIPresentationControlChannelListener)
+ .notifyDisconnected(reason);
+ sendAsyncMessage('control-channel-receiver-closed', reason);
+ },
+ terminate: function(presentaionId) {
+ },
+ sendIceCandidate: function(candidate) {
+ mockControlChannelOfReceiver.notifyIceCandidate(candidate);
+ },
+ notifyIceCandidate: function(candidate) {
+ if (!this._listener) {
+ return;
+ }
+
+ this._listener
+ .QueryInterface(Ci.nsIPresentationControlChannelListener)
+ .onIceCandidate(candidate);
+ },
+};
+
+const mockDevice = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDevice]),
+ id: 'id',
+ name: 'name',
+ type: 'type',
+ establishControlChannel: function(url, presentationId) {
+ if (triggerControlChannelError) {
+ throw Cr.NS_ERROR_FAILURE;
+ }
+ sendAsyncMessage('control-channel-established');
+ return mockControlChannelOfSender;
+ },
+ disconnect: function() {
+ sendAsyncMessage('device-disconnected');
+ },
+ isRequestedUrlSupported: function(requestedUrl) {
+ return true;
+ },
+};
+
+const mockDevicePrompt = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDevicePrompt,
+ Ci.nsIFactory]),
+ createInstance: function(aOuter, aIID) {
+ if (aOuter) {
+ throw Components.results.NS_ERROR_NO_AGGREGATION;
+ }
+ return this.QueryInterface(aIID);
+ },
+ set request(request) {
+ this._request = request;
+ },
+ get request() {
+ return this._request;
+ },
+ promptDeviceSelection: function(request) {
+ this._request = request;
+ sendAsyncMessage('device-prompt');
+ },
+ simulateSelect: function() {
+ this._request.select(mockDevice);
+ },
+ simulateCancel: function() {
+ this._request.cancel();
+ }
+};
+
+const mockRequestUIGlue = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationRequestUIGlue,
+ Ci.nsIFactory]),
+ set promise(aPromise) {
+ this._promise = aPromise
+ },
+ get promise() {
+ return this._promise;
+ },
+ createInstance: function(aOuter, aIID) {
+ if (aOuter) {
+ throw Components.results.NS_ERROR_NO_AGGREGATION;
+ }
+ return this.QueryInterface(aIID);
+ },
+ sendRequest: function(aUrl, aSessionId) {
+ return this.promise;
+ },
+};
+
+function initMockAndListener() {
+
+ function registerMockFactory(contractId, mockClassId, mockFactory) {
+ var originalClassId, originalFactory;
+
+ var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+ if (!registrar.isCIDRegistered(mockClassId)) {
+ try {
+ originalClassId = registrar.contractIDToCID(contractId);
+ originalFactory = Cm.getClassObject(Cc[contractId], Ci.nsIFactory);
+ } catch (ex) {
+ originalClassId = "";
+ originalFactory = null;
+ }
+ if (originalFactory) {
+ registrar.unregisterFactory(originalClassId, originalFactory);
+ }
+ registrar.registerFactory(mockClassId, "", contractId, mockFactory);
+ }
+
+ return { contractId: contractId,
+ mockClassId: mockClassId,
+ mockFactory: mockFactory,
+ originalClassId: originalClassId,
+ originalFactory: originalFactory };
+ }
+ // Register mock factories.
+ const uuidGenerator = Cc["@mozilla.org/uuid-generator;1"]
+ .getService(Ci.nsIUUIDGenerator);
+ originalFactoryData.push(registerMockFactory("@mozilla.org/presentation-device/prompt;1",
+ uuidGenerator.generateUUID(),
+ mockDevicePrompt));
+ originalFactoryData.push(registerMockFactory("@mozilla.org/presentation/requestuiglue;1",
+ uuidGenerator.generateUUID(),
+ mockRequestUIGlue));
+
+ addMessageListener('trigger-device-add', function() {
+ debug('Got message: trigger-device-add');
+ var deviceManager = Cc['@mozilla.org/presentation-device/manager;1']
+ .getService(Ci.nsIPresentationDeviceManager);
+ deviceManager.QueryInterface(Ci.nsIPresentationDeviceListener)
+ .addDevice(mockDevice);
+ });
+
+ addMessageListener('trigger-device-prompt-select', function() {
+ debug('Got message: trigger-device-prompt-select');
+ mockDevicePrompt.simulateSelect();
+ });
+
+ addMessageListener('trigger-on-session-request', function(url) {
+ debug('Got message: trigger-on-session-request');
+ var deviceManager = Cc['@mozilla.org/presentation-device/manager;1']
+ .getService(Ci.nsIPresentationDeviceManager);
+ deviceManager.QueryInterface(Ci.nsIPresentationDeviceListener)
+ .onSessionRequest(mockDevice,
+ url,
+ sessionId,
+ mockControlChannelOfReceiver);
+ });
+
+ addMessageListener('trigger-on-terminate-request', function() {
+ debug('Got message: trigger-on-terminate-request');
+ var deviceManager = Cc['@mozilla.org/presentation-device/manager;1']
+ .getService(Ci.nsIPresentationDeviceManager);
+ deviceManager.QueryInterface(Ci.nsIPresentationDeviceListener)
+ .onTerminateRequest(mockDevice,
+ sessionId,
+ mockControlChannelOfReceiver,
+ false);
+ });
+
+ addMessageListener('trigger-control-channel-open', function(reason) {
+ debug('Got message: trigger-control-channel-open');
+ mockControlChannelOfSender.notifyConnected();
+ mockControlChannelOfReceiver.notifyConnected();
+ });
+
+ addMessageListener('trigger-control-channel-error', function(reason) {
+ debug('Got message: trigger-control-channel-open');
+ triggerControlChannelError = true;
+ });
+
+ addMessageListener('trigger-reconnected-acked', function(url) {
+ debug('Got message: trigger-reconnected-acked');
+ mockControlChannelOfSender.notifyReconnected();
+ var deviceManager = Cc['@mozilla.org/presentation-device/manager;1']
+ .getService(Ci.nsIPresentationDeviceManager);
+ deviceManager.QueryInterface(Ci.nsIPresentationDeviceListener)
+ .onReconnectRequest(mockDevice,
+ url,
+ sessionId,
+ mockControlChannelOfReceiver);
+ });
+
+ // Used to call sendAsyncMessage in chrome script from receiver.
+ addMessageListener('forward-command', function(command_data) {
+ let command = JSON.parse(command_data);
+ sendAsyncMessage(command.name, command.data);
+ });
+
+ addMessageListener('teardown', teardown);
+
+ var obs = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+ obs.addObserver(function setupRequestPromiseHandler(aSubject, aTopic, aData) {
+ debug('Got observer: setup-request-promise');
+ obs.removeObserver(setupRequestPromiseHandler, aTopic);
+ mockRequestUIGlue.promise = aSubject;
+ sendAsyncMessage('promise-setup-ready');
+ }, 'setup-request-promise', false);
+}
+
+function teardown() {
+
+ function registerOriginalFactory(contractId, mockedClassId, mockedFactory, originalClassId, originalFactory) {
+ if (originalFactory) {
+ var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+ registrar.unregisterFactory(mockedClassId, mockedFactory);
+ registrar.registerFactory(originalClassId, "", contractId, originalFactory);
+ }
+ }
+
+ mockRequestUIGlue.promise = null;
+ mockControlChannelOfSender.listener = null;
+ mockControlChannelOfReceiver.listener = null;
+ mockDevicePrompt.request = null;
+
+ var deviceManager = Cc['@mozilla.org/presentation-device/manager;1']
+ .getService(Ci.nsIPresentationDeviceManager);
+ deviceManager.QueryInterface(Ci.nsIPresentationDeviceListener)
+ .removeDevice(mockDevice);
+ // Register original factories.
+ for (var data of originalFactoryData) {
+ registerOriginalFactory(data.contractId, data.mockClassId,
+ data.mockFactory, data.originalClassId,
+ data.originalFactory);
+ }
+ sendAsyncMessage('teardown-complete');
+}
+
+initMockAndListener();
diff --git a/dom/presentation/tests/mochitest/PresentationSessionFrameScript.js b/dom/presentation/tests/mochitest/PresentationSessionFrameScript.js
new file mode 100644
index 000000000..77240ab5f
--- /dev/null
+++ b/dom/presentation/tests/mochitest/PresentationSessionFrameScript.js
@@ -0,0 +1,258 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function loadPrivilegedScriptTest() {
+ /**
+ * The script is loaded as
+ * (a) a privileged script in content process for dc_sender.html
+ * (b) a frame script in the remote iframe process for dc_receiver_oop.html
+ * |type port == "undefined"| indicates the script is load by
+ * |loadPrivilegedScript| which is the first case.
+ */
+ function sendMessage(type, data) {
+ if (typeof port == "undefined") {
+ sendAsyncMessage(type, {'data': data});
+ } else {
+ port.postMessage({'type': type,
+ 'data': data
+ });
+ }
+ }
+
+ if (typeof port != "undefined") {
+ /**
+ * When the script is loaded by |loadPrivilegedScript|, these APIs
+ * are exposed to this script.
+ */
+ port.onmessage = (e) => {
+ var type = e.data['type'];
+ if (!handlers.hasOwnProperty(type)) {
+ return;
+ }
+ var args = [e];
+ handlers[type].forEach(handler => handler.apply(null, args));
+ };
+ var handlers = {};
+ addMessageListener = function(message, handler) {
+ if (handlers.hasOwnProperty(message)) {
+ handlers[message].push(handler);
+ } else {
+ handlers[message] = [handler];
+ }
+ };
+ removeMessageListener = function(message, handler) {
+ if (!handler || !handlers.hasOwnProperty(message)) {
+ return;
+ }
+ var index = handlers[message].indexOf(handler);
+ if (index != -1) {
+ handlers[message].splice(index, 1);
+ }
+ };
+ }
+
+ const { classes: Cc, interfaces: Ci, manager: Cm, utils: Cu, results: Cr } = Components;
+
+ const mockedChannelDescription = {
+ QueryInterface : function (iid) {
+ const interfaces = [Ci.nsIPresentationChannelDescription];
+
+ if (!interfaces.some(v => iid.equals(v))) {
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+ return this;
+ },
+ get type() {
+ if (Services.prefs.getBoolPref("dom.presentation.session_transport.data_channel.enable")) {
+ return Ci.nsIPresentationChannelDescription.TYPE_DATACHANNEL;
+ }
+ return Ci.nsIPresentationChannelDescription.TYPE_TCP;
+ },
+ get dataChannelSDP() {
+ return "test-sdp";
+ }
+ };
+
+ function setTimeout(callback, delay) {
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback({ notify: callback },
+ delay,
+ Ci.nsITimer.TYPE_ONE_SHOT);
+ return timer;
+ }
+
+ const mockedSessionTransport = {
+ QueryInterface : function (iid) {
+ const interfaces = [Ci.nsIPresentationSessionTransport,
+ Ci.nsIPresentationDataChannelSessionTransportBuilder,
+ Ci.nsIFactory];
+
+ if (!interfaces.some(v => iid.equals(v))) {
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+ return this;
+ },
+ createInstance: function(aOuter, aIID) {
+ if (aOuter) {
+ throw Components.results.NS_ERROR_NO_AGGREGATION;
+ }
+ return this.QueryInterface(aIID);
+ },
+ set callback(callback) {
+ this._callback = callback;
+ },
+ get callback() {
+ return this._callback;
+ },
+ /* OOP case */
+ buildDataChannelTransport: function(role, window, listener) {
+ dump("PresentationSessionFrameScript: build data channel transport\n");
+ this._listener = listener;
+ this._role = role;
+
+ var hasNavigator = window ? (typeof window.navigator != "undefined") : false;
+ sendMessage('check-navigator', hasNavigator);
+
+ if (this._role == Ci.nsIPresentationService.ROLE_CONTROLLER) {
+ this._listener.sendOffer(mockedChannelDescription);
+ }
+ },
+
+ enableDataNotification: function() {
+ sendMessage('data-transport-notification-enabled');
+ },
+ send: function(data) {
+ sendMessage('message-sent', data);
+ },
+ close: function(reason) {
+ sendMessage('data-transport-closed', reason);
+ this._callback.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyTransportClosed(reason);
+ this._callback = null;
+ },
+ simulateTransportReady: function() {
+ this._callback.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyTransportReady();
+ },
+ simulateIncomingMessage: function(message) {
+ this._callback.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyData(message, false);
+ },
+ onOffer: function(aOffer) {
+ this._listener.sendAnswer(mockedChannelDescription);
+ this._onSessionTransport();
+ },
+ onAnswer: function(aAnswer) {
+ this._onSessionTransport();
+ },
+ _onSessionTransport: function() {
+ setTimeout(()=>{
+ this._listener.onSessionTransport(this);
+ this.simulateTransportReady();
+ this._listener = null;
+ }, 0);
+ }
+ };
+
+
+ function tearDown() {
+ mockedSessionTransport.callback = null;
+
+ /* Register original factories. */
+ for (var data of originalFactoryData) {
+ registerOriginalFactory(data.contractId, data.mockedClassId,
+ data.mockedFactory, data.originalClassId,
+ data.originalFactory);
+ }
+ sendMessage("teardown-complete");
+ }
+
+
+ function registerMockedFactory(contractId, mockedClassId, mockedFactory) {
+ var originalClassId, originalFactory;
+ var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+
+ if (!registrar.isCIDRegistered(mockedClassId)) {
+ try {
+ originalClassId = registrar.contractIDToCID(contractId);
+ originalFactory = Cm.getClassObject(Cc[contractId], Ci.nsIFactory);
+ } catch (ex) {
+ originalClassId = "";
+ originalFactory = null;
+ }
+ if (originalFactory) {
+ registrar.unregisterFactory(originalClassId, originalFactory);
+ }
+ registrar.registerFactory(mockedClassId, "", contractId, mockedFactory);
+ }
+
+ return { contractId: contractId,
+ mockedClassId: mockedClassId,
+ mockedFactory: mockedFactory,
+ originalClassId: originalClassId,
+ originalFactory: originalFactory };
+ }
+
+ function registerOriginalFactory(contractId, mockedClassId, mockedFactory, originalClassId, originalFactory) {
+ if (originalFactory) {
+ var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+ registrar.unregisterFactory(mockedClassId, mockedFactory);
+ registrar.registerFactory(originalClassId, "", contractId, originalFactory);
+ }
+ }
+
+ /* Register mocked factories. */
+ const originalFactoryData = [];
+ const uuidGenerator = Cc["@mozilla.org/uuid-generator;1"]
+ .getService(Ci.nsIUUIDGenerator);
+ originalFactoryData.push(registerMockedFactory("@mozilla.org/presentation/datachanneltransportbuilder;1",
+ uuidGenerator.generateUUID(),
+ mockedSessionTransport));
+
+ addMessageListener('trigger-incoming-message', function(event) {
+ mockedSessionTransport.simulateIncomingMessage(event.data.data);
+ });
+ addMessageListener('teardown', ()=>tearDown());
+}
+
+// Exposed to the caller of |loadPrivilegedScript|
+var contentScript = {
+ handlers: {},
+ addMessageListener: function(message, handler) {
+ if (this.handlers.hasOwnProperty(message)) {
+ this.handlers[message].push(handler);
+ } else {
+ this.handlers[message] = [handler];
+ }
+ },
+ removeMessageListener: function(message, handler) {
+ if (!handler || !this.handlers.hasOwnProperty(message)) {
+ return;
+ }
+ var index = this.handlers[message].indexOf(handler);
+ if (index != -1) {
+ this.handlers[message].splice(index, 1);
+ }
+ },
+ sendAsyncMessage: function(message, data) {
+ port.postMessage({'type': message,
+ 'data': data
+ });
+ }
+}
+
+if (!SpecialPowers.isMainProcess()) {
+ var port;
+ try {
+ port = SpecialPowers.loadPrivilegedScript(loadPrivilegedScriptTest.toSource());
+ } catch (e) {
+ ok(false, "loadPrivilegedScript shoulde not throw" + e);
+ }
+
+ port.onmessage = (e) => {
+ var type = e.data['type'];
+ if (!contentScript.handlers.hasOwnProperty(type)) {
+ return;
+ }
+ var args = [e.data['data']];
+ contentScript.handlers[type].forEach(handler => handler.apply(null, args));
+ };
+}
diff --git a/dom/presentation/tests/mochitest/chrome.ini b/dom/presentation/tests/mochitest/chrome.ini
new file mode 100644
index 000000000..83841f4f8
--- /dev/null
+++ b/dom/presentation/tests/mochitest/chrome.ini
@@ -0,0 +1,14 @@
+[DEFAULT]
+support-files =
+ PresentationDeviceInfoChromeScript.js
+ PresentationSessionChromeScript.js
+
+[test_presentation_datachannel_sessiontransport.html]
+skip-if = os == 'android'
+[test_presentation_device_info.html]
+[test_presentation_sender_startWithDevice.html]
+skip-if = toolkit == 'android' # Bug 1129785
+[test_presentation_tcp_sender.html]
+skip-if = toolkit == 'android' # Bug 1129785
+[test_presentation_tcp_sender_default_request.html]
+skip-if = toolkit == 'android' # Bug 1129785
diff --git a/dom/presentation/tests/mochitest/file_presentation_1ua_receiver.html b/dom/presentation/tests/mochitest/file_presentation_1ua_receiver.html
new file mode 100644
index 000000000..cf02d2b2c
--- /dev/null
+++ b/dom/presentation/tests/mochitest/file_presentation_1ua_receiver.html
@@ -0,0 +1,220 @@
+<!DOCTYPE HTML>
+<!-- vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: -->
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Test for B2G PresentationReceiver at receiver side</title>
+ </head>
+ <body>
+ <div id="content"></div>
+<script type="application/javascript;version=1.7">
+
+"use strict";
+
+function is(a, b, msg) {
+ if (a === b) {
+ alert('OK ' + msg);
+ } else {
+ alert('KO ' + msg + ' | reason: ' + a + ' != ' + b);
+ }
+}
+
+function ok(a, msg) {
+ alert((a ? 'OK ' : 'KO ') + msg);
+}
+
+function info(msg) {
+ alert('INFO ' + msg);
+}
+
+function command(name, data) {
+ alert('COMMAND ' + JSON.stringify({name: name, data: data}));
+}
+
+function finish() {
+ alert('DONE');
+}
+
+var connection;
+const DATA_ARRAY = [0, 255, 254, 0, 1, 2, 3, 0, 255, 255, 254, 0];
+const DATA_ARRAY_BUFFER = new ArrayBuffer(DATA_ARRAY.length);
+const TYPED_DATA_ARRAY = new Uint8Array(DATA_ARRAY_BUFFER);
+TYPED_DATA_ARRAY.set(DATA_ARRAY);
+
+function is_same_buffer(recv_data, expect_data) {
+ let recv_dataview = new Uint8Array(recv_data);
+ let expected_dataview = new Uint8Array(expect_data);
+
+ if (recv_dataview.length !== expected_dataview.length) {
+ return false;
+ }
+
+ for (let i = 0; i < recv_dataview.length; i++) {
+ if (recv_dataview[i] != expected_dataview[i]) {
+ info('discover byte differenct at ' + i);
+ return false;
+ }
+ }
+ return true;
+}
+
+function testConnectionAvailable() {
+ return new Promise(function(aResolve, aReject) {
+ info('Receiver: --- testConnectionAvailable ---');
+ ok(navigator.presentation, "Receiver: navigator.presentation should be available.");
+ ok(navigator.presentation.receiver, "Receiver: navigator.presentation.receiver should be available.");
+ is(navigator.presentation.defaultRequest, null, "Receiver: navigator.presentation.defaultRequest should be null.");
+
+ navigator.presentation.receiver.connectionList
+ .then((aList) => {
+ is(aList.connections.length, 1, "Should get one conncetion.");
+ connection = aList.connections[0];
+ ok(connection.id, "Connection ID should be set: " + connection.id);
+ is(connection.state, "connected", "Connection state at receiver side should be connected.");
+ aResolve();
+ })
+ .catch((aError) => {
+ ok(false, "Receiver: Error occurred when getting the connection: " + aError);
+ finish();
+ aReject();
+ });
+ });
+}
+
+function testConnectionReady() {
+ return new Promise(function(aResolve, aReject) {
+ info('Receiver: --- testConnectionReady ---');
+ connection.onconnect = function() {
+ connection.onconnect = null;
+ ok(false, "Should not get |onconnect| event.")
+ aReject();
+ };
+ if (connection.state === "connected") {
+ connection.onconnect = null;
+ is(connection.state, "connected", "Receiver: Connection state should become connected.");
+ aResolve();
+ }
+ });
+}
+
+function testIncomingMessage() {
+ return new Promise(function(aResolve, aReject) {
+ info('Receiver: --- testIncomingMessage ---');
+ connection.addEventListener('message', function messageHandler(evt) {
+ connection.removeEventListener('message', messageHandler);
+ let msg = evt.data;
+ is(msg, 'msg-sender-to-receiver', 'Receiver: Receiver should receive message from sender.');
+ command('forward-command', JSON.stringify({ name: 'message-from-sender-received' }));
+ aResolve();
+ });
+ command('forward-command', JSON.stringify({ name: 'trigger-message-from-sender' }));
+ });
+}
+
+function testSendMessage() {
+ return new Promise(function(aResolve, aReject) {
+ window.addEventListener('hashchange', function hashchangeHandler(evt) {
+ var message = JSON.parse(decodeURIComponent(window.location.hash.substring(1)));
+ if (message.type === 'trigger-message-from-receiver') {
+ info('Receiver: --- testSendMessage ---');
+ connection.send('msg-receiver-to-sender');
+ }
+ if (message.type === 'message-from-receiver-received') {
+ window.removeEventListener('hashchange', hashchangeHandler);
+ aResolve();
+ }
+ });
+ });
+}
+
+function testIncomingBlobMessage() {
+ return new Promise(function(aResolve, aReject) {
+ info('Receiver: --- testIncomingBlobMessage ---');
+ connection.send('testIncomingBlobMessage');
+ connection.addEventListener('message', function messageHandler(evt) {
+ connection.removeEventListener('message', messageHandler);
+ let recvData= String.fromCharCode.apply(null, new Uint8Array(evt.data));
+ is(recvData, "Hello World", 'expected same string data');
+ aResolve();
+ });
+ });
+}
+
+function testConnectionClosed() {
+ return new Promise(function(aResolve, aReject) {
+ info('Receiver: --- testConnectionClosed ---');
+ connection.onclose = function() {
+ connection.onclose = null;
+ is(connection.state, "closed", "Receiver: Connection should be closed.");
+ command('forward-command', JSON.stringify({ name: 'receiver-closed' }));
+ aResolve();
+ };
+ command('forward-command', JSON.stringify({ name: 'ready-to-close' }));
+ });
+}
+
+function testReconnectConnection() {
+ return new Promise(function(aResolve, aReject) {
+ info('Receiver: --- testReconnectConnection ---');
+ window.addEventListener('hashchange', function hashchangeHandler(evt) {
+ var message = JSON.parse(decodeURIComponent(window.location.hash.substring(1)));
+ if (message.type === 'prepare-for-reconnect') {
+ command('forward-command', JSON.stringify({ name: 'ready-to-reconnect' }));
+ }
+ });
+ connection.onconnect = function() {
+ connection.onconnect = null;
+ ok(true, "The connection is reconnected.")
+ aResolve();
+ };
+ });
+}
+
+function testIncomingArrayBuffer() {
+ return new Promise(function(aResolve, aReject) {
+ info('Receiver: --- testIncomingArrayBuffer ---');
+ connection.binaryType = "blob";
+ connection.send('testIncomingArrayBuffer');
+ connection.addEventListener('message', function messageHandler(evt) {
+ connection.removeEventListener('message', messageHandler);
+ var fileReader = new FileReader();
+ fileReader.onload = function() {
+ ok(is_same_buffer(DATA_ARRAY_BUFFER, this.result), "expected same buffer data");
+ aResolve();
+ };
+ fileReader.readAsArrayBuffer(evt.data);
+ });
+ });
+}
+
+function testIncomingArrayBufferView() {
+ return new Promise(function(aResolve, aReject) {
+ info('Receiver: --- testIncomingArrayBufferView ---');
+ connection.binaryType = "arraybuffer";
+ connection.send('testIncomingArrayBufferView');
+ connection.addEventListener('message', function messageHandler(evt) {
+ connection.removeEventListener('message', messageHandler);
+ ok(is_same_buffer(evt.data, TYPED_DATA_ARRAY), "expected same buffer data");
+ aResolve();
+ });
+ });
+}
+
+function runTests() {
+ testConnectionAvailable()
+ .then(testConnectionReady)
+ .then(testIncomingMessage)
+ .then(testSendMessage)
+ .then(testIncomingBlobMessage)
+ .then(testConnectionClosed)
+ .then(testReconnectConnection)
+ .then(testIncomingArrayBuffer)
+ .then(testIncomingArrayBufferView)
+ .then(testConnectionClosed);
+}
+
+runTests();
+
+</script>
+ </body>
+</html>
diff --git a/dom/presentation/tests/mochitest/file_presentation_1ua_wentaway.html b/dom/presentation/tests/mochitest/file_presentation_1ua_wentaway.html
new file mode 100644
index 000000000..370cb92e1
--- /dev/null
+++ b/dom/presentation/tests/mochitest/file_presentation_1ua_wentaway.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<!-- vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: -->
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Test for B2G PresentationReceiver at receiver side</title>
+ </head>
+ <body>
+ <div id="content"></div>
+<script type="application/javascript;version=1.7">
+
+"use strict";
+
+function is(a, b, msg) {
+ if (a === b) {
+ alert('OK ' + msg);
+ } else {
+ alert('KO ' + msg + ' | reason: ' + a + ' != ' + b);
+ }
+}
+
+function ok(a, msg) {
+ alert((a ? 'OK ' : 'KO ') + msg);
+}
+
+function info(msg) {
+ alert('INFO ' + msg);
+}
+
+function command(name, data) {
+ alert('COMMAND ' + JSON.stringify({name: name, data: data}));
+}
+
+function finish() {
+ alert('DONE');
+}
+
+var connection;
+
+function testConnectionAvailable() {
+ return new Promise(function(aResolve, aReject) {
+ info('Receiver: --- testConnectionAvailable ---');
+ ok(navigator.presentation, "Receiver: navigator.presentation should be available.");
+ ok(navigator.presentation.receiver, "Receiver: navigator.presentation.receiver should be available.");
+
+ navigator.presentation.receiver.connectionList
+ .then((aList) => {
+ is(aList.connections.length, 1, "Should get one conncetion.");
+ connection = aList.connections[0];
+ ok(connection.id, "Connection ID should be set: " + connection.id);
+ is(connection.state, "connected", "Connection state at receiver side should be connected.");
+ aResolve();
+ })
+ .catch((aError) => {
+ ok(false, "Receiver: Error occurred when getting the connection: " + aError);
+ finish();
+ aReject();
+ });
+ });
+}
+
+function testConnectionReady() {
+ return new Promise(function(aResolve, aReject) {
+ info('Receiver: --- testConnectionReady ---');
+ connection.onconnect = function() {
+ connection.onconnect = null;
+ ok(false, "Should not get |onconnect| event.")
+ aReject();
+ };
+ if (connection.state === "connected") {
+ connection.onconnect = null;
+ is(connection.state, "connected", "Receiver: Connection state should become connected.");
+ aResolve();
+ }
+ });
+}
+
+function testConnectionWentaway() {
+ return new Promise(function(aResolve, aReject) {
+ info('Receiver: --- testConnectionWentaway ---\n');
+ command('forward-command', JSON.stringify({ name: 'ready-to-remove-receiverFrame' }));
+ });
+}
+
+function runTests() {
+ testConnectionAvailable()
+ .then(testConnectionReady)
+ .then(testConnectionWentaway);
+}
+
+runTests();
+
+</script>
+ </body>
+</html>
diff --git a/dom/presentation/tests/mochitest/file_presentation_mixed_security_contexts.html b/dom/presentation/tests/mochitest/file_presentation_mixed_security_contexts.html
new file mode 100644
index 000000000..f042d2994
--- /dev/null
+++ b/dom/presentation/tests/mochitest/file_presentation_mixed_security_contexts.html
@@ -0,0 +1,159 @@
+
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test allow-presentation sandboxing flag</title>
+<script type="application/javascript;version=1.8">
+
+"use strict";
+
+function is(a, b, msg) {
+ window.parent.postMessage((a === b ? "OK " : "KO ") + msg, "*");
+}
+
+function ok(a, msg) {
+ window.parent.postMessage((a ? "OK " : "KO ") + msg, "*");
+}
+
+function info(msg) {
+ window.parent.postMessage("INFO " + msg, "*");
+}
+
+function command(msg) {
+ window.parent.postMessage("COMMAND " + JSON.stringify(msg), "*");
+}
+
+function finish() {
+ window.parent.postMessage("DONE", "*");
+}
+
+function testGetAvailability() {
+ return new Promise(function(aResolve, aReject) {
+ ok(navigator.presentation, "navigator.presentation should be available.");
+ var request = new PresentationRequest("http://example.com");
+
+ request.getAvailability().then(
+ function(aAvailability) {
+ ok(false, "Unexpected success, should get a security error.");
+ aReject();
+ },
+ function(aError) {
+ is(aError.name, "SecurityError", "Should get a security error.");
+ aResolve();
+ }
+ );
+ });
+}
+
+function testStartRequest() {
+ return new Promise(function(aResolve, aReject) {
+ var request = new PresentationRequest("http://example.com");
+
+ request.start().then(
+ function(aAvailability) {
+ ok(false, "Unexpected success, should get a security error.");
+ aReject();
+ },
+ function(aError) {
+ is(aError.name, "SecurityError", "Should get a security error.");
+ aResolve();
+ }
+ );
+ });
+}
+
+function testReconnectRequest() {
+ return new Promise(function(aResolve, aReject) {
+ var request = new PresentationRequest("http://example.com");
+
+ request.reconnect("dummyId").then(
+ function(aConnection) {
+ ok(false, "Unexpected success, should get a security error.");
+ aReject();
+ },
+ function(aError) {
+ is(aError.name, "SecurityError", "Should get a security error.");
+ aResolve();
+ }
+ );
+ });
+}
+
+function testGetAvailabilityForAboutBlank() {
+ return new Promise(function(aResolve, aReject) {
+ var request = new PresentationRequest("about:blank");
+
+ request.getAvailability().then(
+ function(aAvailability) {
+ ok(true, "Success due to a priori authenticated URL.");
+ aResolve();
+ },
+ function(aError) {
+ ok(false, "Error occurred when getting availability: " + aError);
+ aReject();
+ }
+ );
+ });
+}
+
+function testGetAvailabilityForAboutSrcdoc() {
+ return new Promise(function(aResolve, aReject) {
+ var request = new PresentationRequest("about:srcdoc");
+
+ request.getAvailability().then(
+ function(aAvailability) {
+ ok(true, "Success due to a priori authenticated URL.");
+ aResolve();
+ },
+ function(aError) {
+ ok(false, "Error occurred when getting availability: " + aError);
+ aReject();
+ }
+ );
+ });
+}
+
+function testGetAvailabilityForDataURL() {
+ return new Promise(function(aResolve, aReject) {
+ var request = new PresentationRequest("data:text/html,1");
+
+ request.getAvailability().then(
+ function(aAvailability) {
+ ok(true, "Success due to a priori authenticated URL.");
+ aResolve();
+ },
+ function(aError) {
+ ok(false, "Error occurred when getting availability: " + aError);
+ aReject();
+ }
+ );
+ });
+}
+
+function runTest() {
+ testGetAvailability()
+ .then(testStartRequest)
+ .then(testReconnectRequest)
+ .then(testGetAvailabilityForAboutBlank)
+ .then(testGetAvailabilityForAboutSrcdoc)
+ .then(testGetAvailabilityForDataURL)
+ .then(finish);
+}
+
+window.addEventListener("message", function onMessage(evt) {
+ window.removeEventListener("message", onMessage);
+ if (evt.data === "start") {
+ runTest();
+ }
+}, false);
+
+window.setTimeout(function() {
+ command("ready-to-start");
+}, 3000);
+
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/presentation/tests/mochitest/file_presentation_non_receiver.html b/dom/presentation/tests/mochitest/file_presentation_non_receiver.html
new file mode 100644
index 000000000..1203523ac
--- /dev/null
+++ b/dom/presentation/tests/mochitest/file_presentation_non_receiver.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for B2G PresentationReceiver on a non-receiver page at receiver side</title>
+</head>
+<body>
+<div id="content"></div>
+<script type="application/javascript;version=1.7">
+
+"use strict";
+
+function is(a, b, msg) {
+ alert((a === b ? 'OK ' : 'KO ') + msg);
+}
+
+function ok(a, msg) {
+ alert((a ? 'OK ' : 'KO ') + msg);
+}
+
+function info(msg) {
+ alert('INFO ' + msg);
+}
+
+function finish() {
+ alert('DONE');
+}
+
+function testConnectionAvailable() {
+ return new Promise(function(aResolve, aReject) {
+ is(navigator.presentation.receiver, null, "navigator.presentation.receiver shouldn't be available in non-receiving pages.");
+ aResolve();
+ });
+}
+
+testConnectionAvailable().
+then(finish);
+
+</script>
+</body>
+</html>
diff --git a/dom/presentation/tests/mochitest/file_presentation_non_receiver_inner_iframe.html b/dom/presentation/tests/mochitest/file_presentation_non_receiver_inner_iframe.html
new file mode 100644
index 000000000..c95eddf57
--- /dev/null
+++ b/dom/presentation/tests/mochitest/file_presentation_non_receiver_inner_iframe.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for B2G PresentationReceiver on a non-receiver inner iframe of the receiver page at receiver side</title>
+</head>
+<body onload="testConnectionAvailable()">
+<div id="content"></div>
+<script type="application/javascript;version=1.7">
+
+"use strict";
+
+function ok(a, msg) {
+ alert((a ? 'OK ' : 'KO ') + msg);
+}
+
+function testConnectionAvailable() {
+ return new Promise(function(aResolve, aReject) {
+ is(navigator.presentation.receiver, null, "navigator.presentation.receiver shouldn't be available in inner iframes with different origins from receiving pages.");
+ aResolve();
+ });
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/presentation/tests/mochitest/file_presentation_receiver.html b/dom/presentation/tests/mochitest/file_presentation_receiver.html
new file mode 100644
index 000000000..46a330b5f
--- /dev/null
+++ b/dom/presentation/tests/mochitest/file_presentation_receiver.html
@@ -0,0 +1,140 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for B2G PresentationReceiver at receiver side</title>
+</head>
+<body>
+<div id="content"></div>
+<script type="application/javascript;version=1.7">
+
+"use strict";
+
+function is(a, b, msg) {
+ alert((a === b ? 'OK ' : 'KO ') + msg);
+}
+
+function ok(a, msg) {
+ alert((a ? 'OK ' : 'KO ') + msg);
+}
+
+function info(msg) {
+ alert('INFO ' + msg);
+}
+
+function command(msg) {
+ alert('COMMAND ' + JSON.stringify(msg));
+}
+
+function finish() {
+ alert('DONE');
+}
+
+var connection;
+
+function testConnectionAvailable() {
+ return new Promise(function(aResolve, aReject) {
+ ok(navigator.presentation, "navigator.presentation should be available in receiving pages.");
+ ok(navigator.presentation.receiver, "navigator.presentation.receiver should be available in receiving pages.");
+
+ navigator.presentation.receiver.connectionList.then(
+ function(aList) {
+ is(aList.connections.length, 1, "Should get one conncetion.");
+ connection = aList.connections[0];
+ ok(connection.id, "Connection ID should be set: " + connection.id);
+ is(connection.state, "connected", "Connection state at receiver side should be connected.");
+ aResolve();
+ },
+ function(aError) {
+ ok(false, "Error occurred when getting the connection list: " + aError);
+ finish();
+ aReject();
+ }
+ );
+ command({ name: 'trigger-incoming-offer' });
+ });
+}
+
+function testDefaultRequestIsUndefined() {
+ return new Promise(function(aResolve, aReject) {
+ is(navigator.presentation.defaultRequest, undefined, "navigator.presentation.defaultRequest should not be available in receiving UA");
+ aResolve();
+ });
+}
+
+function testConnectionAvailableSameOriginInnerIframe() {
+ return new Promise(function(aResolve, aReject) {
+ var iframe = document.createElement('iframe');
+ iframe.setAttribute('src', './file_presentation_receiver_inner_iframe.html');
+ document.body.appendChild(iframe);
+
+ aResolve();
+ });
+}
+
+function testConnectionUnavailableDiffOriginInnerIframe() {
+ return new Promise(function(aResolve, aReject) {
+ var iframe = document.createElement('iframe');
+ iframe.setAttribute('src', 'http://example.com/tests/dom/presentation/tests/mochitest/file_presentation_non_receiver_inner_iframe.html');
+ document.body.appendChild(iframe);
+
+ aResolve();
+ });
+}
+
+function testConnectionListSameObject() {
+ return new Promise(function(aResolve, aReject) {
+ is(navigator.presentation.receiver.connectionList, navigator.presentation.receiver.connectionList, "The promise should be the same object.");
+ var promise = navigator.presentation.receiver.connectionList.then(
+ function(aList) {
+ is(connection, aList.connections[0], "The connection from list and the one from |connectionavailable| event should be the same.");
+ aResolve();
+ },
+ function(aError) {
+ ok(false, "Error occurred when getting the connection list: " + aError);
+ finish();
+ aReject();
+ }
+ );
+ });
+}
+
+function testIncomingMessage() {
+ return new Promise(function(aResolve, aReject) {
+ const incomingMessage = "test incoming message";
+
+ connection.addEventListener('message', function messageHandler(aEvent) {
+ connection.removeEventListener('message', messageHandler);
+ is(aEvent.data, incomingMessage, "An incoming message should be received.");
+ aResolve();
+ });
+
+ command({ name: 'trigger-incoming-message',
+ data: incomingMessage });
+ });
+}
+
+function testCloseConnection() {
+ return new Promise(function(aResolve, aReject) {
+ connection.onclose = function() {
+ connection.onclose = null;
+ is(connection.state, "closed", "Connection should be closed.");
+ aResolve();
+ };
+
+ connection.close();
+ });
+}
+
+testConnectionAvailable().
+then(testDefaultRequestIsUndefined).
+then(testConnectionAvailableSameOriginInnerIframe).
+then(testConnectionUnavailableDiffOriginInnerIframe).
+then(testConnectionListSameObject).
+then(testIncomingMessage).
+then(testCloseConnection).
+then(finish);
+
+</script>
+</body>
+</html>
diff --git a/dom/presentation/tests/mochitest/file_presentation_receiver_auxiliary_navigation.html b/dom/presentation/tests/mochitest/file_presentation_receiver_auxiliary_navigation.html
new file mode 100644
index 000000000..3a6060310
--- /dev/null
+++ b/dom/presentation/tests/mochitest/file_presentation_receiver_auxiliary_navigation.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for sandboxed auxiliary navigation flag in receiver page</title>
+</head>
+<body>
+<div id="content"></div>
+<script type="application/javascript;version=1.7">
+
+"use strict";
+
+function is(a, b, msg) {
+ alert((a === b ? 'OK ' : 'KO ') + msg);
+}
+
+function ok(a, msg) {
+ alert((a ? 'OK ' : 'KO ') + msg);
+}
+
+function info(msg) {
+ alert('INFO ' + msg);
+}
+
+function command(msg) {
+ alert('COMMAND ' + JSON.stringify(msg));
+}
+
+function finish() {
+ alert('DONE');
+}
+
+function testConnectionAvailable() {
+ return new Promise(function(aResolve, aReject) {
+ ok(navigator.presentation, "navigator.presentation should be available in OOP receiving pages.");
+ ok(navigator.presentation.receiver, "navigator.presentation.receiver should be available in receiving pages.");
+
+ aResolve();
+ });
+}
+
+function testOpenWindow() {
+ return new Promise(function(aResolve, aReject) {
+ try {
+ window.open("http://example.com");
+ ok(false, "receiver page should not be able to open a new window.");
+ } catch(e) {
+ ok(true, "receiver page should not be able to open a new window.");
+ aResolve();
+ }
+ });
+}
+
+testConnectionAvailable().
+then(testOpenWindow).
+then(finish);
+
+</script>
+</body>
+</html>
diff --git a/dom/presentation/tests/mochitest/file_presentation_receiver_establish_connection_error.html b/dom/presentation/tests/mochitest/file_presentation_receiver_establish_connection_error.html
new file mode 100644
index 000000000..6b1f2152f
--- /dev/null
+++ b/dom/presentation/tests/mochitest/file_presentation_receiver_establish_connection_error.html
@@ -0,0 +1,79 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for connection establishing errors of B2G Presentation API at receiver side</title>
+</head>
+<body>
+<div id="content"></div>
+<script type="application/javascript;version=1.7">
+
+"use strict";
+
+function is(a, b, msg) {
+ if (a === b) {
+ alert('OK ' + msg);
+ } else {
+ alert('KO ' + msg + ' | reason: ' + a + ' != ' + b);
+ }
+}
+
+function ok(a, msg) {
+ alert((a ? 'OK ' : 'KO ') + msg);
+}
+
+function info(msg) {
+ alert('INFO ' + msg);
+}
+
+function command(name, data) {
+ alert('COMMAND ' + JSON.stringify({name: name, data: data}));
+}
+
+function finish() {
+ alert('DONE');
+}
+
+function testConnectionAvailable() {
+ return new Promise(function(aResolve, aReject) {
+ ok(navigator.presentation, "navigator.presentation should be available.");
+ ok(navigator.presentation.receiver, "navigator.presentation.receiver should be available.");
+ aResolve();
+ });
+}
+
+function testUnexpectedControlChannelClose() {
+ // Trigger the control channel to be closed with error code.
+ command({ name: 'trigger-control-channel-close', data: 0x80004004 /* NS_ERROR_ABORT */ });
+
+ return new Promise(function(aResolve, aReject) {
+ return Promise.race([
+ navigator.presentation.receiver.connectionList.then(
+ (aList) => {
+ ok(false, "Should not get a connection list.")
+ aReject();
+ },
+ (aError) => {
+ ok(false, "Error occurred when getting the connection list: " + aError);
+ aReject();
+ }
+ ),
+ new Promise(
+ () => {
+ setTimeout(() => {
+ ok(true, "Not getting a conenction list.");
+ aResolve();
+ }, 3000);
+ }
+ ),
+ ]);
+ });
+}
+
+testConnectionAvailable().
+then(testUnexpectedControlChannelClose).
+then(finish);
+
+</script>
+</body>
+</html>
diff --git a/dom/presentation/tests/mochitest/file_presentation_receiver_inner_iframe.html b/dom/presentation/tests/mochitest/file_presentation_receiver_inner_iframe.html
new file mode 100644
index 000000000..3bd5ac4b1
--- /dev/null
+++ b/dom/presentation/tests/mochitest/file_presentation_receiver_inner_iframe.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for B2G PresentationReceiver in an inner iframe of the receiver page at receiver side</title>
+</head>
+<body onload="testConnectionAvailable()">
+<div id="content"></div>
+<script type="application/javascript;version=1.7">
+
+"use strict";
+
+function ok(a, msg) {
+ alert((a ? 'OK ' : 'KO ') + msg);
+}
+
+function testConnectionAvailable() {
+ return new Promise(function(aResolve, aReject) {
+ ok(navigator.presentation.receiver, "navigator.presentation.receiver should be available in same-origin inner iframes of receiving pages.");
+ aResolve();
+ });
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/presentation/tests/mochitest/file_presentation_reconnect.html b/dom/presentation/tests/mochitest/file_presentation_reconnect.html
new file mode 100644
index 000000000..174ccd3f3
--- /dev/null
+++ b/dom/presentation/tests/mochitest/file_presentation_reconnect.html
@@ -0,0 +1,102 @@
+
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test allow-presentation sandboxing flag</title>
+<script type="application/javascript;version=1.8">
+
+"use strict";
+
+function is(a, b, msg) {
+ window.parent.postMessage((a === b ? "OK " : "KO ") + msg, "*");
+}
+
+function ok(a, msg) {
+ window.parent.postMessage((a ? "OK " : "KO ") + msg, "*");
+}
+
+function info(msg) {
+ window.parent.postMessage("INFO " + msg, "*");
+}
+
+function command(msg) {
+ window.parent.postMessage("COMMAND " + JSON.stringify(msg), "*");
+}
+
+function finish() {
+ window.parent.postMessage("DONE", "*");
+}
+
+var request;
+var connection;
+
+function testStartRequest() {
+ return new Promise(function(aResolve, aReject) {
+ ok(navigator.presentation, "navigator.presentation should be available.");
+ request = new PresentationRequest("http://example1.com");
+
+ request.start().then(
+ function(aConnection) {
+ connection = aConnection;
+ ok(connection, "Connection should be available.");
+ ok(connection.id, "Connection ID should be set.");
+ is(connection.state, "connecting", "The initial state should be connecting.");
+
+ connection.onclose = function() {
+ connection.onclose = null;
+ command({ name: "notify-connection-closed", id: connection.id });
+ };
+ connection.onconnect = function() {
+ connection.onconnect = null;
+ is(connection.state, "connected", "Connection should be connected.");
+ aResolve();
+ };
+ },
+ function(aError) {
+ ok(false, "Error occurred when establishing a connection: " + aError);
+ teardown();
+ aReject();
+ }
+ );
+ });
+}
+
+function testCloseConnection() {
+ return new Promise(function(aResolve, aReject) {
+ if (connection.state === "closed") {
+ aResolve();
+ return;
+ }
+ connection.onclose = function() {
+ connection.onclose = null;
+ is(connection.state, "closed", "The connection should be closed.");
+ aResolve();
+ };
+
+ connection.close();
+ });
+}
+
+window.addEventListener("message", function onMessage(evt) {
+ if (evt.data === "startConnection") {
+ testStartRequest().then(
+ function () {
+ command({ name: "connection-connected", id: connection.id });
+ }
+ );
+ }
+ else if (evt.data === "closeConnection") {
+ testCloseConnection().then(
+ function () {
+ command({ name: "connection-closed", id: connection.id });
+ }
+ );
+ }
+}, false);
+
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/presentation/tests/mochitest/file_presentation_sandboxed_presentation.html b/dom/presentation/tests/mochitest/file_presentation_sandboxed_presentation.html
new file mode 100644
index 000000000..369621cee
--- /dev/null
+++ b/dom/presentation/tests/mochitest/file_presentation_sandboxed_presentation.html
@@ -0,0 +1,114 @@
+
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test allow-presentation sandboxing flag</title>
+<script type="application/javascript;version=1.8">
+
+"use strict";
+
+function is(a, b, msg) {
+ window.parent.postMessage((a === b ? "OK " : "KO ") + msg, "*");
+}
+
+function ok(a, msg) {
+ window.parent.postMessage((a ? "OK " : "KO ") + msg, "*");
+}
+
+function info(msg) {
+ window.parent.postMessage("INFO " + msg, "*");
+}
+
+function command(msg) {
+ window.parent.postMessage("COMMAND " + JSON.stringify(msg), "*");
+}
+
+function finish() {
+ window.parent.postMessage("DONE", "*");
+}
+
+function testGetAvailability() {
+ return new Promise(function(aResolve, aReject) {
+ ok(navigator.presentation, "navigator.presentation should be available.");
+ var request = new PresentationRequest("http://example.com");
+
+ request.getAvailability().then(
+ function(aAvailability) {
+ ok(false, "Unexpected success, should get a security error.");
+ aReject();
+ },
+ function(aError) {
+ is(aError.name, "SecurityError", "Should get a security error.");
+ aResolve();
+ }
+ );
+ });
+}
+
+function testStartRequest() {
+ return new Promise(function(aResolve, aReject) {
+ var request = new PresentationRequest("http://example.com");
+
+ request.start().then(
+ function(aAvailability) {
+ ok(false, "Unexpected success, should get a security error.");
+ aReject();
+ },
+ function(aError) {
+ is(aError.name, "SecurityError", "Should get a security error.");
+ aResolve();
+ }
+ );
+ });
+}
+
+function testDefaultRequest() {
+ return new Promise(function(aResolve, aReject) {
+ navigator.presentation.defaultRequest = new PresentationRequest("http://example.com");
+ is(navigator.presentation.defaultRequest, null, "DefaultRequest shoud be null.");
+ aResolve();
+ });
+}
+
+function testReconnectRequest() {
+ return new Promise(function(aResolve, aReject) {
+ var request = new PresentationRequest("http://example.com");
+
+ request.reconnect("dummyId").then(
+ function(aConnection) {
+ ok(false, "Unexpected success, should get a security error.");
+ aReject();
+ },
+ function(aError) {
+ is(aError.name, "SecurityError", "Should get a security error.");
+ aResolve();
+ }
+ );
+ });
+}
+
+function runTest() {
+ testGetAvailability()
+ .then(testStartRequest)
+ .then(testDefaultRequest)
+ .then(testReconnectRequest)
+ .then(finish);
+}
+
+window.addEventListener("message", function onMessage(evt) {
+ window.removeEventListener("message", onMessage);
+ if (evt.data === "start") {
+ runTest();
+ }
+}, false);
+
+window.setTimeout(function() {
+ command("ready-to-start");
+}, 3000);
+
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/presentation/tests/mochitest/file_presentation_terminate.html b/dom/presentation/tests/mochitest/file_presentation_terminate.html
new file mode 100644
index 000000000..a26a44b90
--- /dev/null
+++ b/dom/presentation/tests/mochitest/file_presentation_terminate.html
@@ -0,0 +1,104 @@
+<!DOCTYPE HTML>
+<!-- vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: -->
+<html>
+ <head>
+ <meta charset='utf-8'>
+ <title>Test for B2G PresentationReceiver at receiver side</title>
+ </head>
+ <body>
+ <div id='content'></div>
+<script type='application/javascript;version=1.7'>
+
+'use strict';
+
+function is(a, b, msg) {
+ if (a === b) {
+ alert('OK ' + msg);
+ } else {
+ alert('KO ' + msg + ' | reason: ' + a + ' != ' + b);
+ }
+}
+
+function ok(a, msg) {
+ alert((a ? 'OK ' : 'KO ') + msg);
+}
+
+function info(msg) {
+ alert('INFO ' + msg);
+}
+
+function command(name, data) {
+ alert('COMMAND ' + JSON.stringify({name: name, data: data}));
+}
+
+function finish() {
+ alert('DONE');
+}
+
+var connection;
+
+function testConnectionAvailable() {
+ return new Promise(function(aResolve, aReject) {
+ info('Receiver: --- testConnectionAvailable ---');
+ ok(navigator.presentation, 'Receiver: navigator.presentation should be available.');
+ ok(navigator.presentation.receiver, 'Receiver: navigator.presentation.receiver should be available.');
+
+ navigator.presentation.receiver.connectionList
+ .then((aList) => {
+ is(aList.connections.length, 1, 'Should get one conncetion.');
+ connection = aList.connections[0];
+ ok(connection.id, 'Connection ID should be set: ' + connection.id);
+ is(connection.state, 'connected', 'Connection state at receiver side should be connected.');
+ aResolve();
+ })
+ .catch((aError) => {
+ ok(false, 'Receiver: Error occurred when getting the connection: ' + aError);
+ finish();
+ aReject();
+ });
+ });
+}
+
+function testConnectionReady() {
+ return new Promise(function(aResolve, aReject) {
+ info('Receiver: --- testConnectionReady ---');
+ connection.onconnect = function() {
+ connection.onconnect = null;
+ ok(false, 'Should not get |onconnect| event.')
+ aReject();
+ };
+ if (connection.state === 'connected') {
+ connection.onconnect = null;
+ is(connection.state, 'connected', 'Receiver: Connection state should become connected.');
+ aResolve();
+ }
+ });
+}
+
+function testConnectionTerminate() {
+ return new Promise(function(aResolve, aReject) {
+ info('Receiver: --- testConnectionTerminate ---');
+ connection.onterminate = function() {
+ connection.onterminate = null;
+ // Using window.alert at this stage will cause window.close() fail.
+ // Only trigger it if verdict fail.
+ if (connection.state !== 'terminated') {
+ is(connection.state, 'terminated', 'Receiver: Connection should be terminated.');
+ }
+ aResolve();
+ };
+ command('forward-command', JSON.stringify({ name: 'ready-to-terminate' }));
+ });
+}
+
+function runTests() {
+ testConnectionAvailable()
+ .then(testConnectionReady)
+ .then(testConnectionTerminate)
+}
+
+runTests();
+
+</script>
+ </body>
+</html>
diff --git a/dom/presentation/tests/mochitest/file_presentation_terminate_establish_connection_error.html b/dom/presentation/tests/mochitest/file_presentation_terminate_establish_connection_error.html
new file mode 100644
index 000000000..d8df8a1a6
--- /dev/null
+++ b/dom/presentation/tests/mochitest/file_presentation_terminate_establish_connection_error.html
@@ -0,0 +1,114 @@
+<!DOCTYPE HTML>
+<!-- vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: -->
+<html>
+ <head>
+ <meta charset='utf-8'>
+ <title>Test for B2G PresentationReceiver at receiver side</title>
+ </head>
+ <body>
+ <div id='content'></div>
+<script type='application/javascript;version=1.7'>
+
+'use strict';
+
+function is(a, b, msg) {
+ if (a === b) {
+ alert('OK ' + msg);
+ } else {
+ alert('KO ' + msg + ' | reason: ' + a + ' != ' + b);
+ }
+}
+
+function ok(a, msg) {
+ alert((a ? 'OK ' : 'KO ') + msg);
+}
+
+function info(msg) {
+ alert('INFO ' + msg);
+}
+
+function command(name, data) {
+ alert('COMMAND ' + JSON.stringify({name: name, data: data}));
+}
+
+function finish() {
+ alert('DONE');
+}
+
+var connection;
+
+function testConnectionAvailable() {
+ return new Promise(function(aResolve, aReject) {
+ info('Receiver: --- testConnectionAvailable ---');
+ ok(navigator.presentation, 'Receiver: navigator.presentation should be available.');
+ ok(navigator.presentation.receiver, 'Receiver: navigator.presentation.receiver should be available.');
+
+ navigator.presentation.receiver.connectionList
+ .then((aList) => {
+ is(aList.connections.length, 1, 'Should get one connection.');
+ connection = aList.connections[0];
+ ok(connection.id, 'Connection ID should be set: ' + connection.id);
+ is(connection.state, 'connected', 'Connection state at receiver side should be connected.');
+ aResolve();
+ })
+ .catch((aError) => {
+ ok(false, 'Receiver: Error occurred when getting the connection: ' + aError);
+ finish();
+ aReject();
+ });
+ });
+}
+
+function testConnectionReady() {
+ return new Promise(function(aResolve, aReject) {
+ info('Receiver: --- testConnectionReady ---');
+ connection.onconnect = function() {
+ connection.onconnect = null;
+ ok(false, 'Should not get |onconnect| event.')
+ aReject();
+ };
+ if (connection.state === 'connected') {
+ connection.onconnect = null;
+ is(connection.state, 'connected', 'Receiver: Connection state should become connected.');
+ aResolve();
+ }
+ });
+}
+
+function testConnectionTerminate() {
+ return new Promise(function(aResolve, aReject) {
+ info('Receiver: --- testConnectionTerminate ---');
+ connection.onterminate = function() {
+ connection.onterminate = null;
+ // Using window.alert at this stage will cause window.close() fail.
+ // Only trigger it if verdict fail.
+ if (connection.state !== 'terminated') {
+ is(connection.state, 'terminated', 'Receiver: Connection should be terminated.');
+ }
+ aResolve();
+ };
+
+ window.addEventListener('hashchange', function hashchangeHandler(evt) {
+ var message = JSON.parse(decodeURIComponent(window.location.hash.substring(1)));
+ if (message.type === 'ready-to-terminate') {
+ info('Receiver: --- ready-to-terminate ---');
+ connection.terminate();
+ }
+ });
+
+
+ command('forward-command', JSON.stringify({ name: 'prepare-for-terminate' }));
+ });
+}
+
+function runTests() {
+ testConnectionAvailable()
+ .then(testConnectionReady)
+ .then(testConnectionTerminate)
+}
+
+runTests();
+
+</script>
+ </body>
+</html>
diff --git a/dom/presentation/tests/mochitest/file_presentation_unknown_content_type.test b/dom/presentation/tests/mochitest/file_presentation_unknown_content_type.test
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/dom/presentation/tests/mochitest/file_presentation_unknown_content_type.test
@@ -0,0 +1 @@
+
diff --git a/dom/presentation/tests/mochitest/file_presentation_unknown_content_type.test^headers^ b/dom/presentation/tests/mochitest/file_presentation_unknown_content_type.test^headers^
new file mode 100644
index 000000000..fc044e3c4
--- /dev/null
+++ b/dom/presentation/tests/mochitest/file_presentation_unknown_content_type.test^headers^
@@ -0,0 +1 @@
+Content-Type: application/unknown
diff --git a/dom/presentation/tests/mochitest/mochitest.ini b/dom/presentation/tests/mochitest/mochitest.ini
new file mode 100644
index 000000000..f96e07f1e
--- /dev/null
+++ b/dom/presentation/tests/mochitest/mochitest.ini
@@ -0,0 +1,77 @@
+[DEFAULT]
+support-files =
+ PresentationDeviceInfoChromeScript.js
+ PresentationSessionChromeScript.js
+ PresentationSessionFrameScript.js
+ PresentationSessionChromeScript1UA.js
+ file_presentation_1ua_receiver.html
+ test_presentation_1ua_sender_and_receiver.js
+ file_presentation_non_receiver_inner_iframe.html
+ file_presentation_non_receiver.html
+ file_presentation_receiver.html
+ file_presentation_receiver_establish_connection_error.html
+ file_presentation_receiver_inner_iframe.html
+ file_presentation_1ua_wentaway.html
+ test_presentation_1ua_connection_wentaway.js
+ file_presentation_receiver_auxiliary_navigation.html
+ test_presentation_receiver_auxiliary_navigation.js
+ file_presentation_sandboxed_presentation.html
+ file_presentation_terminate.html
+ test_presentation_terminate.js
+ file_presentation_terminate_establish_connection_error.html
+ test_presentation_terminate_establish_connection_error.js
+ file_presentation_reconnect.html
+ file_presentation_unknown_content_type.test
+ file_presentation_unknown_content_type.test^headers^
+ test_presentation_tcp_receiver_establish_connection_unknown_content_type.js
+ file_presentation_mixed_security_contexts.html
+
+[test_presentation_dc_sender.html]
+[test_presentation_dc_receiver.html]
+skip-if = (e10s || toolkit == 'android') # Bug 1129785
+[test_presentation_dc_receiver_oop.html]
+skip-if = (e10s || toolkit == 'android') # Bug 1129785
+[test_presentation_1ua_sender_and_receiver_inproc.html]
+skip-if = (e10s || toolkit == 'android') # Bug 1129785
+[test_presentation_1ua_sender_and_receiver_oop.html]
+skip-if = (e10s || toolkit == 'android') # Bug 1129785
+[test_presentation_1ua_connection_wentaway_inproc.html]
+skip-if = (e10s || toolkit == 'android') # Bug 1129785
+[test_presentation_1ua_connection_wentaway_oop.html]
+skip-if = (e10s || toolkit == 'android') # Bug 1129785
+[test_presentation_device_info_permission.html]
+[test_presentation_tcp_sender_disconnect.html]
+skip-if = toolkit == 'android' # Bug 1129785
+[test_presentation_tcp_sender_establish_connection_error.html]
+skip-if = toolkit == 'android' # Bug 1129785
+[test_presentation_tcp_receiver_establish_connection_error.html]
+skip-if = (e10s || toolkit == 'android' || os == 'mac' || os == 'win') # Bug 1129785, Bug 1204709
+[test_presentation_tcp_receiver_establish_connection_timeout.html]
+skip-if = (e10s || toolkit == 'android') # Bug 1129785
+[test_presentation_tcp_receiver_establish_connection_unknown_content_type_inproc.html]
+skip-if = (e10s || toolkit == 'android')
+[test_presentation_tcp_receiver_establish_connection_unknown_content_type_oop.html]
+skip-if = (e10s || toolkit == 'android')
+[test_presentation_tcp_receiver.html]
+skip-if = (e10s || toolkit == 'android') # Bug 1129785
+[test_presentation_tcp_receiver_oop.html]
+skip-if = (e10s || toolkit == 'android') # Bug 1129785
+[test_presentation_receiver_auxiliary_navigation_inproc.html]
+skip-if = e10s
+[test_presentation_receiver_auxiliary_navigation_oop.html]
+skip-if = e10s
+[test_presentation_terminate_inproc.html]
+skip-if = (e10s || toolkit == 'android')
+[test_presentation_terminate_oop.html]
+skip-if = (e10s || toolkit == 'android')
+[test_presentation_terminate_establish_connection_error_inproc.html]
+skip-if = (e10s || toolkit == 'android')
+[test_presentation_terminate_establish_connection_error_oop.html]
+skip-if = (e10s || toolkit == 'android')
+[test_presentation_sender_on_terminate_request.html]
+skip-if = toolkit == 'android'
+[test_presentation_sandboxed_presentation.html]
+skip-if = true # bug 1315867
+[test_presentation_reconnect.html]
+[test_presentation_mixed_security_contexts.html]
+[test_presentation_availability.html]
diff --git a/dom/presentation/tests/mochitest/test_presentation_1ua_connection_wentaway.js b/dom/presentation/tests/mochitest/test_presentation_1ua_connection_wentaway.js
new file mode 100644
index 000000000..dbeb4ffcc
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_1ua_connection_wentaway.js
@@ -0,0 +1,175 @@
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout('Test for guarantee not firing async event');
+
+function debug(str) {
+ // info(str);
+}
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript1UA.js'));
+var receiverUrl = SimpleTest.getTestFileURL('file_presentation_1ua_wentaway.html');
+var request;
+var connection;
+var receiverIframe;
+
+function setup() {
+ gScript.addMessageListener('device-prompt', function devicePromptHandler() {
+ debug('Got message: device-prompt');
+ gScript.removeMessageListener('device-prompt', devicePromptHandler);
+ gScript.sendAsyncMessage('trigger-device-prompt-select');
+ });
+
+ gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
+ gScript.removeMessageListener('control-channel-established',
+ controlChannelEstablishedHandler);
+ gScript.sendAsyncMessage("trigger-control-channel-open");
+ });
+
+ gScript.addMessageListener('sender-launch', function senderLaunchHandler(url) {
+ debug('Got message: sender-launch');
+ gScript.removeMessageListener('sender-launch', senderLaunchHandler);
+ is(url, receiverUrl, 'Receiver: should receive the same url');
+ receiverIframe = document.createElement('iframe');
+ receiverIframe.setAttribute("mozbrowser", "true");
+ receiverIframe.setAttribute("mozpresentation", receiverUrl);
+ var oop = location.pathname.indexOf('_inproc') == -1;
+ receiverIframe.setAttribute("remote", oop);
+
+ receiverIframe.setAttribute('src', receiverUrl);
+ receiverIframe.addEventListener("mozbrowserloadend", function mozbrowserloadendHander() {
+ receiverIframe.removeEventListener("mozbrowserloadend", mozbrowserloadendHander);
+ info("Receiver loaded.");
+ });
+
+ // This event is triggered when the iframe calls "alert".
+ receiverIframe.addEventListener("mozbrowsershowmodalprompt", function receiverListener(evt) {
+ var message = evt.detail.message;
+ if (/^OK /.exec(message)) {
+ ok(true, message.replace(/^OK /, ""));
+ } else if (/^KO /.exec(message)) {
+ ok(false, message.replace(/^KO /, ""));
+ } else if (/^INFO /.exec(message)) {
+ info(message.replace(/^INFO /, ""));
+ } else if (/^COMMAND /.exec(message)) {
+ var command = JSON.parse(message.replace(/^COMMAND /, ""));
+ gScript.sendAsyncMessage(command.name, command.data);
+ } else if (/^DONE$/.exec(message)) {
+ receiverIframe.removeEventListener("mozbrowsershowmodalprompt",
+ receiverListener);
+ teardown();
+ }
+ }, false);
+
+ var promise = new Promise(function(aResolve, aReject) {
+ document.body.appendChild(receiverIframe);
+ aResolve(receiverIframe);
+ });
+
+ var obs = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+ .getService(SpecialPowers.Ci.nsIObserverService);
+ obs.notifyObservers(promise, 'setup-request-promise', null);
+ });
+
+ gScript.addMessageListener('promise-setup-ready', function promiseSetupReadyHandler() {
+ debug('Got message: promise-setup-ready');
+ gScript.removeMessageListener('promise-setup-ready',
+ promiseSetupReadyHandler);
+ gScript.sendAsyncMessage('trigger-on-session-request', receiverUrl);
+ });
+
+ return Promise.resolve();
+}
+
+function testCreateRequest() {
+ return new Promise(function(aResolve, aReject) {
+ info('Sender: --- testCreateRequest ---');
+ request = new PresentationRequest(receiverUrl);
+ request.getAvailability().then((aAvailability) => {
+ is(aAvailability.value, false, "Sender: should have no available device after setup");
+ aAvailability.onchange = function() {
+ aAvailability.onchange = null;
+ ok(aAvailability.value, "Sender: Device should be available.");
+ aResolve();
+ }
+
+ gScript.sendAsyncMessage('trigger-device-add');
+ }).catch((aError) => {
+ ok(false, "Sender: Error occurred when getting availability: " + aError);
+ teardown();
+ aReject();
+ });
+ });
+}
+
+function testStartConnection() {
+ return new Promise(function(aResolve, aReject) {
+ request.start().then((aConnection) => {
+ connection = aConnection;
+ ok(connection, "Sender: Connection should be available.");
+ ok(connection.id, "Sender: Connection ID should be set.");
+ is(connection.state, "connecting", "Sender: The initial state should be connecting.");
+ connection.onconnect = function() {
+ connection.onconnect = null;
+ is(connection.state, "connected", "Connection should be connected.");
+ aResolve();
+ };
+ }).catch((aError) => {
+ ok(false, "Sender: Error occurred when establishing a connection: " + aError);
+ teardown();
+ aReject();
+ });
+ });
+}
+
+function testConnectionWentaway() {
+ return new Promise(function(aResolve, aReject) {
+ info('Sender: --- testConnectionWentaway ---');
+ connection.onclose = function() {
+ connection.onclose = null;
+ is(connection.state, "closed", "Sender: Connection should be closed.");
+ receiverIframe.addEventListener('mozbrowserclose', function closeHandler() {
+ ok(false, 'wentaway should not trigger receiver close');
+ aResolve();
+ });
+ setTimeout(aResolve, 3000);
+ };
+ gScript.addMessageListener('ready-to-remove-receiverFrame', function onReadyToRemove() {
+ gScript.removeMessageListener('ready-to-remove-receiverFrame', onReadyToRemove);
+ receiverIframe.src = "http://example.com";
+ });
+ });
+}
+
+function teardown() {
+ gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+ debug('Got message: teardown-complete');
+ gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
+ gScript.destroy();
+ SimpleTest.finish();
+ });
+
+ gScript.sendAsyncMessage('teardown');
+}
+
+function runTests() {
+ setup().then(testCreateRequest)
+ .then(testStartConnection)
+ .then(testConnectionWentaway)
+ .then(teardown);
+}
+
+SpecialPowers.pushPermissions([
+ {type: 'presentation-device-manage', allow: false, context: document},
+ {type: "browser", allow: true, context: document},
+], () => {
+ SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
+ ["dom.presentation.controller.enabled", true],
+ ["dom.presentation.receiver.enabled", true],
+ ["dom.presentation.test.enabled", true],
+ ["dom.mozBrowserFramesEnabled", true],
+ ["dom.ipc.tabs.disabled", false],
+ ["network.disable.ipc.security", true],
+ ["dom.presentation.test.stage", 0]]},
+ runTests);
+});
diff --git a/dom/presentation/tests/mochitest/test_presentation_1ua_connection_wentaway_inproc.html b/dom/presentation/tests/mochitest/test_presentation_1ua_connection_wentaway_inproc.html
new file mode 100644
index 000000000..68491d81b
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_1ua_connection_wentaway_inproc.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<!-- vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: -->
+<html>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <head>
+ <meta charset="utf-8">
+ <title>Test for B2G Presentation API when sender and receiver at the same side</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ </head>
+ <body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1258600">
+ Test for PresentationConnectionCloseEvent with wentaway reason</a>
+ <script type="application/javascript;version=1.8" src="test_presentation_1ua_connection_wentaway.js">
+ </script>
+ </body>
+</html>
diff --git a/dom/presentation/tests/mochitest/test_presentation_1ua_connection_wentaway_oop.html b/dom/presentation/tests/mochitest/test_presentation_1ua_connection_wentaway_oop.html
new file mode 100644
index 000000000..68491d81b
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_1ua_connection_wentaway_oop.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<!-- vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: -->
+<html>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <head>
+ <meta charset="utf-8">
+ <title>Test for B2G Presentation API when sender and receiver at the same side</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ </head>
+ <body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1258600">
+ Test for PresentationConnectionCloseEvent with wentaway reason</a>
+ <script type="application/javascript;version=1.8" src="test_presentation_1ua_connection_wentaway.js">
+ </script>
+ </body>
+</html>
diff --git a/dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver.js b/dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver.js
new file mode 100644
index 000000000..8a7787b40
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver.js
@@ -0,0 +1,370 @@
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+* You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+'use strict';
+
+function debug(str) {
+ // info(str);
+}
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript1UA.js'));
+var receiverUrl = SimpleTest.getTestFileURL('file_presentation_1ua_receiver.html');
+var request;
+var connection;
+var receiverIframe;
+var presentationId;
+const DATA_ARRAY = [0, 255, 254, 0, 1, 2, 3, 0, 255, 255, 254, 0];
+const DATA_ARRAY_BUFFER = new ArrayBuffer(DATA_ARRAY.length);
+const TYPED_DATA_ARRAY = new Uint8Array(DATA_ARRAY_BUFFER);
+TYPED_DATA_ARRAY.set(DATA_ARRAY);
+
+function postMessageToIframe(aType) {
+ receiverIframe.src = receiverUrl + "#" +
+ encodeURIComponent(JSON.stringify({ type: aType }));
+}
+
+function setup() {
+
+ gScript.addMessageListener('device-prompt', function devicePromptHandler() {
+ debug('Got message: device-prompt');
+ gScript.removeMessageListener('device-prompt', devicePromptHandler);
+ gScript.sendAsyncMessage('trigger-device-prompt-select');
+ });
+
+ gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
+ gScript.removeMessageListener('control-channel-established',
+ controlChannelEstablishedHandler);
+ gScript.sendAsyncMessage("trigger-control-channel-open");
+ });
+
+ gScript.addMessageListener('sender-launch', function senderLaunchHandler(url) {
+ debug('Got message: sender-launch');
+ gScript.removeMessageListener('sender-launch', senderLaunchHandler);
+ is(url, receiverUrl, 'Receiver: should receive the same url');
+ receiverIframe = document.createElement('iframe');
+ receiverIframe.setAttribute('src', receiverUrl);
+ receiverIframe.setAttribute("mozbrowser", "true");
+ receiverIframe.setAttribute("mozpresentation", receiverUrl);
+ var oop = location.pathname.indexOf('_inproc') == -1;
+ receiverIframe.setAttribute("remote", oop);
+
+ // This event is triggered when the iframe calls "alert".
+ receiverIframe.addEventListener("mozbrowsershowmodalprompt", function receiverListener(evt) {
+ var message = evt.detail.message;
+ debug('Got iframe message: ' + message);
+ if (/^OK /.exec(message)) {
+ ok(true, message.replace(/^OK /, ""));
+ } else if (/^KO /.exec(message)) {
+ ok(false, message.replace(/^KO /, ""));
+ } else if (/^INFO /.exec(message)) {
+ info(message.replace(/^INFO /, ""));
+ } else if (/^COMMAND /.exec(message)) {
+ var command = JSON.parse(message.replace(/^COMMAND /, ""));
+ gScript.sendAsyncMessage(command.name, command.data);
+ } else if (/^DONE$/.exec(message)) {
+ receiverIframe.removeEventListener("mozbrowsershowmodalprompt",
+ receiverListener);
+ }
+ }, false);
+
+ var promise = new Promise(function(aResolve, aReject) {
+ document.body.appendChild(receiverIframe);
+ aResolve(receiverIframe);
+ });
+
+ var obs = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+ .getService(SpecialPowers.Ci.nsIObserverService);
+ obs.notifyObservers(promise, 'setup-request-promise', null);
+ });
+
+ gScript.addMessageListener('promise-setup-ready', function promiseSetupReadyHandler() {
+ debug('Got message: promise-setup-ready');
+ gScript.removeMessageListener('promise-setup-ready', promiseSetupReadyHandler);
+ gScript.sendAsyncMessage('trigger-on-session-request', receiverUrl);
+ });
+
+ return Promise.resolve();
+}
+
+function testCreateRequest() {
+ return new Promise(function(aResolve, aReject) {
+ info('Sender: --- testCreateRequest ---');
+ request = new PresentationRequest("file_presentation_1ua_receiver.html");
+ request.getAvailability().then((aAvailability) => {
+ is(aAvailability.value, false, "Sender: should have no available device after setup");
+ aAvailability.onchange = function() {
+ aAvailability.onchange = null;
+ ok(aAvailability.value, "Sender: Device should be available.");
+ aResolve();
+ }
+
+ gScript.sendAsyncMessage('trigger-device-add');
+ }).catch((aError) => {
+ ok(false, "Sender: Error occurred when getting availability: " + aError);
+ teardown();
+ aReject();
+ });
+ });
+}
+
+function testStartConnection() {
+ return new Promise(function(aResolve, aReject) {
+ request.start().then((aConnection) => {
+ connection = aConnection;
+ ok(connection, "Sender: Connection should be available.");
+ ok(connection.id, "Sender: Connection ID should be set.");
+ is(connection.state, "connecting", "The initial state should be connecting.");
+ is(connection.url, receiverUrl, "request URL should be expanded to absolute URL");
+ connection.onconnect = function() {
+ connection.onconnect = null;
+ is(connection.state, "connected", "Connection should be connected.");
+ presentationId = connection.id;
+ aResolve();
+ };
+ }).catch((aError) => {
+ ok(false, "Sender: Error occurred when establishing a connection: " + aError);
+ teardown();
+ aReject();
+ });
+
+ let request2 = new PresentationRequest("/");
+ request2.start().then(() => {
+ ok(false, "Sender: session start should fail while there is an unsettled promise.");
+ }).catch((aError) => {
+ is(aError.name, "OperationError", "Expect to get OperationError.");
+ });
+ });
+}
+
+function testSendMessage() {
+ return new Promise(function(aResolve, aReject) {
+ info('Sender: --- testSendMessage ---');
+ gScript.addMessageListener('trigger-message-from-sender', function triggerMessageFromSenderHandler() {
+ debug('Got message: trigger-message-from-sender');
+ gScript.removeMessageListener('trigger-message-from-sender', triggerMessageFromSenderHandler);
+ info('Send message to receiver');
+ connection.send('msg-sender-to-receiver');
+ });
+
+ gScript.addMessageListener('message-from-sender-received', function messageFromSenderReceivedHandler() {
+ debug('Got message: message-from-sender-received');
+ gScript.removeMessageListener('message-from-sender-received', messageFromSenderReceivedHandler);
+ aResolve();
+ });
+ });
+}
+
+function testIncomingMessage() {
+ return new Promise(function(aResolve, aReject) {
+ info('Sender: --- testIncomingMessage ---');
+ connection.addEventListener('message', function messageHandler(evt) {
+ connection.removeEventListener('message', messageHandler);
+ let msg = evt.data;
+ is(msg, "msg-receiver-to-sender", "Sender: Sender should receive message from Receiver");
+ postMessageToIframe('message-from-receiver-received');
+ aResolve();
+ });
+ postMessageToIframe('trigger-message-from-receiver');
+ });
+}
+
+function testSendBlobMessage() {
+ return new Promise(function(aResolve, aReject) {
+ info('Sender: --- testSendBlobMessage ---');
+ connection.addEventListener('message', function messageHandler(evt) {
+ connection.removeEventListener('message', messageHandler);
+ let msg = evt.data;
+ is(msg, "testIncomingBlobMessage", "Sender: Sender should receive message from Receiver");
+ let blob = new Blob(["Hello World"], {type : 'text/plain'});
+ connection.send(blob);
+ aResolve();
+ });
+ });
+}
+
+function testSendArrayBuffer() {
+ return new Promise(function(aResolve, aReject) {
+ info('Sender: --- testSendArrayBuffer ---');
+ connection.addEventListener('message', function messageHandler(evt) {
+ connection.removeEventListener('message', messageHandler);
+ let msg = evt.data;
+ is(msg, "testIncomingArrayBuffer", "Sender: Sender should receive message from Receiver");
+ connection.send(DATA_ARRAY_BUFFER);
+ aResolve();
+ });
+ });
+}
+
+function testSendArrayBufferView() {
+ return new Promise(function(aResolve, aReject) {
+ info('Sender: --- testSendArrayBufferView ---');
+ connection.addEventListener('message', function messageHandler(evt) {
+ connection.removeEventListener('message', messageHandler);
+ let msg = evt.data;
+ is(msg, "testIncomingArrayBufferView", "Sender: Sender should receive message from Receiver");
+ connection.send(TYPED_DATA_ARRAY);
+ aResolve();
+ });
+ });
+}
+
+function testCloseConnection() {
+ info('Sender: --- testCloseConnection ---');
+ // Test terminate immediate after close.
+ function controlChannelEstablishedHandler()
+ {
+ gScript.removeMessageListener('control-channel-established',
+ controlChannelEstablishedHandler);
+ ok(false, "terminate after close should do nothing");
+ }
+ gScript.addMessageListener('ready-to-close', function onReadyToClose() {
+ gScript.removeMessageListener('ready-to-close', onReadyToClose);
+ connection.close();
+
+ gScript.addMessageListener('control-channel-established', controlChannelEstablishedHandler);
+ connection.terminate();
+ });
+
+ return Promise.all([
+ new Promise(function(aResolve, aReject) {
+ connection.onclose = function() {
+ connection.onclose = null;
+ is(connection.state, 'closed', 'Sender: Connection should be closed.');
+ gScript.removeMessageListener('control-channel-established',
+ controlChannelEstablishedHandler);
+ aResolve();
+ };
+ }),
+ new Promise(function(aResolve, aReject) {
+ let timeout = setTimeout(function() {
+ gScript.removeMessageListener('device-disconnected',
+ deviceDisconnectedHandler);
+ ok(true, "terminate after close should not trigger device.disconnect");
+ aResolve();
+ }, 3000);
+
+ function deviceDisconnectedHandler() {
+ gScript.removeMessageListener('device-disconnected',
+ deviceDisconnectedHandler);
+ ok(false, "terminate after close should not trigger device.disconnect");
+ clearTimeout(timeout);
+ aResolve();
+ }
+
+ gScript.addMessageListener('device-disconnected', deviceDisconnectedHandler);
+ }),
+ new Promise(function(aResolve, aReject) {
+ gScript.addMessageListener('receiver-closed', function onReceiverClosed() {
+ gScript.removeMessageListener('receiver-closed', onReceiverClosed);
+ gScript.removeMessageListener('control-channel-established',
+ controlChannelEstablishedHandler);
+ aResolve();
+ });
+ }),
+ ]);
+}
+
+function testTerminateAfterClose() {
+ info('Sender: --- testTerminateAfterClose ---');
+ return Promise.race([
+ new Promise(function(aResolve, aReject) {
+ connection.onterminate = function() {
+ connection.onterminate = null;
+ ok(false, 'terminate after close should do nothing');
+ aResolve();
+ };
+ connection.terminate();
+ }),
+ new Promise(function(aResolve, aReject) {
+ setTimeout(function() {
+ is(connection.state, 'closed', 'Sender: Connection should be closed.');
+ aResolve();
+ }, 3000);
+ }),
+ ]);
+}
+
+function testReconnect() {
+ return new Promise(function(aResolve, aReject) {
+ info('Sender: --- testReconnect ---');
+ gScript.addMessageListener('control-channel-established', function controlChannelEstablished() {
+ gScript.removeMessageListener('control-channel-established', controlChannelEstablished);
+ gScript.sendAsyncMessage("trigger-control-channel-open");
+ });
+
+ gScript.addMessageListener('start-reconnect', function startReconnectHandler(url) {
+ debug('Got message: start-reconnect');
+ gScript.removeMessageListener('start-reconnect', startReconnectHandler);
+ is(url, receiverUrl, "URLs should be the same.")
+ gScript.sendAsyncMessage('trigger-reconnected-acked', url);
+ });
+
+ gScript.addMessageListener('ready-to-reconnect', function onReadyToReconnect() {
+ gScript.removeMessageListener('ready-to-reconnect', onReadyToReconnect);
+ request.reconnect(presentationId).then((aConnection) => {
+ connection = aConnection;
+ ok(connection, "Sender: Connection should be available.");
+ is(connection.id, presentationId, "The presentationId should be the same.");
+ is(connection.state, "connecting", "The initial state should be connecting.");
+ connection.onconnect = function() {
+ connection.onconnect = null;
+ is(connection.state, "connected", "Connection should be connected.");
+ aResolve();
+ };
+ }).catch((aError) => {
+ ok(false, "Sender: Error occurred when establishing a connection: " + aError);
+ teardown();
+ aReject();
+ });
+ });
+
+ postMessageToIframe('prepare-for-reconnect');
+ });
+}
+
+function teardown() {
+ gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+ debug('Got message: teardown-complete');
+ gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
+ gScript.destroy();
+ SimpleTest.finish();
+ });
+
+ gScript.sendAsyncMessage('teardown');
+}
+
+function runTests() {
+ setup().then(testCreateRequest)
+ .then(testStartConnection)
+ .then(testSendMessage)
+ .then(testIncomingMessage)
+ .then(testSendBlobMessage)
+ .then(testCloseConnection)
+ .then(testReconnect)
+ .then(testSendArrayBuffer)
+ .then(testSendArrayBufferView)
+ .then(testCloseConnection)
+ .then(testTerminateAfterClose)
+ .then(teardown);
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout('Test for guarantee not firing async event');
+SpecialPowers.pushPermissions([
+ {type: 'presentation-device-manage', allow: false, context: document},
+ {type: "browser", allow: true, context: document},
+], () => {
+ SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
+ /* Mocked TCP session transport builder in the test */
+ ["dom.presentation.session_transport.data_channel.enable", true],
+ ["dom.presentation.controller.enabled", true],
+ ["dom.presentation.receiver.enabled", true],
+ ["dom.presentation.test.enabled", true],
+ ["dom.presentation.test.stage", 0],
+ ["dom.mozBrowserFramesEnabled", true],
+ ["network.disable.ipc.security", true],
+ ["media.navigator.permission.disabled", true]]},
+ runTests);
+});
diff --git a/dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver_inproc.html b/dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver_inproc.html
new file mode 100644
index 000000000..520b1a98c
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver_inproc.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<!-- vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: -->
+<html>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <head>
+ <meta charset="utf-8">
+ <title>Test for B2G Presentation API when sender and receiver at the same side</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ </head>
+ <body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1234492">
+ Test for B2G Presentation API when sender and receiver at the same side</a>
+ <script type="application/javascript;version=1.8" src="test_presentation_1ua_sender_and_receiver.js">
+ </script>
+ </body>
+</html>
diff --git a/dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver_oop.html b/dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver_oop.html
new file mode 100644
index 000000000..e744e6802
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver_oop.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<!-- vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: -->
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <head>
+ <meta charset="utf-8">
+ <title>Test for B2G Presentation API when sender and receiver at the same side (OOP ver.)</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ </head>
+ <body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1234492">
+ Test for B2G Presentation API when sender and receiver at the same side (OOP ver.)</a>
+ <script type="application/javascript;version=1.8" src="test_presentation_1ua_sender_and_receiver.js">
+ </script>
+ </body>
+</html>
diff --git a/dom/presentation/tests/mochitest/test_presentation_availability.html b/dom/presentation/tests/mochitest/test_presentation_availability.html
new file mode 100644
index 000000000..89f1ad1b7
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_availability.html
@@ -0,0 +1,236 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+ <meta charset="utf-8">
+ <title>Test for PresentationAvailability</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1228508">Test PresentationAvailability</a>
+<script type="application/javascript;version=1.8">
+
+"use strict";
+
+var testDevice = {
+ id: 'id',
+ name: 'name',
+ type: 'type',
+};
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationDeviceInfoChromeScript.js'));
+var request;
+var availability;
+
+function testSetup() {
+ return new Promise(function(aResolve, aReject) {
+ gScript.addMessageListener('setup-complete', function() {
+ aResolve();
+ });
+ gScript.sendAsyncMessage('setup');
+ });
+}
+
+function testInitialUnavailable() {
+ request = new PresentationRequest("https://example.com");
+
+ return request.getAvailability().then(function(aAvailability) {
+ is(aAvailability.value, false, "Should have no available device after setup");
+ aAvailability.onchange = function() {
+ aAvailability.onchange = null;
+ ok(aAvailability.value, "Device should be available.");
+ }
+ availability = aAvailability;
+ gScript.sendAsyncMessage('trigger-device-add', testDevice);
+ }).catch(function(aError) {
+ ok(false, "Error occurred when getting availability: " + aError);
+ teardown();
+ });
+}
+
+function testInitialAvailable() {
+ let anotherRequest = new PresentationRequest("https://example.net");
+ return anotherRequest.getAvailability().then(function(aAvailability) {
+ is(aAvailability.value, true, "Should have available device initially");
+ isnot(aAvailability, availability, "Should get different availability object for different request URL");
+ }).catch(function(aError) {
+ ok(false, "Error occurred when getting availability: " + aError);
+ teardown();
+ });
+}
+
+function testSameObject() {
+ let sameUrlRequest = new PresentationRequest("https://example.com");
+ return sameUrlRequest.getAvailability().then(function(aAvailability) {
+ is(aAvailability, availability, "Should get same availability object for same request URL");
+ }).catch(function(aError) {
+ ok(false, "Error occurred when getting availability: " + aError);
+ teardown();
+ });
+}
+
+function testOnChangeEvent() {
+ return new Promise(function(aResolve, aReject) {
+ availability.onchange = function() {
+ availability.onchange = null;
+ is(availability.value, false, "Should have no available device after device removed");
+ aResolve();
+ }
+ gScript.sendAsyncMessage('trigger-device-remove');
+ });
+}
+
+function testConsecutiveGetAvailability() {
+ let request = new PresentationRequest("https://example.org");
+ let firstAvailabilityResolved = false;
+ return Promise.all([
+ request.getAvailability().then(function() {
+ firstAvailabilityResolved = true;
+ }),
+ request.getAvailability().then(function() {
+ ok(firstAvailabilityResolved, "getAvailability() should be resolved in sequence");
+ })
+ ]).catch(function(aError) {
+ ok(false, "Error occurred when getting availability: " + aError);
+ teardown();
+ });
+}
+
+function testUnsupportedDeviceAvailability() {
+ return Promise.race([
+ new Promise(function(aResolve, aReject) {
+ let request = new PresentationRequest("https://test.com");
+ request.getAvailability().then(function(aAvailability) {
+ availability = aAvailability;
+ aAvailability.onchange = function() {
+ availability.onchange = null;
+ ok(false, "Should not get onchange event.");
+ teardown();
+ }
+ });
+ gScript.sendAsyncMessage('trigger-add-unsupport-url-device');
+ }),
+ new Promise(function(aResolve, aReject) {
+ setTimeout(function() {
+ ok(true, "Should not get onchange event.");
+ availability.onchange = null;
+ gScript.sendAsyncMessage('trigger-remove-unsupported-device');
+ aResolve();
+ }, 3000);
+ }),
+ ]);
+}
+
+function testMultipleAvailabilityURLs() {
+ let request1 = new PresentationRequest(["https://example.com",
+ "https://example1.com"]);
+ let request2 = new PresentationRequest(["https://example1.com",
+ "https://example2.com"]);
+ return Promise.all([
+ request1.getAvailability().then(function(aAvailability) {
+ return new Promise(function(aResolve) {
+ aAvailability.onchange = function() {
+ aAvailability.onchange = null;
+ ok(true, "Should get onchange event.");
+ aResolve();
+ };
+ });
+ }),
+ request2.getAvailability().then(function(aAvailability) {
+ return new Promise(function(aResolve) {
+ aAvailability.onchange = function() {
+ aAvailability.onchange = null;
+ ok(true, "Should get onchange event.");
+ aResolve();
+ };
+ });
+ }),
+ new Promise(function(aResolve) {
+ gScript.sendAsyncMessage('trigger-add-multiple-devices');
+ aResolve();
+ }),
+ ]).then(new Promise(function(aResolve) {
+ gScript.sendAsyncMessage('trigger-remove-multiple-devices');
+ aResolve();
+ }));
+}
+
+function testPartialSupportedDeviceAvailability() {
+ let request1 = new PresentationRequest(["https://supportedUrl.com"]);
+ let request2 = new PresentationRequest(["http://notSupportedUrl.com"]);
+
+ return Promise.all([
+ request1.getAvailability().then(function(aAvailability) {
+ return new Promise(function(aResolve) {
+ aAvailability.onchange = function() {
+ aAvailability.onchange = null;
+ ok(true, "Should get onchange event.");
+ aResolve();
+ };
+ });
+ }),
+ Promise.race([
+ request2.getAvailability().then(function(aAvailability) {
+ return new Promise(function(aResolve) {
+ aAvailability.onchange = function() {
+ aAvailability.onchange = null;
+ ok(false, "Should get onchange event.");
+ aResolve();
+ };
+ });
+ }),
+ new Promise(function(aResolve) {
+ setTimeout(function() {
+ ok(true, "Should not get onchange event.");
+ availability.onchange = null;
+ aResolve();
+ }, 3000);
+ }),
+ ]),
+ new Promise(function(aResolve) {
+ gScript.sendAsyncMessage('trigger-add-https-devices');
+ aResolve();
+ }),
+ ]).then(new Promise(function(aResolve) {
+ gScript.sendAsyncMessage('trigger-remove-https-devices');
+ aResolve();
+ }));
+}
+
+function teardown() {
+ request = null;
+ availability = null;
+ gScript.sendAsyncMessage('teardown');
+ gScript.destroy();
+ SimpleTest.finish();
+}
+
+function runTests() {
+ ok(navigator.presentation, "navigator.presentation should be available.");
+ testSetup().then(testInitialUnavailable)
+ .then(testInitialAvailable)
+ .then(testSameObject)
+ .then(testOnChangeEvent)
+ .then(testConsecutiveGetAvailability)
+ .then(testMultipleAvailabilityURLs)
+ .then(testUnsupportedDeviceAvailability)
+ .then(testPartialSupportedDeviceAvailability)
+ .then(teardown);
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout('Test for guarantee not firing async event');
+SpecialPowers.pushPermissions([
+ {type: "presentation-device-manage", allow: false, context: document},
+], function() {
+ SpecialPowers.pushPrefEnv({ "set": [["dom.presentation.enabled", true],
+ ["dom.presentation.controller.enabled", true],
+ ["dom.presentation.session_transport.data_channel.enable", false]]},
+ runTests);
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/presentation/tests/mochitest/test_presentation_datachannel_sessiontransport.html b/dom/presentation/tests/mochitest/test_presentation_datachannel_sessiontransport.html
new file mode 100644
index 000000000..89a51afb7
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_datachannel_sessiontransport.html
@@ -0,0 +1,245 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+ <meta charset="utf-8">
+ <title>Test for data channel as session transport in Presentation API</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1148307">Test for data channel as session transport in Presentation API</a>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+const loadingTimeoutPref = "presentation.receiver.loading.timeout";
+
+var clientBuilder;
+var serverBuilder;
+var clientTransport;
+var serverTransport;
+
+const clientMessage = "Client Message";
+const serverMessage = "Server Message";
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+const { Services } = Cu.import("resource://gre/modules/Services.jsm");
+
+var isClientReady = false;
+var isServerReady = false;
+var isClientClosed = false;
+var isServerClosed = false;
+
+var gResolve;
+var gReject;
+
+const clientCallback = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransportCallback]),
+ notifyTransportReady: function () {
+ info("Client transport ready.");
+
+ isClientReady = true;
+ if (isClientReady && isServerReady) {
+ gResolve();
+ }
+ },
+ notifyTransportClosed: function (aReason) {
+ info("Client transport is closed.");
+
+ isClientClosed = true;
+ if (isClientClosed && isServerClosed) {
+ gResolve();
+ }
+ },
+ notifyData: function(aData) {
+ is(aData, serverMessage, "Client transport receives data.");
+ gResolve();
+ },
+};
+
+const serverCallback = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransportCallback]),
+ notifyTransportReady: function () {
+ info("Server transport ready.");
+
+ isServerReady = true;
+ if (isClientReady && isServerReady) {
+ gResolve();
+ }
+ },
+ notifyTransportClosed: function (aReason) {
+ info("Server transport is closed.");
+
+ isServerClosed = true;
+ if (isClientClosed && isServerClosed) {
+ gResolve();
+ }
+ },
+ notifyData: function(aData) {
+ is(aData, clientMessage, "Server transport receives data.");
+ gResolve()
+ },
+};
+
+const clientListener = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransportBuilderListener]),
+ onSessionTransport: function(aTransport) {
+ info("Client Transport is built.");
+ clientTransport = aTransport;
+ clientTransport.callback = clientCallback;
+ },
+ onError: function(aError) {
+ ok(false, "client's builder reports error " + aError);
+ },
+ sendOffer: function(aOffer) {
+ setTimeout(()=>this._remoteBuilder.onOffer(aOffer), 0);
+ },
+ sendAnswer: function(aAnswer) {
+ setTimeout(()=>this._remoteBuilder.onAnswer(aAnswer), 0);
+ },
+ sendIceCandidate: function(aCandidate) {
+ setTimeout(()=>this._remoteBuilder.onIceCandidate(aCandidate), 0);
+ },
+ disconnect: function(aReason) {
+ setTimeout(()=>this._localBuilder.notifyDisconnected(aReason), 0);
+ setTimeout(()=>this._remoteBuilder.notifyDisconnected(aReason), 0);
+ },
+ set remoteBuilder(aRemoteBuilder) {
+ this._remoteBuilder = aRemoteBuilder;
+ },
+ set localBuilder(aLocalBuilder) {
+ this._localBuilder = aLocalBuilder;
+ },
+}
+
+const serverListener = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransportBuilderListener]),
+ onSessionTransport: function(aTransport) {
+ info("Server Transport is built.");
+ serverTransport = aTransport;
+ serverTransport.callback = serverCallback;
+ serverTransport.enableDataNotification();
+ },
+ onError: function(aError) {
+ ok(false, "server's builder reports error " + aError);
+ },
+ sendOffer: function(aOffer) {
+ setTimeout(()=>this._remoteBuilder.onOffer(aOffer), 0);
+ },
+ sendAnswer: function(aAnswer) {
+ setTimeout(()=>this._remoteBuilder.onAnswer(aAnswer), 0);
+ },
+ sendIceCandidate: function(aCandidate) {
+ setTimeout(()=>this._remoteBuilder.onIceCandidate(aCandidate), 0);
+ },
+ disconnect: function(aReason) {
+ setTimeout(()=>this._localBuilder.notifyDisconnected(aReason), 0);
+ setTimeout(()=>this._remoteBuilder.notifyDisconnected(aReason), 0);
+ },
+ set remoteBuilder(aRemoteBuilder) {
+ this._remoteBuilder = aRemoteBuilder;
+ },
+ set localBuilder(aLocalBuilder) {
+ this._localBuilder = aLocalBuilder;
+ },
+}
+
+function testBuilder() {
+ return new Promise(function(aResolve, aReject) {
+ gResolve = aResolve;
+ gReject = aReject;
+
+ clientBuilder = Cc["@mozilla.org/presentation/datachanneltransportbuilder;1"]
+ .createInstance(Ci.nsIPresentationDataChannelSessionTransportBuilder);
+ serverBuilder = Cc["@mozilla.org/presentation/datachanneltransportbuilder;1"]
+ .createInstance(Ci.nsIPresentationDataChannelSessionTransportBuilder);
+
+ clientListener.localBuilder = clientBuilder;
+ clientListener.remoteBuilder = serverBuilder;
+ serverListener.localBuilder = serverBuilder;
+ serverListener.remoteBuilder = clientBuilder;
+
+ clientBuilder
+ .buildDataChannelTransport(Ci.nsIPresentationService.ROLE_CONTROLLER,
+ window,
+ clientListener);
+
+ serverBuilder
+ .buildDataChannelTransport(Ci.nsIPresentationService.ROLE_RECEIVER,
+ window,
+ serverListener);
+ });
+}
+
+function testClientSendMessage() {
+ return new Promise(function(aResolve, aReject) {
+ info("client sends message");
+ gResolve = aResolve;
+ gReject = aReject;
+
+ clientTransport.send(clientMessage);
+ });
+}
+
+function testServerSendMessage() {
+ return new Promise(function(aResolve, aReject) {
+ info("server sends message");
+ gResolve = aResolve;
+ gReject = aReject;
+
+ serverTransport.send(serverMessage);
+ setTimeout(()=>clientTransport.enableDataNotification(), 0);
+ });
+}
+
+function testCloseSessionTransport() {
+ return new Promise(function(aResolve, aReject) {
+ info("close session transport");
+ gResolve = aResolve;
+ gReject = aReject;
+
+ serverTransport.close(Cr.NS_OK);
+ });
+}
+
+function finish() {
+ info("test finished, teardown");
+ Services.prefs.clearUserPref(loadingTimeoutPref);
+
+ SimpleTest.finish();
+}
+
+function error(aError) {
+ ok(false, "report Error " + aError.name + ":" + aError.message);
+ gReject();
+}
+
+function runTests() {
+ Services.prefs.setIntPref(loadingTimeoutPref, 30000);
+
+ testBuilder()
+ .then(testClientSendMessage)
+ .then(testServerSendMessage)
+ .then(testCloseSessionTransport)
+ .then(finish)
+ .catch(error);
+
+}
+
+window.addEventListener("load", function() {
+ runTests();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/presentation/tests/mochitest/test_presentation_dc_receiver.html b/dom/presentation/tests/mochitest/test_presentation_dc_receiver.html
new file mode 100644
index 000000000..a42489bdb
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_dc_receiver.html
@@ -0,0 +1,141 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+ <meta charset="utf-8">
+ <title>Test for B2G PresentationConnection API at receiver side</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1148307">Test for B2G PresentationConnection API at receiver side</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script type="application/javascript">
+
+'use strict';
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript.js'));
+var receiverUrl = SimpleTest.getTestFileURL('file_presentation_receiver.html');
+
+var obs = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+ .getService(SpecialPowers.Ci.nsIObserverService);
+
+function setup() {
+ return new Promise(function(aResolve, aReject) {
+ gScript.sendAsyncMessage('trigger-device-add');
+
+ var iframe = document.createElement('iframe');
+ iframe.setAttribute('src', receiverUrl);
+ iframe.setAttribute("mozbrowser", "true");
+ iframe.setAttribute("mozpresentation", receiverUrl);
+
+ // This event is triggered when the iframe calls "alert".
+ iframe.addEventListener("mozbrowsershowmodalprompt", function receiverListener(evt) {
+ var message = evt.detail.message;
+ if (/^OK /.exec(message)) {
+ ok(true, message.replace(/^OK /, ""));
+ } else if (/^KO /.exec(message)) {
+ ok(false, message.replace(/^KO /, ""));
+ } else if (/^INFO /.exec(message)) {
+ info(message.replace(/^INFO /, ""));
+ } else if (/^COMMAND /.exec(message)) {
+ var command = JSON.parse(message.replace(/^COMMAND /, ""));
+ gScript.sendAsyncMessage(command.name, command.data);
+ } else if (/^DONE$/.exec(message)) {
+ iframe.removeEventListener("mozbrowsershowmodalprompt",
+ receiverListener);
+ teardown();
+ }
+ }, false);
+
+ var promise = new Promise(function(aResolve, aReject) {
+ document.body.appendChild(iframe);
+
+ aResolve(iframe);
+ });
+ obs.notifyObservers(promise, 'setup-request-promise', null);
+
+ gScript.addMessageListener('offer-received', function offerReceivedHandler() {
+ gScript.removeMessageListener('offer-received', offerReceivedHandler);
+ info("An offer is received.");
+ });
+
+ gScript.addMessageListener('answer-sent', function answerSentHandler(aIsValid) {
+ gScript.removeMessageListener('answer-sent', answerSentHandler);
+ ok(aIsValid, "A valid answer is sent.");
+ });
+
+ gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+ gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+ is(aReason, SpecialPowers.Cr.NS_OK, "The control channel is closed normally.");
+ });
+
+ gScript.addMessageListener('check-navigator', function checknavigatorHandler(aSuccess) {
+ gScript.removeMessageListener('check-navigator', checknavigatorHandler);
+ ok(aSuccess, "buildDataChannel get correct window object");
+ });
+
+ gScript.addMessageListener('data-transport-notification-enabled', function dataTransportNotificationEnabledHandler() {
+ gScript.removeMessageListener('data-transport-notification-enabled', dataTransportNotificationEnabledHandler);
+ info("Data notification is enabled for data transport channel.");
+ });
+
+ gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
+ gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
+ is(aReason, SpecialPowers.Cr.NS_OK, "The data transport should be closed normally.");
+ });
+
+ aResolve();
+ });
+}
+
+function testIncomingSessionRequest() {
+ return new Promise(function(aResolve, aReject) {
+ gScript.addMessageListener('receiver-launching', function launchReceiverHandler(aSessionId) {
+ gScript.removeMessageListener('receiver-launching', launchReceiverHandler);
+ info("Trying to launch receiver page.");
+
+ ok(navigator.presentation, "navigator.presentation should be available in in-process pages.");
+ is(navigator.presentation.receiver, null, "Non-receiving in-process pages shouldn't get a presentation receiver instance.");
+ aResolve();
+ });
+
+ gScript.sendAsyncMessage('trigger-incoming-session-request', receiverUrl);
+ });
+}
+
+function teardown() {
+ gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+ gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
+ gScript.destroy();
+ SimpleTest.finish();
+ });
+
+ gScript.sendAsyncMessage('teardown');
+}
+
+function runTests() {
+ setup().
+ then(testIncomingSessionRequest);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPermissions([
+ {type: 'presentation-device-manage', allow: false, context: document},
+ {type: "browser", allow: true, context: document},
+], function() {
+ SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
+ ["dom.presentation.controller.enabled", false],
+ ["dom.presentation.receiver.enabled", true],
+ ["dom.presentation.session_transport.data_channel.enable", true],
+ ["dom.mozBrowserFramesEnabled", true],
+ ["network.disable.ipc.security", true]]},
+ runTests);
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/presentation/tests/mochitest/test_presentation_dc_receiver_oop.html b/dom/presentation/tests/mochitest/test_presentation_dc_receiver_oop.html
new file mode 100644
index 000000000..b289b0be6
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_dc_receiver_oop.html
@@ -0,0 +1,213 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+ <meta charset="utf-8">
+ <title>Test for B2G PresentationConnection API at receiver side (OOP)</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="PresentationSessionFrameScript.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1148307">Test B2G PresentationConnection API at receiver side (OOP)</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script type="application/javascript">
+
+'use strict';
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript.js'));
+var receiverUrl = SimpleTest.getTestFileURL('file_presentation_receiver.html');
+var nonReceiverUrl = SimpleTest.getTestFileURL('file_presentation_non_receiver.html');
+
+var isReceiverFinished = false;
+var isNonReceiverFinished = false;
+
+var obs = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+ .getService(SpecialPowers.Ci.nsIObserverService);
+var receiverIframe;
+
+function setup() {
+ return new Promise(function(aResolve, aReject) {
+ gScript.sendAsyncMessage('trigger-device-add');
+
+ // Create a receiver OOP iframe.
+ receiverIframe = document.createElement('iframe');
+ receiverIframe.setAttribute('remote', 'true');
+ receiverIframe.setAttribute('mozbrowser', 'true');
+ receiverIframe.setAttribute('mozpresentation', receiverUrl);
+ receiverIframe.setAttribute('src', receiverUrl);
+
+ // This event is triggered when the iframe calls "alert".
+ receiverIframe.addEventListener('mozbrowsershowmodalprompt', function receiverListener(aEvent) {
+ var message = aEvent.detail.message;
+ if (/^OK /.exec(message)) {
+ ok(true, "Message from iframe: " + message);
+ } else if (/^KO /.exec(message)) {
+ ok(false, "Message from iframe: " + message);
+ } else if (/^INFO /.exec(message)) {
+ info("Message from iframe: " + message);
+ } else if (/^COMMAND /.exec(message)) {
+ var command = JSON.parse(message.replace(/^COMMAND /, ''));
+ if (command.name == "trigger-incoming-message") {
+ var mm = SpecialPowers.getBrowserFrameMessageManager(receiverIframe);
+ mm.sendAsyncMessage('trigger-incoming-message', {"data": command.data});
+ } else {
+ gScript.sendAsyncMessage(command.name, command.data);
+ }
+ } else if (/^DONE$/.exec(message)) {
+ ok(true, "Messaging from iframe complete.");
+ receiverIframe.removeEventListener('mozbrowsershowmodalprompt', receiverListener);
+
+ isReceiverFinished = true;
+
+ if (isNonReceiverFinished) {
+ teardown();
+ }
+ }
+ }, false);
+
+ var promise = new Promise(function(aResolve, aReject) {
+ document.body.appendChild(receiverIframe);
+ receiverIframe.addEventListener("mozbrowserloadstart", function onLoadEnd() {
+ receiverIframe.removeEventListener("mozbrowserloadstart", onLoadEnd);
+ var mm = SpecialPowers.getBrowserFrameMessageManager(receiverIframe);
+ mm.loadFrameScript("data:,(" + loadPrivilegedScriptTest.toString() + ")();", false);
+ });
+
+ aResolve(receiverIframe);
+ });
+ obs.notifyObservers(promise, 'setup-request-promise', null);
+
+ // Create a non-receiver OOP iframe.
+ var nonReceiverIframe = document.createElement('iframe');
+ nonReceiverIframe.setAttribute('remote', 'true');
+ nonReceiverIframe.setAttribute('mozbrowser', 'true');
+ nonReceiverIframe.setAttribute('src', nonReceiverUrl);
+
+ // This event is triggered when the iframe calls "alert".
+ nonReceiverIframe.addEventListener('mozbrowsershowmodalprompt', function nonReceiverListener(aEvent) {
+ var message = aEvent.detail.message;
+ if (/^OK /.exec(message)) {
+ ok(true, "Message from iframe: " + message);
+ } else if (/^KO /.exec(message)) {
+ ok(false, "Message from iframe: " + message);
+ } else if (/^INFO /.exec(message)) {
+ info("Message from iframe: " + message);
+ } else if (/^COMMAND /.exec(message)) {
+ var command = JSON.parse(message.replace(/^COMMAND /, ''));
+ gScript.sendAsyncMessage(command.name, command.data);
+ } else if (/^DONE$/.exec(message)) {
+ ok(true, "Messaging from iframe complete.");
+ nonReceiverIframe.removeEventListener('mozbrowsershowmodalprompt', nonReceiverListener);
+
+ isNonReceiverFinished = true;
+
+ if (isReceiverFinished) {
+ teardown();
+ }
+ }
+ }, false);
+
+ document.body.appendChild(nonReceiverIframe);
+
+ gScript.addMessageListener('offer-received', function offerReceivedHandler() {
+ gScript.removeMessageListener('offer-received', offerReceivedHandler);
+ info("An offer is received.");
+ });
+
+ gScript.addMessageListener('answer-sent', function answerSentHandler(aIsValid) {
+ gScript.removeMessageListener('answer-sent', answerSentHandler);
+ ok(aIsValid, "A valid answer is sent.");
+ });
+
+ gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+ gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+ is(aReason, SpecialPowers.Cr.NS_OK, "The control channel is closed normally.");
+ });
+
+ var mm = SpecialPowers.getBrowserFrameMessageManager(receiverIframe);
+ mm.addMessageListener('check-navigator', function checknavigatorHandler(aSuccess) {
+ mm.removeMessageListener('check-navigator', checknavigatorHandler);
+ ok(aSuccess.data.data, "buildDataChannel get correct window object");
+ });
+
+ mm.addMessageListener('data-transport-notification-enabled', function dataTransportNotificationEnabledHandler() {
+ mm.removeMessageListener('data-transport-notification-enabled', dataTransportNotificationEnabledHandler);
+ info("Data notification is enabled for data transport channel.");
+ });
+
+ mm.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
+ mm.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
+ is(aReason.data.data, SpecialPowers.Cr.NS_OK, "The data transport should be closed normally.");
+ });
+
+ aResolve();
+ });
+}
+
+function testIncomingSessionRequest() {
+ return new Promise(function(aResolve, aReject) {
+ gScript.addMessageListener('receiver-launching', function launchReceiverHandler(aSessionId) {
+ gScript.removeMessageListener('receiver-launching', launchReceiverHandler);
+ info("Trying to launch receiver page.");
+
+ aResolve();
+ });
+
+ gScript.sendAsyncMessage('trigger-incoming-session-request', receiverUrl);
+ });
+}
+
+var mmTeardownComplete = false;
+var gScriptTeardownComplete = false;
+function teardown() {
+ var mm = SpecialPowers.getBrowserFrameMessageManager(receiverIframe);
+ mm.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+ mm.removeMessageListener('teardown-complete', teardownCompleteHandler);
+ mmTeardownComplete = true;
+ if (gScriptTeardownComplete) {
+ SimpleTest.finish();
+ }
+ });
+
+ mm.sendAsyncMessage('teardown');
+
+ gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+ gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
+ gScript.destroy();
+ gScriptTeardownComplete = true;
+ if (mmTeardownComplete) {
+ SimpleTest.finish();
+ }
+ });
+
+ gScript.sendAsyncMessage('teardown');
+}
+
+function runTests() {
+ setup().
+ then(testIncomingSessionRequest);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPermissions([
+ {type: 'presentation-device-manage', allow: false, context: document},
+ {type: 'browser', allow: true, context: document},
+], function() {
+ SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
+ ["dom.presentation.controller.enabled", false],
+ ["dom.presentation.receiver.enabled", true],
+ ["dom.presentation.session_transport.data_channel.enable", true],
+ ["dom.mozBrowserFramesEnabled", true],
+ ["network.disable.ipc.security", true],
+ ["dom.ipc.browser_frames.oop_by_default", true],
+ ["presentation.receiver.loading.timeout", 5000000]]},
+ runTests);
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/presentation/tests/mochitest/test_presentation_dc_sender.html b/dom/presentation/tests/mochitest/test_presentation_dc_sender.html
new file mode 100644
index 000000000..97e252e84
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_dc_sender.html
@@ -0,0 +1,291 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+ <meta charset="utf-8">
+ <title>Test for B2G Presentation API at sender side</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="PresentationSessionFrameScript.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1148307">Test for B2G Presentation API at sender side</a>
+<script type="application/javascript;version=1.8">
+
+'use strict';
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript.js'));
+var frameScript = SpecialPowers.isMainProcess() ? gScript : contentScript;
+var request;
+var connection;
+
+function testSetup() {
+ return new Promise(function(aResolve, aReject) {
+ request = new PresentationRequest("http://example.com/");
+
+ request.getAvailability().then(
+ function(aAvailability) {
+ is(aAvailability.value, false, "Sender: should have no available device after setup");
+ aAvailability.onchange = function() {
+ aAvailability.onchange = null;
+ ok(aAvailability.value, "Device should be available.");
+ aResolve();
+ }
+
+ gScript.sendAsyncMessage('trigger-device-add');
+ },
+ function(aError) {
+ ok(false, "Error occurred when getting availability: " + aError);
+ teardown();
+ aReject();
+ }
+ );
+ });
+}
+
+function testStartConnection() {
+ return new Promise(function(aResolve, aReject) {
+ gScript.addMessageListener('device-prompt', function devicePromptHandler() {
+ gScript.removeMessageListener('device-prompt', devicePromptHandler);
+ info("Device prompt is triggered.");
+ gScript.sendAsyncMessage('trigger-device-prompt-select');
+ });
+
+ gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
+ gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
+ info("A control channel is established.");
+ gScript.sendAsyncMessage('trigger-control-channel-open');
+ });
+
+ gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler(aReason) {
+ gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
+ info("The control channel is opened.");
+ });
+
+ gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+ gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+ info("The control channel is closed. " + aReason);
+ });
+
+ frameScript.addMessageListener('check-navigator', function checknavigatorHandler(aSuccess) {
+ frameScript.removeMessageListener('check-navigator', checknavigatorHandler);
+ ok(aSuccess, "buildDataChannel get correct window object");
+ });
+
+ gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
+ gScript.removeMessageListener('offer-sent', offerSentHandler);
+ ok(aIsValid, "A valid offer is sent out.");
+ gScript.sendAsyncMessage('trigger-incoming-answer');
+ });
+
+ gScript.addMessageListener('answer-received', function answerReceivedHandler() {
+ gScript.removeMessageListener('answer-received', answerReceivedHandler);
+ info("An answer is received.");
+ });
+
+ frameScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
+ frameScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
+ info("Data transport channel is initialized.");
+ });
+
+ frameScript.addMessageListener('data-transport-notification-enabled', function dataTransportNotificationEnabledHandler() {
+ frameScript.removeMessageListener('data-transport-notification-enabled', dataTransportNotificationEnabledHandler);
+ info("Data notification is enabled for data transport channel.");
+ });
+
+ var connectionFromEvent;
+ request.onconnectionavailable = function(aEvent) {
+ request.onconnectionavailable = null;
+ connectionFromEvent = aEvent.connection;
+ ok(connectionFromEvent, "|connectionavailable| event is fired with a connection.");
+
+ if (connection) {
+ is(connection, connectionFromEvent, "The connection from promise and the one from |connectionavailable| event should be the same.");
+ }
+ };
+
+ request.start().then(
+ function(aConnection) {
+ connection = aConnection;
+ ok(connection, "Connection should be available.");
+ ok(connection.id, "Connection ID should be set.");
+ is(connection.state, "connecting", "The initial state should be connecting.");
+
+ if (connectionFromEvent) {
+ is(connection, connectionFromEvent, "The connection from promise and the one from |connectionavailable| event should be the same.");
+ }
+ connection.onconnect = function() {
+ connection.onconnect = null;
+ is(connection.state, "connected", "Connection should be connected.");
+ aResolve();
+ };
+ },
+ function(aError) {
+ ok(false, "Error occurred when establishing a connection: " + aError);
+ teardown();
+ aReject();
+ }
+ );
+ });
+}
+
+function testSend() {
+ return new Promise(function(aResolve, aReject) {
+ const outgoingMessage = "test outgoing message";
+
+ frameScript.addMessageListener('message-sent', function messageSentHandler(aMessage) {
+ frameScript.removeMessageListener('message-sent', messageSentHandler);
+ is(aMessage, outgoingMessage, "The message is sent out.");
+ aResolve();
+ });
+
+ connection.send(outgoingMessage);
+ });
+}
+
+function testIncomingMessage() {
+ return new Promise(function(aResolve, aReject) {
+ const incomingMessage = "test incoming message";
+
+ connection.addEventListener('message', function messageHandler(aEvent) {
+ connection.removeEventListener('message', messageHandler);
+ is(aEvent.data, incomingMessage, "An incoming message should be received.");
+ aResolve();
+ });
+
+ frameScript.sendAsyncMessage('trigger-incoming-message', incomingMessage);
+ });
+}
+
+function testCloseConnection() {
+ return new Promise(function(aResolve, aReject) {
+ frameScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
+ frameScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
+ info("The data transport is closed. " + aReason);
+ });
+
+ connection.onclose = function() {
+ connection.onclose = null;
+ is(connection.state, "closed", "Connection should be closed.");
+ aResolve();
+ };
+
+ connection.close();
+ });
+}
+
+function testReconnect() {
+ return new Promise(function(aResolve, aReject) {
+ info('--- testReconnect ---');
+ gScript.addMessageListener('control-channel-established', function controlChannelEstablished() {
+ gScript.removeMessageListener('control-channel-established', controlChannelEstablished);
+ gScript.sendAsyncMessage("trigger-control-channel-open");
+ });
+
+ gScript.addMessageListener('start-reconnect', function startReconnectHandler(url) {
+ gScript.removeMessageListener('start-reconnect', startReconnectHandler);
+ is(url, "http://example.com/", "URLs should be the same.")
+ gScript.sendAsyncMessage('trigger-reconnected-acked', url);
+ });
+
+ gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
+ gScript.removeMessageListener('offer-sent', offerSentHandler);
+ ok(aIsValid, "A valid offer is sent out.");
+ gScript.sendAsyncMessage('trigger-incoming-answer');
+ });
+
+ gScript.addMessageListener('answer-received', function answerReceivedHandler() {
+ gScript.removeMessageListener('answer-received', answerReceivedHandler);
+ info("An answer is received.");
+ });
+
+ frameScript.addMessageListener('check-navigator', function checknavigatorHandler(aSuccess) {
+ frameScript.removeMessageListener('check-navigator', checknavigatorHandler);
+ ok(aSuccess, "buildDataChannel get correct window object");
+ });
+
+ request.reconnect(connection.id).then(
+ function(aConnection) {
+ ok(aConnection, "Connection should be available.");
+ ok(aConnection.id, "Connection ID should be set.");
+ is(aConnection.state, "connecting", "The initial state should be connecting.");
+ is(aConnection, connection, "The reconnected connection should be the same.");
+
+ aConnection.onconnect = function() {
+ aConnection.onconnect = null;
+ is(aConnection.state, "connected", "Connection should be connected.");
+ aResolve();
+ };
+ },
+ function(aError) {
+ ok(false, "Error occurred when establishing a connection: " + aError);
+ teardown();
+ aReject();
+ }
+ );
+ });
+}
+
+function teardown() {
+ gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+ gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
+ gScript.destroy();
+ info('teardown-complete');
+ SimpleTest.finish();
+ });
+
+ gScript.sendAsyncMessage('teardown');
+}
+
+function testConstructRequestError() {
+ return Promise.all([
+ // XXX: Bug 1305204 - uncomment when bug 1275746 is fixed again.
+ // new Promise(function(aResolve, aReject) {
+ // try {
+ // request = new PresentationRequest("\\\\\\");
+ // }
+ // catch(e) {
+ // is(e.name, "SyntaxError", "Expect to get SyntaxError.");
+ // aResolve();
+ // }
+ // }),
+ new Promise(function(aResolve, aReject) {
+ try {
+ request = new PresentationRequest([]);
+ }
+ catch(e) {
+ is(e.name, "NotSupportedError", "Expect to get NotSupportedError.");
+ aResolve();
+ }
+ }),
+ ]);
+}
+
+function runTests() {
+ ok(window.PresentationRequest, "PresentationRequest should be available.");
+
+ testSetup().
+ then(testStartConnection).
+ then(testSend).
+ then(testIncomingMessage).
+ then(testCloseConnection).
+ then(testReconnect).
+ then(testCloseConnection).
+ then(testConstructRequestError).
+ then(teardown);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPermissions([
+ {type: 'presentation-device-manage', allow: false, context: document},
+], function() {
+ SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
+ ["dom.presentation.controller.enabled", true],
+ ["dom.presentation.session_transport.data_channel.enable", true]]},
+ runTests);
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/presentation/tests/mochitest/test_presentation_device_info.html b/dom/presentation/tests/mochitest/test_presentation_device_info.html
new file mode 100644
index 000000000..77253e41d
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_device_info.html
@@ -0,0 +1,144 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+ <meta charset="utf-8">
+ <title>Test for B2G Presentation Device Info API</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1080474">Test for B2G Presentation Device Info API</a>
+<script type="application/javascript;version=1.8">
+
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+
+var testDevice = {
+ id: 'id',
+ name: 'name',
+ type: 'type',
+};
+
+var gUrl = SimpleTest.getTestFileURL('PresentationDeviceInfoChromeScript.js');
+var gScript = SpecialPowers.loadChromeScript(gUrl);
+
+function testSetup() {
+ return new Promise(function(resolve, reject) {
+ gScript.addMessageListener('setup-complete', function() {
+ resolve();
+ });
+ gScript.sendAsyncMessage('setup');
+ });
+}
+
+function testForceDiscovery() {
+ info('test force discovery');
+ return new Promise(function(resolve, reject) {
+ gScript.addMessageListener('force-discovery', function() {
+ ok(true, 'nsIPresentationDeviceProvider.forceDiscovery is invoked');
+ resolve();
+ });
+ navigator.mozPresentationDeviceInfo.forceDiscovery();
+ });
+}
+
+function testDeviceAdd() {
+ info('test device add');
+ return new Promise(function(resolve, reject) {
+ navigator.mozPresentationDeviceInfo.addEventListener('devicechange', function deviceChangeHandler(e) {
+ navigator.mozPresentationDeviceInfo.removeEventListener('devicechange', deviceChangeHandler);
+ let detail = e.detail;
+ is(detail.type, 'add', 'expected update type');
+ is(detail.deviceInfo.id, testDevice.id, 'expected device id');
+ is(detail.deviceInfo.name, testDevice.name, 'expected device name');
+ is(detail.deviceInfo.type, testDevice.type, 'expected device type');
+
+ navigator.mozPresentationDeviceInfo.getAll()
+ .then(function(devices) {
+ is(devices.length, 1, 'expected 1 available device');
+ is(devices[0].id, testDevice.id, 'expected device id');
+ is(devices[0].name, testDevice.name, 'expected device name');
+ is(devices[0].type, testDevice.type, 'expected device type');
+ resolve();
+ });
+ });
+ gScript.sendAsyncMessage('trigger-device-add', testDevice);
+ });
+}
+
+function testDeviceUpdate() {
+ info('test device update');
+ return new Promise(function(resolve, reject) {
+ testDevice.name = 'name-update';
+
+ navigator.mozPresentationDeviceInfo.addEventListener('devicechange', function deviceChangeHandler(e) {
+ navigator.mozPresentationDeviceInfo.removeEventListener('devicechange', deviceChangeHandler);
+ let detail = e.detail;
+ is(detail.type, 'update', 'expected update type');
+ is(detail.deviceInfo.id, testDevice.id, 'expected device id');
+ is(detail.deviceInfo.name, testDevice.name, 'expected device name');
+ is(detail.deviceInfo.type, testDevice.type, 'expected device type');
+
+ navigator.mozPresentationDeviceInfo.getAll()
+ .then(function(devices) {
+ is(devices.length, 1, 'expected 1 available device');
+ is(devices[0].id, testDevice.id, 'expected device id');
+ is(devices[0].name, testDevice.name, 'expected device name');
+ is(devices[0].type, testDevice.type, 'expected device type');
+ resolve();
+ });
+ });
+ gScript.sendAsyncMessage('trigger-device-update', testDevice);
+ });
+}
+
+function testDeviceRemove() {
+ info('test device remove');
+ return new Promise(function(resolve, reject) {
+ navigator.mozPresentationDeviceInfo.addEventListener('devicechange', function deviceChangeHandler(e) {
+ navigator.mozPresentationDeviceInfo.removeEventListener('devicechange', deviceChangeHandler);
+ let detail = e.detail;
+ is(detail.type, 'remove', 'expected update type');
+ is(detail.deviceInfo.id, testDevice.id, 'expected device id');
+ is(detail.deviceInfo.name, testDevice.name, 'expected device name');
+ is(detail.deviceInfo.type, testDevice.type, 'expected device type');
+
+ navigator.mozPresentationDeviceInfo.getAll()
+ .then(function(devices) {
+ is(devices.length, 0, 'expected 0 available device');
+ resolve();
+ });
+ });
+ gScript.sendAsyncMessage('trigger-device-remove');
+ });
+}
+
+function runTests() {
+ testSetup()
+ .then(testForceDiscovery)
+ .then(testDeviceAdd)
+ .then(testDeviceUpdate)
+ .then(testDeviceRemove)
+ .then(function() {
+ info('test finished, teardown');
+ gScript.sendAsyncMessage('teardown', '');
+ gScript.destroy();
+ SimpleTest.finish();
+ });
+}
+
+window.addEventListener('load', function() {
+ SpecialPowers.pushPrefEnv({
+ 'set': [
+ ['dom.presentation.enabled', true],
+ ]
+ }, runTests);
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/presentation/tests/mochitest/test_presentation_device_info_permission.html b/dom/presentation/tests/mochitest/test_presentation_device_info_permission.html
new file mode 100644
index 000000000..c7f7ac96d
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_device_info_permission.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+ <meta charset="utf-8">
+ <title>Test for B2G Presentation Device Info API Permission</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1080474">Test for B2G Presentation Device Info API Permission</a>
+<script type="application/javascript;version=1.8">
+
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+
+function runTests() {
+ is(navigator.mozPresentationDeviceInfo, undefined, 'navigator.mozPresentationDeviceInfo is undefined');
+ SimpleTest.finish();
+}
+
+window.addEventListener('load', function() {
+ SpecialPowers.pushPrefEnv({
+ 'set': [
+ ['dom.presentation.enabled', true],
+ ]
+ }, runTests);
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/presentation/tests/mochitest/test_presentation_mixed_security_contexts.html b/dom/presentation/tests/mochitest/test_presentation_mixed_security_contexts.html
new file mode 100644
index 000000000..31918a2c4
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_mixed_security_contexts.html
@@ -0,0 +1,81 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+ <meta charset="utf-8">
+ <title>Test default request for B2G Presentation API at sender side</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1268758">Test allow-presentation sandboxing flag</a>
+<iframe id="iframe" src="https://example.com/tests/dom/presentation/tests/mochitest/file_presentation_mixed_security_contexts.html"></iframe>
+<script type="application/javascript;version=1.8">
+
+"use strict";
+
+var iframe = document.getElementById("iframe");
+var readyToStart = false;
+var testSetuped = false;
+
+function setup() {
+ SpecialPowers.addPermission("presentation",
+ true, { url: "https://example.com/tests/dom/presentation/tests/mochitest/file_presentation_mixed_security_contexts.html",
+ originAttributes: {
+ appId: SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID,
+ inIsolatedMozBrowser: false }});
+
+ return new Promise(function(aResolve, aReject) {
+ addEventListener("message", function listener(event) {
+ var message = event.data;
+ if (/^OK /.exec(message)) {
+ ok(true, message.replace(/^OK /, ""));
+ } else if (/^KO /.exec(message)) {
+ ok(false, message.replace(/^KO /, ""));
+ } else if (/^INFO /.exec(message)) {
+ info(message.replace(/^INFO /, ""));
+ } else if (/^COMMAND /.exec(message)) {
+ var command = JSON.parse(message.replace(/^COMMAND /, ""));
+ if (command === "ready-to-start") {
+ readyToStart = true;
+ startTest();
+ }
+ } else if (/^DONE$/.exec(message)) {
+ window.removeEventListener('message', listener);
+ SimpleTest.finish();
+ }
+ }, false);
+
+ testSetuped = true;
+ aResolve();
+ });
+}
+
+iframe.onload = startTest();
+
+function startTest() {
+ if (!(testSetuped && readyToStart)) {
+ return;
+ }
+ iframe.contentWindow.postMessage("start", "*");
+}
+
+function runTests() {
+ ok(navigator.presentation, "navigator.presentation should be available.");
+ setup().then(startTest);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPermissions([
+ {type: "presentation-device-manage", allow: false, context: document},
+], function() {
+ SpecialPowers.pushPrefEnv({ "set": [["dom.presentation.enabled", true],
+ ["dom.presentation.controller.enabled", true],
+ ["dom.presentation.session_transport.data_channel.enable", false]]},
+ runTests);
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/presentation/tests/mochitest/test_presentation_receiver_auxiliary_navigation.js b/dom/presentation/tests/mochitest/test_presentation_receiver_auxiliary_navigation.js
new file mode 100644
index 000000000..0647bff3a
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_receiver_auxiliary_navigation.js
@@ -0,0 +1,77 @@
+"use strict";
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL("PresentationSessionChromeScript.js"));
+var receiverUrl = SimpleTest.getTestFileURL("file_presentation_receiver_auxiliary_navigation.html");
+
+var obs = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+ .getService(SpecialPowers.Ci.nsIObserverService);
+
+function setup() {
+ return new Promise(function(aResolve, aReject) {
+ gScript.sendAsyncMessage("trigger-device-add");
+
+ var iframe = document.createElement("iframe");
+ iframe.setAttribute("mozbrowser", "true");
+ iframe.setAttribute("mozpresentation", receiverUrl);
+ var oop = location.pathname.indexOf('_inproc') == -1;
+ iframe.setAttribute("remote", oop);
+ iframe.setAttribute("src", receiverUrl);
+
+ // This event is triggered when the iframe calls "postMessage".
+ iframe.addEventListener("mozbrowsershowmodalprompt", function listener(aEvent) {
+ var message = aEvent.detail.message;
+ if (/^OK /.exec(message)) {
+ ok(true, "Message from iframe: " + message);
+ } else if (/^KO /.exec(message)) {
+ ok(false, "Message from iframe: " + message);
+ } else if (/^INFO /.exec(message)) {
+ info("Message from iframe: " + message);
+ } else if (/^COMMAND /.exec(message)) {
+ var command = JSON.parse(message.replace(/^COMMAND /, ""));
+ gScript.sendAsyncMessage(command.name, command.data);
+ } else if (/^DONE$/.exec(message)) {
+ ok(true, "Messaging from iframe complete.");
+ iframe.removeEventListener("mozbrowsershowmodalprompt", listener);
+
+ teardown();
+ }
+ }, false);
+
+ var promise = new Promise(function(aResolve, aReject) {
+ document.body.appendChild(iframe);
+
+ aResolve(iframe);
+ });
+ obs.notifyObservers(promise, "setup-request-promise", null);
+
+ aResolve();
+ });
+}
+
+function teardown() {
+ gScript.addMessageListener("teardown-complete", function teardownCompleteHandler() {
+ gScript.removeMessageListener("teardown-complete", teardownCompleteHandler);
+ gScript.destroy();
+ SimpleTest.finish();
+ });
+
+ gScript.sendAsyncMessage("teardown");
+}
+
+function runTests() {
+ setup().then();
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPermissions([
+ {type: "presentation-device-manage", allow: false, context: document},
+ {type: "browser", allow: true, context: document},
+], function() {
+ SpecialPowers.pushPrefEnv({ "set": [["dom.presentation.enabled", true],
+ ["dom.presentation.controller.enabled", true],
+ ["dom.presentation.receiver.enabled", true],
+ ["dom.mozBrowserFramesEnabled", true],
+ ["network.disable.ipc.security", true],
+ ["dom.presentation.session_transport.data_channel.enable", false]]},
+ runTests);
+});
diff --git a/dom/presentation/tests/mochitest/test_presentation_receiver_auxiliary_navigation_inproc.html b/dom/presentation/tests/mochitest/test_presentation_receiver_auxiliary_navigation_inproc.html
new file mode 100644
index 000000000..f873fa3da
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_receiver_auxiliary_navigation_inproc.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<!-- vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: -->
+<html>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <head>
+ <meta charset="utf-8">
+ <title>Test for B2G Presentation API when sender and receiver at the same side</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ </head>
+ <body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1268810">
+ Test for receiver page with sandboxed auxiliary navigation browsing context flag.</a>
+ <script type="application/javascript;version=1.8" src="test_presentation_receiver_auxiliary_navigation.js">
+ </script>
+ </body>
+</html>
diff --git a/dom/presentation/tests/mochitest/test_presentation_receiver_auxiliary_navigation_oop.html b/dom/presentation/tests/mochitest/test_presentation_receiver_auxiliary_navigation_oop.html
new file mode 100644
index 000000000..f873fa3da
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_receiver_auxiliary_navigation_oop.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<!-- vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: -->
+<html>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <head>
+ <meta charset="utf-8">
+ <title>Test for B2G Presentation API when sender and receiver at the same side</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ </head>
+ <body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1268810">
+ Test for receiver page with sandboxed auxiliary navigation browsing context flag.</a>
+ <script type="application/javascript;version=1.8" src="test_presentation_receiver_auxiliary_navigation.js">
+ </script>
+ </body>
+</html>
diff --git a/dom/presentation/tests/mochitest/test_presentation_reconnect.html b/dom/presentation/tests/mochitest/test_presentation_reconnect.html
new file mode 100644
index 000000000..079b7f5c5
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_reconnect.html
@@ -0,0 +1,379 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+ <meta charset="utf-8">
+ <title>Test for B2G Presentation API at sender side</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="PresentationSessionFrameScript.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1197690">Test for Presentation API at sender side</a>
+<iframe id="iframe" src="file_presentation_reconnect.html"></iframe>
+<script type="application/javascript;version=1.8">
+
+'use strict';
+
+var iframe = document.getElementById("iframe");
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript.js'));
+var frameScript = SpecialPowers.isMainProcess() ? gScript : contentScript;
+var request;
+var connection;
+var commandHandler = {};
+
+function testSetup() {
+ return new Promise(function(aResolve, aReject) {
+ addEventListener("message", function listener(event) {
+ var message = event.data;
+ if (/^OK /.exec(message)) {
+ ok(true, message.replace(/^OK /, ""));
+ } else if (/^KO /.exec(message)) {
+ ok(false, message.replace(/^KO /, ""));
+ } else if (/^INFO /.exec(message)) {
+ info(message.replace(/^INFO /, ""));
+ } else if (/^COMMAND /.exec(message)) {
+ var command = JSON.parse(message.replace(/^COMMAND /, ""));
+ if (command.name in commandHandler) {
+ commandHandler[command.name](command);
+ }
+ } else if (/^DONE$/.exec(message)) {
+ window.removeEventListener('message', listener);
+ SimpleTest.finish();
+ }
+ }, false);
+
+ request = new PresentationRequest("http://example.com/");
+
+ request.getAvailability().then(
+ function(aAvailability) {
+ is(aAvailability.value, false, "Sender: should have no available device after setup");
+ aAvailability.onchange = function() {
+ aAvailability.onchange = null;
+ ok(aAvailability.value, "Device should be available.");
+ aResolve();
+ }
+
+ gScript.sendAsyncMessage('trigger-device-add');
+ },
+ function(aError) {
+ ok(false, "Error occurred when getting availability: " + aError);
+ teardown();
+ aReject();
+ }
+ );
+ });
+}
+
+function testStartConnection() {
+ return new Promise(function(aResolve, aReject) {
+ gScript.addMessageListener('device-prompt', function devicePromptHandler() {
+ info("Device prompt is triggered.");
+ gScript.sendAsyncMessage('trigger-device-prompt-select');
+ });
+
+ gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
+ info("A control channel is established.");
+ gScript.sendAsyncMessage('trigger-control-channel-open');
+ });
+
+ gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler(aReason) {
+ info("The control channel is opened.");
+ });
+
+ gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+ info("The control channel is closed. " + aReason);
+ });
+
+ frameScript.addMessageListener('check-navigator', function checknavigatorHandler(aSuccess) {
+ ok(aSuccess, "buildDataChannel get correct window object");
+ });
+
+ gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
+ ok(aIsValid, "A valid offer is sent out.");
+ gScript.sendAsyncMessage('trigger-incoming-answer');
+ });
+
+ gScript.addMessageListener('answer-received', function answerReceivedHandler() {
+ info("An answer is received.");
+ });
+
+ frameScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
+ info("Data transport channel is initialized.");
+ });
+
+ frameScript.addMessageListener('data-transport-notification-enabled', function dataTransportNotificationEnabledHandler() {
+ info("Data notification is enabled for data transport channel.");
+ });
+
+ var connectionFromEvent;
+ request.onconnectionavailable = function(aEvent) {
+ request.onconnectionavailable = null;
+ connectionFromEvent = aEvent.connection;
+ ok(connectionFromEvent, "|connectionavailable| event is fired with a connection.");
+
+ if (connection) {
+ is(connection, connectionFromEvent, "The connection from promise and the one from |connectionavailable| event should be the same.");
+ }
+ };
+
+ request.start().then(
+ function(aConnection) {
+ connection = aConnection;
+ ok(connection, "Connection should be available.");
+ ok(connection.id, "Connection ID should be set.");
+ is(connection.state, "connecting", "The initial state should be connecting.");
+
+ if (connectionFromEvent) {
+ is(connection, connectionFromEvent, "The connection from promise and the one from |connectionavailable| event should be the same.");
+ }
+ connection.onconnect = function() {
+ connection.onconnect = null;
+ is(connection.state, "connected", "Connection should be connected.");
+ aResolve();
+ };
+ },
+ function(aError) {
+ ok(false, "Error occurred when establishing a connection: " + aError);
+ teardown();
+ aReject();
+ }
+ );
+ });
+}
+
+function testCloseConnection() {
+ return new Promise(function(aResolve, aReject) {
+ frameScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
+ frameScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
+ info("The data transport is closed. " + aReason);
+ });
+
+ connection.onclose = function() {
+ connection.onclose = null;
+ is(connection.state, "closed", "Connection should be closed.");
+ aResolve();
+ };
+
+ connection.close();
+ });
+}
+
+function testReconnectAConnectedConnection() {
+ return new Promise(function(aResolve, aReject) {
+ info('--- testReconnectAConnectedConnection ---');
+ ok(connection.state, "connected", "Make sure the state is connected.");
+
+ request.reconnect(connection.id).then(
+ function(aConnection) {
+ ok(aConnection, "Connection should be available.");
+ is(aConnection.id, connection.id, "Connection ID should be the same.");
+ is(aConnection.state, "connected", "The state should be connected.");
+ is(aConnection, connection, "The connection should be the same.");
+
+ aResolve();
+ },
+ function(aError) {
+ ok(false, "Error occurred when establishing a connection: " + aError);
+ teardown();
+ aReject();
+ }
+ );
+ });
+}
+
+function testReconnectInvalidID() {
+ return new Promise(function(aResolve, aReject) {
+ info('--- testReconnectInvalidID ---');
+
+ request.reconnect("dummyID").then(
+ function(aConnection) {
+ ok(false, "Unexpected success.");
+ teardown();
+ aReject();
+ },
+ function(aError) {
+ is(aError.name, "NotFoundError", "Should get NotFoundError.");
+ aResolve();
+ }
+ );
+ });
+}
+
+function testReconnectInvalidURL() {
+ return new Promise(function(aResolve, aReject) {
+ info('--- testReconnectInvalidURL ---');
+
+ var request1 = new PresentationRequest("http://invalidURL");
+ request1.reconnect(connection.id).then(
+ function(aConnection) {
+ ok(false, "Unexpected success.");
+ teardown();
+ aReject();
+ },
+ function(aError) {
+ is(aError.name, "NotFoundError", "Should get NotFoundError.");
+ aResolve();
+ }
+ );
+ });
+}
+
+function testReconnectIframeConnectedConnection() {
+ info('--- testReconnectIframeConnectedConnection ---');
+ gScript.sendAsyncMessage('save-control-channel-listener');
+ return Promise.all([
+ new Promise(function(aResolve, aReject) {
+ commandHandler["connection-connected"] = function(command) {
+ gScript.addMessageListener('start-reconnect', function startReconnectHandler(url) {
+ gScript.removeMessageListener('start-reconnect', startReconnectHandler);
+ gScript.sendAsyncMessage('trigger-reconnected-acked', url);
+ });
+
+ var request1 = new PresentationRequest("http://example1.com");
+ request1.reconnect(command.id).then(
+ function(aConnection) {
+ is(aConnection.state, "connecting", "The state should be connecting.");
+ aConnection.onclose = function() {
+ delete commandHandler["connection-connected"];
+ gScript.sendAsyncMessage('restore-control-channel-listener');
+ aResolve();
+ };
+ aConnection.close();
+ },
+ function(aError) {
+ ok(false, "Error occurred when establishing a connection: " + aError);
+ teardown();
+ aReject();
+ }
+ );
+ };
+ iframe.contentWindow.postMessage("startConnection", "*");
+ }),
+ new Promise(function(aResolve, aReject) {
+ commandHandler["notify-connection-closed"] = function(command) {
+ delete commandHandler["notify-connection-closed"];
+ aResolve();
+ };
+ }),
+ ]);
+}
+
+function testReconnectIframeClosedConnection() {
+ return new Promise(function(aResolve, aReject) {
+ info('--- testReconnectIframeClosedConnection ---');
+ gScript.sendAsyncMessage('save-control-channel-listener');
+ commandHandler["connection-closed"] = function(command) {
+ gScript.addMessageListener('start-reconnect', function startReconnectHandler(url) {
+ gScript.removeMessageListener('start-reconnect', startReconnectHandler);
+ gScript.sendAsyncMessage('trigger-reconnected-acked', url);
+ });
+
+ var request1 = new PresentationRequest("http://example1.com");
+ request1.reconnect(command.id).then(
+ function(aConnection) {
+ aConnection.onconnect = function() {
+ aConnection.onconnect = null;
+ is(aConnection.state, "connected", "The connection should be connected.");
+ aConnection.onclose = function() {
+ aConnection.onclose = null;
+ ok(true, "The connection is closed.");
+ delete commandHandler["connection-closed"];
+ aResolve();
+ };
+ aConnection.close();
+ gScript.sendAsyncMessage('restore-control-channel-listener');
+ };
+ },
+ function(aError) {
+ ok(false, "Error occurred when establishing a connection: " + aError);
+ teardown();
+ aReject();
+ }
+ );
+ };
+ iframe.contentWindow.postMessage("closeConnection", "*");
+ });
+}
+
+function testReconnect() {
+ return new Promise(function(aResolve, aReject) {
+ info('--- testReconnect ---');
+ gScript.addMessageListener('start-reconnect', function startReconnectHandler(url) {
+ gScript.removeMessageListener('start-reconnect', startReconnectHandler);
+ is(url, "http://example.com/", "URLs should be the same.");
+ gScript.sendAsyncMessage('trigger-reconnected-acked', url);
+ });
+
+ request.reconnect(connection.id).then(
+ function(aConnection) {
+ ok(aConnection, "Connection should be available.");
+ ok(aConnection.id, "Connection ID should be set.");
+ is(aConnection.state, "connecting", "The initial state should be connecting.");
+ is(aConnection, connection, "The reconnected connection should be the same.");
+
+ aConnection.onconnect = function() {
+ aConnection.onconnect = null;
+ is(aConnection.state, "connected", "Connection should be connected.");
+
+ const incomingMessage = "test incoming message";
+ aConnection.addEventListener('message', function messageHandler(aEvent) {
+ aConnection.removeEventListener('message', messageHandler);
+ is(aEvent.data, incomingMessage, "An incoming message should be received.");
+ aResolve();
+ });
+
+ frameScript.sendAsyncMessage('trigger-incoming-message', incomingMessage);
+ };
+ },
+ function(aError) {
+ ok(false, "Error occurred when establishing a connection: " + aError);
+ teardown();
+ aReject();
+ }
+ );
+ });
+}
+
+function teardown() {
+ gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+ gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
+ gScript.destroy();
+ info('teardown-complete');
+ SimpleTest.finish();
+ });
+
+ gScript.sendAsyncMessage('teardown');
+}
+
+function runTests() {
+ ok(window.PresentationRequest, "PresentationRequest should be available.");
+
+ testSetup().
+ then(testStartConnection).
+ then(testReconnectInvalidID).
+ then(testReconnectInvalidURL).
+ then(testReconnectAConnectedConnection).
+ then(testReconnectIframeConnectedConnection).
+ then(testReconnectIframeClosedConnection).
+ then(testCloseConnection).
+ then(testReconnect).
+ then(testCloseConnection).
+ then(teardown);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPermissions([
+ {type: 'presentation-device-manage', allow: false, context: document},
+], function() {
+ SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
+ ["dom.presentation.controller.enabled", true],
+ ["dom.presentation.receiver.enabled", true],
+ ["dom.presentation.session_transport.data_channel.enable", true]]},
+ runTests);
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/presentation/tests/mochitest/test_presentation_sandboxed_presentation.html b/dom/presentation/tests/mochitest/test_presentation_sandboxed_presentation.html
new file mode 100644
index 000000000..dc17209c5
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_sandboxed_presentation.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+ <meta charset="utf-8">
+ <title>Test default request for B2G Presentation API at sender side</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1268758">Test allow-presentation sandboxing flag</a>
+<iframe sandbox="allow-popups allow-scripts allow-same-origin" id="iframe" src="file_presentation_sandboxed_presentation.html"></iframe>
+<script type="application/javascript;version=1.8">
+
+"use strict";
+
+var iframe = document.getElementById("iframe");
+var readyToStart = false;
+var testSetuped = false;
+function setup() {
+ return new Promise(function(aResolve, aReject) {
+ addEventListener("message", function listener(event) {
+ var message = event.data;
+ if (/^OK /.exec(message)) {
+ ok(true, message.replace(/^OK /, ""));
+ } else if (/^KO /.exec(message)) {
+ ok(false, message.replace(/^KO /, ""));
+ } else if (/^INFO /.exec(message)) {
+ info(message.replace(/^INFO /, ""));
+ } else if (/^COMMAND /.exec(message)) {
+ var command = JSON.parse(message.replace(/^COMMAND /, ""));
+ if (command === "ready-to-start") {
+ readyToStart = true;
+ startTest();
+ }
+ } else if (/^DONE$/.exec(message)) {
+ window.removeEventListener('message', listener);
+ SimpleTest.finish();
+ }
+ }, false);
+
+ testSetuped = true;
+ aResolve();
+ });
+}
+
+iframe.onload = startTest();
+
+function startTest() {
+ if (!(testSetuped && readyToStart)) {
+ return;
+ }
+ iframe.contentWindow.postMessage("start", "*");
+}
+
+function runTests() {
+ ok(navigator.presentation, "navigator.presentation should be available.");
+ setup().then(startTest);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPermissions([
+ {type: "presentation-device-manage", allow: false, context: document},
+], function() {
+ SpecialPowers.pushPrefEnv({ "set": [["dom.presentation.enabled", true],
+ ["dom.presentation.controller.enabled", true],
+ ["dom.presentation.receiver.enabled", false],
+ ["dom.presentation.session_transport.data_channel.enable", false]]},
+ runTests);
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/presentation/tests/mochitest/test_presentation_sender_on_terminate_request.html b/dom/presentation/tests/mochitest/test_presentation_sender_on_terminate_request.html
new file mode 100644
index 000000000..d0c8af0ad
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_sender_on_terminate_request.html
@@ -0,0 +1,187 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+ <meta charset="utf-8">
+ <title>Test onTerminateRequest at sender side</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1276378">Test onTerminateRequest at sender side</a>
+<script type="application/javascript;version=1.8">
+
+'use strict';
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript.js'));
+var request;
+var connection;
+
+function testSetup() {
+ return new Promise(function(aResolve, aReject) {
+ request = new PresentationRequest("http://example.com");
+
+ request.getAvailability().then(
+ function(aAvailability) {
+ is(aAvailability.value, false, "Sender: should have no available device after setup");
+ aAvailability.onchange = function() {
+ aAvailability.onchange = null;
+ ok(aAvailability.value, "Device should be available.");
+ aResolve();
+ }
+
+ gScript.sendAsyncMessage('trigger-device-add');
+ },
+ function(aError) {
+ ok(false, "Error occurred when getting availability: " + aError);
+ teardown();
+ aReject();
+ }
+ );
+ });
+}
+
+function testStartConnection() {
+ return new Promise(function(aResolve, aReject) {
+ gScript.addMessageListener('device-prompt', function devicePromptHandler() {
+ gScript.removeMessageListener('device-prompt', devicePromptHandler);
+ info("Device prompt is triggered.");
+ gScript.sendAsyncMessage('trigger-device-prompt-select');
+ });
+
+ gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
+ gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
+ info("A control channel is established.");
+ gScript.sendAsyncMessage('trigger-control-channel-open');
+ });
+
+ gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler(aReason) {
+ gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
+ info("The control channel is opened.");
+ });
+
+ gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+ gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+ info("The control channel is closed. " + aReason);
+ });
+
+ gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
+ gScript.removeMessageListener('offer-sent', offerSentHandler);
+ ok(aIsValid, "A valid offer is sent out.");
+ gScript.sendAsyncMessage('trigger-incoming-transport');
+ });
+
+ gScript.addMessageListener('answer-received', function answerReceivedHandler() {
+ gScript.removeMessageListener('answer-received', answerReceivedHandler);
+ info("An answer is received.");
+ });
+
+ gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
+ gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
+ info("Data transport channel is initialized.");
+ gScript.sendAsyncMessage('trigger-incoming-answer');
+ });
+
+ gScript.addMessageListener('data-transport-notification-enabled', function dataTransportNotificationEnabledHandler() {
+ gScript.removeMessageListener('data-transport-notification-enabled', dataTransportNotificationEnabledHandler);
+ info("Data notification is enabled for data transport channel.");
+ });
+
+ var connectionFromEvent;
+ request.onconnectionavailable = function(aEvent) {
+ request.onconnectionavailable = null;
+ connectionFromEvent = aEvent.connection;
+ ok(connectionFromEvent, "|connectionavailable| event is fired with a connection.");
+
+ if (connection) {
+ is(connection, connectionFromEvent, "The connection from promise and the one from |connectionavailable| event should be the same.");
+ }
+ };
+
+ request.start().then(
+ function(aConnection) {
+ connection = aConnection;
+ ok(connection, "Connection should be available.");
+ ok(connection.id, "Connection ID should be set.");
+ is(connection.state, "connecting", "The initial state should be connecting.");
+
+ if (connectionFromEvent) {
+ is(connection, connectionFromEvent, "The connection from promise and the one from |connectionavailable| event should be the same.");
+ }
+ connection.onconnect = function() {
+ connection.onconnect = null;
+ is(connection.state, "connected", "Connection should be connected.");
+ aResolve();
+ };
+ },
+ function(aError) {
+ ok(false, "Error occurred when establishing a connection: " + aError);
+ teardown();
+ aReject();
+ }
+ );
+ });
+}
+
+function testOnTerminateRequest() {
+ return new Promise(function(aResolve, aReject) {
+ gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler(aReason) {
+ gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
+ info("The control channel is opened.");
+ });
+
+ gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+ gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+ info("The control channel is closed. " + aReason);
+ });
+
+ gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
+ gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
+ info("The data transport is closed. " + aReason);
+ });
+
+ connection.onterminate = function() {
+ connection.onterminate = null;
+ is(connection.state, "terminated", "Connection should be closed.");
+ aResolve();
+ };
+
+ gScript.sendAsyncMessage('trigger-incoming-terminate-request');
+ gScript.sendAsyncMessage('trigger-control-channel-open');
+ });
+}
+
+function teardown() {
+ gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+ gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
+ gScript.destroy();
+ SimpleTest.finish();
+ });
+
+ gScript.sendAsyncMessage('teardown');
+}
+
+function runTests() {
+ ok(window.PresentationRequest, "PresentationRequest should be available.");
+
+ testSetup().
+ then(testStartConnection).
+ then(testOnTerminateRequest).
+ then(teardown);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPermissions([
+ {type: 'presentation-device-manage', allow: false, context: document},
+], function() {
+ SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
+ ["dom.presentation.controller.enabled", true],
+ ["dom.presentation.receiver.enabled", false],
+ ["dom.presentation.session_transport.data_channel.enable", false]]},
+ runTests);
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/presentation/tests/mochitest/test_presentation_sender_startWithDevice.html b/dom/presentation/tests/mochitest/test_presentation_sender_startWithDevice.html
new file mode 100644
index 000000000..27b17bb32
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_sender_startWithDevice.html
@@ -0,0 +1,173 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+ <meta charset="utf-8">
+ <title>Test startWithDevice for B2G Presentation API at sender side</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1239242">Test startWithDevice for B2G Presentation API at sender side</a>
+<script type="application/javascript;version=1.8">
+
+'use strict';
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript.js'));
+var request;
+var connection;
+
+function testSetup() {
+ return new Promise(function(aResolve, aReject) {
+ request = new PresentationRequest("https://example.com");
+
+ request.getAvailability().then(
+ function(aAvailability) {
+ is(aAvailability.value, false, "Sender: should have no available device after setup");
+ aAvailability.onchange = function() {
+ aAvailability.onchange = null;
+ ok(aAvailability.value, "Device should be available.");
+ aResolve();
+ }
+
+ gScript.sendAsyncMessage('trigger-device-add');
+ },
+ function(aError) {
+ ok(false, "Error occurred when getting availability: " + aError);
+ teardown();
+ aReject();
+ }
+ );
+ });
+}
+
+function testStartConnectionWithDevice() {
+ return new Promise(function(aResolve, aReject) {
+ gScript.addMessageListener('device-prompt', function devicePromptHandler() {
+ gScript.removeMessageListener('device-prompt', devicePromptHandler);
+ ok(false, "Device prompt should not be triggered.");
+ teardown();
+ aReject();
+ });
+
+ gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
+ gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
+ info("A control channel is established.");
+ gScript.sendAsyncMessage('trigger-control-channel-open');
+ });
+
+ gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler(aReason) {
+ gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
+ info("The control channel is opened.");
+ });
+
+ gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+ gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+ info("The control channel is closed. " + aReason);
+ });
+
+ gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
+ gScript.removeMessageListener('offer-sent', offerSentHandler);
+ ok(aIsValid, "A valid offer is sent out.");
+ gScript.sendAsyncMessage('trigger-incoming-transport');
+ });
+
+ gScript.addMessageListener('answer-received', function answerReceivedHandler() {
+ gScript.removeMessageListener('answer-received', answerReceivedHandler);
+ info("An answer is received.");
+ });
+
+ gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
+ gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
+ info("Data transport channel is initialized.");
+ gScript.sendAsyncMessage('trigger-incoming-answer');
+ });
+
+ gScript.addMessageListener('data-transport-notification-enabled', function dataTransportNotificationEnabledHandler() {
+ gScript.removeMessageListener('data-transport-notification-enabled', dataTransportNotificationEnabledHandler);
+ info("Data notification is enabled for data transport channel.");
+ });
+
+ var connectionFromEvent;
+ request.onconnectionavailable = function(aEvent) {
+ request.onconnectionavailable = null;
+ connectionFromEvent = aEvent.connection;
+ ok(connectionFromEvent, "|connectionavailable| event is fired with a connection.");
+
+ if (connection) {
+ is(connection, connectionFromEvent, "The connection from promise and the one from |connectionavailable| event should be the same.");
+ }
+ };
+
+ request.startWithDevice('id').then(
+ function(aConnection) {
+ connection = aConnection;
+ ok(connection, "Connection should be available.");
+ ok(connection.id, "Connection ID should be set.");
+ is(connection.state, "connecting", "The initial state should be connecting.");
+
+ if (connectionFromEvent) {
+ is(connection, connectionFromEvent, "The connection from promise and the one from |connectionavailable| event should be the same.");
+ }
+ connection.onconnect = function() {
+ connection.onconnect = null;
+ is(connection.state, "connected", "Connection should be connected.");
+ aResolve();
+ };
+ },
+ function(aError) {
+ ok(false, "Error occurred when establishing a connection: " + aError);
+ teardown();
+ aReject();
+ }
+ );
+ });
+}
+
+function testStartConnectionWithDeviceNotFoundError() {
+ return new Promise(function(aResolve, aReject) {
+ request.startWithDevice('').then(
+ function(aConnection) {
+ ok(false, "Should not establish connection to an unknown device");
+ teardown();
+ aReject();
+ },
+ function(aError) {
+ is(aError.name, 'NotFoundError', "Expect NotFoundError occurred when establishing a connection");
+ aResolve();
+ }
+ );
+ });
+}
+
+function teardown() {
+ gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+ gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
+ gScript.destroy();
+ SimpleTest.finish();
+ });
+
+ gScript.sendAsyncMessage('teardown');
+}
+
+function runTests() {
+ ok(window.PresentationRequest, "PresentationRequest should be available.");
+
+ testSetup().
+ then(testStartConnectionWithDevice).
+ then(testStartConnectionWithDeviceNotFoundError).
+ then(teardown);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
+ ["dom.presentation.session_transport.data_channel.enable", false],
+ ["dom.presentation.controller.enabled", true],
+ ["dom.presentation.test.enabled", true],
+ ["dom.presentation.test.stage", 0]]},
+ runTests);
+
+</script>
+</body>
+</html>
diff --git a/dom/presentation/tests/mochitest/test_presentation_tcp_receiver.html b/dom/presentation/tests/mochitest/test_presentation_tcp_receiver.html
new file mode 100644
index 000000000..f26184f0b
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_receiver.html
@@ -0,0 +1,137 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+ <meta charset="utf-8">
+ <title>Test for B2G PresentationConnection API at receiver side</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1069230">Test for B2G PresentationConnection API at receiver side</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script type="application/javascript">
+
+'use strict';
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript.js'));
+var receiverUrl = SimpleTest.getTestFileURL('file_presentation_receiver.html');
+
+var obs = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+ .getService(SpecialPowers.Ci.nsIObserverService);
+
+function setup() {
+ return new Promise(function(aResolve, aReject) {
+ gScript.sendAsyncMessage('trigger-device-add');
+
+ var iframe = document.createElement('iframe');
+ iframe.setAttribute('mozbrowser', 'true');
+ iframe.setAttribute('mozpresentation', receiverUrl);
+ iframe.setAttribute('src', receiverUrl);
+
+ // This event is triggered when the iframe calls "postMessage".
+ iframe.addEventListener('mozbrowsershowmodalprompt', function listener(aEvent) {
+ var message = aEvent.detail.message;
+ if (/^OK /.exec(message)) {
+ ok(true, "Message from iframe: " + message);
+ } else if (/^KO /.exec(message)) {
+ ok(false, "Message from iframe: " + message);
+ } else if (/^INFO /.exec(message)) {
+ info("Message from iframe: " + message);
+ } else if (/^COMMAND /.exec(message)) {
+ var command = JSON.parse(message.replace(/^COMMAND /, ''));
+ gScript.sendAsyncMessage(command.name, command.data);
+ } else if (/^DONE$/.exec(message)) {
+ ok(true, "Messaging from iframe complete.");
+ iframe.removeEventListener('mozbrowsershowmodalprompt', listener);
+
+ teardown();
+ }
+ }, false);
+
+ var promise = new Promise(function(aResolve, aReject) {
+ document.body.appendChild(iframe);
+
+ aResolve(iframe);
+ });
+ obs.notifyObservers(promise, 'setup-request-promise', null);
+
+ gScript.addMessageListener('offer-received', function offerReceivedHandler() {
+ gScript.removeMessageListener('offer-received', offerReceivedHandler);
+ info("An offer is received.");
+ });
+
+ gScript.addMessageListener('answer-sent', function answerSentHandler(aIsValid) {
+ gScript.removeMessageListener('answer-sent', answerSentHandler);
+ ok(aIsValid, "A valid answer is sent.");
+ });
+
+ gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+ gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+ is(aReason, SpecialPowers.Cr.NS_OK, "The control channel is closed normally.");
+ });
+
+ gScript.addMessageListener('data-transport-notification-enabled', function dataTransportNotificationEnabledHandler() {
+ gScript.removeMessageListener('data-transport-notification-enabled', dataTransportNotificationEnabledHandler);
+ info("Data notification is enabled for data transport channel.");
+ });
+
+ gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
+ gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
+ is(aReason, SpecialPowers.Cr.NS_OK, "The data transport should be closed normally.");
+ });
+
+ aResolve();
+ });
+}
+
+function testIncomingSessionRequest() {
+ return new Promise(function(aResolve, aReject) {
+ gScript.addMessageListener('receiver-launching', function launchReceiverHandler(aSessionId) {
+ gScript.removeMessageListener('receiver-launching', launchReceiverHandler);
+ info("Trying to launch receiver page.");
+
+ ok(navigator.presentation, "navigator.presentation should be available in in-process pages.");
+ is(navigator.presentation.receiver, null, "Non-receiving in-process pages shouldn't get a presentation receiver instance.");
+ aResolve();
+ });
+
+ gScript.sendAsyncMessage('trigger-incoming-session-request', receiverUrl);
+ });
+}
+
+function teardown() {
+ gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+ gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
+ gScript.destroy();
+ SimpleTest.finish();
+ });
+
+ gScript.sendAsyncMessage('teardown');
+}
+
+function runTests() {
+ setup().
+ then(testIncomingSessionRequest);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPermissions([
+ {type: 'presentation-device-manage', allow: false, context: document},
+ {type: 'browser', allow: true, context: document},
+], function() {
+ SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
+ ["dom.presentation.controller.enabled", false],
+ ["dom.presentation.receiver.enabled", true],
+ ["dom.mozBrowserFramesEnabled", true],
+ ["network.disable.ipc.security", true],
+ ["dom.presentation.session_transport.data_channel.enable", false]]},
+ runTests);
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_error.html b/dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_error.html
new file mode 100644
index 000000000..0935aaaf9
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_error.html
@@ -0,0 +1,110 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+ <meta charset="utf-8">
+ <title>Test for connection establishing errors of B2G Presentation API at receiver side</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1069230">Test for connection establishing errors of B2G Presentation API at receiver side</a>
+<script type="application/javascript;version=1.8">
+
+'use strict';
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript.js'));
+var receiverUrl = SimpleTest.getTestFileURL('file_presentation_receiver_establish_connection_error.html');
+
+var obs = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+ .getService(SpecialPowers.Ci.nsIObserverService);
+
+function setup() {
+ return new Promise(function(aResolve, aReject) {
+ gScript.sendAsyncMessage('trigger-device-add');
+
+ var iframe = document.createElement('iframe');
+ iframe.setAttribute('src', receiverUrl);
+ iframe.setAttribute("mozbrowser", "true");
+ iframe.setAttribute("mozpresentation", receiverUrl);
+
+ // This event is triggered when the iframe calls "alert".
+ iframe.addEventListener("mozbrowsershowmodalprompt", function receiverListener(evt) {
+ var message = evt.detail.message;
+ if (/^OK /.exec(message)) {
+ ok(true, message.replace(/^OK /, ""));
+ } else if (/^KO /.exec(message)) {
+ ok(false, message.replace(/^KO /, ""));
+ } else if (/^INFO /.exec(message)) {
+ info(message.replace(/^INFO /, ""));
+ } else if (/^COMMAND /.exec(message)) {
+ var command = JSON.parse(message.replace(/^COMMAND /, ""));
+ gScript.sendAsyncMessage(command.name, command.data);
+ } else if (/^DONE$/.exec(message)) {
+ iframe.removeEventListener("mozbrowsershowmodalprompt",
+ receiverListener);
+ teardown();
+ }
+ }, false);
+
+ var promise = new Promise(function(aResolve, aReject) {
+ document.body.appendChild(iframe);
+
+ aResolve(iframe);
+ });
+ obs.notifyObservers(promise, 'setup-request-promise', null);
+
+ gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+ gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+ is(aReason, 0x80004004 /* NS_ERROR_ABORT */, "The control channel is closed abnormally.");
+ });
+
+ aResolve();
+ });
+}
+
+function testIncomingSessionRequest() {
+ return new Promise(function(aResolve, aReject) {
+ gScript.addMessageListener('receiver-launching', function launchReceiverHandler(aSessionId) {
+ gScript.removeMessageListener('receiver-launching', launchReceiverHandler);
+ info("Trying to launch receiver page.");
+
+ aResolve();
+ });
+
+ gScript.sendAsyncMessage('trigger-incoming-session-request', receiverUrl);
+ });
+}
+
+function teardown() {
+ gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+ gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
+ gScript.destroy();
+ SimpleTest.finish();
+ });
+
+ gScript.sendAsyncMessage('teardown');
+}
+
+function runTests() {
+ setup().
+ then(testIncomingSessionRequest);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPermissions([
+ {type: 'presentation-device-manage', allow: false, context: document},
+ {type: "browser", allow: true, context: document},
+], function() {
+ SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
+ ["dom.presentation.receiver.enabled", true],
+ ["dom.presentation.session_transport.data_channel.enable", false],
+ ["dom.mozBrowserFramesEnabled", true],
+ ["network.disable.ipc.security", true]]},
+ runTests);
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_timeout.html b/dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_timeout.html
new file mode 100644
index 000000000..1dc002644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_timeout.html
@@ -0,0 +1,81 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+ <meta charset="utf-8">
+ <title>Test for connection establishing timeout of B2G Presentation API at receiver side</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1069230">Test for connection establishing timeout of B2G Presentation API at receiver side</a>
+<script type="application/javascript;version=1.8">
+
+'use strict';
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript.js'));
+
+var obs = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+ .getService(SpecialPowers.Ci.nsIObserverService);
+
+function setup() {
+ return new Promise(function(aResolve, aReject) {
+ gScript.sendAsyncMessage('trigger-device-add');
+
+ var promise = new Promise(function(aResolve, aReject) {
+ // In order to trigger timeout, do not resolve the promise.
+ });
+ obs.notifyObservers(promise, 'setup-request-promise', null);
+
+ aResolve();
+ });
+}
+
+function testIncomingSessionRequestReceiverLaunchTimeout() {
+ return new Promise(function(aResolve, aReject) {
+ gScript.addMessageListener('receiver-launching', function launchReceiverHandler(aSessionId) {
+ gScript.removeMessageListener('receiver-launching', launchReceiverHandler);
+ info("Trying to launch receiver page.");
+ });
+
+ gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+ gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+ is(aReason, 0x80530017 /* NS_ERROR_DOM_TIMEOUT_ERR */, "The control channel is closed due to timeout.");
+ aResolve();
+ });
+
+ gScript.sendAsyncMessage('trigger-incoming-session-request', 'http://example.com');
+ });
+}
+
+function teardown() {
+ gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+ gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
+ gScript.destroy();
+ SimpleTest.finish();
+ });
+
+ gScript.sendAsyncMessage('teardown');
+}
+
+function runTests() {
+ setup().
+ then(testIncomingSessionRequestReceiverLaunchTimeout).
+ then(teardown);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPermissions([
+ {type: 'presentation-device-manage', allow: false, context: document},
+], function() {
+ SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
+ ["dom.presentation.receiver.enabled", true],
+ ["dom.presentation.session_transport.data_channel.enable", false],
+ ["presentation.receiver.loading.timeout", 10]]},
+ runTests);
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_unknown_content_type.js b/dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_unknown_content_type.js
new file mode 100644
index 000000000..d73f84cf8
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_unknown_content_type.js
@@ -0,0 +1,88 @@
+'use strict';
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript.js'));
+var receiverUrl = SimpleTest.getTestFileURL('file_presentation_unknown_content_type.test');
+
+var obs = SpecialPowers.Cc['@mozilla.org/observer-service;1']
+ .getService(SpecialPowers.Ci.nsIObserverService);
+
+var receiverIframe;
+
+function setup() {
+ return new Promise(function(aResolve, aReject) {
+ gScript.sendAsyncMessage('trigger-device-add');
+
+ receiverIframe = document.createElement('iframe');
+ receiverIframe.setAttribute('mozbrowser', 'true');
+ receiverIframe.setAttribute('mozpresentation', receiverUrl);
+ receiverIframe.setAttribute('src', receiverUrl);
+ var oop = location.pathname.indexOf('_inproc') == -1;
+ receiverIframe.setAttribute("remote", oop);
+
+ var promise = new Promise(function(aResolve, aReject) {
+ document.body.appendChild(receiverIframe);
+
+ aResolve(receiverIframe);
+ });
+ obs.notifyObservers(promise, 'setup-request-promise', null);
+
+ aResolve();
+ });
+}
+
+function testIncomingSessionRequestReceiverLaunchUnknownContentType() {
+ let promise = Promise.all([
+ new Promise(function(aResolve, aReject) {
+ gScript.addMessageListener('receiver-launching', function launchReceiverHandler(aSessionId) {
+ gScript.removeMessageListener('receiver-launching', launchReceiverHandler);
+ info('Trying to launch receiver page.');
+
+ receiverIframe.addEventListener('mozbrowserclose', function() {
+ ok(true, 'observe receiver window closed');
+ aResolve();
+ });
+ });
+ }),
+ new Promise(function(aResolve, aReject) {
+ gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+ gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+ is(aReason, 0x80530020 /* NS_ERROR_DOM_OPERATION_ERR */, 'The control channel is closed due to load failure.');
+ aResolve();
+ });
+ })
+ ]);
+
+ gScript.sendAsyncMessage('trigger-incoming-session-request', receiverUrl);
+ return promise;
+}
+
+function teardown() {
+ gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+ gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
+ gScript.destroy();
+ SimpleTest.finish();
+ });
+
+ gScript.sendAsyncMessage('teardown');
+}
+
+function runTests() {
+ setup().
+ then(testIncomingSessionRequestReceiverLaunchUnknownContentType).
+ then(teardown);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPermissions([
+ {type: 'presentation-device-manage', allow: false, context: document},
+ {type: 'browser', allow: true, context: document},
+], function() {
+ SpecialPowers.pushPrefEnv({ 'set': [['dom.presentation.enabled', true],
+ ["dom.presentation.controller.enabled", true],
+ ["dom.presentation.receiver.enabled", true],
+ ['dom.presentation.session_transport.data_channel.enable', false],
+ ['dom.mozBrowserFramesEnabled', true],
+ ["network.disable.ipc.security", true],
+ ['dom.ipc.tabs.disabled', false]]},
+ runTests);
+});
diff --git a/dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_unknown_content_type_inproc.html b/dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_unknown_content_type_inproc.html
new file mode 100644
index 000000000..8ade1d72d
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_unknown_content_type_inproc.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+ <meta charset="utf-8">
+ <title>Test for unknown content type of B2G Presentation API at receiver side</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1287717">Test for unknown content type of B2G Presentation API at receiver side</a>
+ <script type="application/javascript;version=1.8" src="test_presentation_tcp_receiver_establish_connection_unknown_content_type.js">
+ </script>
+</body>
+</html>
diff --git a/dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_unknown_content_type_oop.html b/dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_unknown_content_type_oop.html
new file mode 100644
index 000000000..b2d2d3c6e
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_unknown_content_type_oop.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+ <meta charset="utf-8">
+ <title>Test for unknown content type of B2G Presentation API at receiver side (OOP)</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1287717">Test for unknown content type of B2G Presentation API at receiver side (OOP)</a>
+ <script type="application/javascript;version=1.8" src="test_presentation_tcp_receiver_establish_connection_unknown_content_type.js">
+ </script>
+</body>
+</html>
diff --git a/dom/presentation/tests/mochitest/test_presentation_tcp_receiver_oop.html b/dom/presentation/tests/mochitest/test_presentation_tcp_receiver_oop.html
new file mode 100644
index 000000000..bfbc7947a
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_receiver_oop.html
@@ -0,0 +1,178 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+ <meta charset="utf-8">
+ <title>Test for B2G PresentationConnection API at receiver side (OOP)</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1069230">Test B2G PresentationConnection API at receiver side (OOP)</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script type="application/javascript">
+
+'use strict';
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript.js'));
+var receiverUrl = SimpleTest.getTestFileURL('file_presentation_receiver.html');
+var nonReceiverUrl = SimpleTest.getTestFileURL('file_presentation_non_receiver.html');
+
+var isReceiverFinished = false;
+var isNonReceiverFinished = false;
+
+var obs = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+ .getService(SpecialPowers.Ci.nsIObserverService);
+
+function setup() {
+ return new Promise(function(aResolve, aReject) {
+ gScript.sendAsyncMessage('trigger-device-add');
+
+ // Create a receiver OOP iframe.
+ var receiverIframe = document.createElement('iframe');
+ receiverIframe.setAttribute('remote', 'true');
+ receiverIframe.setAttribute('mozbrowser', 'true');
+ receiverIframe.setAttribute('mozpresentation', receiverUrl);
+ receiverIframe.setAttribute('src', receiverUrl);
+
+ // This event is triggered when the iframe calls "alert".
+ receiverIframe.addEventListener('mozbrowsershowmodalprompt', function receiverListener(aEvent) {
+ var message = aEvent.detail.message;
+ if (/^OK /.exec(message)) {
+ ok(true, "Message from iframe: " + message);
+ } else if (/^KO /.exec(message)) {
+ ok(false, "Message from iframe: " + message);
+ } else if (/^INFO /.exec(message)) {
+ info("Message from iframe: " + message);
+ } else if (/^COMMAND /.exec(message)) {
+ var command = JSON.parse(message.replace(/^COMMAND /, ''));
+ gScript.sendAsyncMessage(command.name, command.data);
+ } else if (/^DONE$/.exec(message)) {
+ ok(true, "Messaging from iframe complete.");
+ receiverIframe.removeEventListener('mozbrowsershowmodalprompt', receiverListener);
+
+ isReceiverFinished = true;
+
+ if (isNonReceiverFinished) {
+ teardown();
+ }
+ }
+ }, false);
+
+ var promise = new Promise(function(aResolve, aReject) {
+ document.body.appendChild(receiverIframe);
+
+ aResolve(receiverIframe);
+ });
+ obs.notifyObservers(promise, 'setup-request-promise', null);
+
+ // Create a non-receiver OOP iframe.
+ var nonReceiverIframe = document.createElement('iframe');
+ nonReceiverIframe.setAttribute('remote', 'true');
+ nonReceiverIframe.setAttribute('mozbrowser', 'true');
+ nonReceiverIframe.setAttribute('src', nonReceiverUrl);
+
+ // This event is triggered when the iframe calls "alert".
+ nonReceiverIframe.addEventListener('mozbrowsershowmodalprompt', function nonReceiverListener(aEvent) {
+ var message = aEvent.detail.message;
+ if (/^OK /.exec(message)) {
+ ok(true, "Message from iframe: " + message);
+ } else if (/^KO /.exec(message)) {
+ ok(false, "Message from iframe: " + message);
+ } else if (/^INFO /.exec(message)) {
+ info("Message from iframe: " + message);
+ } else if (/^COMMAND /.exec(message)) {
+ var command = JSON.parse(message.replace(/^COMMAND /, ''));
+ gScript.sendAsyncMessage(command.name, command.data);
+ } else if (/^DONE$/.exec(message)) {
+ ok(true, "Messaging from iframe complete.");
+ nonReceiverIframe.removeEventListener('mozbrowsershowmodalprompt', nonReceiverListener);
+
+ isNonReceiverFinished = true;
+
+ if (isReceiverFinished) {
+ teardown();
+ }
+ }
+ }, false);
+
+ document.body.appendChild(nonReceiverIframe);
+
+ gScript.addMessageListener('offer-received', function offerReceivedHandler() {
+ gScript.removeMessageListener('offer-received', offerReceivedHandler);
+ info("An offer is received.");
+ });
+
+ gScript.addMessageListener('answer-sent', function answerSentHandler(aIsValid) {
+ gScript.removeMessageListener('answer-sent', answerSentHandler);
+ ok(aIsValid, "A valid answer is sent.");
+ });
+
+ gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+ gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+ is(aReason, SpecialPowers.Cr.NS_OK, "The control channel is closed normally.");
+ });
+
+ gScript.addMessageListener('data-transport-notification-enabled', function dataTransportNotificationEnabledHandler() {
+ gScript.removeMessageListener('data-transport-notification-enabled', dataTransportNotificationEnabledHandler);
+ info("Data notification is enabled for data transport channel.");
+ });
+
+ gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
+ gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
+ is(aReason, SpecialPowers.Cr.NS_OK, "The data transport should be closed normally.");
+ });
+
+ aResolve();
+ });
+}
+
+function testIncomingSessionRequest() {
+ return new Promise(function(aResolve, aReject) {
+ gScript.addMessageListener('receiver-launching', function launchReceiverHandler(aSessionId) {
+ gScript.removeMessageListener('receiver-launching', launchReceiverHandler);
+ info("Trying to launch receiver page.");
+
+ aResolve();
+ });
+
+ gScript.sendAsyncMessage('trigger-incoming-session-request', receiverUrl);
+ });
+}
+
+function teardown() {
+ gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+ gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
+ gScript.destroy();
+ SimpleTest.finish();
+ });
+
+ gScript.sendAsyncMessage('teardown');
+}
+
+function runTests() {
+ setup().
+ then(testIncomingSessionRequest);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPermissions([
+ {type: 'presentation-device-manage', allow: false, context: document},
+ {type: 'browser', allow: true, context: document},
+], function() {
+ SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
+ ["dom.presentation.controller.enabled", false],
+ ["dom.presentation.receiver.enabled", true],
+ ["dom.presentation.session_transport.data_channel.enable", false],
+ ["dom.mozBrowserFramesEnabled", true],
+ ["network.disable.ipc.security", true],
+ ["dom.ipc.browser_frames.oop_by_default", true]]},
+ runTests);
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/presentation/tests/mochitest/test_presentation_tcp_sender.html b/dom/presentation/tests/mochitest/test_presentation_tcp_sender.html
new file mode 100644
index 000000000..8df34c884
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_sender.html
@@ -0,0 +1,260 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+ <meta charset="utf-8">
+ <title>Test for B2G Presentation API at sender side</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1069230">Test for B2G Presentation API at sender side</a>
+<script type="application/javascript;version=1.8">
+
+'use strict';
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript.js'));
+var request;
+var connection;
+
+function testSetup() {
+ return new Promise(function(aResolve, aReject) {
+ request = new PresentationRequest("https://example.com");
+
+ request.getAvailability().then(
+ function(aAvailability) {
+ is(aAvailability.value, false, "Sender: should have no available device after setup");
+ aAvailability.onchange = function() {
+ aAvailability.onchange = null;
+ ok(aAvailability.value, "Device should be available.");
+ aResolve();
+ }
+ gScript.sendAsyncMessage('trigger-device-add');
+ },
+ function(aError) {
+ ok(false, "Error occurred when getting availability: " + aError);
+ teardown();
+ aReject();
+ }
+ );
+
+ });
+}
+
+function testStartConnection() {
+ return new Promise(function(aResolve, aReject) {
+ gScript.addMessageListener('device-prompt', function devicePromptHandler() {
+ gScript.removeMessageListener('device-prompt', devicePromptHandler);
+ info("Device prompt is triggered.");
+ gScript.sendAsyncMessage('trigger-device-prompt-select');
+ });
+
+ gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
+ gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
+ info("A control channel is established.");
+ gScript.sendAsyncMessage('trigger-control-channel-open');
+ });
+
+ gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler(aReason) {
+ gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
+ info("The control channel is opened.");
+ });
+
+ gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+ gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+ info("The control channel is closed. " + aReason);
+ });
+
+ gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
+ gScript.removeMessageListener('offer-sent', offerSentHandler);
+ ok(aIsValid, "A valid offer is sent out.");
+ gScript.sendAsyncMessage('trigger-incoming-transport');
+ });
+
+ gScript.addMessageListener('answer-received', function answerReceivedHandler() {
+ gScript.removeMessageListener('answer-received', answerReceivedHandler);
+ info("An answer is received.");
+ });
+
+ gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
+ gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
+ info("Data transport channel is initialized.");
+ gScript.sendAsyncMessage('trigger-incoming-answer');
+ });
+
+ gScript.addMessageListener('data-transport-notification-enabled', function dataTransportNotificationEnabledHandler() {
+ gScript.removeMessageListener('data-transport-notification-enabled', dataTransportNotificationEnabledHandler);
+ info("Data notification is enabled for data transport channel.");
+ });
+
+ var connectionFromEvent;
+ request.onconnectionavailable = function(aEvent) {
+ request.onconnectionavailable = null;
+ connectionFromEvent = aEvent.connection;
+ ok(connectionFromEvent, "|connectionavailable| event is fired with a connection.");
+
+ if (connection) {
+ is(connection, connectionFromEvent, "The connection from promise and the one from |connectionavailable| event should be the same.");
+ }
+ };
+
+ request.start().then(
+ function(aConnection) {
+ connection = aConnection;
+ ok(connection, "Connection should be available.");
+ ok(connection.id, "Connection ID should be set.");
+ is(connection.state, "connecting", "The initial state should be connecting.");
+
+ if (connectionFromEvent) {
+ is(connection, connectionFromEvent, "The connection from promise and the one from |connectionavailable| event should be the same.");
+ }
+ connection.onconnect = function() {
+ connection.onconnect = null;
+ is(connection.state, "connected", "Connection should be connected.");
+ aResolve();
+ };
+ },
+ function(aError) {
+ ok(false, "Error occurred when establishing a connection: " + aError);
+ teardown();
+ aReject();
+ }
+ );
+ });
+}
+
+function testSend() {
+ return new Promise(function(aResolve, aReject) {
+ const outgoingMessage = "test outgoing message";
+
+ gScript.addMessageListener('message-sent', function messageSentHandler(aMessage) {
+ gScript.removeMessageListener('message-sent', messageSentHandler);
+ is(aMessage, outgoingMessage, "The message is sent out.");
+ aResolve();
+ });
+
+ connection.send(outgoingMessage);
+ });
+}
+
+function testIncomingMessage() {
+ return new Promise(function(aResolve, aReject) {
+ const incomingMessage = "test incoming message";
+
+ connection.addEventListener('message', function messageHandler(aEvent) {
+ connection.removeEventListener('message', messageHandler);
+ is(aEvent.data, incomingMessage, "An incoming message should be received.");
+ aResolve();
+ });
+
+ gScript.sendAsyncMessage('trigger-incoming-message', incomingMessage);
+ });
+}
+
+function testCloseConnection() {
+ return new Promise(function(aResolve, aReject) {
+ gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
+ gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
+ info("The data transport is closed. " + aReason);
+ });
+
+ connection.onclose = function() {
+ connection.onclose = null;
+ is(connection.state, "closed", "Connection should be closed.");
+ aResolve();
+ };
+
+ connection.close();
+ });
+}
+
+function testReconnect() {
+ return new Promise(function(aResolve, aReject) {
+ info('--- testReconnect ---');
+ gScript.addMessageListener('control-channel-established', function controlChannelEstablished() {
+ gScript.removeMessageListener('control-channel-established', controlChannelEstablished);
+ gScript.sendAsyncMessage("trigger-control-channel-open");
+ });
+
+ gScript.addMessageListener('start-reconnect', function startReconnectHandler(url) {
+ gScript.removeMessageListener('start-reconnect', startReconnectHandler);
+ is(url, "https://example.com/", "URLs should be the same.")
+ gScript.sendAsyncMessage('trigger-reconnected-acked', url);
+ });
+
+ gScript.addMessageListener('offer-sent', function offerSentHandler() {
+ gScript.removeMessageListener('offer-sent', offerSentHandler);
+ gScript.sendAsyncMessage('trigger-incoming-transport');
+ });
+
+ gScript.addMessageListener('answer-received', function answerReceivedHandler() {
+ gScript.removeMessageListener('answer-received', answerReceivedHandler);
+ info("An answer is received.");
+ });
+
+ gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
+ gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
+ info("Data transport channel is initialized.");
+ gScript.sendAsyncMessage('trigger-incoming-answer');
+ });
+
+ gScript.addMessageListener('data-transport-notification-enabled', function dataTransportNotificationEnabledHandler() {
+ gScript.removeMessageListener('data-transport-notification-enabled', dataTransportNotificationEnabledHandler);
+ info("Data notification is enabled for data transport channel.");
+ });
+
+ request.reconnect(connection.id).then(
+ function(aConnection) {
+ ok(aConnection, "Connection should be available.");
+ ok(aConnection.id, "Connection ID should be set.");
+ is(aConnection.state, "connecting", "The initial state should be connecting.");
+ is(aConnection, connection, "The reconnected connection should be the same.");
+
+ aConnection.onconnect = function() {
+ aConnection.onconnect = null;
+ is(aConnection.state, "connected", "Connection should be connected.");
+ aResolve();
+ };
+ },
+ function(aError) {
+ ok(false, "Error occurred when establishing a connection: " + aError);
+ teardown();
+ aReject();
+ }
+ );
+ });
+}
+
+function teardown() {
+ gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+ gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
+ gScript.destroy();
+ SimpleTest.finish();
+ });
+
+ gScript.sendAsyncMessage('teardown');
+}
+
+function runTests() {
+ ok(window.PresentationRequest, "PresentationRequest should be available.");
+
+ testSetup().
+ then(testStartConnection).
+ then(testSend).
+ then(testIncomingMessage).
+ then(testCloseConnection).
+ then(testReconnect).
+ then(testCloseConnection).
+ then(teardown);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
+ ["dom.presentation.controller.enabled", true],
+ ["dom.presentation.session_transport.data_channel.enable", false]]},
+ runTests);
+
+</script>
+</body>
+</html>
diff --git a/dom/presentation/tests/mochitest/test_presentation_tcp_sender_default_request.html b/dom/presentation/tests/mochitest/test_presentation_tcp_sender_default_request.html
new file mode 100644
index 000000000..60247ec98
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_sender_default_request.html
@@ -0,0 +1,151 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+ <meta charset="utf-8">
+ <title>Test default request for B2G Presentation API at sender side</title>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1069230">Test default request for B2G Presentation API at sender side</a>
+<script type="application/javascript;version=1.8">
+
+'use strict';
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript.js'));
+var connection;
+
+function testSetup() {
+ return new Promise(function(aResolve, aReject) {
+ navigator.presentation.defaultRequest = new PresentationRequest("https://example.com");
+
+ navigator.presentation.defaultRequest.getAvailability().then(
+ function(aAvailability) {
+ is(aAvailability.value, false, "Sender: should have no available device after setup");
+ aAvailability.onchange = function() {
+ aAvailability.onchange = null;
+ ok(aAvailability.value, "Device should be available.");
+ aResolve();
+ }
+
+ gScript.sendAsyncMessage('trigger-device-add');
+ },
+ function(aError) {
+ ok(false, "Error occurred when getting availability: " + aError);
+ teardown();
+ aReject();
+ }
+ );
+ });
+}
+
+function testStartConnection() {
+ return new Promise(function(aResolve, aReject) {
+ gScript.addMessageListener('device-prompt', function devicePromptHandler() {
+ gScript.removeMessageListener('device-prompt', devicePromptHandler);
+ info("Device prompt is triggered.");
+ gScript.sendAsyncMessage('trigger-device-prompt-select');
+ });
+
+ gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
+ gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
+ info("A control channel is established.");
+ gScript.sendAsyncMessage('trigger-control-channel-open');
+ });
+
+ gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler(aReason) {
+ gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
+ info("The control channel is opened.");
+ });
+
+ gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+ gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+ info("The control channel is closed. " + aReason);
+ });
+
+ gScript.addMessageListener('offer-sent', function offerSentHandler() {
+ gScript.removeMessageListener('offer-sent', offerSentHandler);
+ info("An offer is sent out.");
+ gScript.sendAsyncMessage('trigger-incoming-transport');
+ });
+
+ gScript.addMessageListener('answer-received', function answerReceivedHandler() {
+ gScript.removeMessageListener('answer-received', answerReceivedHandler);
+ info("An answer is received.");
+ });
+
+ gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
+ gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
+ info("Data transport channel is initialized.");
+ gScript.sendAsyncMessage('trigger-incoming-answer');
+ });
+
+ is(navigator.presentation.receiver, undefined, "Sender shouldn't get a presentation receiver instance.");
+
+ navigator.presentation.defaultRequest.onconnectionavailable = function(aEvent) {
+ navigator.presentation.defaultRequest.onconnectionavailable = null;
+ connection = aEvent.connection;
+ ok(connection, "|connectionavailable| event is fired with a connection.");
+ ok(connection.id, "Connection ID should be set.");
+ is(connection.state, "connecting", "The initial state should be connecting.");
+ connection.onconnect = function() {
+ connection.onconnect = null;
+ is(connection.state, "connected", "Connection should be connected.");
+ aResolve();
+ };
+ };
+
+ // Simulate the UA triggers |start()| of the default request.
+ navigator.presentation.defaultRequest.start();
+ });
+}
+
+function testCloseConnection() {
+ return new Promise(function(aResolve, aReject) {
+ gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
+ gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
+ info("The data transport is closed. " + aReason);
+ });
+
+ connection.onclose = function() {
+ connection.onclose = null;
+ is(connection.state, "closed", "Connection should be closed.");
+ aResolve();
+ };
+
+ connection.close();
+ });
+}
+
+function teardown() {
+ gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+ gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
+ gScript.destroy();
+ SimpleTest.finish();
+ });
+
+ gScript.sendAsyncMessage('teardown');
+}
+
+function runTests() {
+ ok(window.PresentationRequest, "PresentationRequest should be available.");
+ ok(navigator.presentation, "navigator.presentation should be available.");
+
+ testSetup().
+ then(testStartConnection).
+ then(testCloseConnection).
+ then(teardown);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
+ ["dom.presentation.controller.enabled", true],
+ ["dom.presentation.receiver.enabled", false],
+ ["dom.presentation.session_transport.data_channel.enable", false]]},
+ runTests);
+
+</script>
+</body>
+</html>
diff --git a/dom/presentation/tests/mochitest/test_presentation_tcp_sender_disconnect.html b/dom/presentation/tests/mochitest/test_presentation_tcp_sender_disconnect.html
new file mode 100644
index 000000000..a95da104f
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_sender_disconnect.html
@@ -0,0 +1,160 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+ <meta charset="utf-8">
+ <title>Test for disconnection of B2G Presentation API at sender side</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1069230">Test for disconnection of B2G Presentation API at sender side</a>
+<script type="application/javascript;version=1.8">
+
+'use strict';
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript.js'));
+var request;
+var connection;
+
+function testSetup() {
+ return new Promise(function(aResolve, aReject) {
+ request = new PresentationRequest("http://example.com");
+
+ request.getAvailability().then(
+ function(aAvailability) {
+ is(aAvailability.value, false, "Sender: should have no available device after setup");
+ aAvailability.onchange = function() {
+ aAvailability.onchange = null;
+ ok(aAvailability.value, "Device should be available.");
+ aResolve();
+ }
+
+ gScript.sendAsyncMessage('trigger-device-add');
+ },
+ function(aError) {
+ ok(false, "Error occurred when getting availability: " + aError);
+ teardown();
+ aReject();
+ }
+ );
+ });
+}
+
+function testStartConnection() {
+ return new Promise(function(aResolve, aReject) {
+ gScript.addMessageListener('device-prompt', function devicePromptHandler() {
+ gScript.removeMessageListener('device-prompt', devicePromptHandler);
+ info("Device prompt is triggered.");
+ gScript.sendAsyncMessage('trigger-device-prompt-select');
+ });
+
+ gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
+ gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
+ info("A control channel is established.");
+ gScript.sendAsyncMessage('trigger-control-channel-open');
+ });
+
+ gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler(aReason) {
+ gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
+ info("The control channel is opened.");
+ });
+
+ gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+ gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+ info("The control channel is closed. " + aReason);
+ });
+
+ gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
+ gScript.removeMessageListener('offer-sent', offerSentHandler);
+ ok(aIsValid, "A valid offer is sent out.");
+ gScript.sendAsyncMessage('trigger-incoming-answer');
+ });
+
+ gScript.addMessageListener('answer-received', function answerReceivedHandler() {
+ gScript.removeMessageListener('answer-received', answerReceivedHandler);
+ info("An answer is received.");
+ gScript.sendAsyncMessage('trigger-incoming-transport');
+ });
+
+ gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
+ gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
+ info("Data transport channel is initialized.");
+ });
+
+ gScript.addMessageListener('data-transport-notification-enabled', function dataTransportNotificationEnabledHandler() {
+ gScript.removeMessageListener('data-transport-notification-enabled', dataTransportNotificationEnabledHandler);
+ info("Data notification is enabled for data transport channel.");
+ });
+
+ request.start().then(
+ function(aConnection) {
+ connection = aConnection;
+ ok(connection, "Connection should be available.");
+ ok(connection.id, "Connection ID should be set.");
+ is(connection.state, "connecting", "The initial state should be connecting.");
+ connection.onconnect = function() {
+ connection.onconnect = null;
+ is(connection.state, "connected", "Connection should be connected.");
+ aResolve();
+ };
+ },
+ function(aError) {
+ ok(false, "Error occurred when establishing a connection: " + aError);
+ teardown();
+ aReject();
+ }
+ );
+ });
+}
+
+function testDisconnection() {
+ return new Promise(function(aResolve, aReject) {
+ gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
+ gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
+ info("The data transport is closed. " + aReason);
+ });
+
+ connection.onclose = function() {
+ connection.onclose = null;
+ is(connection.state, "closed", "Connection should be closed.");
+ aResolve();
+ };
+
+ gScript.sendAsyncMessage('trigger-data-transport-close', SpecialPowers.Cr.NS_ERROR_FAILURE);
+ });
+}
+
+function teardown() {
+ gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+ gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
+ gScript.destroy();
+ SimpleTest.finish();
+ });
+
+ gScript.sendAsyncMessage('teardown');
+}
+
+function runTests() {
+ ok(window.PresentationRequest, "PresentationRequest should be available.");
+
+ testSetup().
+ then(testStartConnection).
+ then(testDisconnection).
+ then(teardown);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPermissions([
+ {type: 'presentation-device-manage', allow: false, context: document},
+], function() {
+ SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
+ ["dom.presentation.controller.enabled", true],
+ ["dom.presentation.session_transport.data_channel.enable", false]]},
+ runTests);
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/presentation/tests/mochitest/test_presentation_tcp_sender_establish_connection_error.html b/dom/presentation/tests/mochitest/test_presentation_tcp_sender_establish_connection_error.html
new file mode 100644
index 000000000..557ae71a6
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_sender_establish_connection_error.html
@@ -0,0 +1,514 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+ <meta charset="utf-8">
+ <title>Test for connection establishing errors of B2G Presentation API at sender side</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1069230">Test for connection establishing errors of B2G Presentation API at sender side</a>
+<script type="application/javascript;version=1.8">
+
+'use strict';
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript.js'));
+var request;
+
+function setup() {
+ return new Promise(function(aResolve, aReject) {
+ request = new PresentationRequest("http://example.com");
+
+ request.getAvailability().then(
+ function(aAvailability) {
+ is(aAvailability.value, false, "Sender: should have no available device after setup");
+ aAvailability.onchange = function() {
+ aAvailability.onchange = null;
+ ok(aAvailability.value, "Device should be available.");
+ aResolve();
+ }
+
+ gScript.sendAsyncMessage('trigger-device-add');
+ },
+ function(aError) {
+ ok(false, "Error occurred when getting availability: " + aError);
+ teardown();
+ aReject();
+ }
+ );
+ });
+}
+
+function testStartConnectionCancelPrompt() {
+ info('--- testStartConnectionCancelPrompt ---');
+ return Promise.all([
+ new Promise((resolve) => {
+ gScript.addMessageListener('device-prompt', function devicePromptHandler() {
+ gScript.removeMessageListener('device-prompt', devicePromptHandler);
+ info("Device prompt is triggered.");
+ gScript.sendAsyncMessage('trigger-device-prompt-cancel', SpecialPowers.Cr.NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ resolve();
+ });
+ }),
+ request.start().then(
+ function(aConnection) {
+ ok(false, "|start| shouldn't succeed in this case.");
+ },
+ function(aError) {
+ is(aError.name, "NotAllowedError", "NotAllowedError is expected when the prompt is canceled.");
+ }
+ ),
+ ]);
+}
+
+function testStartConnectionNoDevice() {
+ info('--- testStartConnectionNoDevice ---');
+ return Promise.all([
+ new Promise((resolve) => {
+ gScript.addMessageListener('device-prompt', function devicePromptHandler() {
+ gScript.removeMessageListener('device-prompt', devicePromptHandler);
+ info("Device prompt is triggered.");
+ gScript.sendAsyncMessage('trigger-device-prompt-cancel', SpecialPowers.Cr.NS_ERROR_DOM_NOT_FOUND_ERR);
+ resolve();
+ });
+ }),
+ request.start().then(
+ function(aConnection) {
+ ok(false, "|start| shouldn't succeed in this case.");
+ },
+ function(aError) {
+ is(aError.name, "NotFoundError", "NotFoundError is expected when no available device.");
+ }
+ ),
+ ]);
+}
+
+function testStartConnectionUnexpectedControlChannelCloseBeforeDataTransportInit() {
+ info('--- testStartConnectionUnexpectedControlChannelCloseBeforeDataTransportInit ---');
+ return Promise.all([
+
+ new Promise((resolve) => {
+ gScript.addMessageListener('device-prompt', function devicePromptHandler() {
+ gScript.removeMessageListener('device-prompt', devicePromptHandler);
+ info("Device prompt is triggered.");
+ gScript.sendAsyncMessage('trigger-device-prompt-select');
+ resolve();
+ });
+ }),
+
+ new Promise((resolve) => {
+ gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
+ gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
+ info("A control channel is established.");
+ gScript.sendAsyncMessage('trigger-control-channel-open');
+ resolve();
+ });
+ }),
+
+ new Promise((resolve) => {
+ gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler() {
+ gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
+ info("The control channel is opened.");
+ resolve();
+ });
+ }),
+
+ new Promise((resolve) => {
+ gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+ gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+ info("The control channel is closed. " + aReason);
+ is(aReason, SpecialPowers.Cr.NS_ERROR_FAILURE, "The control channel is closed with NS_ERROR_FAILURE");
+ resolve();
+ });
+ }),
+
+ new Promise((resolve) => {
+ gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
+ gScript.removeMessageListener('offer-sent', offerSentHandler);
+ ok(aIsValid, "A valid offer is sent out.");
+ gScript.sendAsyncMessage('trigger-control-channel-close', SpecialPowers.Cr.NS_ERROR_FAILURE);
+ resolve();
+ });
+ }),
+
+ request.start().then(
+ function(aConnection) {
+ is(aConnection.state, "connecting", "The initial state should be connecting.");
+ return new Promise((resolve) => {
+ aConnection.onclose = function() {
+ aConnection.onclose = null;
+ is(aConnection.state, "closed", "Connection should be closed.");
+ resolve();
+ };
+ });
+ },
+ function(aError) {
+ ok(false, "Error occurred when establishing a connection: " + aError);
+ teardown();
+ }
+ ),
+
+ ]);
+}
+
+function testStartConnectionUnexpectedControlChannelCloseNoReasonBeforeDataTransportInit() {
+ info('--- testStartConnectionUnexpectedControlChannelCloseNoReasonBeforeDataTransportInit ---');
+ return Promise.all([
+
+ new Promise((resolve) => {
+ gScript.addMessageListener('device-prompt', function devicePromptHandler() {
+ gScript.removeMessageListener('device-prompt', devicePromptHandler);
+ info("Device prompt is triggered.");
+ gScript.sendAsyncMessage('trigger-device-prompt-select');
+ resolve();
+ });
+ }),
+
+ new Promise((resolve) => {
+ gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
+ gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
+ info("A control channel is established.");
+ gScript.sendAsyncMessage('trigger-control-channel-open');
+ resolve();
+ });
+ }),
+
+ new Promise((resolve) => {
+ gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler() {
+ gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
+ info("The control channel is opened.");
+ resolve();
+ });
+ }),
+
+ new Promise((resolve) => {
+ gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+ gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+ info("The control channel is closed. " + aReason);
+ is(aReason, SpecialPowers.Cr.NS_OK, "The control channel is closed with NS_OK");
+ resolve();
+ });
+ }),
+
+ new Promise((resolve) => {
+ gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
+ gScript.removeMessageListener('offer-sent', offerSentHandler);
+ ok(aIsValid, "A valid offer is sent out.");
+ gScript.sendAsyncMessage('trigger-control-channel-close', SpecialPowers.Cr.NS_OK);
+ resolve();
+ });
+ }),
+
+ request.start().then(
+ function(aConnection) {
+ is(aConnection.state, "connecting", "The initial state should be connecting.");
+ return new Promise((resolve) => {
+ aConnection.onclose = function() {
+ aConnection.onclose = null;
+ is(aConnection.state, "closed", "Connection should be closed.");
+ resolve();
+ };
+ });
+ },
+ function(aError) {
+ ok(false, "Error occurred when establishing a connection: " + aError);
+ teardown();
+ }
+ ),
+
+ ]);
+}
+
+function testStartConnectionUnexpectedControlChannelCloseBeforeDataTransportReady() {
+ info('--- testStartConnectionUnexpectedControlChannelCloseBeforeDataTransportReady ---');
+ return Promise.all([
+
+ new Promise((resolve) => {
+ gScript.addMessageListener('device-prompt', function devicePromptHandler() {
+ gScript.removeMessageListener('device-prompt', devicePromptHandler);
+ info("Device prompt is triggered.");
+ gScript.sendAsyncMessage('trigger-device-prompt-select');
+ resolve();
+ });
+ }),
+
+ new Promise((resolve) => {
+ gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
+ gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
+ info("A control channel is established.");
+ gScript.sendAsyncMessage('trigger-control-channel-open');
+ resolve();
+ });
+ }),
+
+ new Promise((resolve) => {
+ gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler() {
+ gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
+ info("The control channel is opened.");
+ resolve();
+ });
+ }),
+
+ new Promise((resolve) => {
+ gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+ gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+ is(aReason, SpecialPowers.Cr.NS_ERROR_ABORT, "The control channel is closed with NS_ERROR_ABORT");
+ resolve();
+ });
+ }),
+
+ new Promise((resolve) => {
+ gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
+ gScript.removeMessageListener('offer-sent', offerSentHandler);
+ ok(aIsValid, "A valid offer is sent out.");
+ gScript.sendAsyncMessage('trigger-incoming-transport');
+ resolve();
+ });
+ }),
+
+ new Promise((resolve) => {
+ gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
+ gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
+ info("Data transport channel is initialized.");
+ gScript.sendAsyncMessage('trigger-control-channel-close', SpecialPowers.Cr.NS_ERROR_ABORT);
+ resolve();
+ });
+ }),
+
+ new Promise((resolve) => {
+ gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
+ gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
+ info("The data transport is closed. " + aReason);
+ resolve();
+ });
+ }),
+
+ request.start().then(
+ function(aConnection) {
+ is(aConnection.state, "connecting", "The initial state should be connecting.");
+ return new Promise((resolve) => {
+ aConnection.onclose = function() {
+ aConnection.onclose = null;
+ is(aConnection.state, "closed", "Connection should be closed.");
+ resolve();
+ };
+ });
+ },
+ function(aError) {
+ ok(false, "Error occurred when establishing a connection: " + aError);
+ teardown();
+ }
+ ),
+
+ ]);
+}
+
+function testStartConnectionUnexpectedControlChannelCloseNoReasonBeforeDataTransportReady() {
+ info('--- testStartConnectionUnexpectedControlChannelCloseNoReasonBeforeDataTransportReady -- ');
+ return Promise.all([
+
+ new Promise((resolve) => {
+ gScript.addMessageListener('device-prompt', function devicePromptHandler() {
+ gScript.removeMessageListener('device-prompt', devicePromptHandler);
+ info("Device prompt is triggered.");
+ gScript.sendAsyncMessage('trigger-device-prompt-select');
+ resolve();
+ });
+ }),
+
+ new Promise((resolve) => {
+ gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
+ gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
+ info("A control channel is established.");
+ gScript.sendAsyncMessage('trigger-control-channel-open');
+ resolve();
+ });
+ }),
+
+ new Promise((resolve) => {
+ gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler() {
+ gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
+ info("The control channel is opened.");
+ resolve();
+ });
+ }),
+
+ new Promise((resolve) => {
+ gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+ gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+ info("The control channel is closed. " + aReason);
+ is(aReason, SpecialPowers.Cr.NS_OK, "The control channel is closed with NS_OK");
+ resolve();
+ });
+ }),
+
+ new Promise((resolve) => {
+ gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
+ gScript.removeMessageListener('offer-sent', offerSentHandler);
+ ok(aIsValid, "A valid offer is sent out.");
+ gScript.sendAsyncMessage('trigger-incoming-transport');
+ resolve();
+ });
+ }),
+
+ new Promise((resolve) => {
+ gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
+ gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
+ info("Data transport channel is initialized.");
+ gScript.sendAsyncMessage('trigger-control-channel-close', SpecialPowers.Cr.NS_OK);
+ resolve();
+ });
+ }),
+
+ new Promise((resolve) => {
+ gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
+ gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
+ info("The data transport is closed. " + aReason);
+ resolve();
+ });
+ }),
+
+ request.start().then(
+ function(aConnection) {
+ is(aConnection.state, "connecting", "The initial state should be connecting.");
+ return new Promise((resolve) => {
+ aConnection.onclose = function() {
+ aConnection.onclose = null;
+ is(aConnection.state, "closed", "Connection should be closed.");
+ resolve();
+ };
+ });
+ },
+ function(aError) {
+ ok(false, "Error occurred when establishing a connection: " + aError);
+ teardown();
+ }
+ ),
+
+ ]);
+}
+
+function testStartConnectionUnexpectedDataTransportClose() {
+ info('--- testStartConnectionUnexpectedDataTransportClose ---');
+ return Promise.all([
+
+ new Promise((resolve) => {
+ gScript.addMessageListener('device-prompt', function devicePromptHandler() {
+ gScript.removeMessageListener('device-prompt', devicePromptHandler);
+ info("Device prompt is triggered.");
+ gScript.sendAsyncMessage('trigger-device-prompt-select');
+ resolve();
+ });
+ }),
+
+ new Promise((resolve) => {
+ gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
+ gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
+ info("A control channel is established.");
+ gScript.sendAsyncMessage('trigger-control-channel-open');
+ resolve();
+ });
+ }),
+
+ new Promise((resolve) => {
+ gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler() {
+ gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
+ info("The control channel is opened.");
+ resolve();
+ });
+ }),
+
+ new Promise((resolve) => {
+ gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+ gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+ info("The control channel is closed. " + aReason);
+ resolve();
+ });
+ }),
+
+ new Promise((resolve) => {
+ gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
+ gScript.removeMessageListener('offer-sent', offerSentHandler);
+ ok(aIsValid, "A valid offer is sent out.");
+ info("recv offer-sent.");
+ gScript.sendAsyncMessage('trigger-incoming-transport');
+ resolve();
+ });
+ }),
+
+ new Promise((resolve) => {
+ gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
+ gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
+ info("Data transport channel is initialized.");
+ gScript.sendAsyncMessage('trigger-data-transport-close', SpecialPowers.Cr.NS_ERROR_UNEXPECTED);
+ resolve();
+ });
+ }),
+
+ new Promise((resolve) => {
+ gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
+ gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
+ info("The data transport is closed. " + aReason);
+ resolve();
+ });
+ }),
+
+ request.start().then(
+ function(aConnection) {
+ is(aConnection.state, "connecting", "The initial state should be connecting.");
+ return new Promise((resolve) => {
+ aConnection.onclose = function() {
+ aConnection.onclose = null;
+ is(aConnection.state, "closed", "Connection should be closed.");
+ resolve();
+ };
+ });
+ },
+ function(aError) {
+ ok(false, "Error occurred when establishing a connection: " + aError);
+ teardown();
+ }
+ ),
+
+ ]);
+}
+
+function teardown() {
+ gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+ gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
+ gScript.destroy();
+ SimpleTest.finish();
+ });
+
+ gScript.sendAsyncMessage('teardown');
+}
+
+function runTests() {
+ ok(window.PresentationRequest, "PresentationRequest should be available.");
+
+ setup().
+ then(testStartConnectionCancelPrompt).
+ then(testStartConnectionNoDevice).
+ then(testStartConnectionUnexpectedControlChannelCloseBeforeDataTransportInit).
+ then(testStartConnectionUnexpectedControlChannelCloseNoReasonBeforeDataTransportInit).
+ then(testStartConnectionUnexpectedControlChannelCloseBeforeDataTransportReady).
+ then(testStartConnectionUnexpectedControlChannelCloseNoReasonBeforeDataTransportReady).
+ then(testStartConnectionUnexpectedDataTransportClose).
+ then(teardown);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPermissions([
+ {type: 'presentation-device-manage', allow: false, context: document},
+], function() {
+ SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
+ ["dom.presentation.controller.enabled", true],
+ ["dom.presentation.session_transport.data_channel.enable", false]]},
+ runTests);
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/presentation/tests/mochitest/test_presentation_terminate.js b/dom/presentation/tests/mochitest/test_presentation_terminate.js
new file mode 100644
index 000000000..8ebfd9d64
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_terminate.js
@@ -0,0 +1,243 @@
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout('Test for guarantee not firing async event');
+
+function debug(str) {
+ // info(str);
+}
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript1UA.js'));
+var receiverUrl = SimpleTest.getTestFileURL('file_presentation_terminate.html');
+var request;
+var connection;
+var receiverIframe;
+
+function setup() {
+ gScript.addMessageListener('device-prompt', function devicePromptHandler() {
+ debug('Got message: device-prompt');
+ gScript.removeMessageListener('device-prompt', devicePromptHandler);
+ gScript.sendAsyncMessage('trigger-device-prompt-select');
+ });
+
+ gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
+ gScript.removeMessageListener('control-channel-established',
+ controlChannelEstablishedHandler);
+ gScript.sendAsyncMessage('trigger-control-channel-open');
+ });
+
+ gScript.addMessageListener('sender-launch', function senderLaunchHandler(url) {
+ debug('Got message: sender-launch');
+ gScript.removeMessageListener('sender-launch', senderLaunchHandler);
+ is(url, receiverUrl, 'Receiver: should receive the same url');
+ receiverIframe = document.createElement('iframe');
+ receiverIframe.setAttribute('mozbrowser', 'true');
+ receiverIframe.setAttribute('mozpresentation', receiverUrl);
+ var oop = location.pathname.indexOf('_inproc') == -1;
+ receiverIframe.setAttribute('remote', oop);
+
+ receiverIframe.setAttribute('src', receiverUrl);
+ receiverIframe.addEventListener('mozbrowserloadend', function mozbrowserloadendHander() {
+ receiverIframe.removeEventListener('mozbrowserloadend', mozbrowserloadendHander);
+ info('Receiver loaded.');
+ });
+
+ // This event is triggered when the iframe calls 'alert'.
+ receiverIframe.addEventListener('mozbrowsershowmodalprompt', function receiverListener(evt) {
+ var message = evt.detail.message;
+ if (/^OK /.exec(message)) {
+ ok(true, message.replace(/^OK /, ''));
+ } else if (/^KO /.exec(message)) {
+ ok(false, message.replace(/^KO /, ''));
+ } else if (/^INFO /.exec(message)) {
+ info(message.replace(/^INFO /, ''));
+ } else if (/^COMMAND /.exec(message)) {
+ var command = JSON.parse(message.replace(/^COMMAND /, ''));
+ gScript.sendAsyncMessage(command.name, command.data);
+ } else if (/^DONE$/.exec(message)) {
+ ok(true, 'Messaging from iframe complete.');
+ receiverIframe.removeEventListener('mozbrowsershowmodalprompt',
+ receiverListener);
+ }
+ }, false);
+
+ var promise = new Promise(function(aResolve, aReject) {
+ document.body.appendChild(receiverIframe);
+ aResolve(receiverIframe);
+ });
+
+ var obs = SpecialPowers.Cc['@mozilla.org/observer-service;1']
+ .getService(SpecialPowers.Ci.nsIObserverService);
+ obs.notifyObservers(promise, 'setup-request-promise', null);
+ });
+
+ gScript.addMessageListener('promise-setup-ready', function promiseSetupReadyHandler() {
+ debug('Got message: promise-setup-ready');
+ gScript.removeMessageListener('promise-setup-ready',
+ promiseSetupReadyHandler);
+ gScript.sendAsyncMessage('trigger-on-session-request', receiverUrl);
+ });
+
+ return Promise.resolve();
+}
+
+function testCreateRequest() {
+ return new Promise(function(aResolve, aReject) {
+ info('Sender: --- testCreateRequest ---');
+ request = new PresentationRequest(receiverUrl);
+ request.getAvailability().then((aAvailability) => {
+ is(aAvailability.value, false, "Sender: should have no available device after setup");
+ aAvailability.onchange = function() {
+ aAvailability.onchange = null;
+ ok(aAvailability.value, 'Sender: Device should be available.');
+ aResolve();
+ }
+
+ gScript.sendAsyncMessage('trigger-device-add');
+ }).catch((aError) => {
+ ok(false, 'Sender: Error occurred when getting availability: ' + aError);
+ teardown();
+ aReject();
+ });
+ });
+}
+
+function testStartConnection() {
+ return new Promise(function(aResolve, aReject) {
+ request.start().then((aConnection) => {
+ connection = aConnection;
+ ok(connection, 'Sender: Connection should be available.');
+ ok(connection.id, 'Sender: Connection ID should be set.');
+ is(connection.state, 'connecting', 'Sender: The initial state should be connecting.');
+ connection.onconnect = function() {
+ connection.onconnect = null;
+ is(connection.state, 'connected', 'Connection should be connected.');
+ aResolve();
+ };
+
+ info('Sender: test terminate at connecting state');
+ connection.onterminate = function() {
+ connection.onterminate = null;
+ ok(false, 'Should not be able to terminate at connecting state');
+ aReject();
+ }
+ connection.terminate();
+ }).catch((aError) => {
+ ok(false, 'Sender: Error occurred when establishing a connection: ' + aError);
+ teardown();
+ aReject();
+ });
+ });
+}
+
+function testConnectionTerminate() {
+ return new Promise(function(aResolve, aReject) {
+ info('Sender: --- testConnectionTerminate---');
+ connection.onterminate = function() {
+ connection.onterminate = null;
+ is(connection.state, 'terminated', 'Sender: Connection should be terminated.');
+ };
+ gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
+ gScript.removeMessageListener('control-channel-established',
+ controlChannelEstablishedHandler);
+ gScript.sendAsyncMessage('trigger-control-channel-open');
+ });
+ gScript.addMessageListener('sender-terminate', function senderTerminateHandler() {
+ gScript.removeMessageListener('sender-terminate',
+ senderTerminateHandler);
+
+ Promise.all([
+ new Promise((resolve) => {
+ gScript.addMessageListener('device-disconnected', function deviceDisconnectedHandler() {
+ gScript.removeMessageListener('device-disconnected', deviceDisconnectedHandler);
+ ok(true, 'observe device disconnect');
+ resolve();
+ });
+ }),
+ new Promise((resolve) => {
+ receiverIframe.addEventListener('mozbrowserclose', function() {
+ ok(true, 'observe receiver page closing');
+ resolve();
+ });
+ }),
+ ]).then(aResolve);
+
+ gScript.sendAsyncMessage('trigger-on-terminate-request');
+ });
+ gScript.addMessageListener('ready-to-terminate', function onReadyToTerminate() {
+ gScript.removeMessageListener('ready-to-terminate', onReadyToTerminate);
+ connection.terminate();
+
+ // test unexpected close right after terminate
+ connection.onclose = function() {
+ ok(false, 'close after terminate should do nothing');
+ };
+ connection.close();
+ });
+ });
+}
+
+function testSendAfterTerminate() {
+ return new Promise(function(aResolve, aReject) {
+ try {
+ connection.send('something');
+ ok(false, 'PresentationConnection.send should be failed');
+ } catch (e) {
+ is(e.name, 'InvalidStateError', 'Must throw InvalidStateError');
+ }
+ aResolve();
+ });
+}
+
+function testCloseAfterTerminate() {
+ return Promise.race([
+ new Promise(function(aResolve, aReject) {
+ connection.onclose = function() {
+ connection.onclose = null;
+ ok(false, 'close at terminated state should do nothing');
+ aResolve();
+ };
+ connection.close();
+ }),
+ new Promise(function(aResolve, aReject) {
+ setTimeout(function() {
+ is(connection.state, 'terminated', 'Sender: Connection should be terminated.');
+ aResolve();
+ }, 3000);
+ }),
+ ]);
+}
+
+function teardown() {
+ gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+ debug('Got message: teardown-complete');
+ gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
+ gScript.destroy();
+ SimpleTest.finish();
+ });
+ gScript.sendAsyncMessage('teardown');
+}
+
+function runTests() {
+ setup().then(testCreateRequest)
+ .then(testStartConnection)
+ .then(testConnectionTerminate)
+ .then(testSendAfterTerminate)
+ .then(testCloseAfterTerminate)
+ .then(teardown);
+}
+
+SpecialPowers.pushPermissions([
+ {type: 'presentation-device-manage', allow: false, context: document},
+ {type: 'browser', allow: true, context: document},
+], () => {
+ SpecialPowers.pushPrefEnv({ 'set': [['dom.presentation.enabled', true],
+ ["dom.presentation.controller.enabled", true],
+ ["dom.presentation.receiver.enabled", true],
+ ['dom.presentation.test.enabled', true],
+ ['dom.mozBrowserFramesEnabled', true],
+ ["network.disable.ipc.security", true],
+ ['dom.ipc.tabs.disabled', false],
+ ['dom.presentation.test.stage', 0]]},
+ runTests);
+});
diff --git a/dom/presentation/tests/mochitest/test_presentation_terminate_establish_connection_error.js b/dom/presentation/tests/mochitest/test_presentation_terminate_establish_connection_error.js
new file mode 100644
index 000000000..a1d477aab
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_terminate_establish_connection_error.js
@@ -0,0 +1,197 @@
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout('Test for guarantee not firing async event');
+
+function debug(str) {
+ // info(str);
+}
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript1UA.js'));
+var receiverUrl = SimpleTest.getTestFileURL('file_presentation_terminate_establish_connection_error.html');
+var request;
+var connection;
+var receiverIframe;
+
+function postMessageToIframe(aType) {
+ receiverIframe.src = receiverUrl + "#" +
+ encodeURIComponent(JSON.stringify({ type: aType }));
+}
+
+function setup() {
+ gScript.addMessageListener('device-prompt', function devicePromptHandler() {
+ debug('Got message: device-prompt');
+ gScript.removeMessageListener('device-prompt', devicePromptHandler);
+ gScript.sendAsyncMessage('trigger-device-prompt-select');
+ });
+
+ gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
+ gScript.removeMessageListener('control-channel-established',
+ controlChannelEstablishedHandler);
+ gScript.sendAsyncMessage('trigger-control-channel-open');
+ });
+
+ gScript.addMessageListener('sender-launch', function senderLaunchHandler(url) {
+ debug('Got message: sender-launch');
+ gScript.removeMessageListener('sender-launch', senderLaunchHandler);
+ is(url, receiverUrl, 'Receiver: should receive the same url');
+ receiverIframe = document.createElement('iframe');
+ receiverIframe.setAttribute('mozbrowser', 'true');
+ receiverIframe.setAttribute('mozpresentation', receiverUrl);
+ var oop = location.pathname.indexOf('_inproc') == -1;
+ receiverIframe.setAttribute('remote', oop);
+
+ receiverIframe.setAttribute('src', receiverUrl);
+ receiverIframe.addEventListener('mozbrowserloadend', function mozbrowserloadendHander() {
+ receiverIframe.removeEventListener('mozbrowserloadend', mozbrowserloadendHander);
+ info('Receiver loaded.');
+ });
+
+ // This event is triggered when the iframe calls 'alert'.
+ receiverIframe.addEventListener('mozbrowsershowmodalprompt', function receiverListener(evt) {
+ var message = evt.detail.message;
+ if (/^OK /.exec(message)) {
+ ok(true, message.replace(/^OK /, ''));
+ } else if (/^KO /.exec(message)) {
+ ok(false, message.replace(/^KO /, ''));
+ } else if (/^INFO /.exec(message)) {
+ info(message.replace(/^INFO /, ''));
+ } else if (/^COMMAND /.exec(message)) {
+ var command = JSON.parse(message.replace(/^COMMAND /, ''));
+ gScript.sendAsyncMessage(command.name, command.data);
+ } else if (/^DONE$/.exec(message)) {
+ ok(true, 'Messaging from iframe complete.');
+ receiverIframe.removeEventListener('mozbrowsershowmodalprompt',
+ receiverListener);
+ }
+ }, false);
+
+ var promise = new Promise(function(aResolve, aReject) {
+ document.body.appendChild(receiverIframe);
+ aResolve(receiverIframe);
+ });
+
+ var obs = SpecialPowers.Cc['@mozilla.org/observer-service;1']
+ .getService(SpecialPowers.Ci.nsIObserverService);
+ obs.notifyObservers(promise, 'setup-request-promise', null);
+ });
+
+ gScript.addMessageListener('promise-setup-ready', function promiseSetupReadyHandler() {
+ debug('Got message: promise-setup-ready');
+ gScript.removeMessageListener('promise-setup-ready',
+ promiseSetupReadyHandler);
+ gScript.sendAsyncMessage('trigger-on-session-request', receiverUrl);
+ });
+
+ return Promise.resolve();
+}
+
+function testCreateRequest() {
+ return new Promise(function(aResolve, aReject) {
+ info('Sender: --- testCreateRequest ---');
+ request = new PresentationRequest(receiverUrl);
+ request.getAvailability().then((aAvailability) => {
+ is(aAvailability.value, false, "Sender: should have no available device after setup");
+ aAvailability.onchange = function() {
+ aAvailability.onchange = null;
+ ok(aAvailability.value, 'Sender: Device should be available.');
+ aResolve();
+ }
+
+ gScript.sendAsyncMessage('trigger-device-add');
+ }).catch((aError) => {
+ ok(false, 'Sender: Error occurred when getting availability: ' + aError);
+ teardown();
+ aReject();
+ });
+ });
+}
+
+function testStartConnection() {
+ return new Promise(function(aResolve, aReject) {
+ request.start().then((aConnection) => {
+ connection = aConnection;
+ ok(connection, 'Sender: Connection should be available.');
+ ok(connection.id, 'Sender: Connection ID should be set.');
+ is(connection.state, 'connecting', 'Sender: The initial state should be connecting.');
+ connection.onconnect = function() {
+ connection.onconnect = null;
+ is(connection.state, 'connected', 'Connection should be connected.');
+ aResolve();
+ };
+ }).catch((aError) => {
+ ok(false, 'Sender: Error occurred when establishing a connection: ' + aError);
+ teardown();
+ aReject();
+ });
+ });
+}
+
+function testConnectionTerminate() {
+ info('Sender: --- testConnectionTerminate---');
+ let promise = Promise.all([
+ new Promise(function(aResolve, aReject) {
+ connection.onclose = function() {
+ connection.onclose = null;
+ is(connection.state, 'closed', 'Sender: Connection should be closed.');
+ aResolve();
+ };
+ }),
+ new Promise(function(aResolve, aReject) {
+ function deviceDisconnectedHandler() {
+ gScript.removeMessageListener('device-disconnected', deviceDisconnectedHandler);
+ ok(true, 'should not receive device disconnect');
+ aResolve();
+ }
+
+ gScript.addMessageListener('device-disconnected', deviceDisconnectedHandler);
+ }),
+ new Promise(function(aResolve, aReject) {
+ receiverIframe.addEventListener('mozbrowserclose', function() {
+ ok(true, 'observe receiver page closing');
+ aResolve();
+ });
+ })
+ ]);
+
+ gScript.addMessageListener('prepare-for-terminate', function prepareForTerminateHandler() {
+ debug('Got message: prepare-for-terminate');
+ gScript.removeMessageListener('prepare-for-terminate', prepareForTerminateHandler);
+ gScript.sendAsyncMessage('trigger-control-channel-error');
+ postMessageToIframe('ready-to-terminate');
+ });
+
+ return promise;
+}
+
+function teardown() {
+ gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+ debug('Got message: teardown-complete');
+ gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
+ gScript.destroy();
+ SimpleTest.finish();
+ });
+ gScript.sendAsyncMessage('teardown');
+}
+
+function runTests() {
+ setup().then(testCreateRequest)
+ .then(testStartConnection)
+ .then(testConnectionTerminate)
+ .then(teardown);
+}
+
+SpecialPowers.pushPermissions([
+ {type: 'presentation-device-manage', allow: false, context: document},
+ {type: 'browser', allow: true, context: document},
+], () => {
+ SpecialPowers.pushPrefEnv({ 'set': [['dom.presentation.enabled', true],
+ ["dom.presentation.controller.enabled", true],
+ ["dom.presentation.receiver.enabled", true],
+ ['dom.presentation.test.enabled', true],
+ ['dom.mozBrowserFramesEnabled', true],
+ ["network.disable.ipc.security", true],
+ ['dom.ipc.tabs.disabled', false],
+ ['dom.presentation.test.stage', 0]]},
+ runTests);
+});
diff --git a/dom/presentation/tests/mochitest/test_presentation_terminate_establish_connection_error_inproc.html b/dom/presentation/tests/mochitest/test_presentation_terminate_establish_connection_error_inproc.html
new file mode 100644
index 000000000..ccf0767f1
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_terminate_establish_connection_error_inproc.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<!-- vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: -->
+<html>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <head>
+ <meta charset='utf-8'>
+ <title>Test for control channel establish error during PresentationConnection.terminate()</title>
+ <link rel='stylesheet' type='text/css' href='/tests/SimpleTest/test.css'/>
+ <script type='application/javascript' src='/tests/SimpleTest/SimpleTest.js'></script>
+ </head>
+ <body>
+ <a target='_blank' href='https://bugzilla.mozilla.org/show_bug.cgi?id=1289292'>
+ Test for constrol channel establish error during PresentationConnection.terminate()</a>
+ <script type='application/javascript;version=1.8' src='test_presentation_terminate_establish_connection_error.js'>
+ </script>
+ </body>
+</html>
diff --git a/dom/presentation/tests/mochitest/test_presentation_terminate_establish_connection_error_oop.html b/dom/presentation/tests/mochitest/test_presentation_terminate_establish_connection_error_oop.html
new file mode 100644
index 000000000..ccf0767f1
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_terminate_establish_connection_error_oop.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<!-- vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: -->
+<html>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <head>
+ <meta charset='utf-8'>
+ <title>Test for control channel establish error during PresentationConnection.terminate()</title>
+ <link rel='stylesheet' type='text/css' href='/tests/SimpleTest/test.css'/>
+ <script type='application/javascript' src='/tests/SimpleTest/SimpleTest.js'></script>
+ </head>
+ <body>
+ <a target='_blank' href='https://bugzilla.mozilla.org/show_bug.cgi?id=1289292'>
+ Test for constrol channel establish error during PresentationConnection.terminate()</a>
+ <script type='application/javascript;version=1.8' src='test_presentation_terminate_establish_connection_error.js'>
+ </script>
+ </body>
+</html>
diff --git a/dom/presentation/tests/mochitest/test_presentation_terminate_inproc.html b/dom/presentation/tests/mochitest/test_presentation_terminate_inproc.html
new file mode 100644
index 000000000..33bbcda57
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_terminate_inproc.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<!-- vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: -->
+<html>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <head>
+ <meta charset='utf-8'>
+ <title>Test for PresentationConnection.terminate()</title>
+ <link rel='stylesheet' type='text/css' href='/tests/SimpleTest/test.css'/>
+ <script type='application/javascript' src='/tests/SimpleTest/SimpleTest.js'></script>
+ </head>
+ <body>
+ <a target='_blank' href='https://bugzilla.mozilla.org/show_bug.cgi?id=1276378'>
+ Test for PresentationConnection.terminate()</a>
+ <script type='application/javascript;version=1.8' src='test_presentation_terminate.js'>
+ </script>
+ </body>
+</html>
diff --git a/dom/presentation/tests/mochitest/test_presentation_terminate_oop.html b/dom/presentation/tests/mochitest/test_presentation_terminate_oop.html
new file mode 100644
index 000000000..33bbcda57
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_terminate_oop.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<!-- vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: -->
+<html>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <head>
+ <meta charset='utf-8'>
+ <title>Test for PresentationConnection.terminate()</title>
+ <link rel='stylesheet' type='text/css' href='/tests/SimpleTest/test.css'/>
+ <script type='application/javascript' src='/tests/SimpleTest/SimpleTest.js'></script>
+ </head>
+ <body>
+ <a target='_blank' href='https://bugzilla.mozilla.org/show_bug.cgi?id=1276378'>
+ Test for PresentationConnection.terminate()</a>
+ <script type='application/javascript;version=1.8' src='test_presentation_terminate.js'>
+ </script>
+ </body>
+</html>
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]