1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/devtools/gcli/commands/addon.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,256 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +const { Cc, Ci, Cu } = require("chrome"); 1.11 +const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {}); 1.12 +const gcli = require("gcli/index"); 1.13 +const { Promise: promise } = require("resource://gre/modules/Promise.jsm"); 1.14 + 1.15 +const BRAND_SHORT_NAME = Cc["@mozilla.org/intl/stringbundle;1"] 1.16 + .getService(Ci.nsIStringBundleService) 1.17 + .createBundle("chrome://branding/locale/brand.properties") 1.18 + .GetStringFromName("brandShortName"); 1.19 + 1.20 +/** 1.21 + * Takes a function that uses a callback as its last parameter, and returns a 1.22 + * new function that returns a promise instead. 1.23 + * This should probably live in async-util 1.24 + */ 1.25 +const promiseify = function(scope, functionWithLastParamCallback) { 1.26 + return (...args) => { 1.27 + return new Promise(resolve => { 1.28 + args.push((...results) => { 1.29 + resolve(results.length > 1 ? results : results[0]); 1.30 + }); 1.31 + functionWithLastParamCallback.apply(scope, args); 1.32 + }); 1.33 + } 1.34 +}; 1.35 + 1.36 +// Convert callback based functions to promise based ones 1.37 +const getAllAddons = promiseify(AddonManager, AddonManager.getAllAddons); 1.38 +const getAddonsByTypes = promiseify(AddonManager, AddonManager.getAddonsByTypes); 1.39 + 1.40 +/** 1.41 + * Return a string array containing the pending operations on an addon 1.42 + */ 1.43 +function pendingOperations(addon) { 1.44 + let allOperations = [ 1.45 + "PENDING_ENABLE", "PENDING_DISABLE", "PENDING_UNINSTALL", 1.46 + "PENDING_INSTALL", "PENDING_UPGRADE" 1.47 + ]; 1.48 + return allOperations.reduce(function(operations, opName) { 1.49 + return addon.pendingOperations & AddonManager[opName] ? 1.50 + operations.concat(opName) : 1.51 + operations; 1.52 + }, []); 1.53 +} 1.54 + 1.55 +exports.items = [ 1.56 + { 1.57 + item: "type", 1.58 + name: "addon", 1.59 + parent: "selection", 1.60 + stringifyProperty: "name", 1.61 + cacheable: true, 1.62 + constructor: function() { 1.63 + // Tell GCLI to clear the cache of addons when one is added or removed 1.64 + let listener = { 1.65 + onInstalled: addon => { this.clearCache(); }, 1.66 + onUninstalled: addon => { this.clearCache(); }, 1.67 + }; 1.68 + AddonManager.addAddonListener(listener); 1.69 + }, 1.70 + lookup: function() { 1.71 + return getAllAddons().then(addons => { 1.72 + return addons.map(addon => { 1.73 + let name = addon.name + " " + addon.version; 1.74 + name = name.trim().replace(/\s/g, "_"); 1.75 + return { name: name, value: addon }; 1.76 + }); 1.77 + }); 1.78 + } 1.79 + }, 1.80 + { 1.81 + name: "addon", 1.82 + description: gcli.lookup("addonDesc") 1.83 + }, 1.84 + { 1.85 + name: "addon list", 1.86 + description: gcli.lookup("addonListDesc"), 1.87 + returnType: "addonsInfo", 1.88 + params: [{ 1.89 + name: "type", 1.90 + type: { 1.91 + name: "selection", 1.92 + data: [ "dictionary", "extension", "locale", "plugin", "theme", "all" ] 1.93 + }, 1.94 + defaultValue: "all", 1.95 + description: gcli.lookup("addonListTypeDesc") 1.96 + }], 1.97 + exec: function(args, context) { 1.98 + let types = (args.type === "all") ? null : [ args.type ]; 1.99 + return getAddonsByTypes(types).then(addons => { 1.100 + addons = addons.map(function(addon) { 1.101 + return { 1.102 + name: addon.name, 1.103 + version: addon.version, 1.104 + isActive: addon.isActive, 1.105 + pendingOperations: pendingOperations(addon) 1.106 + }; 1.107 + }); 1.108 + return { addons: addons, type: args.type }; 1.109 + }); 1.110 + } 1.111 + }, 1.112 + { 1.113 + item: "converter", 1.114 + from: "addonsInfo", 1.115 + to: "view", 1.116 + exec: function(addonsInfo, context) { 1.117 + if (!addonsInfo.addons.length) { 1.118 + return context.createView({ 1.119 + html: "<p>${message}</p>", 1.120 + data: { message: gcli.lookup("addonNoneOfType") } 1.121 + }); 1.122 + } 1.123 + 1.124 + let headerLookups = { 1.125 + "dictionary": "addonListDictionaryHeading", 1.126 + "extension": "addonListExtensionHeading", 1.127 + "locale": "addonListLocaleHeading", 1.128 + "plugin": "addonListPluginHeading", 1.129 + "theme": "addonListThemeHeading", 1.130 + "all": "addonListAllHeading" 1.131 + }; 1.132 + let header = gcli.lookup(headerLookups[addonsInfo.type] || 1.133 + "addonListUnknownHeading"); 1.134 + 1.135 + let operationLookups = { 1.136 + "PENDING_ENABLE": "addonPendingEnable", 1.137 + "PENDING_DISABLE": "addonPendingDisable", 1.138 + "PENDING_UNINSTALL": "addonPendingUninstall", 1.139 + "PENDING_INSTALL": "addonPendingInstall", 1.140 + "PENDING_UPGRADE": "addonPendingUpgrade" 1.141 + }; 1.142 + function lookupOperation(opName) { 1.143 + let lookupName = operationLookups[opName]; 1.144 + return lookupName ? gcli.lookup(lookupName) : opName; 1.145 + } 1.146 + 1.147 + function arrangeAddons(addons) { 1.148 + let enabledAddons = []; 1.149 + let disabledAddons = []; 1.150 + addons.forEach(function(addon) { 1.151 + if (addon.isActive) { 1.152 + enabledAddons.push(addon); 1.153 + } else { 1.154 + disabledAddons.push(addon); 1.155 + } 1.156 + }); 1.157 + 1.158 + function compareAddonNames(nameA, nameB) { 1.159 + return String.localeCompare(nameA.name, nameB.name); 1.160 + } 1.161 + enabledAddons.sort(compareAddonNames); 1.162 + disabledAddons.sort(compareAddonNames); 1.163 + 1.164 + return enabledAddons.concat(disabledAddons); 1.165 + } 1.166 + 1.167 + function isActiveForToggle(addon) { 1.168 + return (addon.isActive && ~~addon.pendingOperations.indexOf("PENDING_DISABLE")); 1.169 + } 1.170 + 1.171 + return context.createView({ 1.172 + html: 1.173 + "<table>" + 1.174 + " <caption>${header}</caption>" + 1.175 + " <tbody>" + 1.176 + " <tr foreach='addon in ${addons}'" + 1.177 + " class=\"gcli-addon-${addon.status}\">" + 1.178 + " <td>${addon.name} ${addon.version}</td>" + 1.179 + " <td>${addon.pendingOperations}</td>" + 1.180 + " <td>" + 1.181 + " <span class='gcli-out-shortcut'" + 1.182 + " data-command='addon ${addon.toggleActionName} ${addon.label}'" + 1.183 + " onclick='${onclick}' ondblclick='${ondblclick}'" + 1.184 + " >${addon.toggleActionMessage}</span>" + 1.185 + " </td>" + 1.186 + " </tr>" + 1.187 + " </tbody>" + 1.188 + "</table>", 1.189 + data: { 1.190 + header: header, 1.191 + addons: arrangeAddons(addonsInfo.addons).map(function(addon) { 1.192 + return { 1.193 + name: addon.name, 1.194 + label: addon.name.replace(/\s/g, "_") + 1.195 + (addon.version ? "_" + addon.version : ""), 1.196 + status: addon.isActive ? "enabled" : "disabled", 1.197 + version: addon.version, 1.198 + pendingOperations: addon.pendingOperations.length ? 1.199 + (" (" + gcli.lookup("addonPending") + ": " 1.200 + + addon.pendingOperations.map(lookupOperation).join(", ") 1.201 + + ")") : 1.202 + "", 1.203 + toggleActionName: isActiveForToggle(addon) ? "disable": "enable", 1.204 + toggleActionMessage: isActiveForToggle(addon) ? 1.205 + gcli.lookup("addonListOutDisable") : 1.206 + gcli.lookup("addonListOutEnable") 1.207 + }; 1.208 + }), 1.209 + onclick: context.update, 1.210 + ondblclick: context.updateExec 1.211 + } 1.212 + }); 1.213 + } 1.214 + }, 1.215 + { 1.216 + name: "addon enable", 1.217 + description: gcli.lookup("addonEnableDesc"), 1.218 + params: [ 1.219 + { 1.220 + name: "addon", 1.221 + type: "addon", 1.222 + description: gcli.lookup("addonNameDesc") 1.223 + } 1.224 + ], 1.225 + exec: function(args, context) { 1.226 + let name = (args.addon.name + " " + args.addon.version).trim(); 1.227 + if (args.addon.userDisabled) { 1.228 + args.addon.userDisabled = false; 1.229 + return gcli.lookupFormat("addonEnabled", [ name ]); 1.230 + } 1.231 + 1.232 + return gcli.lookupFormat("addonAlreadyEnabled", [ name ]); 1.233 + } 1.234 + }, 1.235 + { 1.236 + name: "addon disable", 1.237 + description: gcli.lookup("addonDisableDesc"), 1.238 + params: [ 1.239 + { 1.240 + name: "addon", 1.241 + type: "addon", 1.242 + description: gcli.lookup("addonNameDesc") 1.243 + } 1.244 + ], 1.245 + exec: function(args, context) { 1.246 + // If the addon is not disabled or is set to "click to play" then 1.247 + // disable it. Otherwise display the message "Add-on is already 1.248 + // disabled." 1.249 + let name = (args.addon.name + " " + args.addon.version).trim(); 1.250 + if (!args.addon.userDisabled || 1.251 + args.addon.userDisabled === AddonManager.STATE_ASK_TO_ACTIVATE) { 1.252 + args.addon.userDisabled = true; 1.253 + return gcli.lookupFormat("addonDisabled", [ name ]); 1.254 + } 1.255 + 1.256 + return gcli.lookupFormat("addonAlreadyDisabled", [ name ]); 1.257 + } 1.258 + } 1.259 +];