michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: const { Cc, Ci, Cu } = require("chrome"); michael@0: const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {}); michael@0: const gcli = require("gcli/index"); michael@0: const { Promise: promise } = require("resource://gre/modules/Promise.jsm"); michael@0: michael@0: const BRAND_SHORT_NAME = Cc["@mozilla.org/intl/stringbundle;1"] michael@0: .getService(Ci.nsIStringBundleService) michael@0: .createBundle("chrome://branding/locale/brand.properties") michael@0: .GetStringFromName("brandShortName"); michael@0: michael@0: /** michael@0: * Takes a function that uses a callback as its last parameter, and returns a michael@0: * new function that returns a promise instead. michael@0: * This should probably live in async-util michael@0: */ michael@0: const promiseify = function(scope, functionWithLastParamCallback) { michael@0: return (...args) => { michael@0: return new Promise(resolve => { michael@0: args.push((...results) => { michael@0: resolve(results.length > 1 ? results : results[0]); michael@0: }); michael@0: functionWithLastParamCallback.apply(scope, args); michael@0: }); michael@0: } michael@0: }; michael@0: michael@0: // Convert callback based functions to promise based ones michael@0: const getAllAddons = promiseify(AddonManager, AddonManager.getAllAddons); michael@0: const getAddonsByTypes = promiseify(AddonManager, AddonManager.getAddonsByTypes); michael@0: michael@0: /** michael@0: * Return a string array containing the pending operations on an addon michael@0: */ michael@0: function pendingOperations(addon) { michael@0: let allOperations = [ michael@0: "PENDING_ENABLE", "PENDING_DISABLE", "PENDING_UNINSTALL", michael@0: "PENDING_INSTALL", "PENDING_UPGRADE" michael@0: ]; michael@0: return allOperations.reduce(function(operations, opName) { michael@0: return addon.pendingOperations & AddonManager[opName] ? michael@0: operations.concat(opName) : michael@0: operations; michael@0: }, []); michael@0: } michael@0: michael@0: exports.items = [ michael@0: { michael@0: item: "type", michael@0: name: "addon", michael@0: parent: "selection", michael@0: stringifyProperty: "name", michael@0: cacheable: true, michael@0: constructor: function() { michael@0: // Tell GCLI to clear the cache of addons when one is added or removed michael@0: let listener = { michael@0: onInstalled: addon => { this.clearCache(); }, michael@0: onUninstalled: addon => { this.clearCache(); }, michael@0: }; michael@0: AddonManager.addAddonListener(listener); michael@0: }, michael@0: lookup: function() { michael@0: return getAllAddons().then(addons => { michael@0: return addons.map(addon => { michael@0: let name = addon.name + " " + addon.version; michael@0: name = name.trim().replace(/\s/g, "_"); michael@0: return { name: name, value: addon }; michael@0: }); michael@0: }); michael@0: } michael@0: }, michael@0: { michael@0: name: "addon", michael@0: description: gcli.lookup("addonDesc") michael@0: }, michael@0: { michael@0: name: "addon list", michael@0: description: gcli.lookup("addonListDesc"), michael@0: returnType: "addonsInfo", michael@0: params: [{ michael@0: name: "type", michael@0: type: { michael@0: name: "selection", michael@0: data: [ "dictionary", "extension", "locale", "plugin", "theme", "all" ] michael@0: }, michael@0: defaultValue: "all", michael@0: description: gcli.lookup("addonListTypeDesc") michael@0: }], michael@0: exec: function(args, context) { michael@0: let types = (args.type === "all") ? null : [ args.type ]; michael@0: return getAddonsByTypes(types).then(addons => { michael@0: addons = addons.map(function(addon) { michael@0: return { michael@0: name: addon.name, michael@0: version: addon.version, michael@0: isActive: addon.isActive, michael@0: pendingOperations: pendingOperations(addon) michael@0: }; michael@0: }); michael@0: return { addons: addons, type: args.type }; michael@0: }); michael@0: } michael@0: }, michael@0: { michael@0: item: "converter", michael@0: from: "addonsInfo", michael@0: to: "view", michael@0: exec: function(addonsInfo, context) { michael@0: if (!addonsInfo.addons.length) { michael@0: return context.createView({ michael@0: html: "

${message}

