-// Copyright (c) 2009-2011 by Kris Maglione <maglione.k@gmail.com>
+// Copyright (c) 2009-2014 Kris Maglione <maglione.k@gmail.com>
// Copyright (c) 2009-2010 by Doug Kearns <dougkearns@gmail.com>
//
// This work is licensed for reuse under an MIT license. Details are
try {
-Components.utils.import("resource://dactyl/bootstrap.jsm");
defineModule("addons", {
exports: ["AddonManager", "Addons", "Addon", "addons"],
- require: ["services"],
- use: ["completion", "config", "io", "messages", "prefs", "template", "util"]
-}, this);
+ require: ["services", "util"]
+});
+
+this.lazyRequire("completion", ["completion"]);
+lazyRequire("template", ["template"]);
-var callResult = function callResult(method) {
- let args = Array.slice(arguments, 1);
+var callResult = function callResult(method, ...args) {
return function (result) { result[method].apply(result, args); };
}
var listener = function listener(action, event)
function addonListener(install) {
this.dactyl[install.error ? "echoerr" : "echomsg"](
- "Add-on " + action + " " + event + ": " + (install.name || install.sourceURI.spec) +
- (install.error ? ": " + addonErrors[install.error] : ""));
- }
+ _("addon.error", action, event, (install.name || install.sourceURI.spec) +
+ (install.error ? ": " + addons.errors[install.error] : "")));
+ };
var AddonListener = Class("AddonListener", {
init: function init(modules) {
this.remaining = addons;
this.upgrade = [];
- this.dactyl.echomsg(_("addon.check", addons.map(function (a) a.name).join(", ")));
+ this.dactyl.echomsg(_("addon.check", addons.map(a => a.name).join(", ")));
for (let addon in values(addons))
addon.findUpdates(this, AddonManager.UPDATE_WHEN_USER_REQUESTED, null, null);
install.install();
},
onUpdateFinished: function (addon, error) {
- this.remaining = this.remaining.filter(function (a) a != addon);
+ this.remaining = this.remaining.filter(a => (a.type != addon.type || a.id != addon.id));
if (!this.remaining.length)
this.dactyl.echomsg(
this.upgrade.length
- ? "Installing updates for addons: " + this.upgrade.map(function (i) i.name).join(", ")
- : "No addon updates found");
+ ? _("addon.installingUpdates", this.upgrade.map(i => i.name).join(", "))
+ : _("addon.noUpdates"));
}
});
var actions = {
delete: {
- name: "extde[lete]",
+ name: ["extde[lete]", "extrm"],
description: "Uninstall an extension",
action: callResult("uninstall"),
perm: "uninstall"
name: "exte[nable]",
description: "Enable an extension",
action: function (addon) { addon.userDisabled = false; },
- filter: function ({ item }) item.userDisabled,
+ filter: function (addon) addon.userDisabled,
perm: "enable"
},
disable: {
name: "extd[isable]",
description: "Disable an extension",
action: function (addon) { addon.userDisabled = true; },
- filter: function ({ item }) !item.userDisabled,
+ filter: function (addon) !addon.userDisabled,
perm: "disable"
},
options: {
else
this.dactyl.open(addon.optionsURL, { from: "extoptions" });
},
- filter: function ({ item }) item.isActive && item.optionsURL
+ filter: function (addon) addon.isActive && addon.optionsURL
},
rehash: {
name: "extr[ehash]",
description: "Reload an extension",
action: function (addon) {
- util.assert(util.haveGecko("2b"), _("error.notUseful", config.host));
+ util.flushCache();
util.timeout(function () {
addon.userDisabled = true;
addon.userDisabled = false;
});
},
get filter() {
- let ids = set(keys(JSON.parse(prefs.get("extensions.bootstrappedAddons", "{}"))));
- return function ({ item }) !item.userDisabled && set.has(ids, item.id);
+ return addon => (
+ !addon.userDisabled &&
+ !(addon.operationsRequiringRestart & (AddonManager.OP_NEEDS_RESTART_ENABLE
+ | AddonManager.OP_NEEDS_RESTART_DISABLE)));
},
perm: "disable"
},
this.nodes = {
commandTarget: this
};
- XML.ignoreWhitespace = true;
- util.xmlToDom(
- <tr highlight="Addon" key="row" xmlns:dactyl={NS} xmlns={XHTML}>
- <td highlight="AddonName" key="name"/>
- <td highlight="AddonVersion" key="version"/>
- <td highlight="AddonStatus" key="status"/>
- <td highlight="AddonButtons Buttons">
- <a highlight="Button" key="enable">On </a>
- <a highlight="Button" key="disable">Off</a>
- <a highlight="Button" key="delete">Del</a>
- <a highlight="Button" key="update">Upd</a>
- <a highlight="Button" key="options">Opt</a>
- </td>
- <td highlight="AddonDescription" key="description"/>
- </tr>,
+ DOM.fromJSON(
+ ["tr", { highlight: "Addon", key: "row" },
+ ["td", { highlight: "AddonName", key: "name" }],
+ ["td", { highlight: "AddonVersion", key: "version" }],
+ ["td", { highlight: "AddonButtons Buttons" },
+ ["a", { highlight: "Button", href: "javascript:0", key: "enable" }, _("addon.action.On")],
+ ["a", { highlight: "Button", href: "javascript:0", key: "disable" }, _("addon.action.Off")],
+ ["a", { highlight: "Button", href: "javascript:0", key: "delete" }, _("addon.action.Delete")],
+ ["a", { highlight: "Button", href: "javascript:0", key: "update" }, _("addon.action.Update")],
+ ["a", { highlight: "Button", href: "javascript:0", key: "options" }, _("addon.action.Options")]],
+ ["td", { highlight: "AddonStatus", key: "status" }],
+ ["td", { highlight: "AddonDescription", key: "description" }]],
this.list.document, this.nodes);
this.update();
},
commandAllowed: function commandAllowed(cmd) {
- util.assert(set.has(actions, cmd), "Unknown command");
+ util.assert(hasOwnProperty(actions, cmd),
+ _("addon.unknownCommand"));
let action = actions[cmd];
if ("perm" in action && !(this.permissions & AddonManager["PERM_CAN_" + action.perm.toUpperCase()]))
return false;
- if ("filter" in action && !action.filter({ item: this }))
+ if ("filter" in action && !action.filter(this))
return false;
return true;
},
command: function command(cmd) {
- util.assert(this.commandAllowed(cmd), "Command not allowed");
+ util.assert(this.commandAllowed(cmd), _("addon.commandNotAllowed"));
let action = actions[cmd];
if (action.action)
compare: function compare(other) String.localeCompare(this.name, other.name),
get statusInfo() {
- XML.ignoreWhitespace = XML.prettyPrinting = false;
- default xml namespace = XHTML;
-
- let info = this.isActive ? <span highlight="Enabled">enabled</span>
- : <span highlight="Disabled">disabled</span>;
+ let info = this.isActive ? ["span", { highlight: "Enabled" }, "enabled"]
+ : ["span", { highlight: "Disabled" }, "disabled"];
let pending;
if (this.pendingOperations & AddonManager.PENDING_UNINSTALL)
else if (this.pendingOperations & AddonManager.PENDING_UPGRADE)
pending = ["Enabled", "upgraded"];
if (pending)
- return <>{info} (<span highlight={pending[0]}>{pending[1]}</span>
-  on <a href="#" dactyl:command="dactyl.restart" xmlns:dactyl={NS}>restart</a>)</>;
+ return [info, " (",
+ ["span", { highlight: pending[0] }, pending[1]],
+ " on ",
+ ["a", { href: "#", "dactyl:command": "dactyl.restart" }, "restart"],
+ ")"];
return info;
},
update: function callee() {
- let self = this;
- function update(key, xml) {
- let node = self.nodes[key];
+ let update = (key, xml) => {
+ let node = this.nodes[key];
while (node.firstChild)
node.removeChild(node.firstChild);
- node.appendChild(util.xmlToDom(<>{xml}</>, self.list.document));
+
+ DOM(node).append(isArray(xml) ? xml : DOM.DOMString(xml));
}
update("name", template.icon({ icon: this.iconURL }, this.name));
this.nodes.version.textContent = this.version;
update("status", this.statusInfo);
this.nodes.description.textContent = this.description;
+ DOM(this.nodes.row).attr("active", this.isActive || null);
for (let node in values(this.nodes))
if (node.update && node.update !== callee)
this.modules = modules;
this.filter = filter && filter.toLowerCase();
this.nodes = {};
- this.addons = [];
+ this.addons = {};
this.ready = false;
- AddonManager.getAddonsByTypes(types, this.closure(function (addons) {
+ AddonManager.getAddonsByTypes(types, addons => {
this._addons = addons;
if (this.document)
this._init();
- }));
+ });
AddonManager.addAddonListener(this);
},
cleanup: function cleanup() {
},
_init: function _init() {
- this._addons.forEach(this.closure.addAddon);
+ this._addons.forEach(this.bound.addAddon);
this.ready = true;
this.update();
},
- message: Class.memoize(function () {
-
- XML.ignoreWhitespace = true;
- util.xmlToDom(<table highlight="Addons" key="list" xmlns={XHTML}>
- <tr highlight="AddonHead">
- <td>Name</td>
- <td>Version</td>
- <td>Status</td>
- <td/>
- <td>Description</td>
- </tr>
- </table>, this.document, this.nodes);
+ message: Class.Memoize(function () {
+ DOM.fromJSON(["table", { highlight: "Addons", key: "list" },
+ ["tr", { highlight: "AddonHead" },
+ ["td", {}, _("title.Name")],
+ ["td", {}, _("title.Version")],
+ ["td"],
+ ["td", {}, _("title.Status")],
+ ["td", {}, _("title.Description")]]],
+ this.document, this.nodes);
if (this._addons)
this._init();
addon = Addon(addon, this);
this.addons[addon.id] = addon;
- let index = values(this.addons).sort(function (a, b) a.compare(b))
+ let index = values(this.addons).sort((a, b) => a.compare(b))
.indexOf(addon);
this.nodes.list.insertBefore(addon.nodes.row,
});
var Addons = Module("addons", {
+ errors: Class.Memoize(() =>
+ array(["ERROR_NETWORK_FAILURE", "ERROR_INCORRECT_HASH",
+ "ERROR_CORRUPT_FILE", "ERROR_FILE_ACCESS"])
+ .map(e => [AddonManager[e], _("AddonManager." + e)])
+ .toObject())
}, {
}, {
- commands: function (dactyl, modules, window) {
- const { CommandOption, commands, completion } = modules;
+ commands: function initCommands(dactyl, modules, window) {
+ const { CommandOption, commands, completion, io } = modules;
commands.add(["addo[ns]", "ao"],
"List installed extensions",
modules.commandline.echo(addons);
if (modules.commandline.savingOutput)
- util.waitFor(function () addons.ready);
+ util.waitFor(() => addons.ready);
},
{
argCount: "?",
}
if (!file.exists())
- AddonManager.getInstallForURL(url, install, "application/x-xpinstall");
+ AddonManager.getInstallForURL(url, install, "application/x-xpinstall");
else if (file.isReadable() && file.isFile())
- AddonManager.getInstallForFile(file, install, "application/x-xpinstall");
+ AddonManager.getInstallForFile(file.file, install, "application/x-xpinstall");
else if (file.isDirectory())
dactyl.echoerr(_("addon.cantInstallDir", file.path.quote()));
else
- dactyl.echoerr(_("io.notReadable-1", file.path));
+ dactyl.echoerr(_("io.notReadable", file.path));
}, {
argCount: "1",
completer: function (context) {
- context.filters.push(function ({ isdir, text }) isdir || /\.xpi$/.test(text));
+ context.filters.push(({ isdir, text }) => isdir || /\.xpi$/.test(text));
completion.file(context);
},
literal: 0
// TODO: handle extension dependencies
values(actions).forEach(function (command) {
let perm = command.perm && AddonManager["PERM_CAN_" + command.perm.toUpperCase()];
- function ok(addon) !perm || addon.permissions & perm;
+ function ok(addon) (!perm || addon.permissions & perm) && (!command.filter || command.filter(addon));
commands.add(Array.concat(command.name),
command.description,
function (args) {
let name = args[0];
if (args.bang && !command.bang)
- dactyl.assert(!name, _("error.trailing"));
+ dactyl.assert(!name, _("error.trailingCharacters"));
else
dactyl.assert(name, _("error.argumentRequired"));
- AddonManager.getAddonsByTypes(["extension"], dactyl.wrapCallback(function (list) {
+ AddonManager.getAddonsByTypes(args["-types"], dactyl.wrapCallback(function (list) {
if (!args.bang || command.bang) {
- list = list.filter(function (extension) extension.name == name);
- if (list.length == 0)
- return void dactyl.echoerr(_("error.invalidArgument", name));
- if (!list.every(ok))
- return void dactyl.echoerr(_("error.invalidOperation"));
+ list = list.filter(addon => (addon.id == name || addon.name == name));
+ dactyl.assert(list.length, _("error.invalidArgument", name));
+ dactyl.assert(list.some(ok), _("error.invalidOperation"));
+ list = list.filter(ok);
}
+ dactyl.assert(list.every(ok));
if (command.actions)
command.actions(list, this.modules);
else
- list.forEach(function (addon) command.action.call(this.modules, addon, args.bang), this);
+ list.forEach(addon => { command.action.call(this.modules, addon, args.bang) });
}));
}, {
argCount: "?", // FIXME: should be "1"
bang: true,
- completer: function (context) {
- completion.extension(context);
- context.filters.push(function ({ item }) ok(item));
- if (command.filter)
- context.filters.push(command.filter);
+ completer: function (context, args) {
+ completion.addon(context, args["-types"]);
+ context.filters.push(({ item }) => ok(item));
},
- literal: 0
+ literal: 0,
+ options: [
+ {
+ names: ["-types", "-type", "-t"],
+ description: "The add-on types to operate on",
+ default: ["extension"],
+ completer: function (context, args) completion.addonType(context),
+ type: CommandOption.LIST
+ }
+ ]
});
});
},
- completion: function (dactyl, modules, window) {
+ completion: function initCompletion(dactyl, modules, window) {
completion.addonType = function addonType(context) {
let base = ["extension", "theme"];
function update(types) {
- context.completions = types.map(function (t) [t, util.capitalize(t)]);
+ context.completions = types.map(t => [t, util.capitalize(t)]);
}
context.generate = function generate() {
context.incomplete = true;
AddonManager.getAllAddons(function (addons) {
context.incomplete = false;
- update(array.uniq(base.concat(addons.map(function (a) a.type)),
+ update(array.uniq(base.concat(addons.map(a => a.type)),
true));
});
}
- }
- }
+ };
+ };
- completion.extension = function extension(context, types) {
- context.title = ["Extension"];
+ completion.addon = function addon(context, types) {
+ context.title = ["Add-on"];
context.anchored = false;
- context.keys = { text: "name", description: "description", icon: "iconURL" },
+ context.keys = {
+ text: addon => [addon.name, addon.id],
+ description: "description",
+ icon: "iconURL"
+ };
context.generate = function () {
context.incomplete = true;
AddonManager.getAddonsByTypes(types || ["extension"], function (addons) {
}
});
-if (!services.has("extensionManager"))
- Components.utils.import("resource://gre/modules/AddonManager.jsm");
-else
- var AddonManager = {
- PERM_CAN_UNINSTALL: 1,
- PERM_CAN_ENABLE: 2,
- PERM_CAN_DISABLE: 4,
- PERM_CAN_UPGRADE: 8,
-
- getAddonByID: function (id, callback) {
- callback = callback || util.identity;
- addon = services.extensionManager.getItemForID(id);
- if (addon)
- addon = this.wrapAddon(addon);
- return callback(addon);
- },
- wrapAddon: function wrapAddon(addon) {
- addon = Object.create(addon.QueryInterface(Ci.nsIUpdateItem));
-
- function getRdfProperty(item, property) {
- let resource = services.rdf.GetResource("urn:mozilla:item:" + item.id);
- let value = "";
-
- if (resource) {
- let target = services.extensionManager.datasource.GetTarget(resource,
- services.rdf.GetResource("http://www.mozilla.org/2004/em-rdf#" + property), true);
- if (target && target instanceof Ci.nsIRDFLiteral)
- value = target.Value;
- }
-
- return value;
- }
-
- ["aboutURL", "creator", "description", "developers",
- "homepageURL", "installDate", "optionsURL",
- "releaseNotesURI", "updateDate"].forEach(function (item) {
- memoize(addon, item, function (item) getRdfProperty(this, item));
- });
-
- update(addon, {
-
- get permissions() 1 | (this.userDisabled ? 2 : 4),
-
- appDisabled: false,
-
- installLocation: Class.memoize(function () services.extensionManager.getInstallLocation(this.id)),
- getResourceURI: function getResourceURI(path) {
- let file = this.installLocation.getItemFile(this.id, path);
- return services.io.newFileURI(file);
- },
-
- isActive: getRdfProperty(addon, "isDisabled") != "true",
-
- uninstall: function uninstall() {
- services.extensionManager.uninstallItem(this.id);
- },
-
- get userDisabled() getRdfProperty(addon, "userDisabled") === "true",
- set userDisabled(val) {
- services.extensionManager[val ? "disableItem" : "enableItem"](this.id);
- }
- });
-
- return addon;
- },
- getAddonsByTypes: function (types, callback) {
- let res = [];
- for (let [, type] in Iterator(types))
- for (let [, item] in Iterator(services.extensionManager
- .getItemList(Ci.nsIUpdateItem["TYPE_" + type.toUpperCase()], {})))
- res.push(this.wrapAddon(item));
-
- if (callback)
- util.timeout(function () { callback(res); });
- return res;
- },
- getInstallForFile: function (file, callback, mimetype) {
- callback({
- addListener: function () {},
- install: function () {
- services.extensionManager.installItemFromFile(file, "app-profile");
- }
- });
- },
- getInstallForURL: function (url, callback, mimetype) {
- util.assert(false, _("error.unavailable", config.host, services.runtime.version));
- },
- observers: [],
- addAddonListener: function (listener) {
- observer.listener = listener;
- function observer(subject, topic, data) {
- if (subject instanceof Ci.nsIUpdateItem)
- subject = AddonManager.wrapAddon(subject);
-
- if (data === "item-installed")
- listener.onInstalling(subject, true);
- else if (data === "item-uninstalled")
- listener.onUnistalling(subject, true);
- else if (data === "item-upgraded")
- listener.onInstalling(subject, true);
- else if (data === "item-enabled")
- listener.onEnabling(subject, true);
- else if (data === "item-disabled")
- listener.onDisabling(subject, true);
- }
- services.observer.addObserver(observer, "em-action-requested", false);
- this.observers.push(observer);
- },
- removeAddonListener: function (listener) {
- this.observers = this.observers.filter(function (observer) {
- if (observer.listener !== listener)
- return true;
- services.observer.removeObserver(observer, "em-action-requested");
- });
- }
- };
-
-var addonErrors = array.toObject([
- [AddonManager.ERROR_NETWORK_FAILURE, "A network error occurred"],
- [AddonManager.ERROR_INCORRECT_HASH, "The downloaded file did not match the expected hash"],
- [AddonManager.ERROR_CORRUPT_FILE, "The file appears to be corrupt"],
- [AddonManager.ERROR_FILE_ACCESS, "There was an error accessing the filesystem"]]);
+Components.utils.import("resource://gre/modules/AddonManager.jsm", this);
endModule();
} catch(e){ if (isString(e)) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
-// vim: set fdm=marker sw=4 ts=4 et ft=javascript:
+// vim: set fdm=marker sw=4 sts=4 ts=8 et ft=javascript: