/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ const HAWK_TOKEN_LENGTH = 64; const { LOOP_SESSION_TYPE, MozLoopServiceInternal, } = Cu.import("resource:///modules/loop/MozLoopService.jsm", {}); // Cache this value only once, at the beginning of a // test run, so that it doesn't pick up the offline=true // if offline mode is requested multiple times in a test run. const WAS_OFFLINE = Services.io.offline; var gMozLoopAPI; function promiseGetMozLoopAPI() { let deferred = Promise.defer(); let loopPanel = document.getElementById("loop-notification-panel"); let btn = document.getElementById("loop-call-button"); // Wait for the popup to be shown if it's not already, then we can get the iframe and // wait for the iframe's load to be completed. if (loopPanel.state == "closing" || loopPanel.state == "closed") { loopPanel.addEventListener("popupshown", () => { loopPanel.removeEventListener("popupshown", onpopupshown, true); onpopupshown(); }, true); // Now we're setup, click the button. btn.click(); } else { setTimeout(onpopupshown, 0); } function onpopupshown() { let iframe = document.getElementById(btn.getAttribute("notificationFrameId")); if (iframe.contentDocument && iframe.contentDocument.readyState == "complete") { gMozLoopAPI = iframe.contentWindow.navigator.wrappedJSObject.mozLoop; deferred.resolve(); } else { iframe.addEventListener("load", function panelOnLoad(e) { iframe.removeEventListener("load", panelOnLoad, true); gMozLoopAPI = iframe.contentWindow.navigator.wrappedJSObject.mozLoop; // We do this in an execute soon to allow any other event listeners to // be handled, just in case. deferred.resolve(); }, true); } } // Remove the iframe after each test. This also avoids mochitest complaining // about leaks on shutdown as we intentionally hold the iframe open for the // life of the application. registerCleanupFunction(function() { loopPanel.hidePopup(); let frameId = btn.getAttribute("notificationFrameId"); let frame = document.getElementById(frameId); if (frame) { loopPanel.removeChild(frame); } }); return deferred.promise; } /** * Loads the loop panel by clicking the button and waits for its open to complete. * It also registers * * This assumes that the tests are running in a generatorTest. */ function loadLoopPanel(aOverrideOptions = {}) { // Set prefs to ensure we don't access the network externally. Services.prefs.setCharPref("services.push.serverURL", aOverrideOptions.pushURL || "ws://localhost/"); Services.prefs.setCharPref("loop.server", aOverrideOptions.loopURL || "http://localhost/"); // Turn off the network for loop tests, so that we don't // try to access the remote servers. If we want to turn this // back on in future, be careful to check for intermittent // failures. if (!aOverrideOptions.stayOnline) { Services.io.offline = true; } registerCleanupFunction(function() { Services.prefs.clearUserPref("services.push.serverURL"); Services.prefs.clearUserPref("loop.server"); Services.io.offline = WAS_OFFLINE; }); // Turn off animations to make tests quicker. let loopPanel = document.getElementById("loop-notification-panel"); loopPanel.setAttribute("animate", "false"); // Now get the actual API. yield promiseGetMozLoopAPI(); } function promiseOAuthParamsSetup(baseURL, params) { let deferred = Promise.defer(); let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]. createInstance(Ci.nsIXMLHttpRequest); xhr.open("POST", baseURL + "/setup_params", true); xhr.setRequestHeader("X-Params", JSON.stringify(params)); xhr.addEventListener("load", () => deferred.resolve(xhr)); xhr.addEventListener("error", error => deferred.reject(error)); xhr.send(); return deferred.promise; } function* resetFxA() { let global = Cu.import("resource:///modules/loop/MozLoopService.jsm", {}); global.gHawkClient = null; global.gFxAOAuthClientPromise = null; global.gFxAOAuthClient = null; MozLoopServiceInternal.fxAOAuthProfile = null; MozLoopServiceInternal.fxAOAuthTokenData = null; const fxASessionPref = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA); Services.prefs.clearUserPref(fxASessionPref); MozLoopService.errors.clear(); let notified = promiseObserverNotified("loop-status-changed"); MozLoopServiceInternal.notifyStatusChanged(); yield notified; } function checkFxAOAuthTokenData(aValue) { ise(MozLoopServiceInternal.fxAOAuthTokenData, aValue, "fxAOAuthTokenData should be " + aValue); } function checkLoggedOutState() { let global = Cu.import("resource:///modules/loop/MozLoopService.jsm", {}); ise(global.gFxAOAuthClientPromise, null, "gFxAOAuthClientPromise should be cleared"); ise(MozLoopService.userProfile, null, "fxAOAuthProfile should be cleared"); ise(global.gFxAOAuthClient, null, "gFxAOAuthClient should be cleared"); checkFxAOAuthTokenData(null); const fxASessionPref = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA); ise(Services.prefs.getPrefType(fxASessionPref), Services.prefs.PREF_INVALID, "FxA hawk session should be cleared anyways"); } function promiseDeletedOAuthParams(baseURL) { let deferred = Promise.defer(); let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]. createInstance(Ci.nsIXMLHttpRequest); xhr.open("DELETE", baseURL + "/setup_params", true); xhr.addEventListener("load", () => deferred.resolve(xhr)); xhr.addEventListener("error", deferred.reject); xhr.send(); return deferred.promise; } function promiseObserverNotified(aTopic, aExpectedData = null) { let deferred = Promise.defer(); Services.obs.addObserver(function onNotification(aSubject, aTopic, aData) { Services.obs.removeObserver(onNotification, aTopic); is(aData, aExpectedData, "observer data should match expected data") deferred.resolve({subject: aSubject, data: aData}); }, aTopic, false); return deferred.promise; } /** * Get the last registration on the test server. */ function promiseOAuthGetRegistration(baseURL) { let deferred = Promise.defer(); let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]. createInstance(Ci.nsIXMLHttpRequest); xhr.open("GET", baseURL + "/get_registration", true); xhr.responseType = "json"; xhr.addEventListener("load", () => deferred.resolve(xhr)); xhr.addEventListener("error", deferred.reject); xhr.send(); return deferred.promise; } function getLoopString(stringID) { return MozLoopServiceInternal.localizedStrings[stringID].textContent; } /** * This is used to fake push registration and notifications for * MozLoopService tests. There is only one object created per test instance, as * once registration has taken place, the object cannot currently be changed. */ let mockPushHandler = { // This sets the registration result to be returned when initialize // is called. By default, it is equivalent to success. registrationResult: null, pushUrl: undefined, /** * MozLoopPushHandler API */ initialize: function(registerCallback, notificationCallback) { registerCallback(this.registrationResult, this.pushUrl); this._notificationCallback = notificationCallback; }, /** * Test-only API to simplify notifying a push notification result. */ notify: function(version) { this._notificationCallback(version); } }; const mockDb = { _store: { }, _next_guid: 1, add: function(details, callback) { if (!("id" in details)) { callback(new Error("No 'id' field present")); return; } details._guid = this._next_guid++; this._store[details._guid] = details; callback(null, details); }, remove: function(guid, callback) { if (!guid in this._store) { callback(new Error("Could not find _guid '" + guid + "' in database")); return; } delete this._store[guid]; callback(null); }, getAll: function(callback) { callback(null, this._store); }, get: function(guid, callback) { callback(null, this._store[guid]); }, getByServiceId: function(serviceId, callback) { for (let guid in this._store) { if (serviceId === this._store[guid].id) { callback(null, this._store[guid]); return; } } callback(null, null); }, removeAll: function(callback) { this._store = {}; this._next_guid = 1; callback(null); }, promise: function(method, ...params) { return new Promise(resolve => { this[method](...params, (err, res) => err ? reject(err) : resolve(res)); }); } };