", michael@0: data: { message: gcli.lookup("addonNoneOfType") } michael@0: }); michael@0: } michael@0: michael@0: let headerLookups = { michael@0: "dictionary": "addonListDictionaryHeading", michael@0: "extension": "addonListExtensionHeading", michael@0: "locale": "addonListLocaleHeading", michael@0: "plugin": "addonListPluginHeading", michael@0: "theme": "addonListThemeHeading", michael@0: "all": "addonListAllHeading" michael@0: }; michael@0: let header = gcli.lookup(headerLookups[addonsInfo.type] || michael@0: "addonListUnknownHeading"); michael@0: michael@0: let operationLookups = { michael@0: "PENDING_ENABLE": "addonPendingEnable", michael@0: "PENDING_DISABLE": "addonPendingDisable", michael@0: "PENDING_UNINSTALL": "addonPendingUninstall", michael@0: "PENDING_INSTALL": "addonPendingInstall", michael@0: "PENDING_UPGRADE": "addonPendingUpgrade" michael@0: }; michael@0: function lookupOperation(opName) { michael@0: let lookupName = operationLookups[opName]; michael@0: return lookupName ? gcli.lookup(lookupName) : opName; michael@0: } michael@0: michael@0: function arrangeAddons(addons) { michael@0: let enabledAddons = []; michael@0: let disabledAddons = []; michael@0: addons.forEach(function(addon) { michael@0: if (addon.isActive) { michael@0: enabledAddons.push(addon); michael@0: } else { michael@0: disabledAddons.push(addon); michael@0: } michael@0: }); michael@0: michael@0: function compareAddonNames(nameA, nameB) { michael@0: return String.localeCompare(nameA.name, nameB.name); michael@0: } michael@0: enabledAddons.sort(compareAddonNames); michael@0: disabledAddons.sort(compareAddonNames); michael@0: michael@0: return enabledAddons.concat(disabledAddons); michael@0: } michael@0: michael@0: function isActiveForToggle(addon) { michael@0: return (addon.isActive && ~~addon.pendingOperations.indexOf("PENDING_DISABLE")); michael@0: } michael@0: michael@0: return context.createView({ michael@0: html: michael@0: "" + michael@0: " " + michael@0: " " + michael@0: " " + michael@0: " " + michael@0: " " + michael@0: " " + michael@0: " " + michael@0: " " + michael@0: "
${header}
${addon.name} ${addon.version}${addon.pendingOperations}" + michael@0: " ${addon.toggleActionMessage}" + michael@0: "
", michael@0: data: { michael@0: header: header, michael@0: addons: arrangeAddons(addonsInfo.addons).map(function(addon) { michael@0: return { michael@0: name: addon.name, michael@0: label: addon.name.replace(/\s/g, "_") + michael@0: (addon.version ? "_" + addon.version : ""), michael@0: status: addon.isActive ? "enabled" : "disabled", michael@0: version: addon.version, michael@0: pendingOperations: addon.pendingOperations.length ? michael@0: (" (" + gcli.lookup("addonPending") + ": " michael@0: + addon.pendingOperations.map(lookupOperation).join(", ") michael@0: + ")") : michael@0: "", michael@0: toggleActionName: isActiveForToggle(addon) ? "disable": "enable", michael@0: toggleActionMessage: isActiveForToggle(addon) ? michael@0: gcli.lookup("addonListOutDisable") : michael@0: gcli.lookup("addonListOutEnable") michael@0: }; michael@0: }), michael@0: onclick: context.update, michael@0: ondblclick: context.updateExec michael@0: } michael@0: }); michael@0: } michael@0: }, michael@0: { michael@0: name: "addon enable", michael@0: description: gcli.lookup("addonEnableDesc"), michael@0: params: [ michael@0: { michael@0: name: "addon", michael@0: type: "addon", michael@0: description: gcli.lookup("addonNameDesc") michael@0: } michael@0: ], michael@0: exec: function(args, context) { michael@0: let name = (args.addon.name + " " + args.addon.version).trim(); michael@0: if (args.addon.userDisabled) { michael@0: args.addon.userDisabled = false; michael@0: return gcli.lookupFormat("addonEnabled", [ name ]); michael@0: } michael@0: michael@0: return gcli.lookupFormat("addonAlreadyEnabled", [ name ]); michael@0: } michael@0: }, michael@0: { michael@0: name: "addon disable", michael@0: description: gcli.lookup("addonDisableDesc"), michael@0: params: [ michael@0: { michael@0: name: "addon", michael@0: type: "addon", michael@0: description: gcli.lookup("addonNameDesc") michael@0: } michael@0: ], michael@0: exec: function(args, context) { michael@0: // If the addon is not disabled or is set to "click to play" then michael@0: // disable it. Otherwise display the message "Add-on is already michael@0: // disabled." michael@0: let name = (args.addon.name + " " + args.addon.version).trim(); michael@0: if (!args.addon.userDisabled || michael@0: args.addon.userDisabled === AddonManager.STATE_ASK_TO_ACTIVATE) { michael@0: args.addon.userDisabled = true; michael@0: return gcli.lookupFormat("addonDisabled", [ name ]); michael@0: } michael@0: michael@0: return gcli.lookupFormat("addonAlreadyDisabled", [ name ]); michael@0: } michael@0: } michael@0: ];