/* 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();