summaryrefslogtreecommitdiffstats
path: root/dom/presentation/tests/mochitest
diff options
context:
space:
mode:
Diffstat (limited to 'dom/presentation/tests/mochitest')
-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
59 files changed, 7770 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>