diff --git a/common/bootstrap.js b/common/bootstrap.js index cc6977ed..774584d0 100755 --- a/common/bootstrap.js +++ b/common/bootstrap.js @@ -241,11 +241,11 @@ function shutdown(data, reason) { reportError(e); } - if ([ADDON_UPGRADE, ADDON_DOWNGRADE, ADDON_UNINSTALL].indexOf(reason) >= 0) + if (~[ADDON_UPGRADE, ADDON_DOWNGRADE, ADDON_UNINSTALL].indexOf(reason)) Services.obs.notifyObservers(null, "dactyl-purge", null); - Services.obs.notifyObservers(null, "dactyl-cleanup", null); - Services.obs.notifyObservers(null, "dactyl-cleanup-modules", null); + Services.obs.notifyObservers(null, "dactyl-cleanup", reasonToString(reason)); + Services.obs.notifyObservers(null, "dactyl-cleanup-modules", reasonToString(reason)); JSMLoader.purge(); for each (let [category, entry] in categories) diff --git a/common/content/abbreviations.js b/common/content/abbreviations.js index ab2bca02..ca714341 100644 --- a/common/content/abbreviations.js +++ b/common/content/abbreviations.js @@ -329,6 +329,7 @@ var Abbreviations = Module("abbreviations", { args["-group"].add(modes, lhs, rhs); } }, { + identifier: "abbreviate", completer: function (context, args) { if (args.length == 1) return completion.abbreviation(context, modes, args["-group"]); diff --git a/common/content/dactyl.js b/common/content/dactyl.js index 294e8a4a..e218da30 100644 --- a/common/content/dactyl.js +++ b/common/content/dactyl.js @@ -70,21 +70,36 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { autocommands.trigger("Leave", {}); }, + // initially hide all GUI elements, they are later restored unless the user + // has :set go= or something similar in his config + hideGUI: function () { + let guioptions = config.guioptions; + for (let option in guioptions) { + guioptions[option].forEach(function (elem) { + try { + document.getElementById(elem).collapsed = true; + } + catch (e) {} + }); + } + }, + + observers: { - "dactyl-cleanup": function dactyl_cleanup() { + "dactyl-cleanup": function dactyl_cleanup(subject, reason) { let modules = dactyl.modules; for (let mod in values(modules.moduleList.reverse())) { mod.stale = true; if ("cleanup" in mod) - this.trapErrors("cleanup", mod); + this.trapErrors("cleanup", mod, reason); if ("destroy" in mod) - this.trapErrors("destroy", mod); + this.trapErrors("destroy", mod, reason); } for (let mod in values(modules.ownPropertyValues.reverse())) if (mod instanceof Class && "INIT" in mod && "cleanup" in mod.INIT) - this.trapErrors(mod.cleanup, mod, dactyl, modules, window); + this.trapErrors(mod.cleanup, mod, dactyl, modules, window, reason); for (let name in values(Object.getOwnPropertyNames(modules).reverse())) try { @@ -1547,19 +1562,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { get windows() [win for (win in iter(services.windowMediator.getEnumerator("navigator:browser"))) if (win.dactyl)], }, { - // initially hide all GUI elements, they are later restored unless the user - // has :set go= or something similar in his config - hideGUI: function () { - let guioptions = config.guioptions; - for (let option in guioptions) { - guioptions[option].forEach(function (elem) { - try { - document.getElementById(elem).collapsed = true; - } - catch (e) {} - }); - } - } + toolbarHidden: function hidden(elem) (elem.getAttribute("autohide") || elem.getAttribute("collapsed")) == "true" }, { events: function () { events.listen(window, "click", dactyl.closure.onClick, true); @@ -1664,7 +1667,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { // FIXME: cleanup cleanupValue: config.cleanups.guioptions || "r" + [k for ([k, v] in iter(groups[1].opts)) - if (!document.getElementById(v[1][0]).collapsed)].join(""), + if (!Dactyl.toolbarHidden(document.getElementById(v[1][0])))].join(""), values: array(groups).map(function (g) [[k, v[0]] for ([k, v] in Iterator(g.opts))]).flatten(), @@ -1938,8 +1941,6 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { var toolbox = document.getElementById("navigator-toolbox"); if (toolbox) { - let hidden = function hidden(elem) (elem.getAttribute("autohide") || elem.getAttribute("collapsed")) == "true"; - let toolbarCommand = function (names, desc, action, filter) { commands.add(names, desc, function (args) { @@ -1960,12 +1961,12 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { toolbarCommand(["toolbars[how]", "tbs[how]"], "Show the named toolbar", function (toolbar) dactyl.setNodeVisible(toolbar, true), - function ({ item }) hidden(item)); + function ({ item }) Dactyl.toolbarHidden(item)); toolbarCommand(["toolbarh[ide]", "tbh[ide]"], "Hide the named toolbar", function (toolbar) dactyl.setNodeVisible(toolbar, false), - function ({ item }) !hidden(item)); + function ({ item }) !Dactyl.toolbarHidden(item)); toolbarCommand(["toolbart[oggle]", "tbt[oggle]"], "Toggle the named toolbar", - function (toolbar) dactyl.setNodeVisible(toolbar, hidden(toolbar))); + function (toolbar) dactyl.setNodeVisible(toolbar, Dactyl.toolbarHidden(toolbar))); } commands.add(["time"], @@ -2171,7 +2172,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { } // TODO: we should have some class where all this guioptions stuff fits well - // Dactyl.hideGUI(); + // dactyl.hideGUI(); if (dactyl.userEval("typeof document", null, "test.js") === "undefined") jsmodules.__proto__ = XPCSafeJSObjectWrapper(window); diff --git a/common/content/mappings.js b/common/content/mappings.js index 5f5dcb6b..1e7fe8e8 100644 --- a/common/content/mappings.js +++ b/common/content/mappings.js @@ -507,86 +507,87 @@ var Mappings = Module("mappings", { } const opts = { - completer: function (context, args) { - let mapmodes = array.uniq(args["-modes"].map(findMode)); - if (args.length == 1) - return completion.userMapping(context, mapmodes, args["-group"]); - if (args.length == 2) { - if (args["-javascript"]) - return completion.javascript(context); - if (args["-ex"]) - return completion.ex(context); - } - }, - hereDoc: true, - literal: 1, - options: [ - { - names: ["-arg", "-a"], - description: "Accept an argument after the requisite key press", - }, - { - names: ["-builtin", "-b"], - description: "Execute this mapping as if there were no user-defined mappings" - }, - { - names: ["-count", "-c"], - description: "Accept a count before the requisite key press" - }, - { - names: ["-description", "-desc", "-d"], - description: "A description of this mapping", - default: /*L*/"User-defined mapping", - type: CommandOption.STRING - }, - { - names: ["-ex", "-e"], - description: "Execute this mapping as an Ex command rather than keys" - }, - contexts.GroupFlag("mappings"), - { - names: ["-javascript", "-js", "-j"], - description: "Execute this mapping as JavaScript rather than keys" - }, - update({}, modeFlag, { - names: ["-modes", "-mode", "-m"], - type: CommandOption.LIST, - description: "Create this mapping in the given modes", - default: mapmodes || ["n", "v"] - }), - { - names: ["-nopersist", "-n"], - description: "Do not save this mapping to an auto-generated RC file" - }, - { - names: ["-silent", "-s", "", ""], - description: "Do not echo any generated keys to the command line" - } - ], - serialize: function () { - return this.name != "map" ? [] : - array(mappings.userHives) - .filter(function (h) h.persist) - .map(function (hive) [ - { - command: "map", - options: array([ - hive.name !== "user" && ["-group", hive.name], - ["-modes", uniqueModes(map.modes)], - ["-description", map.description], - map.silent && ["-silent"]]) - - .filter(util.identity) - .toObject(), - arguments: [map.names[0]], - literalArg: map.rhs, - ignoreDefaults: true - } - for (map in userMappings(hive)) - if (map.persist) - ]) - .flatten().array; + identifier: "map", + completer: function (context, args) { + let mapmodes = array.uniq(args["-modes"].map(findMode)); + if (args.length == 1) + return completion.userMapping(context, mapmodes, args["-group"]); + if (args.length == 2) { + if (args["-javascript"]) + return completion.javascript(context); + if (args["-ex"]) + return completion.ex(context); } + }, + hereDoc: true, + literal: 1, + options: [ + { + names: ["-arg", "-a"], + description: "Accept an argument after the requisite key press", + }, + { + names: ["-builtin", "-b"], + description: "Execute this mapping as if there were no user-defined mappings" + }, + { + names: ["-count", "-c"], + description: "Accept a count before the requisite key press" + }, + { + names: ["-description", "-desc", "-d"], + description: "A description of this mapping", + default: /*L*/"User-defined mapping", + type: CommandOption.STRING + }, + { + names: ["-ex", "-e"], + description: "Execute this mapping as an Ex command rather than keys" + }, + contexts.GroupFlag("mappings"), + { + names: ["-javascript", "-js", "-j"], + description: "Execute this mapping as JavaScript rather than keys" + }, + update({}, modeFlag, { + names: ["-modes", "-mode", "-m"], + type: CommandOption.LIST, + description: "Create this mapping in the given modes", + default: mapmodes || ["n", "v"] + }), + { + names: ["-nopersist", "-n"], + description: "Do not save this mapping to an auto-generated RC file" + }, + { + names: ["-silent", "-s", "", ""], + description: "Do not echo any generated keys to the command line" + } + ], + serialize: function () { + return this.name != "map" ? [] : + array(mappings.userHives) + .filter(function (h) h.persist) + .map(function (hive) [ + { + command: "map", + options: array([ + hive.name !== "user" && ["-group", hive.name], + ["-modes", uniqueModes(map.modes)], + ["-description", map.description], + map.silent && ["-silent"]]) + + .filter(util.identity) + .toObject(), + arguments: [map.names[0]], + literalArg: map.rhs, + ignoreDefaults: true + } + for (map in userMappings(hive)) + if (map.persist) + ]) + .flatten().array; + } }; function userMappings(hive) { let seen = {}; @@ -629,6 +630,7 @@ var Mappings = Module("mappings", { dactyl.echoerr(_("map.noSuch", args[0])); }, { + identifier: "unmap", argCount: "?", bang: true, completer: opts.completer, diff --git a/common/modules/base.jsm b/common/modules/base.jsm index 758c34e5..50e08ffb 100644 --- a/common/modules/base.jsm +++ b/common/modules/base.jsm @@ -958,8 +958,9 @@ Class.prototype = { func.superapply(self, Array.slice(arguments, 1)); } } + try { - if ("value" in desc && i in this.localizedProperties) + if ("value" in desc && (k in this.localizedProperties || k in this.magicalProperties)) this[k] = desc.value; else Object.defineProperty(this, k, desc); @@ -967,7 +968,9 @@ Class.prototype = { catch (e) {} }, this); } - } + }, + + magicalProperties: {} }; Class.makeClosure = function makeClosure() { const self = this; diff --git a/common/modules/javascript.jsm b/common/modules/javascript.jsm index a3fb0592..2c215b8a 100644 --- a/common/modules/javascript.jsm +++ b/common/modules/javascript.jsm @@ -700,7 +700,8 @@ var JavaScript = Module("javascript", { modes.addMode("REPL", { description: "JavaScript Read Eval Print Loop", - bases: [modes.COMMAND_LINE] + bases: [modes.COMMAND_LINE], + displayName: "REPL" }); }, commandline: function initCommandLine(dactyl, modules, window) { diff --git a/common/modules/options.jsm b/common/modules/options.jsm index 9dfecffa..75c76159 100644 --- a/common/modules/options.jsm +++ b/common/modules/options.jsm @@ -78,6 +78,8 @@ var Option = Class("Option", { this.globalValue = this.defaultValue; }, + magicalProperties: Set(["cleanupValue"]), + /** * @property {string} This option's description, as shown in :listoptions. */ @@ -91,6 +93,13 @@ var Option = Class("Option", { get isDefault() this.stringValue === this.stringDefaultValue, + /** @property {value} The value to reset this option to at cleanup time. */ + get cleanupValue() options.cleanupPrefs.get(this.name), + set cleanupValue(value) { + if (options.cleanupPrefs.get(this.name) == null) + options.cleanupPrefs.set(this.name, value); + }, + /** @property {value} The option's global value. @see #scope */ get globalValue() { try { return options.store.get(this.name, {}).value; } catch (e) { util.reportError(e); throw e; } }, set globalValue(val) { options.store.set(this.name, { value: val, time: Date.now() }); }, @@ -282,8 +291,6 @@ var Option = Class("Option", { */ scope: 1, // Option.SCOPE_GLOBAL // XXX set to BOTH by default someday? - kstep - cleanupValue: null, - /** * @property {function(CompletionContext, Args)} This option's completer. * @see CompletionContext @@ -817,7 +824,7 @@ var Options = Module("options", { cleanup: function cleanup() { for (let opt in this) if (opt.cleanupValue != null) - opt.value = opt.parse(opt.cleanupValue); + opt.stringValue = opt.cleanupValue; }, /** @@ -883,6 +890,13 @@ var Options = Module("options", { setPref: deprecated("prefs.set", function setPref() prefs.set.apply(prefs, arguments)), withContext: deprecated("prefs.withContext", function withContext() prefs.withContext.apply(prefs, arguments)), + cleanupPrefs: Class.memoize(function () localPrefs.Branch("cleanup.option.")), + + cleanup: function cleanup(reason) { + if (~["disable", "uninstall"].indexOf(reason)) + this.cleanupPrefs.resetBranch(); + }, + /** * Returns the option with *name* in the specified *scope*. * diff --git a/common/modules/prefs.jsm b/common/modules/prefs.jsm index b546ce53..3fcaf4be 100644 --- a/common/modules/prefs.jsm +++ b/common/modules/prefs.jsm @@ -20,7 +20,7 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) RESTORE: "extensions.dactyl.restore.", INIT: {}, - init: function (branch, defaults) { + init: function init(branch, defaults) { this._prefContexts = []; this.branch = services.pref[defaults ? "getDefaultBranch" : "getBranch"](branch || ""); @@ -34,7 +34,7 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) this._observers = {}; }, - cleanup: function cleanup() { + cleanup: function cleanup(reason) { if (this.defaults != this) this.defaults.cleanup(); this._observers = {}; @@ -43,8 +43,25 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) this.observe.unregister(); delete this.observe; } + + if (reason == "uninstall") + localPrefs.resetBranch(); }, + /** + * Returns the full name of this object's preference branch. + */ + get root() this.branch.root, + + /** + * Returns a new Prefs instance for the sub-branch *branch* of this + * branch. + * + * @param {string} branch The branch to branch to. + * @returns {Prefs} + */ + Branch: function Branch(branch) Prefs(this.root + branch), + observe: null, observers: { "nsPref:changed": function (subject, data) { @@ -68,7 +85,7 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) * @param {function(object)} callback The callback, called with the * new value of the preference whenever it changes. */ - watch: function (pref, callback, strong) { + watch: function watch(pref, callback, strong) { if (!this.observe) { util.addObserver(this); this.branch.addObserver("", this, false); @@ -160,9 +177,9 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) * @param {string} branch The branch in which to search preferences. * @default "" */ - getNames: function (branch) this.branch.getChildList(branch || "", { value: 0 }), + getNames: function getNames(branch) this.branch.getChildList(branch || "", { value: 0 }), - _checkSafe: function (name, message, value) { + _checkSafe: function _checkSafe(name, message, value) { let curval = this.get(name, null); if (arguments.length > 2 && curval === value) return; @@ -184,7 +201,7 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) * @param {string} name The preference name. * @param {value} value The new preference value. */ - safeReset: function (name, message) { + safeReset: function safeReset(name, message) { this._checkSafe(name, message); this.reset(name); this.reset(this.SAVED + name); @@ -197,7 +214,7 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) * @param {string} name The preference name. * @param {value} value The new preference value. */ - safeSet: function (name, value, message, skipSave) { + safeSet: function safeSet(name, value, message, skipSave) { this._checkSafe(name, message, value); this.set(name, value); this[skipSave ? "reset" : "set"](this.SAVED + name, value); @@ -209,7 +226,7 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) * @param {string} name The preference name. * @param {value} value The new preference value. */ - set: function (name, value) { + set: function set(name, value) { if (this._prefContexts.length) this._prefContexts[this._prefContexts.length - 1][name] = this.get(name, null); @@ -242,6 +259,7 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) else throw FailedAssertion("Unknown preference type: " + typeof value + " (" + name + "=" + value + ")"); } + return value; }, /** @@ -250,7 +268,7 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) * * @param {string} name The preference to save. */ - save: function (name) { + save: function save(name) { let val = this.get(name); this.set(this.RESTORE + name, val); this.safeSet(name, val); @@ -262,7 +280,7 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) * @param {string} branch The branch from which to restore * preferences. @optional */ - restore: function (branch) { + restore: function restore(branch) { this.getNames(this.RESTORE + (branch || "")).forEach(function (pref) { this.safeSet(pref.substr(this.RESTORE.length), this.get(pref), null, true); this.reset(pref); @@ -274,19 +292,28 @@ var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) * * @param {string} name The preference name. */ - reset: function (name) { + reset: function reset(name) { try { this.branch.clearUserPref(name); } catch (e) {} // ignore - thrown if not a user set value }, + /** + * Resets the preference branch *branch* to its default value. + * + * @param {string} branch The preference name. @optional + */ + resetBranch: function resetBranch(branch) { + this.getNames(branch).forEach(this.closure.reset); + }, + /** * Toggles the value of the boolean preference *name*. * * @param {string} name The preference name. */ - toggle: function (name) { + toggle: function toggle(name) { util.assert(this.branch.getPrefType(name) === Ci.nsIPrefBranch.PREF_BOOL, _("error.trailingCharacters", name + "!")); this.set(name, !this.get(name)); diff --git a/common/modules/util.jsm b/common/modules/util.jsm index 6f86d5a3..7941ebf5 100644 --- a/common/modules/util.jsm +++ b/common/modules/util.jsm @@ -115,7 +115,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), * * @param {object} obj */ - addObserver: function (obj) { + addObserver: update(function addObserver(obj) { if (!obj.observers) obj.observers = obj.observe; @@ -137,7 +137,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), } catch (e) { if (typeof util === "undefined") - dump("dactyl: error: " + e + "\n" + (e.stack || Error().stack).replace(/^/gm, "dactyl: ")); + addObserver.dump("dactyl: error: " + e + "\n" + (e.stack || addObserver.Error().stack).replace(/^/gm, "dactyl: ")); else util.reportError(e); } @@ -145,7 +145,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), obj.observe.unregister = function () register("removeObserver"); register("addObserver"); - }, + }, { dump: dump, Error: Error }), /* * Tests a condition and throws a FailedAssertion error on @@ -1226,13 +1226,13 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), }, observers: { - "dactyl-cleanup-modules": function () { - defineModule.loadLog.push("dactyl: util: observe: dactyl-cleanup-modules"); + "dactyl-cleanup-modules": function (subject, reason) { + defineModule.loadLog.push("dactyl: util: observe: dactyl-cleanup-modules " + reason); for (let module in values(defineModule.modules)) if (module.cleanup) { util.dump("cleanup: " + module.constructor.className); - util.trapErrors(module.cleanup, module); + util.trapErrors(module.cleanup, module, reason); } JSMLoader.cleanup(); @@ -1256,6 +1256,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), "dactyl-purge": function () { this.rehashing = 1; }, + "toplevel-window-ready": function (window, data) { window.addEventListener("DOMContentLoaded", wrapCallback(function listener(event) { if (event.originalTarget === window.document) {