michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: * http://creativecommons.org/publicdomain/zero/1.0/ michael@0: */ michael@0: michael@0: Components.utils.import("resource://gre/modules/NetUtil.jsm"); michael@0: michael@0: let tmp = {}; michael@0: Components.utils.import("resource://gre/modules/AddonManager.jsm", tmp); michael@0: Components.utils.import("resource://gre/modules/Log.jsm", tmp); michael@0: let AddonManager = tmp.AddonManager; michael@0: let AddonManagerPrivate = tmp.AddonManagerPrivate; michael@0: let Log = tmp.Log; michael@0: michael@0: var pathParts = gTestPath.split("/"); michael@0: // Drop the test filename michael@0: pathParts.splice(pathParts.length - 1, pathParts.length); michael@0: michael@0: var gTestInWindow = /-window$/.test(pathParts[pathParts.length - 1]); michael@0: michael@0: // Drop the UI type michael@0: if (gTestInWindow) { michael@0: pathParts.splice(pathParts.length - 1, pathParts.length); michael@0: } michael@0: michael@0: const RELATIVE_DIR = pathParts.slice(4).join("/") + "/"; michael@0: michael@0: const TESTROOT = "http://example.com/" + RELATIVE_DIR; michael@0: const TESTROOT2 = "http://example.org/" + RELATIVE_DIR; michael@0: const CHROMEROOT = pathParts.join("/") + "/"; michael@0: const PREF_DISCOVERURL = "extensions.webservice.discoverURL"; michael@0: const PREF_DISCOVER_ENABLED = "extensions.getAddons.showPane"; michael@0: const PREF_XPI_ENABLED = "xpinstall.enabled"; michael@0: const PREF_UPDATEURL = "extensions.update.url"; michael@0: const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled"; michael@0: michael@0: const MANAGER_URI = "about:addons"; michael@0: const INSTALL_URI = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul"; michael@0: const PREF_LOGGING_ENABLED = "extensions.logging.enabled"; michael@0: const PREF_SEARCH_MAXRESULTS = "extensions.getAddons.maxResults"; michael@0: const PREF_STRICT_COMPAT = "extensions.strictCompatibility"; michael@0: michael@0: var PREF_CHECK_COMPATIBILITY; michael@0: (function() { michael@0: var channel = "default"; michael@0: try { michael@0: channel = Services.prefs.getCharPref("app.update.channel"); michael@0: } catch (e) { } michael@0: if (channel != "aurora" && michael@0: channel != "beta" && michael@0: channel != "release") { michael@0: var version = "nightly"; michael@0: } else { michael@0: version = Services.appinfo.version.replace(/^([^\.]+\.[0-9]+[a-z]*).*/gi, "$1"); michael@0: } michael@0: PREF_CHECK_COMPATIBILITY = "extensions.checkCompatibility." + version; michael@0: })(); michael@0: michael@0: var gPendingTests = []; michael@0: var gTestsRun = 0; michael@0: var gTestStart = null; michael@0: michael@0: var gUseInContentUI = !gTestInWindow && ("switchToTabHavingURI" in window); michael@0: michael@0: var gRestorePrefs = [{name: PREF_LOGGING_ENABLED}, michael@0: {name: "extensions.webservice.discoverURL"}, michael@0: {name: "extensions.update.url"}, michael@0: {name: "extensions.update.background.url"}, michael@0: {name: "extensions.update.enabled"}, michael@0: {name: "extensions.update.autoUpdateDefault"}, michael@0: {name: "extensions.getAddons.get.url"}, michael@0: {name: "extensions.getAddons.getWithPerformance.url"}, michael@0: {name: "extensions.getAddons.search.browseURL"}, michael@0: {name: "extensions.getAddons.search.url"}, michael@0: {name: "extensions.getAddons.cache.enabled"}, michael@0: {name: "devtools.chrome.enabled"}, michael@0: {name: "devtools.debugger.remote-enabled"}, michael@0: {name: PREF_SEARCH_MAXRESULTS}, michael@0: {name: PREF_STRICT_COMPAT}, michael@0: {name: PREF_CHECK_COMPATIBILITY}]; michael@0: michael@0: for (let pref of gRestorePrefs) { michael@0: if (!Services.prefs.prefHasUserValue(pref.name)) { michael@0: pref.type = "clear"; michael@0: continue; michael@0: } michael@0: pref.type = Services.prefs.getPrefType(pref.name); michael@0: if (pref.type == Services.prefs.PREF_BOOL) michael@0: pref.value = Services.prefs.getBoolPref(pref.name); michael@0: else if (pref.type == Services.prefs.PREF_INT) michael@0: pref.value = Services.prefs.getIntPref(pref.name); michael@0: else if (pref.type == Services.prefs.PREF_STRING) michael@0: pref.value = Services.prefs.getCharPref(pref.name); michael@0: } michael@0: michael@0: // Turn logging on for all tests michael@0: Services.prefs.setBoolPref(PREF_LOGGING_ENABLED, true); michael@0: michael@0: // Helper to register test failures and close windows if any are left open michael@0: function checkOpenWindows(aWindowID) { michael@0: let windows = Services.wm.getEnumerator(aWindowID); michael@0: let found = false; michael@0: while (windows.hasMoreElements()) { michael@0: let win = windows.getNext().QueryInterface(Ci.nsIDOMWindow); michael@0: if (!win.closed) { michael@0: found = true; michael@0: win.close(); michael@0: } michael@0: } michael@0: if (found) michael@0: ok(false, "Found unexpected " + aWindowID + " window still open"); michael@0: } michael@0: michael@0: registerCleanupFunction(function() { michael@0: // Restore prefs michael@0: for (let pref of gRestorePrefs) { michael@0: if (pref.type == "clear") michael@0: Services.prefs.clearUserPref(pref.name); michael@0: else if (pref.type == Services.prefs.PREF_BOOL) michael@0: Services.prefs.setBoolPref(pref.name, pref.value); michael@0: else if (pref.type == Services.prefs.PREF_INT) michael@0: Services.prefs.setIntPref(pref.name, pref.value); michael@0: else if (pref.type == Services.prefs.PREF_STRING) michael@0: Services.prefs.setCharPref(pref.name, pref.value); michael@0: } michael@0: michael@0: // Throw an error if the add-ons manager window is open anywhere michael@0: checkOpenWindows("Addons:Manager"); michael@0: checkOpenWindows("Addons:Compatibility"); michael@0: checkOpenWindows("Addons:Install"); michael@0: michael@0: return new Promise((resolve, reject) => AddonManager.getAllInstalls(resolve)) michael@0: .then(aInstalls => { michael@0: for (let install of aInstalls) { michael@0: if (install instanceof MockInstall) michael@0: continue; michael@0: michael@0: ok(false, "Should not have seen an install of " + install.sourceURI.spec + " in state " + install.state); michael@0: install.cancel(); michael@0: } michael@0: }); michael@0: }); michael@0: michael@0: function log_exceptions(aCallback, ...aArgs) { michael@0: try { michael@0: return aCallback.apply(null, aArgs); michael@0: } michael@0: catch (e) { michael@0: info("Exception thrown: " + e); michael@0: throw e; michael@0: } michael@0: } michael@0: michael@0: function log_callback(aPromise, aCallback) { michael@0: aPromise.then(aCallback) michael@0: .then(null, e => info("Exception thrown: " + e)); michael@0: return aPromise; michael@0: } michael@0: michael@0: function add_test(test) { michael@0: gPendingTests.push(test); michael@0: } michael@0: michael@0: function run_next_test() { michael@0: if (gTestsRun > 0) michael@0: info("Test " + gTestsRun + " took " + (Date.now() - gTestStart) + "ms"); michael@0: michael@0: if (gPendingTests.length == 0) { michael@0: end_test(); michael@0: return; michael@0: } michael@0: michael@0: gTestsRun++; michael@0: var test = gPendingTests.shift(); michael@0: if (test.name) michael@0: info("Running test " + gTestsRun + " (" + test.name + ")"); michael@0: else michael@0: info("Running test " + gTestsRun); michael@0: michael@0: gTestStart = Date.now(); michael@0: log_exceptions(test); michael@0: } michael@0: michael@0: function get_addon_file_url(aFilename) { michael@0: try { michael@0: var cr = Cc["@mozilla.org/chrome/chrome-registry;1"]. michael@0: getService(Ci.nsIChromeRegistry); michael@0: var fileurl = cr.convertChromeURL(makeURI(CHROMEROOT + "addons/" + aFilename)); michael@0: return fileurl.QueryInterface(Ci.nsIFileURL); michael@0: } catch(ex) { michael@0: var jar = getJar(CHROMEROOT + "addons/" + aFilename); michael@0: var tmpDir = extractJarToTmp(jar); michael@0: tmpDir.append(aFilename); michael@0: michael@0: return Services.io.newFileURI(tmpDir).QueryInterface(Ci.nsIFileURL); michael@0: } michael@0: } michael@0: michael@0: function get_test_items_in_list(aManager) { michael@0: var tests = "@tests.mozilla.org"; michael@0: michael@0: let view = aManager.document.getElementById("view-port").selectedPanel; michael@0: let listid = view.id == "search-view" ? "search-list" : "addon-list"; michael@0: let item = aManager.document.getElementById(listid).firstChild; michael@0: let items = []; michael@0: michael@0: while (item) { michael@0: if (item.localName != "richlistitem") { michael@0: item = item.nextSibling; michael@0: continue; michael@0: } michael@0: michael@0: if (!item.mAddon || item.mAddon.id.substring(item.mAddon.id.length - tests.length) == tests) michael@0: items.push(item); michael@0: item = item.nextSibling; michael@0: } michael@0: michael@0: return items; michael@0: } michael@0: michael@0: function check_all_in_list(aManager, aIds, aIgnoreExtras) { michael@0: var doc = aManager.document; michael@0: var view = doc.getElementById("view-port").selectedPanel; michael@0: var listid = view.id == "search-view" ? "search-list" : "addon-list"; michael@0: var list = doc.getElementById(listid); michael@0: michael@0: var inlist = []; michael@0: var node = list.firstChild; michael@0: while (node) { michael@0: if (node.value) michael@0: inlist.push(node.value); michael@0: node = node.nextSibling; michael@0: } michael@0: michael@0: for (let id of aIds) { michael@0: if (inlist.indexOf(id) == -1) michael@0: ok(false, "Should find " + id + " in the list"); michael@0: } michael@0: michael@0: if (aIgnoreExtras) michael@0: return; michael@0: michael@0: for (let inlistItem of inlist) { michael@0: if (aIds.indexOf(inlistItem) == -1) michael@0: ok(false, "Shouldn't have seen " + inlistItem + " in the list"); michael@0: } michael@0: } michael@0: michael@0: function get_addon_element(aManager, aId) { michael@0: var doc = aManager.document; michael@0: var view = doc.getElementById("view-port").selectedPanel; michael@0: var listid = "addon-list"; michael@0: if (view.id == "search-view") michael@0: listid = "search-list"; michael@0: else if (view.id == "updates-view") michael@0: listid = "updates-list"; michael@0: var list = doc.getElementById(listid); michael@0: michael@0: var node = list.firstChild; michael@0: while (node) { michael@0: if (node.value == aId) michael@0: return node; michael@0: node = node.nextSibling; michael@0: } michael@0: return null; michael@0: } michael@0: michael@0: function wait_for_view_load(aManagerWindow, aCallback, aForceWait, aLongerTimeout) { michael@0: requestLongerTimeout(aLongerTimeout ? aLongerTimeout : 2); michael@0: michael@0: if (!aForceWait && !aManagerWindow.gViewController.isLoading) { michael@0: log_exceptions(aCallback, aManagerWindow); michael@0: return; michael@0: } michael@0: michael@0: aManagerWindow.document.addEventListener("ViewChanged", function() { michael@0: aManagerWindow.document.removeEventListener("ViewChanged", arguments.callee, false); michael@0: log_exceptions(aCallback, aManagerWindow); michael@0: }, false); michael@0: } michael@0: michael@0: function wait_for_manager_load(aManagerWindow, aCallback) { michael@0: if (!aManagerWindow.gIsInitializing) { michael@0: log_exceptions(aCallback, aManagerWindow); michael@0: return; michael@0: } michael@0: michael@0: info("Waiting for initialization"); michael@0: aManagerWindow.document.addEventListener("Initialized", function() { michael@0: aManagerWindow.document.removeEventListener("Initialized", arguments.callee, false); michael@0: log_exceptions(aCallback, aManagerWindow); michael@0: }, false); michael@0: } michael@0: michael@0: function open_manager(aView, aCallback, aLoadCallback, aLongerTimeout) { michael@0: let p = new Promise((resolve, reject) => { michael@0: michael@0: function setup_manager(aManagerWindow) { michael@0: if (aLoadCallback) michael@0: log_exceptions(aLoadCallback, aManagerWindow); michael@0: michael@0: if (aView) michael@0: aManagerWindow.loadView(aView); michael@0: michael@0: ok(aManagerWindow != null, "Should have an add-ons manager window"); michael@0: is(aManagerWindow.location, MANAGER_URI, "Should be displaying the correct UI"); michael@0: michael@0: waitForFocus(function() { michael@0: info("window has focus, waiting for manager load"); michael@0: wait_for_manager_load(aManagerWindow, function() { michael@0: info("Manager waiting for view load"); michael@0: wait_for_view_load(aManagerWindow, function() { michael@0: resolve(aManagerWindow); michael@0: }, null, aLongerTimeout); michael@0: }); michael@0: }, aManagerWindow); michael@0: } michael@0: michael@0: if (gUseInContentUI) { michael@0: info("Loading manager window in tab"); michael@0: Services.obs.addObserver(function (aSubject, aTopic, aData) { michael@0: Services.obs.removeObserver(arguments.callee, aTopic); michael@0: if (aSubject.location.href != MANAGER_URI) { michael@0: info("Ignoring load event for " + aSubject.location.href); michael@0: return; michael@0: } michael@0: setup_manager(aSubject); michael@0: }, "EM-loaded", false); michael@0: michael@0: gBrowser.selectedTab = gBrowser.addTab(); michael@0: switchToTabHavingURI(MANAGER_URI, true); michael@0: } else { michael@0: info("Loading manager window in dialog"); michael@0: Services.obs.addObserver(function (aSubject, aTopic, aData) { michael@0: Services.obs.removeObserver(arguments.callee, aTopic); michael@0: setup_manager(aSubject); michael@0: }, "EM-loaded", false); michael@0: michael@0: openDialog(MANAGER_URI); michael@0: } michael@0: }); michael@0: michael@0: // The promise resolves with the manager window, so it is passed to the callback michael@0: return log_callback(p, aCallback); michael@0: } michael@0: michael@0: function close_manager(aManagerWindow, aCallback, aLongerTimeout) { michael@0: let p = new Promise((resolve, reject) => { michael@0: requestLongerTimeout(aLongerTimeout ? aLongerTimeout : 2); michael@0: michael@0: ok(aManagerWindow != null, "Should have an add-ons manager window to close"); michael@0: is(aManagerWindow.location, MANAGER_URI, "Should be closing window with correct URI"); michael@0: michael@0: aManagerWindow.addEventListener("unload", function() { michael@0: try { michael@0: dump("Manager window unload handler"); michael@0: this.removeEventListener("unload", arguments.callee, false); michael@0: resolve(); michael@0: } catch(e) { michael@0: reject(e); michael@0: } michael@0: }, false); michael@0: }); michael@0: michael@0: info("Telling manager window to close"); michael@0: aManagerWindow.close(); michael@0: info("Manager window close() call returned"); michael@0: michael@0: return log_callback(p, aCallback); michael@0: } michael@0: michael@0: function restart_manager(aManagerWindow, aView, aCallback, aLoadCallback) { michael@0: if (!aManagerWindow) { michael@0: return open_manager(aView, aCallback, aLoadCallback); michael@0: } michael@0: michael@0: return close_manager(aManagerWindow) michael@0: .then(() => open_manager(aView, aCallback, aLoadCallback)); michael@0: } michael@0: michael@0: function wait_for_window_open(aCallback) { michael@0: Services.wm.addListener({ michael@0: onOpenWindow: function(aWindow) { michael@0: Services.wm.removeListener(this); michael@0: michael@0: let domwindow = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIDOMWindow); michael@0: domwindow.addEventListener("load", function() { michael@0: domwindow.removeEventListener("load", arguments.callee, false); michael@0: executeSoon(function() { michael@0: aCallback(domwindow); michael@0: }); michael@0: }, false); michael@0: }, michael@0: michael@0: onCloseWindow: function(aWindow) { michael@0: }, michael@0: michael@0: onWindowTitleChange: function(aWindow, aTitle) { michael@0: } michael@0: }); michael@0: } michael@0: michael@0: function get_string(aName, ...aArgs) { michael@0: var bundle = Services.strings.createBundle("chrome://mozapps/locale/extensions/extensions.properties"); michael@0: if (aArgs.length == 0) michael@0: return bundle.GetStringFromName(aName); michael@0: return bundle.formatStringFromName(aName, aArgs, aArgs.length); michael@0: } michael@0: michael@0: function formatDate(aDate) { michael@0: return Cc["@mozilla.org/intl/scriptabledateformat;1"] michael@0: .getService(Ci.nsIScriptableDateFormat) michael@0: .FormatDate("", michael@0: Ci.nsIScriptableDateFormat.dateFormatLong, michael@0: aDate.getFullYear(), michael@0: aDate.getMonth() + 1, michael@0: aDate.getDate() michael@0: ); michael@0: } michael@0: michael@0: function is_hidden(aElement) { michael@0: var style = aElement.ownerDocument.defaultView.getComputedStyle(aElement, ""); michael@0: if (style.display == "none") michael@0: return true; michael@0: if (style.visibility != "visible") michael@0: return true; michael@0: michael@0: // Hiding a parent element will hide all its children michael@0: if (aElement.parentNode != aElement.ownerDocument) michael@0: return is_hidden(aElement.parentNode); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: function is_element_visible(aElement, aMsg) { michael@0: isnot(aElement, null, "Element should not be null, when checking visibility"); michael@0: ok(!is_hidden(aElement), aMsg); michael@0: } michael@0: michael@0: function is_element_hidden(aElement, aMsg) { michael@0: isnot(aElement, null, "Element should not be null, when checking visibility"); michael@0: ok(is_hidden(aElement), aMsg); michael@0: } michael@0: michael@0: /** michael@0: * Install an add-on and call a callback when complete. michael@0: * michael@0: * The callback will receive the Addon for the installed add-on. michael@0: */ michael@0: function install_addon(path, cb, pathPrefix=TESTROOT) { michael@0: let p = new Promise((resolve, reject) => { michael@0: AddonManager.getInstallForURL(pathPrefix + path, (install) => { michael@0: install.addListener({ michael@0: onInstallEnded: () => resolve(install.addon), michael@0: }); michael@0: michael@0: install.install(); michael@0: }, "application/x-xpinstall"); michael@0: }); michael@0: michael@0: return log_callback(p, cb); michael@0: } michael@0: michael@0: function CategoryUtilities(aManagerWindow) { michael@0: this.window = aManagerWindow; michael@0: michael@0: var self = this; michael@0: this.window.addEventListener("unload", function() { michael@0: self.window.removeEventListener("unload", arguments.callee, false); michael@0: self.window = null; michael@0: }, false); michael@0: } michael@0: michael@0: CategoryUtilities.prototype = { michael@0: window: null, michael@0: michael@0: get selectedCategory() { michael@0: isnot(this.window, null, "Should not get selected category when manager window is not loaded"); michael@0: var selectedItem = this.window.document.getElementById("categories").selectedItem; michael@0: isnot(selectedItem, null, "A category should be selected"); michael@0: var view = this.window.gViewController.parseViewId(selectedItem.value); michael@0: return (view.type == "list") ? view.param : view.type; michael@0: }, michael@0: michael@0: get: function(aCategoryType, aAllowMissing) { michael@0: isnot(this.window, null, "Should not get category when manager window is not loaded"); michael@0: var categories = this.window.document.getElementById("categories"); michael@0: michael@0: var viewId = "addons://list/" + aCategoryType; michael@0: var items = categories.getElementsByAttribute("value", viewId); michael@0: if (items.length) michael@0: return items[0]; michael@0: michael@0: viewId = "addons://" + aCategoryType + "/"; michael@0: items = categories.getElementsByAttribute("value", viewId); michael@0: if (items.length) michael@0: return items[0]; michael@0: michael@0: if (!aAllowMissing) michael@0: ok(false, "Should have found a category with type " + aCategoryType); michael@0: return null; michael@0: }, michael@0: michael@0: getViewId: function(aCategoryType) { michael@0: isnot(this.window, null, "Should not get view id when manager window is not loaded"); michael@0: return this.get(aCategoryType).value; michael@0: }, michael@0: michael@0: isVisible: function(aCategory) { michael@0: isnot(this.window, null, "Should not check visible state when manager window is not loaded"); michael@0: if (aCategory.hasAttribute("disabled") && michael@0: aCategory.getAttribute("disabled") == "true") michael@0: return false; michael@0: michael@0: return !is_hidden(aCategory); michael@0: }, michael@0: michael@0: isTypeVisible: function(aCategoryType) { michael@0: return this.isVisible(this.get(aCategoryType)); michael@0: }, michael@0: michael@0: open: function(aCategory, aCallback) { michael@0: michael@0: isnot(this.window, null, "Should not open category when manager window is not loaded"); michael@0: ok(this.isVisible(aCategory), "Category should be visible if attempting to open it"); michael@0: michael@0: EventUtils.synthesizeMouse(aCategory, 2, 2, { }, this.window); michael@0: let p = new Promise((resolve, reject) => wait_for_view_load(this.window, resolve)); michael@0: michael@0: return log_callback(p, aCallback); michael@0: }, michael@0: michael@0: openType: function(aCategoryType, aCallback) { michael@0: return this.open(this.get(aCategoryType), aCallback); michael@0: } michael@0: } michael@0: michael@0: function CertOverrideListener(host, bits) { michael@0: this.host = host; michael@0: this.bits = bits; michael@0: } michael@0: michael@0: CertOverrideListener.prototype = { michael@0: host: null, michael@0: bits: null, michael@0: michael@0: getInterface: function (aIID) { michael@0: return this.QueryInterface(aIID); michael@0: }, michael@0: michael@0: QueryInterface: function(aIID) { michael@0: if (aIID.equals(Ci.nsIBadCertListener2) || michael@0: aIID.equals(Ci.nsIInterfaceRequestor) || michael@0: aIID.equals(Ci.nsISupports)) michael@0: return this; michael@0: michael@0: throw Components.Exception("No interface", Components.results.NS_ERROR_NO_INTERFACE); michael@0: }, michael@0: michael@0: notifyCertProblem: function (socketInfo, sslStatus, targetHost) { michael@0: var cert = sslStatus.QueryInterface(Components.interfaces.nsISSLStatus) michael@0: .serverCert; michael@0: var cos = Cc["@mozilla.org/security/certoverride;1"]. michael@0: getService(Ci.nsICertOverrideService); michael@0: cos.rememberValidityOverride(this.host, -1, cert, this.bits, false); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: // Add overrides for the bad certificates michael@0: function addCertOverride(host, bits) { michael@0: var req = new XMLHttpRequest(); michael@0: try { michael@0: req.open("GET", "https://" + host + "/", false); michael@0: req.channel.notificationCallbacks = new CertOverrideListener(host, bits); michael@0: req.send(null); michael@0: } michael@0: catch (e) { michael@0: // This request will fail since the SSL server is not trusted yet michael@0: } michael@0: } michael@0: michael@0: /***** Mock Provider *****/ michael@0: michael@0: function MockProvider(aUseAsyncCallbacks, aTypes) { michael@0: this.addons = []; michael@0: this.installs = []; michael@0: this.callbackTimers = []; michael@0: this.timerLocations = new Map(); michael@0: this.useAsyncCallbacks = (aUseAsyncCallbacks === undefined) ? true : aUseAsyncCallbacks; michael@0: this.types = (aTypes === undefined) ? [{ michael@0: id: "extension", michael@0: name: "Extensions", michael@0: uiPriority: 4000, michael@0: flags: AddonManager.TYPE_UI_VIEW_LIST michael@0: }] : aTypes; michael@0: michael@0: var self = this; michael@0: registerCleanupFunction(function() { michael@0: if (self.started) michael@0: self.unregister(); michael@0: }); michael@0: michael@0: this.register(); michael@0: } michael@0: michael@0: MockProvider.prototype = { michael@0: addons: null, michael@0: installs: null, michael@0: started: null, michael@0: apiDelay: 10, michael@0: callbackTimers: null, michael@0: timerLocations: null, michael@0: useAsyncCallbacks: null, michael@0: types: null, michael@0: michael@0: /***** Utility functions *****/ michael@0: michael@0: /** michael@0: * Register this provider with the AddonManager michael@0: */ michael@0: register: function MP_register() { michael@0: AddonManagerPrivate.registerProvider(this, this.types); michael@0: }, michael@0: michael@0: /** michael@0: * Unregister this provider with the AddonManager michael@0: */ michael@0: unregister: function MP_unregister() { michael@0: AddonManagerPrivate.unregisterProvider(this); michael@0: }, michael@0: michael@0: /** michael@0: * Adds an add-on to the list of add-ons that this provider exposes to the michael@0: * AddonManager, dispatching appropriate events in the process. michael@0: * michael@0: * @param aAddon michael@0: * The add-on to add michael@0: */ michael@0: addAddon: function MP_addAddon(aAddon) { michael@0: var oldAddons = this.addons.filter(function(aOldAddon) aOldAddon.id == aAddon.id); michael@0: var oldAddon = oldAddons.length > 0 ? oldAddons[0] : null; michael@0: michael@0: this.addons = this.addons.filter(function(aOldAddon) aOldAddon.id != aAddon.id); michael@0: michael@0: this.addons.push(aAddon); michael@0: aAddon._provider = this; michael@0: michael@0: if (!this.started) michael@0: return; michael@0: michael@0: let requiresRestart = (aAddon.operationsRequiringRestart & michael@0: AddonManager.OP_NEEDS_RESTART_INSTALL) != 0; michael@0: AddonManagerPrivate.callInstallListeners("onExternalInstall", null, aAddon, michael@0: oldAddon, requiresRestart) michael@0: }, michael@0: michael@0: /** michael@0: * Removes an add-on from the list of add-ons that this provider exposes to michael@0: * the AddonManager, dispatching the onUninstalled event in the process. michael@0: * michael@0: * @param aAddon michael@0: * The add-on to add michael@0: */ michael@0: removeAddon: function MP_removeAddon(aAddon) { michael@0: var pos = this.addons.indexOf(aAddon); michael@0: if (pos == -1) { michael@0: ok(false, "Tried to remove an add-on that wasn't registered with the mock provider"); michael@0: return; michael@0: } michael@0: michael@0: this.addons.splice(pos, 1); michael@0: michael@0: if (!this.started) michael@0: return; michael@0: michael@0: AddonManagerPrivate.callAddonListeners("onUninstalled", aAddon); michael@0: }, michael@0: michael@0: /** michael@0: * Adds an add-on install to the list of installs that this provider exposes michael@0: * to the AddonManager, dispatching appropriate events in the process. michael@0: * michael@0: * @param aInstall michael@0: * The add-on install to add michael@0: */ michael@0: addInstall: function MP_addInstall(aInstall) { michael@0: this.installs.push(aInstall); michael@0: aInstall._provider = this; michael@0: michael@0: if (!this.started) michael@0: return; michael@0: michael@0: aInstall.callListeners("onNewInstall"); michael@0: }, michael@0: michael@0: removeInstall: function MP_removeInstall(aInstall) { michael@0: var pos = this.installs.indexOf(aInstall); michael@0: if (pos == -1) { michael@0: ok(false, "Tried to remove an install that wasn't registered with the mock provider"); michael@0: return; michael@0: } michael@0: michael@0: this.installs.splice(pos, 1); michael@0: }, michael@0: michael@0: /** michael@0: * Creates a set of mock add-on objects and adds them to the list of add-ons michael@0: * managed by this provider. michael@0: * michael@0: * @param aAddonProperties michael@0: * An array of objects containing properties describing the add-ons michael@0: * @return Array of the new MockAddons michael@0: */ michael@0: createAddons: function MP_createAddons(aAddonProperties) { michael@0: var newAddons = []; michael@0: for (let addonProp of aAddonProperties) { michael@0: let addon = new MockAddon(addonProp.id); michael@0: for (let prop in addonProp) { michael@0: if (prop == "id") michael@0: continue; michael@0: if (prop == "applyBackgroundUpdates") { michael@0: addon._applyBackgroundUpdates = addonProp[prop]; michael@0: continue; michael@0: } michael@0: if (prop == "appDisabled") { michael@0: addon._appDisabled = addonProp[prop]; michael@0: continue; michael@0: } michael@0: addon[prop] = addonProp[prop]; michael@0: } michael@0: if (!addon.optionsType && !!addon.optionsURL) michael@0: addon.optionsType = AddonManager.OPTIONS_TYPE_DIALOG; michael@0: michael@0: // Make sure the active state matches the passed in properties michael@0: addon.isActive = addon.shouldBeActive; michael@0: michael@0: this.addAddon(addon); michael@0: newAddons.push(addon); michael@0: } michael@0: michael@0: return newAddons; michael@0: }, michael@0: michael@0: /** michael@0: * Creates a set of mock add-on install objects and adds them to the list michael@0: * of installs managed by this provider. michael@0: * michael@0: * @param aInstallProperties michael@0: * An array of objects containing properties describing the installs michael@0: * @return Array of the new MockInstalls michael@0: */ michael@0: createInstalls: function MP_createInstalls(aInstallProperties) { michael@0: var newInstalls = []; michael@0: for (let installProp of aInstallProperties) { michael@0: let install = new MockInstall(installProp.name || null, michael@0: installProp.type || null, michael@0: null); michael@0: for (let prop in installProp) { michael@0: switch (prop) { michael@0: case "name": michael@0: case "type": michael@0: break; michael@0: case "sourceURI": michael@0: install[prop] = NetUtil.newURI(installProp[prop]); michael@0: break; michael@0: default: michael@0: install[prop] = installProp[prop]; michael@0: } michael@0: } michael@0: this.addInstall(install); michael@0: newInstalls.push(install); michael@0: } michael@0: michael@0: return newInstalls; michael@0: }, michael@0: michael@0: /***** AddonProvider implementation *****/ michael@0: michael@0: /** michael@0: * Called to initialize the provider. michael@0: */ michael@0: startup: function MP_startup() { michael@0: this.started = true; michael@0: }, michael@0: michael@0: /** michael@0: * Called when the provider should shutdown. michael@0: */ michael@0: shutdown: function MP_shutdown() { michael@0: if (this.callbackTimers.length) { michael@0: info("MockProvider: pending callbacks at shutdown(): calling immediately"); michael@0: } michael@0: while (this.callbackTimers.length > 0) { michael@0: // When we notify the callback timer, it removes itself from our array michael@0: let timer = this.callbackTimers[0]; michael@0: try { michael@0: let setAt = this.timerLocations.get(timer); michael@0: info("Notifying timer set at " + (setAt || "unknown location")); michael@0: timer.callback.notify(timer); michael@0: timer.cancel(); michael@0: } catch(e) { michael@0: info("Timer notify failed: " + e); michael@0: } michael@0: } michael@0: this.callbackTimers = []; michael@0: this.timerLocations = null; michael@0: michael@0: this.started = false; michael@0: }, michael@0: michael@0: /** michael@0: * Called to get an Addon with a particular ID. michael@0: * michael@0: * @param aId michael@0: * The ID of the add-on to retrieve michael@0: * @param aCallback michael@0: * A callback to pass the Addon to michael@0: */ michael@0: getAddonByID: function MP_getAddon(aId, aCallback) { michael@0: for (let addon of this.addons) { michael@0: if (addon.id == aId) { michael@0: this._delayCallback(aCallback, addon); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: aCallback(null); michael@0: }, michael@0: michael@0: /** michael@0: * Called to get Addons of a particular type. michael@0: * michael@0: * @param aTypes michael@0: * An array of types to fetch. Can be null to get all types. michael@0: * @param callback michael@0: * A callback to pass an array of Addons to michael@0: */ michael@0: getAddonsByTypes: function MP_getAddonsByTypes(aTypes, aCallback) { michael@0: var addons = this.addons.filter(function(aAddon) { michael@0: if (aTypes && aTypes.length > 0 && aTypes.indexOf(aAddon.type) == -1) michael@0: return false; michael@0: return true; michael@0: }); michael@0: this._delayCallback(aCallback, addons); michael@0: }, michael@0: michael@0: /** michael@0: * Called to get Addons that have pending operations. michael@0: * michael@0: * @param aTypes michael@0: * An array of types to fetch. Can be null to get all types michael@0: * @param aCallback michael@0: * A callback to pass an array of Addons to michael@0: */ michael@0: getAddonsWithOperationsByTypes: function MP_getAddonsWithOperationsByTypes(aTypes, aCallback) { michael@0: var addons = this.addons.filter(function(aAddon) { michael@0: if (aTypes && aTypes.length > 0 && aTypes.indexOf(aAddon.type) == -1) michael@0: return false; michael@0: return aAddon.pendingOperations != 0; michael@0: }); michael@0: this._delayCallback(aCallback, addons); michael@0: }, michael@0: michael@0: /** michael@0: * Called to get the current AddonInstalls, optionally restricting by type. michael@0: * michael@0: * @param aTypes michael@0: * An array of types or null to get all types michael@0: * @param aCallback michael@0: * A callback to pass the array of AddonInstalls to michael@0: */ michael@0: getInstallsByTypes: function MP_getInstallsByTypes(aTypes, aCallback) { michael@0: var installs = this.installs.filter(function(aInstall) { michael@0: // Appear to have actually removed cancelled installs from the provider michael@0: if (aInstall.state == AddonManager.STATE_CANCELLED) michael@0: return false; michael@0: michael@0: if (aTypes && aTypes.length > 0 && aTypes.indexOf(aInstall.type) == -1) michael@0: return false; michael@0: michael@0: return true; michael@0: }); michael@0: this._delayCallback(aCallback, installs); michael@0: }, michael@0: michael@0: /** michael@0: * Called when a new add-on has been enabled when only one add-on of that type michael@0: * can be enabled. michael@0: * michael@0: * @param aId michael@0: * The ID of the newly enabled add-on michael@0: * @param aType michael@0: * The type of the newly enabled add-on michael@0: * @param aPendingRestart michael@0: * true if the newly enabled add-on will only become enabled after a michael@0: * restart michael@0: */ michael@0: addonChanged: function MP_addonChanged(aId, aType, aPendingRestart) { michael@0: // Not implemented michael@0: }, michael@0: michael@0: /** michael@0: * Update the appDisabled property for all add-ons. michael@0: */ michael@0: updateAddonAppDisabledStates: function MP_updateAddonAppDisabledStates() { michael@0: // Not needed michael@0: }, michael@0: michael@0: /** michael@0: * Called to get an AddonInstall to download and install an add-on from a URL. michael@0: * michael@0: * @param aUrl michael@0: * The URL to be installed michael@0: * @param aHash michael@0: * A hash for the install michael@0: * @param aName michael@0: * A name for the install michael@0: * @param aIconURL michael@0: * An icon URL for the install michael@0: * @param aVersion michael@0: * A version for the install michael@0: * @param aLoadGroup michael@0: * An nsILoadGroup to associate requests with michael@0: * @param aCallback michael@0: * A callback to pass the AddonInstall to michael@0: */ michael@0: getInstallForURL: function MP_getInstallForURL(aUrl, aHash, aName, aIconURL, michael@0: aVersion, aLoadGroup, aCallback) { michael@0: // Not yet implemented michael@0: }, michael@0: michael@0: /** michael@0: * Called to get an AddonInstall to install an add-on from a local file. michael@0: * michael@0: * @param aFile michael@0: * The file to be installed michael@0: * @param aCallback michael@0: * A callback to pass the AddonInstall to michael@0: */ michael@0: getInstallForFile: function MP_getInstallForFile(aFile, aCallback) { michael@0: // Not yet implemented michael@0: }, michael@0: michael@0: /** michael@0: * Called to test whether installing add-ons is enabled. michael@0: * michael@0: * @return true if installing is enabled michael@0: */ michael@0: isInstallEnabled: function MP_isInstallEnabled() { michael@0: return false; michael@0: }, michael@0: michael@0: /** michael@0: * Called to test whether this provider supports installing a particular michael@0: * mimetype. michael@0: * michael@0: * @param aMimetype michael@0: * The mimetype to check for michael@0: * @return true if the mimetype is supported michael@0: */ michael@0: supportsMimetype: function MP_supportsMimetype(aMimetype) { michael@0: return false; michael@0: }, michael@0: michael@0: /** michael@0: * Called to test whether installing add-ons from a URI is allowed. michael@0: * michael@0: * @param aUri michael@0: * The URI being installed from michael@0: * @return true if installing is allowed michael@0: */ michael@0: isInstallAllowed: function MP_isInstallAllowed(aUri) { michael@0: return false; michael@0: }, michael@0: michael@0: michael@0: /***** Internal functions *****/ michael@0: michael@0: /** michael@0: * Delay calling a callback to fake a time-consuming async operation. michael@0: * The delay is specified by the apiDelay property, in milliseconds. michael@0: * Parameters to send to the callback should be specified as arguments after michael@0: * the aCallback argument. michael@0: * michael@0: * @param aCallback Callback to eventually call michael@0: */ michael@0: _delayCallback: function MP_delayCallback(aCallback, ...aArgs) { michael@0: if (!this.useAsyncCallbacks) { michael@0: aCallback(...aArgs); michael@0: return; michael@0: } michael@0: michael@0: let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); michael@0: // Need to keep a reference to the timer, so it doesn't get GC'ed michael@0: this.callbackTimers.push(timer); michael@0: // Capture a stack trace where the timer was set michael@0: // needs the 'new Error' hack until bug 1007656 michael@0: this.timerLocations.set(timer, Log.stackTrace(new Error("dummy"))); michael@0: timer.initWithCallback(() => { michael@0: let idx = this.callbackTimers.indexOf(timer); michael@0: if (idx == -1) { michael@0: dump("MockProvider._delayCallback lost track of timer set at " michael@0: + (this.timerLocations.get(timer) || "unknown location") + "\n"); michael@0: } else { michael@0: this.callbackTimers.splice(idx, 1); michael@0: } michael@0: this.timerLocations.delete(timer); michael@0: aCallback(...aArgs); michael@0: }, this.apiDelay, timer.TYPE_ONE_SHOT); michael@0: } michael@0: }; michael@0: michael@0: /***** Mock Addon object for the Mock Provider *****/ michael@0: michael@0: function MockAddon(aId, aName, aType, aOperationsRequiringRestart) { michael@0: // Only set required attributes. michael@0: this.id = aId || ""; michael@0: this.name = aName || ""; michael@0: this.type = aType || "extension"; michael@0: this.version = ""; michael@0: this.isCompatible = true; michael@0: this.isDebuggable = false; michael@0: this.providesUpdatesSecurely = true; michael@0: this.blocklistState = 0; michael@0: this._appDisabled = false; michael@0: this._userDisabled = false; michael@0: this._applyBackgroundUpdates = AddonManager.AUTOUPDATE_ENABLE; michael@0: this.scope = AddonManager.SCOPE_PROFILE; michael@0: this.isActive = true; michael@0: this.creator = ""; michael@0: this.pendingOperations = 0; michael@0: this._permissions = AddonManager.PERM_CAN_UNINSTALL | michael@0: AddonManager.PERM_CAN_ENABLE | michael@0: AddonManager.PERM_CAN_DISABLE | michael@0: AddonManager.PERM_CAN_UPGRADE; michael@0: this.operationsRequiringRestart = aOperationsRequiringRestart || michael@0: (AddonManager.OP_NEEDS_RESTART_INSTALL | michael@0: AddonManager.OP_NEEDS_RESTART_UNINSTALL | michael@0: AddonManager.OP_NEEDS_RESTART_ENABLE | michael@0: AddonManager.OP_NEEDS_RESTART_DISABLE); michael@0: } michael@0: michael@0: MockAddon.prototype = { michael@0: get shouldBeActive() { michael@0: return !this.appDisabled && !this._userDisabled; michael@0: }, michael@0: michael@0: get appDisabled() { michael@0: return this._appDisabled; michael@0: }, michael@0: michael@0: set appDisabled(val) { michael@0: if (val == this._appDisabled) michael@0: return val; michael@0: michael@0: AddonManagerPrivate.callAddonListeners("onPropertyChanged", this, ["appDisabled"]); michael@0: michael@0: var currentActive = this.shouldBeActive; michael@0: this._appDisabled = val; michael@0: var newActive = this.shouldBeActive; michael@0: this._updateActiveState(currentActive, newActive); michael@0: michael@0: return val; michael@0: }, michael@0: michael@0: get userDisabled() { michael@0: return this._userDisabled; michael@0: }, michael@0: michael@0: set userDisabled(val) { michael@0: if (val == this._userDisabled) michael@0: return val; michael@0: michael@0: var currentActive = this.shouldBeActive; michael@0: this._userDisabled = val; michael@0: var newActive = this.shouldBeActive; michael@0: this._updateActiveState(currentActive, newActive); michael@0: michael@0: return val; michael@0: }, michael@0: michael@0: get permissions() { michael@0: let permissions = this._permissions; michael@0: if (this.appDisabled || !this._userDisabled) michael@0: permissions &= ~AddonManager.PERM_CAN_ENABLE; michael@0: if (this.appDisabled || this._userDisabled) michael@0: permissions &= ~AddonManager.PERM_CAN_DISABLE; michael@0: return permissions; michael@0: }, michael@0: michael@0: set permissions(val) { michael@0: return this._permissions = val; michael@0: }, michael@0: michael@0: get applyBackgroundUpdates() { michael@0: return this._applyBackgroundUpdates; michael@0: }, michael@0: michael@0: set applyBackgroundUpdates(val) { michael@0: if (val != AddonManager.AUTOUPDATE_DEFAULT && michael@0: val != AddonManager.AUTOUPDATE_DISABLE && michael@0: val != AddonManager.AUTOUPDATE_ENABLE) { michael@0: ok(false, "addon.applyBackgroundUpdates set to an invalid value: " + val); michael@0: } michael@0: this._applyBackgroundUpdates = val; michael@0: AddonManagerPrivate.callAddonListeners("onPropertyChanged", this, ["applyBackgroundUpdates"]); michael@0: }, michael@0: michael@0: isCompatibleWith: function(aAppVersion, aPlatformVersion) { michael@0: return true; michael@0: }, michael@0: michael@0: findUpdates: function(aListener, aReason, aAppVersion, aPlatformVersion) { michael@0: // Tests can implement this if they need to michael@0: }, michael@0: michael@0: uninstall: function() { michael@0: if (this.pendingOperations & AddonManager.PENDING_UNINSTALL) michael@0: throw Components.Exception("Add-on is already pending uninstall"); michael@0: michael@0: var needsRestart = !!(this.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_UNINSTALL); michael@0: this.pendingOperations |= AddonManager.PENDING_UNINSTALL; michael@0: AddonManagerPrivate.callAddonListeners("onUninstalling", this, needsRestart); michael@0: if (!needsRestart) { michael@0: this.pendingOperations -= AddonManager.PENDING_UNINSTALL; michael@0: this._provider.removeAddon(this); michael@0: } michael@0: }, michael@0: michael@0: cancelUninstall: function() { michael@0: if (!(this.pendingOperations & AddonManager.PENDING_UNINSTALL)) michael@0: throw Components.Exception("Add-on is not pending uninstall"); michael@0: michael@0: this.pendingOperations -= AddonManager.PENDING_UNINSTALL; michael@0: AddonManagerPrivate.callAddonListeners("onOperationCancelled", this); michael@0: }, michael@0: michael@0: _updateActiveState: function(currentActive, newActive) { michael@0: if (currentActive == newActive) michael@0: return; michael@0: michael@0: if (newActive == this.isActive) { michael@0: this.pendingOperations -= (newActive ? AddonManager.PENDING_DISABLE : AddonManager.PENDING_ENABLE); michael@0: AddonManagerPrivate.callAddonListeners("onOperationCancelled", this); michael@0: } michael@0: else if (newActive) { michael@0: var needsRestart = !!(this.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_ENABLE); michael@0: this.pendingOperations |= AddonManager.PENDING_ENABLE; michael@0: AddonManagerPrivate.callAddonListeners("onEnabling", this, needsRestart); michael@0: if (!needsRestart) { michael@0: this.isActive = newActive; michael@0: this.pendingOperations -= AddonManager.PENDING_ENABLE; michael@0: AddonManagerPrivate.callAddonListeners("onEnabled", this); michael@0: } michael@0: } michael@0: else { michael@0: var needsRestart = !!(this.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_DISABLE); michael@0: this.pendingOperations |= AddonManager.PENDING_DISABLE; michael@0: AddonManagerPrivate.callAddonListeners("onDisabling", this, needsRestart); michael@0: if (!needsRestart) { michael@0: this.isActive = newActive; michael@0: this.pendingOperations -= AddonManager.PENDING_DISABLE; michael@0: AddonManagerPrivate.callAddonListeners("onDisabled", this); michael@0: } michael@0: } michael@0: } michael@0: }; michael@0: michael@0: /***** Mock AddonInstall object for the Mock Provider *****/ michael@0: michael@0: function MockInstall(aName, aType, aAddonToInstall) { michael@0: this.name = aName || ""; michael@0: // Don't expose type until download completed michael@0: this._type = aType || "extension"; michael@0: this.type = null; michael@0: this.version = "1.0"; michael@0: this.iconURL = ""; michael@0: this.infoURL = ""; michael@0: this.state = AddonManager.STATE_AVAILABLE; michael@0: this.error = 0; michael@0: this.sourceURI = null; michael@0: this.file = null; michael@0: this.progress = 0; michael@0: this.maxProgress = -1; michael@0: this.certificate = null; michael@0: this.certName = ""; michael@0: this.existingAddon = null; michael@0: this.addon = null; michael@0: this._addonToInstall = aAddonToInstall; michael@0: this.listeners = []; michael@0: michael@0: // Another type of install listener for tests that want to check the results michael@0: // of code run from standard install listeners michael@0: this.testListeners = []; michael@0: } michael@0: michael@0: MockInstall.prototype = { michael@0: install: function() { michael@0: switch (this.state) { michael@0: case AddonManager.STATE_AVAILABLE: michael@0: this.state = AddonManager.STATE_DOWNLOADING; michael@0: if (!this.callListeners("onDownloadStarted")) { michael@0: this.state = AddonManager.STATE_CANCELLED; michael@0: this.callListeners("onDownloadCancelled"); michael@0: return; michael@0: } michael@0: michael@0: this.type = this._type; michael@0: michael@0: // Adding addon to MockProvider to be implemented when needed michael@0: if (this._addonToInstall) michael@0: this.addon = this._addonToInstall; michael@0: else { michael@0: this.addon = new MockAddon("", this.name, this.type); michael@0: this.addon.version = this.version; michael@0: this.addon.pendingOperations = AddonManager.PENDING_INSTALL; michael@0: } michael@0: this.addon.install = this; michael@0: if (this.existingAddon) { michael@0: if (!this.addon.id) michael@0: this.addon.id = this.existingAddon.id; michael@0: this.existingAddon.pendingUpgrade = this.addon; michael@0: this.existingAddon.pendingOperations |= AddonManager.PENDING_UPGRADE; michael@0: } michael@0: michael@0: this.state = AddonManager.STATE_DOWNLOADED; michael@0: this.callListeners("onDownloadEnded"); michael@0: michael@0: case AddonManager.STATE_DOWNLOADED: michael@0: this.state = AddonManager.STATE_INSTALLING; michael@0: if (!this.callListeners("onInstallStarted")) { michael@0: this.state = AddonManager.STATE_CANCELLED; michael@0: this.callListeners("onInstallCancelled"); michael@0: return; michael@0: } michael@0: michael@0: AddonManagerPrivate.callAddonListeners("onInstalling", this.addon); michael@0: michael@0: this.state = AddonManager.STATE_INSTALLED; michael@0: this.callListeners("onInstallEnded"); michael@0: break; michael@0: case AddonManager.STATE_DOWNLOADING: michael@0: case AddonManager.STATE_CHECKING: michael@0: case AddonManger.STATE_INSTALLING: michael@0: // Installation is already running michael@0: return; michael@0: default: michael@0: ok(false, "Cannot start installing when state = " + this.state); michael@0: } michael@0: }, michael@0: michael@0: cancel: function() { michael@0: switch (this.state) { michael@0: case AddonManager.STATE_AVAILABLE: michael@0: this.state = AddonManager.STATE_CANCELLED; michael@0: break; michael@0: case AddonManager.STATE_INSTALLED: michael@0: this.state = AddonManager.STATE_CANCELLED; michael@0: this._provider.removeInstall(this); michael@0: this.callListeners("onInstallCancelled"); michael@0: break; michael@0: default: michael@0: // Handling cancelling when downloading to be implemented when needed michael@0: ok(false, "Cannot cancel when state = " + this.state); michael@0: } michael@0: }, michael@0: michael@0: michael@0: addListener: function(aListener) { michael@0: if (!this.listeners.some(function(i) i == aListener)) michael@0: this.listeners.push(aListener); michael@0: }, michael@0: michael@0: removeListener: function(aListener) { michael@0: this.listeners = this.listeners.filter(function(i) i != aListener); michael@0: }, michael@0: michael@0: addTestListener: function(aListener) { michael@0: if (!this.testListeners.some(function(i) i == aListener)) michael@0: this.testListeners.push(aListener); michael@0: }, michael@0: michael@0: removeTestListener: function(aListener) { michael@0: this.testListeners = this.testListeners.filter(function(i) i != aListener); michael@0: }, michael@0: michael@0: callListeners: function(aMethod) { michael@0: var result = AddonManagerPrivate.callInstallListeners(aMethod, this.listeners, michael@0: this, this.addon); michael@0: michael@0: // Call test listeners after standard listeners to remove race condition michael@0: // between standard and test listeners michael@0: for (let listener of this.testListeners) { michael@0: try { michael@0: if (aMethod in listener) michael@0: if (listener[aMethod].call(listener, this, this.addon) === false) michael@0: result = false; michael@0: } michael@0: catch (e) { michael@0: ok(false, "Test listener threw exception: " + e); michael@0: } michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: }; michael@0: michael@0: function waitForCondition(condition, nextTest, errorMsg) { michael@0: let tries = 0; michael@0: let interval = setInterval(function() { michael@0: if (tries >= 30) { michael@0: ok(false, errorMsg); michael@0: moveOn(); michael@0: } michael@0: var conditionPassed; michael@0: try { michael@0: conditionPassed = condition(); michael@0: } catch (e) { michael@0: ok(false, e + "\n" + e.stack); michael@0: conditionPassed = false; michael@0: } michael@0: if (conditionPassed) { michael@0: moveOn(); michael@0: } michael@0: tries++; michael@0: }, 100); michael@0: let moveOn = function() { clearInterval(interval); nextTest(); }; michael@0: } michael@0: michael@0: function getTestPluginTag() { michael@0: let ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); michael@0: let tags = ph.getPluginTags(); michael@0: michael@0: // Find the test plugin michael@0: for (let i = 0; i < tags.length; i++) { michael@0: if (tags[i].name == "Test Plug-in") michael@0: return tags[i]; michael@0: } michael@0: ok(false, "Unable to find plugin"); michael@0: return null; michael@0: }