addon.js 9.9 KB

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