summaryrefslogtreecommitdiffstats
path: root/devtools/shared/gcli/commands/addon.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/shared/gcli/commands/addon.js')
-rw-r--r--devtools/shared/gcli/commands/addon.js320
1 files changed, 320 insertions, 0 deletions
diff --git a/devtools/shared/gcli/commands/addon.js b/devtools/shared/gcli/commands/addon.js
new file mode 100644
index 000000000..9a38142a3
--- /dev/null
+++ b/devtools/shared/gcli/commands/addon.js
@@ -0,0 +1,320 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * You can't require the AddonManager in a child process, but GCLI wants to
+ * check for 'items' in all processes, so we return empty array if the
+ * AddonManager is not available
+ */
+function getAddonManager() {
+ try {
+ return {
+ AddonManager: require("resource://gre/modules/AddonManager.jsm").AddonManager,
+ addonManagerActive: true
+ };
+ }
+ catch (ex) {
+ // Fake up an AddonManager just enough to let the file load
+ return {
+ AddonManager: {
+ getAllAddons() {},
+ getAddonsByTypes() {}
+ },
+ addonManagerActive: false
+ };
+ }
+}
+
+const { Cc, Ci, Cu } = require("chrome");
+const { AddonManager, addonManagerActive } = getAddonManager();
+const l10n = require("gcli/l10n");
+
+const BRAND_SHORT_NAME = Cc["@mozilla.org/intl/stringbundle;1"]
+ .getService(Ci.nsIStringBundleService)
+ .createBundle("chrome://branding/locale/brand.properties")
+ .GetStringFromName("brandShortName");
+
+/**
+ * Takes a function that uses a callback as its last parameter, and returns a
+ * new function that returns a promise instead.
+ * This should probably live in async-util
+ */
+const promiseify = function(scope, functionWithLastParamCallback) {
+ return (...args) => {
+ return new Promise(resolve => {
+ args.push((...results) => {
+ resolve(results.length > 1 ? results : results[0]);
+ });
+ functionWithLastParamCallback.apply(scope, args);
+ });
+ }
+};
+
+// Convert callback based functions to promise based ones
+const getAllAddons = promiseify(AddonManager, AddonManager.getAllAddons);
+const getAddonsByTypes = promiseify(AddonManager, AddonManager.getAddonsByTypes);
+
+/**
+ * Return a string array containing the pending operations on an addon
+ */
+function pendingOperations(addon) {
+ let allOperations = [
+ "PENDING_ENABLE", "PENDING_DISABLE", "PENDING_UNINSTALL",
+ "PENDING_INSTALL", "PENDING_UPGRADE"
+ ];
+ return allOperations.reduce(function(operations, opName) {
+ return addon.pendingOperations & AddonManager[opName] ?
+ operations.concat(opName) :
+ operations;
+ }, []);
+}
+
+var items = [
+ {
+ item: "type",
+ name: "addon",
+ parent: "selection",
+ stringifyProperty: "name",
+ cacheable: true,
+ constructor: function() {
+ // Tell GCLI to clear the cache of addons when one is added or removed
+ let listener = {
+ onInstalled: addon => { this.clearCache(); },
+ onUninstalled: addon => { this.clearCache(); },
+ };
+ AddonManager.addAddonListener(listener);
+ },
+ lookup: function() {
+ return getAllAddons().then(addons => {
+ return addons.map(addon => {
+ let name = addon.name + " " + addon.version;
+ name = name.trim().replace(/\s/g, "_");
+ return { name: name, value: addon };
+ });
+ });
+ }
+ },
+ {
+ name: "addon",
+ description: l10n.lookup("addonDesc")
+ },
+ {
+ name: "addon list",
+ description: l10n.lookup("addonListDesc"),
+ returnType: "addonsInfo",
+ params: [{
+ name: "type",
+ type: {
+ name: "selection",
+ data: [ "dictionary", "extension", "locale", "plugin", "theme", "all" ]
+ },
+ defaultValue: "all",
+ description: l10n.lookup("addonListTypeDesc")
+ }],
+ exec: function(args, context) {
+ let types = (args.type === "all") ? null : [ args.type ];
+ return getAddonsByTypes(types).then(addons => {
+ addons = addons.map(function(addon) {
+ return {
+ name: addon.name,
+ version: addon.version,
+ isActive: addon.isActive,
+ pendingOperations: pendingOperations(addon)
+ };
+ });
+ return { addons: addons, type: args.type };
+ });
+ }
+ },
+ {
+ item: "converter",
+ from: "addonsInfo",
+ to: "view",
+ exec: function(addonsInfo, context) {
+ if (!addonsInfo.addons.length) {
+ return context.createView({
+ html: "<p>${message}</p>",
+ data: { message: l10n.lookup("addonNoneOfType") }
+ });
+ }
+
+ let headerLookups = {
+ "dictionary": "addonListDictionaryHeading",
+ "extension": "addonListExtensionHeading",
+ "locale": "addonListLocaleHeading",
+ "plugin": "addonListPluginHeading",
+ "theme": "addonListThemeHeading",
+ "all": "addonListAllHeading"
+ };
+ let header = l10n.lookup(headerLookups[addonsInfo.type] ||
+ "addonListUnknownHeading");
+
+ let operationLookups = {
+ "PENDING_ENABLE": "addonPendingEnable",
+ "PENDING_DISABLE": "addonPendingDisable",
+ "PENDING_UNINSTALL": "addonPendingUninstall",
+ "PENDING_INSTALL": "addonPendingInstall",
+ "PENDING_UPGRADE": "addonPendingUpgrade"
+ };
+ function lookupOperation(opName) {
+ let lookupName = operationLookups[opName];
+ return lookupName ? l10n.lookup(lookupName) : opName;
+ }
+
+ function arrangeAddons(addons) {
+ let enabledAddons = [];
+ let disabledAddons = [];
+ addons.forEach(function(addon) {
+ if (addon.isActive) {
+ enabledAddons.push(addon);
+ } else {
+ disabledAddons.push(addon);
+ }
+ });
+
+ function compareAddonNames(nameA, nameB) {
+ return String.localeCompare(nameA.name, nameB.name);
+ }
+ enabledAddons.sort(compareAddonNames);
+ disabledAddons.sort(compareAddonNames);
+
+ return enabledAddons.concat(disabledAddons);
+ }
+
+ function isActiveForToggle(addon) {
+ return (addon.isActive && ~~addon.pendingOperations.indexOf("PENDING_DISABLE"));
+ }
+
+ return context.createView({
+ html:
+ "<table>" +
+ " <caption>${header}</caption>" +
+ " <tbody>" +
+ " <tr foreach='addon in ${addons}'" +
+ " class=\"gcli-addon-${addon.status}\">" +
+ " <td>${addon.name} ${addon.version}</td>" +
+ " <td>${addon.pendingOperations}</td>" +
+ " <td>" +
+ " <span class='gcli-out-shortcut'" +
+ " data-command='addon ${addon.toggleActionName} ${addon.label}'" +
+ " onclick='${onclick}' ondblclick='${ondblclick}'" +
+ " >${addon.toggleActionMessage}</span>" +
+ " </td>" +
+ " </tr>" +
+ " </tbody>" +
+ "</table>",
+ data: {
+ header: header,
+ addons: arrangeAddons(addonsInfo.addons).map(function(addon) {
+ return {
+ name: addon.name,
+ label: addon.name.replace(/\s/g, "_") +
+ (addon.version ? "_" + addon.version : ""),
+ status: addon.isActive ? "enabled" : "disabled",
+ version: addon.version,
+ pendingOperations: addon.pendingOperations.length ?
+ (" (" + l10n.lookup("addonPending") + ": "
+ + addon.pendingOperations.map(lookupOperation).join(", ")
+ + ")") :
+ "",
+ toggleActionName: isActiveForToggle(addon) ? "disable": "enable",
+ toggleActionMessage: isActiveForToggle(addon) ?
+ l10n.lookup("addonListOutDisable") :
+ l10n.lookup("addonListOutEnable")
+ };
+ }),
+ onclick: context.update,
+ ondblclick: context.updateExec
+ }
+ });
+ }
+ },
+ {
+ item: "command",
+ runAt: "client",
+ name: "addon enable",
+ description: l10n.lookup("addonEnableDesc"),
+ params: [
+ {
+ name: "addon",
+ type: "addon",
+ description: l10n.lookup("addonNameDesc")
+ }
+ ],
+ exec: function(args, context) {
+ let name = (args.addon.name + " " + args.addon.version).trim();
+ if (args.addon.userDisabled) {
+ args.addon.userDisabled = false;
+ return l10n.lookupFormat("addonEnabled", [ name ]);
+ }
+
+ return l10n.lookupFormat("addonAlreadyEnabled", [ name ]);
+ }
+ },
+ {
+ item: "command",
+ runAt: "client",
+ name: "addon disable",
+ description: l10n.lookup("addonDisableDesc"),
+ params: [
+ {
+ name: "addon",
+ type: "addon",
+ description: l10n.lookup("addonNameDesc")
+ }
+ ],
+ exec: function(args, context) {
+ // If the addon is not disabled or is set to "click to play" then
+ // disable it. Otherwise display the message "Add-on is already
+ // disabled."
+ let name = (args.addon.name + " " + args.addon.version).trim();
+ if (!args.addon.userDisabled ||
+ args.addon.userDisabled === AddonManager.STATE_ASK_TO_ACTIVATE) {
+ args.addon.userDisabled = true;
+ return l10n.lookupFormat("addonDisabled", [ name ]);
+ }
+
+ return l10n.lookupFormat("addonAlreadyDisabled", [ name ]);
+ }
+ },
+ {
+ item: "command",
+ runAt: "client",
+ name: "addon ctp",
+ description: l10n.lookup("addonCtpDesc"),
+ params: [
+ {
+ name: "addon",
+ type: "addon",
+ description: l10n.lookup("addonNameDesc")
+ }
+ ],
+ exec: function(args, context) {
+ let name = (args.addon.name + " " + args.addon.version).trim();
+ if (args.addon.type !== "plugin") {
+ return l10n.lookupFormat("addonCantCtp", [ name ]);
+ }
+
+ if (!args.addon.userDisabled ||
+ args.addon.userDisabled === true) {
+ args.addon.userDisabled = AddonManager.STATE_ASK_TO_ACTIVATE;
+
+ if (args.addon.userDisabled !== AddonManager.STATE_ASK_TO_ACTIVATE) {
+ // Some plugins (e.g. OpenH264 shipped with Firefox) cannot be set to
+ // click-to-play. Handle this.
+
+ return l10n.lookupFormat("addonNoCtp", [ name ]);
+ }
+
+ return l10n.lookupFormat("addonCtp", [ name ]);
+ }
+
+ return l10n.lookupFormat("addonAlreadyCtp", [ name ]);
+ }
+ }
+];
+
+exports.items = addonManagerActive ? items : [];