/* 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"; /* eslint-env mozilla/frame-script */ const Cm = Components.manager; const { Services } = ChromeUtils.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: ChromeUtils.generateQI(["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() { // send offer after notifyConnected immediately this._listener .QueryInterface(Ci.nsIPresentationControlChannelListener) .notifyConnected(); }, notifyReconnected() { // send offer after notifyOpened immediately this._listener .QueryInterface(Ci.nsIPresentationControlChannelListener) .notifyReconnected(); }, sendOffer(offer) { Services.tm.dispatchToMainThread(() => { mockControlChannelOfReceiver.onOffer(offer); }); }, onAnswer(answer) { this._listener .QueryInterface(Ci.nsIPresentationControlChannelListener) .onAnswer(answer); }, launch(presentationId, url) { sessionId = presentationId; sendAsyncMessage("sender-launch", url); }, disconnect(reason) { if (!this._listener) { return; } this._listener .QueryInterface(Ci.nsIPresentationControlChannelListener) .notifyDisconnected(reason); mockControlChannelOfReceiver.disconnect(); }, terminate(presentationId) { sendAsyncMessage("sender-terminate"); }, reconnect(presentationId, url) { sendAsyncMessage("start-reconnect", url); }, sendIceCandidate(candidate) { mockControlChannelOfReceiver.notifyIceCandidate(candidate); }, notifyIceCandidate(candidate) { if (!this._listener) { return; } this._listener .QueryInterface(Ci.nsIPresentationControlChannelListener) .onIceCandidate(candidate); }, }; // control channel of receiver const mockControlChannelOfReceiver = { QueryInterface: ChromeUtils.generateQI(["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() { // do nothing if (!this._listener) { this._pendingOpened = true; return; } this._listener .QueryInterface(Ci.nsIPresentationControlChannelListener) .notifyConnected(); }, onOffer(offer) { this._listener .QueryInterface(Ci.nsIPresentationControlChannelListener) .onOffer(offer); }, sendAnswer(answer) { Services.tm.dispatchToMainThread(() => { mockControlChannelOfSender.onAnswer(answer); }); }, disconnect(reason) { if (!this._listener) { return; } this._listener .QueryInterface(Ci.nsIPresentationControlChannelListener) .notifyDisconnected(reason); sendAsyncMessage("control-channel-receiver-closed", reason); }, terminate(presentaionId) {}, sendIceCandidate(candidate) { mockControlChannelOfSender.notifyIceCandidate(candidate); }, notifyIceCandidate(candidate) { if (!this._listener) { return; } this._listener .QueryInterface(Ci.nsIPresentationControlChannelListener) .onIceCandidate(candidate); }, }; const mockDevice = { QueryInterface: ChromeUtils.generateQI(["nsIPresentationDevice"]), id: "id", name: "name", type: "type", establishControlChannel(url, presentationId) { if (triggerControlChannelError) { throw Components.Exception("", Cr.NS_ERROR_FAILURE); } sendAsyncMessage("control-channel-established"); return mockControlChannelOfSender; }, disconnect() { sendAsyncMessage("device-disconnected"); }, isRequestedUrlSupported(requestedUrl) { return true; }, }; const mockDevicePrompt = { QueryInterface: ChromeUtils.generateQI([ "nsIPresentationDevicePrompt", "nsIFactory", ]), createInstance(aOuter, aIID) { if (aOuter) { throw Components.Exception("", Cr.NS_ERROR_NO_AGGREGATION); } return this.QueryInterface(aIID); }, set request(request) { this._request = request; }, get request() { return this._request; }, promptDeviceSelection(request) { this._request = request; sendAsyncMessage("device-prompt"); }, simulateSelect() { this._request.select(mockDevice); }, simulateCancel() { this._request.cancel(); }, }; const mockRequestUIGlue = { QueryInterface: ChromeUtils.generateQI([ "nsIPresentationRequestUIGlue", "nsIFactory", ]), set promise(aPromise) { this._promise = aPromise; }, get promise() { return this._promise; }, createInstance(aOuter, aIID) { if (aOuter) { throw Components.Exception("", Cr.NS_ERROR_NO_AGGREGATION); } return this.QueryInterface(aIID); }, sendRequest(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, mockClassId, mockFactory, originalClassId, originalFactory, }; } // Register mock factories. 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); Services.obs.addObserver(function setupRequestPromiseHandler( aSubject, aTopic, aData ) { debug("Got observer: setup-request-promise"); Services.obs.removeObserver(setupRequestPromiseHandler, aTopic); mockRequestUIGlue.promise = aSubject; sendAsyncMessage("promise-setup-ready"); }, "setup-request-promise"); } 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();