'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); });