|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 "use strict"; |
|
6 |
|
7 const { Cc, Ci, Cu } = require("chrome"); |
|
8 const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {}); |
|
9 const gcli = require("gcli/index"); |
|
10 const { Promise: promise } = require("resource://gre/modules/Promise.jsm"); |
|
11 |
|
12 const BRAND_SHORT_NAME = Cc["@mozilla.org/intl/stringbundle;1"] |
|
13 .getService(Ci.nsIStringBundleService) |
|
14 .createBundle("chrome://branding/locale/brand.properties") |
|
15 .GetStringFromName("brandShortName"); |
|
16 |
|
17 /** |
|
18 * Takes a function that uses a callback as its last parameter, and returns a |
|
19 * new function that returns a promise instead. |
|
20 * This should probably live in async-util |
|
21 */ |
|
22 const promiseify = function(scope, functionWithLastParamCallback) { |
|
23 return (...args) => { |
|
24 return new Promise(resolve => { |
|
25 args.push((...results) => { |
|
26 resolve(results.length > 1 ? results : results[0]); |
|
27 }); |
|
28 functionWithLastParamCallback.apply(scope, args); |
|
29 }); |
|
30 } |
|
31 }; |
|
32 |
|
33 // Convert callback based functions to promise based ones |
|
34 const getAllAddons = promiseify(AddonManager, AddonManager.getAllAddons); |
|
35 const getAddonsByTypes = promiseify(AddonManager, AddonManager.getAddonsByTypes); |
|
36 |
|
37 /** |
|
38 * Return a string array containing the pending operations on an addon |
|
39 */ |
|
40 function pendingOperations(addon) { |
|
41 let allOperations = [ |
|
42 "PENDING_ENABLE", "PENDING_DISABLE", "PENDING_UNINSTALL", |
|
43 "PENDING_INSTALL", "PENDING_UPGRADE" |
|
44 ]; |
|
45 return allOperations.reduce(function(operations, opName) { |
|
46 return addon.pendingOperations & AddonManager[opName] ? |
|
47 operations.concat(opName) : |
|
48 operations; |
|
49 }, []); |
|
50 } |
|
51 |
|
52 exports.items = [ |
|
53 { |
|
54 item: "type", |
|
55 name: "addon", |
|
56 parent: "selection", |
|
57 stringifyProperty: "name", |
|
58 cacheable: true, |
|
59 constructor: function() { |
|
60 // Tell GCLI to clear the cache of addons when one is added or removed |
|
61 let listener = { |
|
62 onInstalled: addon => { this.clearCache(); }, |
|
63 onUninstalled: addon => { this.clearCache(); }, |
|
64 }; |
|
65 AddonManager.addAddonListener(listener); |
|
66 }, |
|
67 lookup: function() { |
|
68 return getAllAddons().then(addons => { |
|
69 return addons.map(addon => { |
|
70 let name = addon.name + " " + addon.version; |
|
71 name = name.trim().replace(/\s/g, "_"); |
|
72 return { name: name, value: addon }; |
|
73 }); |
|
74 }); |
|
75 } |
|
76 }, |
|
77 { |
|
78 name: "addon", |
|
79 description: gcli.lookup("addonDesc") |
|
80 }, |
|
81 { |
|
82 name: "addon list", |
|
83 description: gcli.lookup("addonListDesc"), |
|
84 returnType: "addonsInfo", |
|
85 params: [{ |
|
86 name: "type", |
|
87 type: { |
|
88 name: "selection", |
|
89 data: [ "dictionary", "extension", "locale", "plugin", "theme", "all" ] |
|
90 }, |
|
91 defaultValue: "all", |
|
92 description: gcli.lookup("addonListTypeDesc") |
|
93 }], |
|
94 exec: function(args, context) { |
|
95 let types = (args.type === "all") ? null : [ args.type ]; |
|
96 return getAddonsByTypes(types).then(addons => { |
|
97 addons = addons.map(function(addon) { |
|
98 return { |
|
99 name: addon.name, |
|
100 version: addon.version, |
|
101 isActive: addon.isActive, |
|
102 pendingOperations: pendingOperations(addon) |
|
103 }; |
|
104 }); |
|
105 return { addons: addons, type: args.type }; |
|
106 }); |
|
107 } |
|
108 }, |
|
109 { |
|
110 item: "converter", |
|
111 from: "addonsInfo", |
|
112 to: "view", |
|
113 exec: function(addonsInfo, context) { |
|
114 if (!addonsInfo.addons.length) { |
|
115 return context.createView({ |
|
116 html: "<p>${message}</p>", |
|
117 data: { message: gcli.lookup("addonNoneOfType") } |
|
118 }); |
|
119 } |
|
120 |
|
121 let headerLookups = { |
|
122 "dictionary": "addonListDictionaryHeading", |
|
123 "extension": "addonListExtensionHeading", |
|
124 "locale": "addonListLocaleHeading", |
|
125 "plugin": "addonListPluginHeading", |
|
126 "theme": "addonListThemeHeading", |
|
127 "all": "addonListAllHeading" |
|
128 }; |
|
129 let header = gcli.lookup(headerLookups[addonsInfo.type] || |
|
130 "addonListUnknownHeading"); |
|
131 |
|
132 let operationLookups = { |
|
133 "PENDING_ENABLE": "addonPendingEnable", |
|
134 "PENDING_DISABLE": "addonPendingDisable", |
|
135 "PENDING_UNINSTALL": "addonPendingUninstall", |
|
136 "PENDING_INSTALL": "addonPendingInstall", |
|
137 "PENDING_UPGRADE": "addonPendingUpgrade" |
|
138 }; |
|
139 function lookupOperation(opName) { |
|
140 let lookupName = operationLookups[opName]; |
|
141 return lookupName ? gcli.lookup(lookupName) : opName; |
|
142 } |
|
143 |
|
144 function arrangeAddons(addons) { |
|
145 let enabledAddons = []; |
|
146 let disabledAddons = []; |
|
147 addons.forEach(function(addon) { |
|
148 if (addon.isActive) { |
|
149 enabledAddons.push(addon); |
|
150 } else { |
|
151 disabledAddons.push(addon); |
|
152 } |
|
153 }); |
|
154 |
|
155 function compareAddonNames(nameA, nameB) { |
|
156 return String.localeCompare(nameA.name, nameB.name); |
|
157 } |
|
158 enabledAddons.sort(compareAddonNames); |
|
159 disabledAddons.sort(compareAddonNames); |
|
160 |
|
161 return enabledAddons.concat(disabledAddons); |
|
162 } |
|
163 |
|
164 function isActiveForToggle(addon) { |
|
165 return (addon.isActive && ~~addon.pendingOperations.indexOf("PENDING_DISABLE")); |
|
166 } |
|
167 |
|
168 return context.createView({ |
|
169 html: |
|
170 "<table>" + |
|
171 " <caption>${header}</caption>" + |
|
172 " <tbody>" + |
|
173 " <tr foreach='addon in ${addons}'" + |
|
174 " class=\"gcli-addon-${addon.status}\">" + |
|
175 " <td>${addon.name} ${addon.version}</td>" + |
|
176 " <td>${addon.pendingOperations}</td>" + |
|
177 " <td>" + |
|
178 " <span class='gcli-out-shortcut'" + |
|
179 " data-command='addon ${addon.toggleActionName} ${addon.label}'" + |
|
180 " onclick='${onclick}' ondblclick='${ondblclick}'" + |
|
181 " >${addon.toggleActionMessage}</span>" + |
|
182 " </td>" + |
|
183 " </tr>" + |
|
184 " </tbody>" + |
|
185 "</table>", |
|
186 data: { |
|
187 header: header, |
|
188 addons: arrangeAddons(addonsInfo.addons).map(function(addon) { |
|
189 return { |
|
190 name: addon.name, |
|
191 label: addon.name.replace(/\s/g, "_") + |
|
192 (addon.version ? "_" + addon.version : ""), |
|
193 status: addon.isActive ? "enabled" : "disabled", |
|
194 version: addon.version, |
|
195 pendingOperations: addon.pendingOperations.length ? |
|
196 (" (" + gcli.lookup("addonPending") + ": " |
|
197 + addon.pendingOperations.map(lookupOperation).join(", ") |
|
198 + ")") : |
|
199 "", |
|
200 toggleActionName: isActiveForToggle(addon) ? "disable": "enable", |
|
201 toggleActionMessage: isActiveForToggle(addon) ? |
|
202 gcli.lookup("addonListOutDisable") : |
|
203 gcli.lookup("addonListOutEnable") |
|
204 }; |
|
205 }), |
|
206 onclick: context.update, |
|
207 ondblclick: context.updateExec |
|
208 } |
|
209 }); |
|
210 } |
|
211 }, |
|
212 { |
|
213 name: "addon enable", |
|
214 description: gcli.lookup("addonEnableDesc"), |
|
215 params: [ |
|
216 { |
|
217 name: "addon", |
|
218 type: "addon", |
|
219 description: gcli.lookup("addonNameDesc") |
|
220 } |
|
221 ], |
|
222 exec: function(args, context) { |
|
223 let name = (args.addon.name + " " + args.addon.version).trim(); |
|
224 if (args.addon.userDisabled) { |
|
225 args.addon.userDisabled = false; |
|
226 return gcli.lookupFormat("addonEnabled", [ name ]); |
|
227 } |
|
228 |
|
229 return gcli.lookupFormat("addonAlreadyEnabled", [ name ]); |
|
230 } |
|
231 }, |
|
232 { |
|
233 name: "addon disable", |
|
234 description: gcli.lookup("addonDisableDesc"), |
|
235 params: [ |
|
236 { |
|
237 name: "addon", |
|
238 type: "addon", |
|
239 description: gcli.lookup("addonNameDesc") |
|
240 } |
|
241 ], |
|
242 exec: function(args, context) { |
|
243 // If the addon is not disabled or is set to "click to play" then |
|
244 // disable it. Otherwise display the message "Add-on is already |
|
245 // disabled." |
|
246 let name = (args.addon.name + " " + args.addon.version).trim(); |
|
247 if (!args.addon.userDisabled || |
|
248 args.addon.userDisabled === AddonManager.STATE_ASK_TO_ACTIVATE) { |
|
249 args.addon.userDisabled = true; |
|
250 return gcli.lookupFormat("addonDisabled", [ name ]); |
|
251 } |
|
252 |
|
253 return gcli.lookupFormat("addonAlreadyDisabled", [ name ]); |
|
254 } |
|
255 } |
|
256 ]; |