]> git.donarmstrong.com Git - dactyl.git/blobdiff - common/content/dactyl.js
Import r6923 from upstream hg supporting Firefox up to 22.0a1
[dactyl.git] / common / content / dactyl.js
index 6296b9ac6d26a5db35ccee446d4ecd3c606de94d..02ce9bd177eec85c419f7875c35bdd8caac1e2be 100644 (file)
@@ -1,6 +1,6 @@
 // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
 // Copyright (c) 2007-2011 by Doug Kearns <dougkearns@gmail.com>
-// Copyright (c) 2008-2011 by Kris Maglione <maglione.k@gmail.com>
+// Copyright (c) 2008-2012 Kris Maglione <maglione.k@gmail.com>
 //
 // This work is licensed for reuse under an MIT license. Details are
 // given in the LICENSE.txt file included with this file.
@@ -8,13 +8,6 @@
 
 /** @scope modules */
 
-default xml namespace = XHTML;
-XML.ignoreWhitespace = false;
-XML.prettyPrinting = false;
-
-var userContext = { __proto__: modules };
-var _userContext = newContext(userContext);
-
 var EVAL_ERROR = "__dactyl_eval_error";
 var EVAL_RESULT = "__dactyl_eval_result";
 var EVAL_STRING = "__dactyl_eval_string";
@@ -23,7 +16,8 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
     init: function () {
         window.dactyl = this;
         // cheap attempt at compatibility
-        let prop = { get: deprecated("dactyl", function liberator() dactyl) };
+        let prop = { get: deprecated("dactyl", function liberator() dactyl),
+                     configurable: true };
         Object.defineProperty(window, "liberator", prop);
         Object.defineProperty(modules, "liberator", prop);
         this.commands = {};
@@ -32,46 +26,77 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
         this._observers = {};
         util.addObserver(this);
 
-        this.commands["dactyl.help"] = function (event) {
-            let elem = event.originalTarget;
-            dactyl.help(elem.getAttribute("tag") || elem.textContent);
-        };
         this.commands["dactyl.restart"] = function (event) {
             dactyl.restart();
         };
 
         styles.registerSheet("resource://dactyl-skin/dactyl.css");
+
+        this.cleanups = [];
+        this.cleanups.push(overlay.overlayObject(window, {
+            focusAndSelectUrlBar: function focusAndSelectUrlBar() {
+                switch (options.get("strictfocus").getKey(document.documentURIObject || util.newURI(document.documentURI), "moderate")) {
+                case "laissez-faire":
+                    if (!Events.isHidden(window.gURLBar, true))
+                        return focusAndSelectUrlBar.superapply(this, arguments);
+                default:
+                    // Evil. Ignore.
+                }
+            }
+        }));
     },
 
     cleanup: function () {
+        for (let cleanup in values(this.cleanups))
+            cleanup.call(this);
+
         delete window.dactyl;
         delete window.liberator;
 
+        // Prevents box ordering bugs after our stylesheet is removed.
+        styles.system.add("cleanup-sheet", config.styleableChrome, literal(/*
+            #TabsToolbar tab { display: none; }
+        */));
         styles.unregisterSheet("resource://dactyl-skin/dactyl.css");
+        DOM('#TabsToolbar tab', document).style.display;
     },
 
     destroy: function () {
+        this.observe.unregister();
         autocommands.trigger("LeavePre", {});
         dactyl.triggerObserver("shutdown", null);
         util.dump("All dactyl modules destroyed\n");
         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);
+            modules.moduleManager.initDependencies("cleanup");
 
             for (let name in values(Object.getOwnPropertyNames(modules).reverse()))
                 try {
@@ -82,23 +107,17 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
         }
     },
 
-    /** @property {string} The name of the current user profile. */
-    profileName: Class.memoize(function () {
-        // NOTE: services.profile.selectedProfile.name doesn't return
-        // what you might expect. It returns the last _actively_ selected
-        // profile (i.e. via the Profile Manager or -P option) rather than the
-        // current profile. These will differ if the current process was run
-        // without explicitly selecting a profile.
-
-        let dir = services.directory.get("ProfD", Ci.nsIFile);
-        for (let prof in iter(services.profile.profiles))
-            if (prof.QueryInterface(Ci.nsIToolkitProfile).rootDir.path === dir.path)
-                return prof.name;
-        return "unknown";
-    }),
+    signals: {
+        "io.source": function ioSource(context, file, modTime) {
+            if (contexts.getDocs(context))
+                help.flush("help/plugins.xml", modTime);
+        }
+    },
+
+    profileName: deprecated("config.profileName", { get: function profileName() config.profileName }),
 
     /**
-     * @property {number} The current main mode.
+     * @property {Modes.Mode} The current main mode.
      * @see modes#mainModes
      */
     mode: deprecated("modes.main", {
@@ -106,7 +125,37 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
         set: function mode(val) modes.main = val
     }),
 
-    get menuItems() Dactyl.getMenuItems(),
+    getMenuItems: function getMenuItems(targetPath) {
+        function addChildren(node, parent) {
+            DOM(node).createContents();
+
+            if (~["menu", "menupopup"].indexOf(node.localName) && node.children.length)
+                DOM(node).popupshowing({ bubbles: false });
+
+            for (let [, item] in Iterator(node.childNodes)) {
+                if (item.childNodes.length == 0 && item.localName == "menuitem"
+                    && !item.hidden
+                    && !/rdf:http:/.test(item.getAttribute("label"))) { // FIXME
+                    item.dactylPath = parent + item.getAttribute("label");
+                    if (!targetPath || targetPath.indexOf(item.dactylPath) == 0)
+                        items.push(item);
+                }
+                else {
+                    let path = parent;
+                    if (item.localName == "menu")
+                        path += item.getAttribute("label") + ".";
+                    if (!targetPath || targetPath.indexOf(path) == 0)
+                        addChildren(item, path);
+                }
+            }
+        }
+
+        let items = [];
+        addChildren(document.getElementById(config.guioptions["m"][1]), "");
+        return items;
+    },
+
+    get menuItems() this.getMenuItems(),
 
     // Global constants
     CURRENT_TAB: "here",
@@ -114,8 +163,17 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
     NEW_BACKGROUND_TAB: "background-tab",
     NEW_WINDOW: "window",
 
-    forceNewTab: false,
-    forceNewWindow: false,
+    forceBackground: null,
+    forcePrivate: null,
+    forceTarget: null,
+
+    get forceOpen() ({ background: this.forceBackground,
+                       target: this.forceTarget }),
+    set forceOpen(val) {
+        for (let [k, v] in Iterator({ background: "forceBackground", target: "forceTarget" }))
+            if (k in val)
+                this[v] = val[k];
+    },
 
     version: deprecated("config.version", { get: function version() config.version }),
 
@@ -144,7 +202,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
     registerObserver: function registerObserver(type, callback, weak) {
         if (!(type in this._observers))
             this._observers[type] = [];
-        this._observers[type].push(weak ? Cu.getWeakReference(callback) : { get: function () callback });
+        this._observers[type].push(weak ? util.weakReference(callback) : { get: function () callback });
     },
 
     registerObservers: function registerObservers(obj, prop) {
@@ -157,7 +215,6 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
             this._observers[type] = this._observers[type].filter(function (c) c.get() != callback);
     },
 
-    // TODO: "zoom": if the zoom value of the current buffer changed
     applyTriggerObserver: function triggerObserver(type, args) {
         if (type in this._observers)
             this._observers[type] = this._observers[type].filter(function (callback) {
@@ -184,14 +241,17 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
     },
 
     addUsageCommand: function (params) {
+        function keys(item) (item.names || [item.name]).concat(item.description, item.columns || []);
+
         let name = commands.add(params.name, params.description,
             function (args) {
                 let results = array(params.iterate(args))
                     .sort(function (a, b) String.localeCompare(a.name, b.name));
 
-                let filters = args.map(function (arg) RegExp("\\b" + util.regexp.escape(arg) + "\\b", "i"));
+                let filters = args.map(function (arg) let (re = util.regexp.escape(arg))
+                                        util.regexp("\\b" + re + "\\b|(?:^|[()\\s])" + re + "(?:$|[()\\s])", "i"));
                 if (filters.length)
-                    results = results.filter(function (item) filters.every(function (re) re.test(item.name + " " + item.description)));
+                    results = results.filter(function (item) filters.every(function (re) keys(item).some(re.closure.test)));
 
                 commandline.commandOutput(
                     template.usage(results, params.format));
@@ -200,11 +260,12 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                 argCount: "*",
                 completer: function (context, args) {
                     context.keys.text = util.identity;
-                    context.keys.description = function () seen[this.text] + " matching items";
+                    context.keys.description = function () seen[this.text] + /*L*/" matching items";
+                    context.ignoreCase = true;
                     let seen = {};
-                    context.completions = array(item.description.toLowerCase().split(/[()\s]+/)
+                    context.completions = array(keys(item).join(" ").toLowerCase().split(/[()\s]+/)
                                                 for (item in params.iterate(args)))
-                        .flatten().filter(function (w) /^\w[\w-_']+$/.test(w))
+                        .flatten()
                         .map(function (k) {
                             seen[k] = (seen[k] || 0) + 1;
                             return k;
@@ -218,11 +279,11 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                 let results = array((params.iterateIndex || params.iterate).call(params, commands.get(name).newArgs()))
                         .array.sort(function (a, b) String.localeCompare(a.name, b.name));
 
-                let tags = services["dactyl:"].HELP_TAGS;
+                let haveTag = Set.has(help.tags);
                 for (let obj in values(results)) {
                     let res = dactyl.generateHelp(obj, null, null, true);
-                    if (!set.has(tags, obj.helpTag))
-                        res[1].@tag = obj.helpTag;
+                    if (!haveTag(obj.helpTag))
+                        res[0][1].tag = obj.helpTag;
 
                     yield res;
                 }
@@ -241,20 +302,15 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                 bell: document.getElementById("dactyl-bell"),
                 strut: document.getElementById("dactyl-bell-strut")
             };
-            XML.ignoreWhitespace = true;
             if (!elems.bell)
-                util.overlayWindow(window, {
+                overlay.overlayWindow(window, {
                     objects: elems,
-                    prepend: <>
-                        <window id={document.documentElement.id} xmlns={XUL}>
-                            <hbox style="display: none" highlight="Bell" id="dactyl-bell" key="bell"/>
-                        </window>
-                    </>,
-                    append: <>
-                        <window id={document.documentElement.id} xmlns={XUL}>
-                            <hbox style="display: none" highlight="Bell" id="dactyl-bell-strut" key="strut"/>
-                        </window>
-                    </>
+                    prepend: [
+                        ["window", { id: document.documentElement.id, xmlns: "xul" },
+                            ["hbox", { style: "display: none",  highlight: "Bell", id: "dactyl-bell", key: "bell" }]]],
+                    append: [
+                        ["window", { id: document.documentElement.id, xmlns: "xul" },
+                            ["hbox", { style: "display: none", highlight: "Bell", id: "dactyl-bell-strut", key: "strut" }]]]
                 }, elems);
 
             elems.bell.style.height = window.innerHeight + "px";
@@ -275,16 +331,20 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
      * This is same as Firefox's readFromClipboard function, but is needed for
      * apps like Thunderbird which do not provide it.
      *
+     * @param {string} which Which clipboard to write to. Either
+     *     "global" or "selection". If not provided, both clipboards are
+     *     updated.
+     *     @optional
      * @returns {string}
      */
-    clipboardRead: function clipboardRead(getClipboard) {
+    clipboardRead: function clipboardRead(which) {
         try {
-            const clipboard = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
-            const transferable = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable);
+            const { clipboard } = services;
 
+            let transferable = services.Transferable();
             transferable.addDataFlavor("text/unicode");
 
-            let source = clipboard[getClipboard || !clipboard.supportsSelectionClipboard() ?
+            let source = clipboard[which == "global" || !clipboard.supportsSelectionClipboard() ?
                                    "kGlobalClipboard" : "kSelectionClipboard"];
             clipboard.getData(transferable, source);
 
@@ -303,15 +363,22 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
      * Copies a string to the system clipboard. If *verbose* is specified the
      * copied string is also echoed to the command line.
      *
-     * @param {string} str
-     * @param {boolean} verbose
+     * @param {string} str The string to write.
+     * @param {boolean} verbose If true, the user is notified of the copied data.
+     * @param {string} which Which clipboard to write to. Either
+     *     "global" or "selection". If not provided, both clipboards are
+     *     updated.
+     *     @optional
      */
-    clipboardWrite: function clipboardWrite(str, verbose) {
-        const clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
-        clipboardHelper.copyString(str);
+    clipboardWrite: function clipboardWrite(str, verbose, which) {
+        if (which == null || which == "selection" && !services.clipboard.supportsSelectionClipboard())
+            services.clipboardHelper.copyString(str);
+        else
+            services.clipboardHelper.copyStringToClipboard(str,
+                services.clipboard["k" + util.capitalize(which) + "Clipboard"]);
 
         if (verbose) {
-            let message = { message: "Yanked " + str };
+            let message = { message: _("dactyl.yank", str) };
             try {
                 message.domains = [util.newURI(str).host];
             }
@@ -346,12 +413,14 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
     echoerr: function echoerr(str, flags) {
         flags |= commandline.APPEND_TO_MESSAGES;
 
-        if (isinstance(str, ["Error", "Exception"]))
+        if (isinstance(str, ["DOMException", "Error", "Exception", ErrorBase])
+                || isinstance(str, ["XPCWrappedNative_NoHelper"]) && /^\[Exception/.test(str))
             dactyl.reportError(str);
+
         if (isObject(str) && "echoerr" in str)
             str = str.echoerr;
         else if (isinstance(str, ["Error", FailedAssertion]) && str.fileName)
-            str = <>{str.fileName.replace(/^.* -> /, "")}: {str.lineNumber}: {str}</>;
+            str = [str.fileName.replace(/^.* -> /, ""), ": ", str.lineNumber, ": ", str].join("");
 
         if (options["errorbells"])
             dactyl.beep();
@@ -399,56 +468,63 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
      * @param {Object} context The context object into which the script
      *     should be loaded.
      */
-    loadScript: function (uri, context) {
+    loadScript: function loadScript(uri, context) {
         JSMLoader.loadSubScript(uri, context, File.defaultEncoding);
     },
 
-    userEval: function (str, context, fileName, lineNumber) {
+    userEval: function userEval(str, context, fileName, lineNumber) {
         let ctxt;
-        if (jsmodules.__proto__ != window)
+        if (jsmodules.__proto__ != window && jsmodules.__proto__ != XPCNativeWrapper(window) &&
+                jsmodules.isPrototypeOf(context))
             str = "with (window) { with (modules) { (this.eval || eval)(" + str.quote() + ") } }";
 
         let info = contexts.context;
         if (fileName == null)
-            if (info && info.file[0] !== "[")
+            if (info)
                 ({ file: fileName, line: lineNumber, context: ctxt }) = info;
 
-        if (!context && fileName && fileName[0] !== "[")
-            context = _userContext || ctxt;
+        if (fileName && fileName[0] == "[")
+            fileName = "dactyl://command-line/";
+        else if (!context)
+            context = ctxt || _userContext;
 
         if (isinstance(context, ["Sandbox"]))
             return Cu.evalInSandbox(str, context, "1.8", fileName, lineNumber);
-        else
-            try {
-                if (!context)
-                    context = userContext || ctxt;
-
-                context[EVAL_ERROR] = null;
-                context[EVAL_STRING] = str;
-                context[EVAL_RESULT] = null;
-                this.loadScript("resource://dactyl-content/eval.js", context);
-                if (context[EVAL_ERROR]) {
-                    try {
-                        context[EVAL_ERROR].fileName = info.file;
-                        context[EVAL_ERROR].lineNumber += info.line;
-                    }
-                    catch (e) {}
-                    throw context[EVAL_ERROR];
+
+        if (!context)
+            context = userContext || ctxt;
+
+        if (services.has("dactyl") && services.dactyl.evalInContext)
+            return services.dactyl.evalInContext(str, context, fileName, lineNumber);
+
+        try {
+            context[EVAL_ERROR] = null;
+            context[EVAL_STRING] = str;
+            context[EVAL_RESULT] = null;
+
+            this.loadScript("resource://dactyl-content/eval.js", context);
+            if (context[EVAL_ERROR]) {
+                try {
+                    context[EVAL_ERROR].fileName = info.file;
+                    context[EVAL_ERROR].lineNumber += info.line;
                 }
-                return context[EVAL_RESULT];
-            }
-            finally {
-                delete context[EVAL_ERROR];
-                delete context[EVAL_RESULT];
-                delete context[EVAL_STRING];
+                catch (e) {}
+                throw context[EVAL_ERROR];
             }
+            return context[EVAL_RESULT];
+        }
+        finally {
+            delete context[EVAL_ERROR];
+            delete context[EVAL_RESULT];
+            delete context[EVAL_STRING];
+        }
     },
 
     /**
      * Acts like the Function builtin, but the code executes in the
      * userContext global.
      */
-    userFunc: function () {
+    userFunc: function userFunc() {
         return this.userEval(
             "(function userFunction(" + Array.slice(arguments, 0, -1).join(", ") + ")" +
             " { " + arguments[arguments.length - 1] + " })");
@@ -463,7 +539,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
      * @param {boolean} silent Whether the command should be echoed on the
      *     command line.
      */
-    execute: function (str, modifiers, silent) {
+    execute: function execute(str, modifiers, silent) {
         // skip comments and blank lines
         if (/^\s*("|$)/.test(str))
             return;
@@ -483,19 +559,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
     },
 
     focus: function focus(elem, flags) {
-        flags = flags || services.focus.FLAG_BYMOUSE;
-        try {
-            if (elem instanceof Document)
-                elem = elem.defaultView;
-            if (elem instanceof Element)
-                services.focus.setFocus(elem, flags);
-            else if (elem instanceof Window)
-                services.focus.focusedWindow = elem;
-        }
-        catch (e) {
-            util.dump(elem);
-            util.reportError(e);
-        }
+        DOM(elem).focus(flags);
     },
 
     /**
@@ -559,34 +623,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
      * @param {string} feature The feature name.
      * @returns {boolean}
      */
-    has: function (feature) set.has(config.features, feature),
-
-    /**
-     * Returns the URL of the specified help *topic* if it exists.
-     *
-     * @param {string} topic The help topic to look up.
-     * @param {boolean} consolidated Whether to search the consolidated help page.
-     * @returns {string}
-     */
-    findHelp: function (topic, consolidated) {
-        if (!consolidated && topic in services["dactyl:"].FILE_MAP)
-            return topic;
-        let items = completion._runCompleter("help", topic, null, !!consolidated).items;
-        let partialMatch = null;
-
-        function format(item) item.description + "#" + encodeURIComponent(item.text);
-
-        for (let [i, item] in Iterator(items)) {
-            if (item.text == topic)
-                return format(item);
-            else if (!partialMatch && topic)
-                partialMatch = item;
-        }
-
-        if (partialMatch)
-            return format(partialMatch);
-        return null;
-    },
+    has: function has(feature) Set.has(config.features, feature),
 
     /**
      * @private
@@ -603,339 +640,20 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
         }
     },
 
+    help: deprecated("help.help", { get: function help() modules.help.closure.help }),
+    findHelp: deprecated("help.findHelp", { get: function findHelp() help.closure.findHelp }),
+
     /**
      * @private
      * Initialize the help system.
      */
-    initHelp: function (force) {
-        if (force || !this.helpInitialized) {
-            if ("noscriptOverlay" in window) {
-                noscriptOverlay.safeAllow("chrome-data:", true, false);
-                noscriptOverlay.safeAllow("dactyl:", true, false);
-            }
-
-            // Find help and overlay files with the given name.
-            let findHelpFile = function findHelpFile(file) {
-                let result = [];
-                for (let [, namespace] in Iterator(namespaces)) {
-                    let url = ["dactyl://", namespace, "/", file, ".xml"].join("");
-                    let res = util.httpGet(url);
-                    if (res) {
-                        if (res.responseXML.documentElement.localName == "document")
-                            fileMap[file] = url;
-                        if (res.responseXML.documentElement.localName == "overlay")
-                            overlayMap[file] = url;
-                        result.push(res.responseXML);
-                    }
-                }
-                return result;
-            };
-            // Find the tags in the document.
-            let addTags = function addTags(file, doc) {
-                for (let elem in util.evaluateXPath("//@tag|//dactyl:tags/text()|//dactyl:tag/text()", doc))
-                    for (let tag in values((elem.value || elem.textContent).split(/\s+/)))
-                        tagMap[tag] = file;
-            };
-
-            let namespaces = ["locale-local", "locale"];
-            services["dactyl:"].init({});
-
-            let tagMap = services["dactyl:"].HELP_TAGS;
-            let fileMap = services["dactyl:"].FILE_MAP;
-            let overlayMap = services["dactyl:"].OVERLAY_MAP;
-
-            // Scrape the list of help files from all.xml
-            // Manually process main and overlay files, since XSLTProcessor and
-            // XMLHttpRequest don't allow access to chrome documents.
-            tagMap["all"] = tagMap["all.xml"] = "all";
-            tagMap["versions"] = tagMap["versions.xml"] = "versions";
-            let files = findHelpFile("all").map(function (doc)
-                    [f.value for (f in util.evaluateXPath("//dactyl:include/@href", doc))]);
-
-            // Scrape the tags from the rest of the help files.
-            array.flatten(files).forEach(function (file) {
-                tagMap[file + ".xml"] = file;
-                findHelpFile(file).forEach(function (doc) {
-                    addTags(file, doc);
-                });
-            });
-
-            // Process plugin help entries.
-            XML.ignoreWhiteSpace = XML.prettyPrinting = false;
-
-            let body = XML();
-            for (let [, context] in Iterator(plugins.contexts))
-                if (context && context.INFO instanceof XML) {
-                    let info = context.INFO;
-                    if (info.*.@lang.length()) {
-                        let lang = config.bestLocale(String(a) for each (a in info.*.@lang));
-
-                        info.* = info.*.(function::attribute("lang").length() == 0 || @lang == lang);
-
-                        for each (let elem in info.NS::info)
-                            for each (let attr in ["@name", "@summary", "@href"])
-                                if (elem[attr].length())
-                                    info[attr] = elem[attr];
-                    }
-                    body += <h2 xmlns={NS.uri} tag={context.INFO.@name + '-plugin'}>{context.INFO.@summary}</h2> +
-                        context.INFO;
-                }
-
-            let help =
-                '<?xml version="1.0"?>\n' +
-                '<?xml-stylesheet type="text/xsl" href="dactyl://content/help.xsl"?>\n' +
-                '<!DOCTYPE document SYSTEM "resource://dactyl-content/dactyl.dtd">\n' +
-                unescape(encodeURI( // UTF-8 handling hack.
-                <document xmlns={NS}
-                    name="plugins" title={config.appName + " Plugins"}>
-                    <h1 tag="using-plugins">Using Plugins</h1>
-                    <toc start="2"/>
-
-                    {body}
-                </document>.toXMLString()));
-            fileMap["plugins"] = function () ['text/xml;charset=UTF-8', help];
-
-            fileMap["versions"] = function () {
-                let NEWS = util.httpGet(config.addon.getResourceURI("NEWS").spec,
-                                        { mimeType: "text/plain;charset=UTF-8" })
-                               .responseText;
-
-                let re = util.regexp(<![CDATA[
-                      ^ (?P<comment> \s* # .*\n)
-
-                    | ^ (?P<space> \s*)
-                        (?P<char>  [-•*+]) \ //
-                      (?P<content> .*\n
-                         (?: \2\ \ .*\n | \s*\n)* )
-
-                    | (?P<par>
-                          (?: ^ [^\S\n]*
-                              (?:[^-•*+\s] | [-•*+]\S)
-                              .*\n
-                          )+
-                      )
-
-                    | (?: ^ [^\S\n]* \n) +
-                ]]>, "gmxy");
-
-                let betas = util.regexp(/\[(b\d)\]/, "gx");
-
-                let beta = array(betas.iterate(NEWS))
-                            .map(function (m) m[1]).uniq().slice(-1)[0];
-
-                default xml namespace = NS;
-                function rec(text, level, li) {
-                    let res = <></>;
-                    let list, space, i = 0;
-
-                    for (let match in re.iterate(text)) {
-                        if (match.comment)
-                            continue;
-                        else if (match.char) {
-                            if (!list)
-                                res += list = <ul/>;
-                            let li = <li/>;
-                            li.* += rec(match.content.replace(RegExp("^" + match.space, "gm"), ""), level + 1, li)
-                            list.* += li;
-                        }
-                        else if (match.par) {
-                            let [, par, tags] = /([^]*?)\s*((?:\[[^\]]+\])*)\n*$/.exec(match.par);
-                            let t = tags;
-                            tags = array(betas.iterate(tags)).map(function (m) m[1]);
-
-                            let group = !tags.length                       ? "" :
-                                        !tags.some(function (t) t == beta) ? "HelpNewsOld" : "HelpNewsNew";
-                            if (i === 0 && li) {
-                                li.@highlight = group;
-                                group = "";
-                            }
-
-                            list = null;
-                            if (level == 0 && /^.*:\n$/.test(match.par))
-                                res += <h2>{template.linkifyHelp(par.slice(0, -1), true)}</h2>;
-                            else {
-                                let [, a, b] = /^(IMPORTANT:?)?([^]*)/.exec(par);
-                                res += <p highlight={group + " HelpNews"}>{
-                                    !tags.length ? "" :
-                                    <hl key="HelpNewsTag">{tags.join(" ")}</hl>
-                                }{
-                                    a ? <hl key="HelpWarning">{a}</hl> : ""
-                                }{
-                                    template.linkifyHelp(b, true)
-                                }</p>;
-                            }
-                        }
-                        i++;
-                    }
-                    for each (let attr in res..@highlight) {
-                        attr.parent().@NS::highlight = attr;
-                        delete attr.parent().@highlight;
-                    }
-                    return res;
-                }
-
-                let body = rec(NEWS, 0);
-                for each (let li in body..li) {
-                    let list = li..li.(@NS::highlight == "HelpNewsOld");
-                    if (list.length() && list.length() == li..li.(@NS::highlight != "").length()) {
-                        for each (let li in list)
-                            li.@NS::highlight = "";
-                        li.@NS::highlight = "HelpNewsOld";
-                    }
-                }
-
-                XML.prettyPrinting = XML.ignoreWhitespace = false;
-                return ["application/xml",
-                    '<?xml version="1.0"?>\n' +
-                    '<?xml-stylesheet type="text/xsl" href="dactyl://content/help.xsl"?>\n' +
-                    '<!DOCTYPE document SYSTEM "resource://dactyl-content/dactyl.dtd">\n' +
-                    unescape(encodeURI( // UTF-8 handling hack.
-                    <document xmlns={NS} xmlns:dactyl={NS}
-                        name="versions" title={config.appName + " Versions"}>
-                        <h1 tag="versions news NEWS">{config.appName} Versions</h1>
-                        <toc start="2"/>
-
-                        {body}
-                    </document>.toXMLString()))
-                ];
-            }
-            addTags("versions", util.httpGet("dactyl://help/versions").responseXML);
-            addTags("plugins", util.httpGet("dactyl://help/plugins").responseXML);
-
-            default xml namespace = NS;
-
-            overlayMap["index"] = ['text/xml;charset=UTF-8',
-                '<?xml version="1.0"?>\n' +
-                '<overlay xmlns="' + NS + '">\n' +
-                unescape(encodeURI( // UTF-8 handling hack.
-                template.map(dactyl.indices, function ([name, iter])
-                    <dl insertafter={name + "-index"}>{
-                        template.map(iter(), util.identity)
-                    }</dl>, <>{"\n\n"}</>))) +
-                '\n</overlay>'];
+    initHelp: function initHelp() {
+        if ("noscriptOverlay" in window)
+            window.noscriptOverlay.safeAllow("dactyl:", true, false);
 
-            addTags("index", util.httpGet("dactyl://help-overlay/index").responseXML);
-
-            this.helpInitialized = true;
-        }
+        help.initialize();
     },
 
-    stringifyXML: function (xml) {
-        XML.prettyPrinting = false;
-        XML.ignoreWhitespace = false;
-        return UTF8(xml.toXMLString());
-    },
-
-    exportHelp: JavaScript.setCompleter(function (path) {
-        const FILE = io.File(path);
-        const PATH = FILE.leafName.replace(/\..*/, "") + "/";
-        const TIME = Date.now();
-
-        if (!FILE.exists() && (/\/$/.test(path) && !/\./.test(FILE.leafName)))
-            FILE.create(FILE.DIRECTORY_TYPE, octal(755));
-
-        dactyl.initHelp();
-        if (FILE.isDirectory()) {
-            var addDataEntry = function addDataEntry(file, data) FILE.child(file).write(data);
-            var addURIEntry  = function addURIEntry(file, uri) addDataEntry(file, util.httpGet(uri).responseText);
-        }
-        else {
-            var zip = services.ZipWriter();
-            zip.open(FILE, File.MODE_CREATE | File.MODE_WRONLY | File.MODE_TRUNCATE);
-
-            addURIEntry = function addURIEntry(file, uri)
-                zip.addEntryChannel(PATH + file, TIME, 9,
-                    services.io.newChannel(uri, null, null), false);
-            addDataEntry = function addDataEntry(file, data) // Unideal to an extreme.
-                addURIEntry(file, "data:text/plain;charset=UTF-8," + encodeURI(data));
-        }
-
-        let empty = set("area base basefont br col frame hr img input isindex link meta param"
-                            .split(" "));
-        function fix(node) {
-            switch(node.nodeType) {
-                case Node.ELEMENT_NODE:
-                    if (isinstance(node, [HTMLBaseElement]))
-                        return;
-
-                    data.push("<"); data.push(node.localName);
-                    if (node instanceof HTMLHtmlElement)
-                        data.push(" xmlns=" + XHTML.uri.quote());
-
-                    for (let { name, value } in array.iterValues(node.attributes)) {
-                        if (name == "dactyl:highlight") {
-                            set.add(styles, value);
-                            name = "class";
-                            value = "hl-" + value;
-                        }
-                        if (name == "href") {
-                            value = node.href;
-                            if (value.indexOf("dactyl://help-tag/") == 0) {
-                                let uri = services.io.newChannel(value, null, null).originalURI;
-                                value = uri.spec == value ? "javascript:;" : uri.path.substr(1);
-                            }
-                            if (!/^#|[\/](#|$)|^[a-z]+:/.test(value))
-                                value = value.replace(/(#|$)/, ".xhtml$1");
-                        }
-                        if (name == "src" && value.indexOf(":") > 0) {
-                            chromeFiles[value] = value.replace(/.*\//, "");
-                            value = value.replace(/.*\//, "");
-                        }
-                        data.push(" ");
-                        data.push(name);
-                        data.push('="');
-                        data.push(<>{value}</>.toXMLString());
-                        data.push('"');
-                    }
-                    if (node.localName in empty)
-                        data.push(" />");
-                    else {
-                        data.push(">");
-                        if (node instanceof HTMLHeadElement)
-                            data.push(<link rel="stylesheet" type="text/css" href="help.css"/>.toXMLString());
-                        Array.map(node.childNodes, fix);
-                        data.push("</"); data.push(node.localName); data.push(">");
-                    }
-                    break;
-                case Node.TEXT_NODE:
-                    data.push(<>{node.textContent}</>.toXMLString());
-            }
-        }
-
-        let chromeFiles = {};
-        let styles = {};
-        for (let [file, ] in Iterator(services["dactyl:"].FILE_MAP)) {
-            dactyl.open("dactyl://help/" + file);
-            dactyl.modules.events.waitForPageLoad();
-            let data = [
-                '<?xml version="1.0" encoding="UTF-8"?>\n',
-                '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"\n',
-                '          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n'
-            ];
-            fix(content.document.documentElement);
-            addDataEntry(file + ".xhtml", data.join(""));
-        }
-
-        let data = [h for (h in highlight) if (set.has(styles, h.class) || /^Help/.test(h.class))]
-            .map(function (h) h.selector
-                               .replace(/^\[.*?=(.*?)\]/, ".hl-$1")
-                               .replace(/html\|/g, "") + "\t" + "{" + h.cssText + "}")
-            .join("\n");
-        addDataEntry("help.css", data.replace(/chrome:[^ ")]+\//g, ""));
-
-        addDataEntry("tag-map.json", JSON.stringify(services["dactyl:"].HELP_TAGS));
-
-        let m, re = /(chrome:[^ ");]+\/)([^ ");]+)/g;
-        while ((m = re.exec(data)))
-            chromeFiles[m[0]] = m[2];
-
-        for (let [uri, leaf] in Iterator(chromeFiles))
-            addURIEntry(leaf, uri);
-
-        if (zip)
-            zip.close();
-    }, [function (context, args) completion.file(context)]),
-
     /**
      * Generates a help entry and returns it as a string.
      *
@@ -945,117 +663,94 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
      * @returns {string}
      */
     generateHelp: function generateHelp(obj, extraHelp, str, specOnly) {
-        default xml namespace = "";
-
         let link, tag, spec;
         link = tag = spec = util.identity;
         let args = null;
 
         if (obj instanceof Command) {
-            link = function (cmd) <ex>{cmd}</ex>;
+            link = function (cmd) ["ex", {}, cmd];
             args = obj.parseArgs("", CompletionContext(str || ""));
-            spec = function (cmd) cmd + (obj.bang ? <oa>!</oa> : <></>);
+            tag  = function (cmd) DOM.DOMString(":" + cmd);
+            spec = function (cmd) [
+                obj.count ? ["oa", {}, "count"] : [],
+                cmd,
+                obj.bang ? ["oa", {}, "!"] : []
+            ];
         }
         else if (obj instanceof Map) {
-            spec = function (map) obj.count ? <><oa>count</oa>{map}</> : <>{map}</>;
+            spec = function (map) obj.count ? [["oa", {}, "count"], map] : DOM.DOMString(map);
+            tag = function (map) [
+                let (c = obj.modes[0].char) c ? c + "_" : "",
+                map
+            ]
             link = function (map) {
                 let [, mode, name, extra] = /^(?:(.)_)?(?:<([^>]+)>)?(.*)$/.exec(map);
-                let k = <k>{extra}</k>;
+                let k = ["k", {}, extra];
                 if (name)
-                    k.@name = name;
+                    k[1].name = name;
                 if (mode)
-                    k.@mode = mode;
+                    k[1].mode = mode;
                 return k;
             };
         }
         else if (obj instanceof Option) {
-            link = function (opt, name) <o>{name}</o>;
+            spec = function () template.map(obj.names, tag, " ");
+            tag = function (name) DOM.DOMString("'" + name + "'");
+            link = function (opt, name) ["o", {}, name];
+            args = { value: "", values: [] };
         }
 
-        XML.prettyPrinting = false;
-        XML.ignoreWhitespace = false;
-        default xml namespace = NS;
-
-        // E4X has its warts.
-        let br = <>
-                    </>;
-
-        let res = <res>
-                <dt>{link(obj.helpTag || obj.name, obj.name)}</dt> <dd>{
-                    template.linkifyHelp(obj.description ? obj.description.replace(/\.$/, "") : "", true)
-                }</dd></res>;
+        let res = [
+                ["dt", {}, link(obj.helpTag || tag(obj.name), obj.name)],
+                ["dd", {},
+                    template.linkifyHelp(obj.description ? obj.description.replace(/\.$/, "") : "", true)]];
         if (specOnly)
-            return res.elements();
-
-        res.* += <>
-            <item>
-                <tags>{template.map(obj.names.slice().reverse(), tag, " ")}</tags>
-                <spec>{
-                    spec(template.highlightRegexp((obj.specs || obj.names)[0],
-                                                  /\[(.*?)\]/g,
-                                                  function (m, n0) <oa>{n0}</oa>))
-                }</spec>{
-                !obj.type ? "" : <>
-                <type>{obj.type}</type>
-                <default>{obj.stringDefaultValue}</default></>}
-                <description>{
-                    obj.description ? br + <p>{template.linkifyHelp(obj.description.replace(/\.?$/, "."), true)}</p> : "" }{
-                        extraHelp ? br + extraHelp : "" }{
-                        !(extraHelp || obj.description) ? br + <p>Sorry, no help available.</p> : "" }
-                </description>
-            </item></>;
+            return res;
+
+        let description = ["description", {},
+            obj.description ? ["p", {}, template.linkifyHelp(obj.description.replace(/\.?$/, "."), true)] : "",
+            extraHelp ? extraHelp : "",
+            !(extraHelp || obj.description) ? ["p", {}, /*L*/ "Sorry, no help available."] : ""]
+
+        res.push(
+            ["item", {},
+                ["tags", {}, template.map(obj.names.slice().reverse(),
+                                          tag,
+                                          " ").join("")],
+                ["spec", {},
+                    let (name = (obj.specs || obj.names)[0])
+                          spec(template.highlightRegexp(tag(name),
+                               /\[(.*?)\]/g,
+                               function (m, n0) ["oa", {}, n0]),
+                               name)],
+                !obj.type ? "" : [
+                    ["type", {}, obj.type],
+                    ["default", {}, obj.stringDefaultValue]],
+                description]);
 
         function add(ary) {
-            res.item.description.* += br +
-                let (br = br + <>    </>)
-                    <><dl>{ br + template.map(ary, function ([a, b]) <><dt>{a}</dt> <dd>{b}</dd></>, br) }
-                    </dl>
-                </>;
+            description.push(
+                ["dl", {}, template.map(ary,
+                                        function ([a, b]) [["dt", {}, a], " ",
+                                                           ["dd", {}, b]])]);
         }
 
-        if (obj.completer)
-            add(completion._runCompleter(obj.completer, "", null, args).items
+        if (obj.completer && false)
+            add(completion._runCompleter(obj.closure.completer, "", null, args).items
                           .map(function (i) [i.text, i.description]));
 
-        if (obj.options && obj.options.some(function (o) o.description))
+        if (obj.options && obj.options.some(function (o) o.description) && false)
             add(obj.options.filter(function (o) o.description)
                    .map(function (o) [
                         o.names[0],
-                        <>{o.description}{
-                            o.names.length == 1 ? "" :
-                                <> (short name: {
-                                    template.map(o.names.slice(1), function (n) <em>{n}</em>, <>, </>)
-                                })</>
-                        }</>
+                        [o.description,
+                         o.names.length == 1 ? "" :
+                             ["", " (short name: ",
+                                 template.map(o.names.slice(1), function (n) ["em", {}, n], ", "),
+                              ")"]]
                     ]));
-        return res.*.toXMLString()
-                  .replace(' xmlns="' + NS + '"', "", "g")
-                  .replace(/^ {12}|[ \t]+$/gm, "")
-                  .replace(/^\s*\n|\n\s*$/g, "") + "\n";
-    },
-
-    /**
-     * Opens the help page containing the specified *topic* if it exists.
-     *
-     * @param {string} topic The help topic to open.
-     * @param {boolean} consolidated Whether to use the consolidated help page.
-     */
-    help: function (topic, consolidated) {
-        dactyl.initHelp();
-        if (!topic) {
-            let helpFile = consolidated ? "all" : options["helpfile"];
-
-            if (helpFile in services["dactyl:"].FILE_MAP)
-                dactyl.open("dactyl://help/" + helpFile, { from: "help" });
-            else
-                dactyl.echomsg(_("help.noFile", helpFile.quote()));
-            return;
-        }
-
-        let page = this.findHelp(topic, consolidated);
-        dactyl.assert(page != null, _("help.noTopic", topic));
 
-        dactyl.open("dactyl://help/" + page, { from: "help" });
+        return DOM.toPrettyXML(res, true, null, { "": String(NS) });
     },
 
     /**
@@ -1064,11 +759,11 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
      * These are set and accessed with the "g:" prefix.
      */
     _globalVariables: {},
-    globalVariables: deprecated("the options system", {
+    globalVariables: deprecated(_("deprecated.for.theOptionsSystem"), {
         get: function globalVariables() this._globalVariables
     }),
 
-    loadPlugins: function (args, force) {
+    loadPlugins: function loadPlugins(args, force) {
         function sourceDirectory(dir) {
             dactyl.assert(dir.isReadable(), _("io.notReadable", dir.path));
 
@@ -1076,13 +771,16 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
 
             let loadplugins = options.get("loadplugins");
             if (args)
-                loadplugins = { __proto__: loadplugins, value: args.map(Option.parseRegexp) }
+                loadplugins = { __proto__: loadplugins, value: args.map(Option.parseRegexp) };
 
             dir.readDirectory(true).forEach(function (file) {
-                if (file.isFile() && loadplugins.getKey(file.path) && !(!force && file.path in dactyl.pluginFiles)) {
+                if (file.leafName[0] == ".")
+                    ;
+                else if (file.isFile() && loadplugins.getKey(file.path)
+                        && !(!force && file.path in dactyl.pluginFiles && dactyl.pluginFiles[file.path] >= file.lastModifiedTime)) {
                     try {
                         io.source(file.path);
-                        dactyl.pluginFiles[file.path] = true;
+                        dactyl.pluginFiles[file.path] = file.lastModifiedTime;
                     }
                     catch (e) {
                         dactyl.reportError(e);
@@ -1122,8 +820,8 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
      * @param {string|Object} msg The message to print.
      * @param {number} level The logging level 0 - 15.
      */
-    log: function (msg, level) {
-        let verbose = localPrefs.get("loglevel", 0);
+    log: function log(msg, level) {
+        let verbose = config.prefs.get("loglevel", 0);
 
         if (!level || level <= verbose) {
             if (isObject(msg) && !isinstance(msg, _))
@@ -1133,35 +831,45 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
         }
     },
 
-    onClick: function onClick(event) {
-        if (event.originalTarget instanceof Element) {
-            let command = event.originalTarget.getAttributeNS(NS, "command");
-            if (command && event.button == 0) {
-                event.preventDefault();
+    events: {
+        click: function onClick(event) {
+            let elem = event.originalTarget;
 
-                if (dactyl.commands[command])
-                    dactyl.withSavedValues(["forceNewTab"], function () {
-                        dactyl.forceNewTab = event.ctrlKey || event.shiftKey || event.button == 1;
-                        dactyl.commands[command](event);
-                    });
+            if (elem instanceof Element && services.security.isSystemPrincipal(elem.nodePrincipal)) {
+                let command = elem.getAttributeNS(NS, "command");
+                if (command && event.button == 0) {
+                    event.preventDefault();
+
+                    if (dactyl.commands[command])
+                        dactyl.withSavedValues(["forceTarget"], function () {
+                            if (event.ctrlKey || event.shiftKey || event.button == 1)
+                                dactyl.forceTarget = dactyl.NEW_TAB;
+                            dactyl.commands[command](event);
+                        });
+                }
             }
-        }
-    },
+        },
 
-    onExecute: function onExecute(event) {
-        let cmd = event.originalTarget.getAttribute("dactyl-execute");
-        commands.execute(cmd, null, false, null,
-                         { file: "[Command Line]", line: 1 });
+        "dactyl.execute": function onExecute(event) {
+            let cmd = event.originalTarget.getAttribute("dactyl-execute");
+            commands.execute(cmd, null, false, null,
+                             { file: /*L*/"[Command Line]", line: 1 });
+        }
     },
 
     /**
      * Opens one or more URLs. Returns true when load was initiated, or
      * false on error.
      *
-     * @param {string|Array} urls A representation of the URLs to open. May be
-     *     either a string, which will be passed to
-     *     {@see Dactyl#parseURLs}, or an array in the same format as
-     *     would be returned by the same.
+     * @param {string|Array} urls A representation of the URLs to open.
+     *     A string will be passed to {@link Dactyl#parseURLs}. An array may
+     *     contain elements of the following forms:
+     *
+     *      â€¢ {string}                    A URL to open.
+     *      â€¢ {[string, {string|Array}]}  Pair of a URL and POST data.
+     *      â€¢ {object}                    Object compatible with those returned
+     *                                    by {@link DOM#formData}.
+     *
      * @param {object} params A set of parameters specifying how to open the
      *     URLs. The following properties are recognized:
      *
@@ -1182,12 +890,12 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
      *     tabs.
      * @returns {boolean}
      */
-    open: function (urls, params, force) {
+    open: function open(urls, params, force) {
         if (typeof urls == "string")
             urls = dactyl.parseURLs(urls);
 
         if (urls.length > prefs.get("browser.tabs.maxOpenBeforeWarn", 20) && !force)
-            return commandline.input("This will open " + urls.length + " new tabs. Would you like to continue? (yes/[no]) ",
+            return commandline.input(_("dactyl.prompt.openMany", urls.length) + " ",
                 function (resp) {
                     if (resp && resp.match(/^y(es)?$/i))
                         dactyl.open(urls, params, true);
@@ -1202,8 +910,9 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
             flags |= params[opt] && Ci.nsIWebNavigation["LOAD_FLAGS_" + flag];
 
         let where = params.where || dactyl.CURRENT_TAB;
-        let background = ("background" in params) ? params.background
-                                                  : params.where == dactyl.NEW_BACKGROUND_TAB;
+        let background = dactyl.forceBackground != null ? dactyl.forceBackground :
+                         ("background" in params)       ? params.background
+                                                        : params.where == dactyl.NEW_BACKGROUND_TAB;
 
         if (params.from && dactyl.has("tabs")) {
             if (!params.where && options.get("newtab").has(params.from))
@@ -1215,30 +924,48 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
             return;
 
         let browser = config.tabbrowser;
-        function open(urls, where) {
+        function open(loc, where) {
             try {
-                let url = Array.concat(urls)[0];
-                let postdata = Array.concat(urls)[1];
+                if (isArray(loc))
+                    loc = { url: loc[0], postData: loc[1] };
+                else if (isString(loc))
+                    loc = { url: loc };
+                else
+                    loc = Object.create(loc);
+
+                if (isString(loc.postData))
+                    loc.postData = ["application/x-www-form-urlencoded", loc.postData];
+
+                if (isArray(loc.postData)) {
+                    let stream = services.MIMEStream(services.StringStream(loc.postData[1]));
+                    stream.addHeader("Content-Type", loc.postData[0]);
+                    stream.addContentLength = true;
+                    loc.postData = stream;
+                }
 
                 // decide where to load the first url
                 switch (where) {
 
                 case dactyl.NEW_TAB:
                     if (!dactyl.has("tabs"))
-                        return open(urls, dactyl.NEW_WINDOW);
+                        return open(loc, dactyl.NEW_WINDOW);
 
                     return prefs.withContext(function () {
                         prefs.set("browser.tabs.loadInBackground", true);
-                        return browser.loadOneTab(url, null, null, postdata, background).linkedBrowser.contentDocument;
+                        return browser.loadOneTab(loc.url, null, null, loc.postData, background).linkedBrowser.contentDocument;
                     });
 
                 case dactyl.NEW_WINDOW:
-                    let win = window.openDialog(document.documentURI, "_blank", "chrome,all,dialog=no");
+                    let options = ["chrome", "all", "dialog=no"];
+                    if (dactyl.forcePrivate)
+                        options.push("private");
+
+                    let win = window.openDialog(document.documentURI, "_blank", options.join(","));
                     util.waitFor(function () win.document.readyState === "complete");
                     browser = win.dactyl && win.dactyl.modules.config.tabbrowser || win.getBrowser();
                     // FALLTHROUGH
                 case dactyl.CURRENT_TAB:
-                    browser.loadURIWithFlags(url, flags, null, null, postdata);
+                    browser.loadURIWithFlags(loc.url, flags, null, null, loc.postData);
                     return browser.contentWindow;
                 }
             }
@@ -1248,10 +975,8 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
             // any genuine errors go unreported.
         }
 
-        if (dactyl.forceNewTab)
-            where = dactyl.NEW_TAB;
-        else if (dactyl.forceNewWindow)
-            where = dactyl.NEW_WINDOW;
+        if (dactyl.forceTarget)
+            where = dactyl.forceTarget;
         else if (!where)
             where = dactyl.CURRENT_TAB;
 
@@ -1270,7 +995,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
      * ['www.google.com/search?q=bla', 'www.osnews.com']
      *
      * @param {string} str
-     * @returns {string[]}
+     * @returns {[string]}
      */
     parseURLs: function parseURLs(str) {
         let urls;
@@ -1283,20 +1008,20 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
         return urls.map(function (url) {
             url = url.trim();
 
-            if (/^(\.{0,2}|~)(\/|$)/.test(url)) {
+            if (/^(\.{0,2}|~)(\/|$)/.test(url) || config.OS.isWindows && /^[a-z]:/i.test(url)) {
                 try {
                     // Try to find a matching file.
                     let file = io.File(url);
                     if (file.exists() && file.isReadable())
-                        return services.io.newFileURI(file).spec;
+                        return file.URI.spec;
                 }
                 catch (e) {}
             }
 
             // If it starts with a valid protocol, pass it through.
             let proto = /^([-\w]+):/.exec(url);
-            if (proto && "@mozilla.org/network/protocol;1?name=" + proto[1] in Cc)
-                return url.replace(/\s+/g, "");
+            if (proto && services.PROTOCOL + proto[1] in Cc)
+                return url;
 
             // Check for a matching search keyword.
             let searchURL = this.has("bookmarks") && bookmarks.getSearchURL(url, false);
@@ -1313,15 +1038,15 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
         }, this);
     },
     stringToURLArray: deprecated("dactyl.parseURLs", "parseURLs"),
-    urlish: Class.memoize(function () util.regexp(<![CDATA[
+    urlish: Class.Memoize(function () util.regexp(literal(/*
             ^ (
                 <domain>+ (:\d+)? (/ .*) |
                 <domain>+ (:\d+) |
                 <domain>+ \. [a-z0-9]+ |
                 localhost
             ) $
-        ]]>, "ix", {
-        domain: util.regexp(String.replace(<![CDATA[
+        */), "ix", {
+        domain: util.regexp(String.replace(literal(/*
             [^
                 U0000-U002c // U002d-U002e --.
                 U002f       // /
@@ -1330,7 +1055,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                 U005b-U0060 // U0061-U007a A-Z
                 U007b-U007f
             ]
-        ]]>, /U/g, "\\u"), "x")
+        */), /U/g, "\\u"), "x")
     })),
 
     pluginFiles: {},
@@ -1358,7 +1083,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
      * @param {boolean} force Forcibly quit irrespective of whether all
      *    windows could be closed individually.
      */
-    quit: function (saveSession, force) {
+    quit: function quit(saveSession, force) {
         if (!force && !this.confirmQuit())
             return;
 
@@ -1375,10 +1100,12 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
     /**
      * Restart the host application.
      */
-    restart: function () {
+    restart: function restart(args) {
         if (!this.confirmQuit())
             return;
 
+        config.prefs.set("commandline-args", args);
+
         services.appStartup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
     },
 
@@ -1397,7 +1124,12 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
             return func.apply(self || this, Array.slice(arguments, 2));
         }
         catch (e) {
-            dactyl.reportError(e, true);
+            try {
+                dactyl.reportError(e, true);
+            }
+            catch (e) {
+                util.reportError(e);
+            }
             return e;
         }
     },
@@ -1412,7 +1144,8 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
         if (error instanceof FailedAssertion && error.noTrace || error.message === "Interrupted") {
             let context = contexts.context;
             let prefix = context ? context.file + ":" + context.line + ": " : "";
-            if (error.message && error.message.indexOf(prefix) !== 0)
+            if (error.message && error.message.indexOf(prefix) !== 0 &&
+                    prefix != "[Command Line]:1: ")
                 error.message = prefix + error.message;
 
             if (error.message)
@@ -1424,8 +1157,10 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                 util.reportError(error);
             return;
         }
+
         if (error.result == Cr.NS_BINDING_ABORTED)
             return;
+
         if (echo)
             dactyl.echoerr(error, commandline.FORCE_SINGLELINE);
         else
@@ -1441,7 +1176,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
      * @returns {Object}
      * @see Commands#parseArgs
      */
-    parseCommandLine: function (cmdline) {
+    parseCommandLine: function parseCommandLine(cmdline) {
         try {
             return commands.get("rehash").parseArgs(cmdline);
         }
@@ -1450,10 +1185,9 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
             return [];
         }
     },
-
-    wrapCallback: function (callback, self) {
+    wrapCallback: function wrapCallback(callback, self) {
         self = self || this;
-        let save = ["forceNewTab", "forceNewWindow"];
+        let save = ["forceOpen"];
         let saved = save.map(function (p) dactyl[p]);
         return function wrappedCallback() {
             let args = arguments;
@@ -1470,55 +1204,95 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
     },
 
     /**
-     * @property {Window[]} Returns an array of all the host application's
+     * @property {[Window]} Returns an array of all the host application's
      *     open windows.
      */
-    get windows() [win for (win in iter(services.windowMediator.getEnumerator("navigator:browser")))],
+    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"
+}, {
+    cache: function initCache() {
+        cache.register("help/plugins.xml", function () {
+            // Process plugin help entries.
 
-    // TODO: move this
-    getMenuItems: function () {
-        function addChildren(node, parent) {
-            for (let [, item] in Iterator(node.childNodes)) {
-                if (item.childNodes.length == 0 && item.localName == "menuitem"
-                    && !/rdf:http:/.test(item.getAttribute("label"))) { // FIXME
-                    item.fullMenuPath = parent + item.getAttribute("label");
-                    items.push(item);
+            let body = [];
+            for (let [, context] in Iterator(plugins.contexts))
+                try {
+                    let info = contexts.getDocs(context);
+                    if (DOM.isJSONXML(info)) {
+                        let langs = info.slice(2).filter(function (e) isArray(e) && isObject(e[1]) && e[1].lang);
+                        if (langs) {
+                            let lang = config.bestLocale(l[1].lang for each (l in langs));
+
+                            info = info.slice(0, 2).concat(
+                                info.slice(2).filter(function (e) !isArray(e) || !isObject(e[1])
+                                                               || e[1].lang == lang));
+
+                            for each (let elem in info.slice(2).filter(function (e) isArray(e) && e[0] == "info" && isObject(e[1])))
+                                for (let attr in values(["name", "summary", "href"]))
+                                    if (attr in elem[1])
+                                        info[attr] = elem[1][attr];
+                        }
+                        body.push(["h2", { xmlns: "dactyl", tag: info[1].name + '-plugin' },
+                                       String(info[1].summary)]);
+                        body.push(info);
+                    }
                 }
-                else {
-                    let path = parent;
-                    if (item.localName == "menu")
-                        path += item.getAttribute("label") + ".";
-                    addChildren(item, path);
+                catch (e) {
+                    util.reportError(e);
                 }
-            }
-        }
 
-        let items = [];
-        addChildren(document.getElementById(config.guioptions["m"][1]), "");
-        return items;
-    }
-}, {
-    events: function () {
-        events.listen(window, "click", dactyl.closure.onClick, true);
-        events.listen(window, "dactyl.execute", dactyl.closure.onExecute, true);
+            return '<?xml version="1.0"?>\n' +
+                   '<?xml-stylesheet type="text/xsl" href="dactyl://content/help.xsl"?>\n' +
+                   DOM.toXML(
+                       ["document", { xmlns: "dactyl", name: "plugins",
+                                      title: config.appName + ", Plugins" },
+                           ["h1", { tag: "using-plugins" }, _("help.title.Using Plugins")],
+                           ["toc", { start: "2" }],
+
+                           body]);
+        });
+
+        cache.register("help/index.xml", function () {
+            return '<?xml version="1.0"?>\n' +
+                   DOM.toXML(["overlay", { xmlns: "dactyl" },
+                       template.map(dactyl.indices, function ([name, iter])
+                           ["dl", { insertafter: name + "-index" },
+                               template.map(iter(), util.identity)],
+                           "\n\n")]);
+        });
+
+        cache.register("help/gui.xml", function () {
+            return '<?xml version="1.0"?>\n' +
+                   DOM.toXML(["overlay", { xmlns: "dactyl" },
+                       ["dl", { insertafter: "dialog-list" },
+                           template.map(config.dialogs, function ([name, val])
+                               (!val[2] || val[2]())
+                                   ? [["dt", {}, name],
+                                      ["dd", {}, val[0]]]
+                                   : undefined,
+                               "\n")]]);
+        });
+
+        cache.register("help/privacy.xml", function () {
+            return '<?xml version="1.0"?>\n' +
+                   DOM.toXML(["overlay", { xmlns: "dactyl" },
+                       ["dl", { insertafter: "sanitize-items" },
+                           template.map(options.get("sanitizeitems").values
+                                                .sort(function (a, b) String.localeCompare(a.name,
+                                                                                           b.name)),
+                               function ({ name, description })
+                               [["dt", {}, name],
+                                ["dd", {}, template.linkifyHelp(description, true)]],
+                               "\n")]]);
+        });
+    },
+    events: function initEvents() {
+        events.listen(window, dactyl, "events", true);
     },
     // Only general options are added here, which are valid for all Dactyl extensions
-    options: function () {
+    options: function initOptions() {
         options.add(["errorbells", "eb"],
             "Ring the bell when an error message is displayed",
             "boolean", false);
@@ -1542,7 +1316,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                     M: ["Always show messages outside of the status line"]
                 },
                 setter: function (opts) {
-                    if (loaded.commandline)
+                    if (loaded.commandline || ~opts.indexOf("c"))
                         commandline.widgets.updateVisibility();
                 }
             },
@@ -1578,7 +1352,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                                       true);
 
                     prefs.safeSet("layout.scrollbar.side", opts.indexOf("l") >= 0 ? 3 : 2,
-                                  "See 'guioptions' scrollbar flags.");
+                                  _("option.guioptions.safeSet"));
                 },
                 validator: function (opts) Option.validIf(!(opts.indexOf("l") >= 0 && opts.indexOf("r") >= 0),
                                                           UTF8("Only one of â€˜l’ or â€˜r’ allowed"))
@@ -1611,12 +1385,12 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
 
         options.add(["guioptions", "go"],
             "Show or hide certain GUI elements like the menu or toolbar",
-            "charlist", config.defaults.guioptions || "", {
+            "charlist", "", {
 
                 // FIXME: cleanup
                 cleanupValue: config.cleanups.guioptions ||
-                    "r" + [k for ([k, v] in iter(groups[1].opts))
-                           if (!document.getElementById(v[1][0]).collapsed)].join(""),
+                    "rb" + [k for ([k, v] in iter(groups[1].opts))
+                            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(),
 
@@ -1640,23 +1414,27 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
 
         options.add(["titlestring"],
             "The string shown at the end of the window title",
-            "string", config.defaults.titlestring || config.host,
+            "string", config.host,
             {
                 setter: function (value) {
                     let win = document.documentElement;
                     function updateTitle(old, current) {
-                        document.title = document.title.replace(RegExp("(.*)" + util.regexp.escape(old)), "$1" + current);
+                        if (config.browser.updateTitlebar)
+                            config.browser.updateTitlebar();
+                        else
+                            document.title = document.title.replace(RegExp("(.*)" + util.regexp.escape(old)), "$1" + current);
                     }
 
-                    if (services.has("privateBrowsing")) {
+                    if (win.hasAttribute("titlemodifier_privatebrowsing")) {
                         let oldValue = win.getAttribute("titlemodifier_normal");
                         let suffix = win.getAttribute("titlemodifier_privatebrowsing").substr(oldValue.length);
 
                         win.setAttribute("titlemodifier_normal", value);
                         win.setAttribute("titlemodifier_privatebrowsing", value + suffix);
 
-                        if (services.privateBrowsing.privateBrowsingEnabled) {
+                        if (storage.privateMode) {
                             updateTitle(oldValue + suffix, value + suffix);
+                            win.setAttribute("titlemodifier", value + suffix);
                             return value;
                         }
                     }
@@ -1670,7 +1448,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
 
         options.add(["urlseparator", "urlsep", "us"],
             "The regular expression used to separate multiple URLs in :open and friends",
-            "string", "\\|",
+            "string", " \\| ",
             { validator: function (value) RegExp(value) });
 
         options.add(["verbose", "vbs"],
@@ -1684,21 +1462,13 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
             {
                 setter: function (value) {
                     prefs.safeSet("accessibility.typeaheadfind.enablesound", !value,
-                                  "See 'visualbell' option");
+                                  _("option.safeSet", "visualbell"));
                     return value;
                 }
             });
     },
 
-    mappings: function () {
-        mappings.add([modes.MAIN], ["<F1>"],
-            "Open the introductory help page",
-            function () { dactyl.help(); });
-
-        mappings.add([modes.MAIN], ["<A-F1>"],
-            "Open the single, consolidated help page",
-            function () { ex.helpall(); });
-
+    mappings: function initMappings() {
         if (dactyl.has("session"))
             mappings.add([modes.NORMAL], ["ZQ"],
                 "Quit and don't save the session",
@@ -1709,7 +1479,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
             function () { dactyl.quit(true); });
     },
 
-    commands: function () {
+    commands: function initCommands() {
         commands.add(["dia[log]"],
             "Open a " + config.appName + " dialog",
             function (args) {
@@ -1727,7 +1497,6 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                 }
             }, {
                 argCount: "1",
-                bang: true,
                 completer: function (context) {
                     context.ignoreCase = true;
                     completion.dialog(context);
@@ -1738,14 +1507,16 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
             "Execute the specified menu item from the command line",
             function (args) {
                 let arg = args[0] || "";
-                let items = Dactyl.getMenuItems();
+                let items = dactyl.getMenuItems(arg);
 
-                dactyl.assert(items.some(function (i) i.fullMenuPath == arg),
+                dactyl.assert(items.some(function (i) i.dactylPath == arg),
                               _("emenu.notFound", arg));
 
                 for (let [, item] in Iterator(items)) {
-                    if (item.fullMenuPath == arg)
+                    if (item.dactylPath == arg) {
+                        dactyl.assert(!item.disabled, _("error.disabled", item.dactylPath));
                         item.doCommand();
+                    }
                 }
             }, {
                 argCount: "1",
@@ -1768,32 +1539,8 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                 literal: 0
             });
 
-        [
-            {
-                name: "h[elp]",
-                description: "Open the introductory help page"
-            }, {
-                name: "helpa[ll]",
-                description: "Open the single consolidated help page"
-            }
-        ].forEach(function (command) {
-            let consolidated = command.name == "helpa[ll]";
-
-            commands.add([command.name],
-                command.description,
-                function (args) {
-                    dactyl.assert(!args.bang, _("help.dontPanic"));
-                    dactyl.help(args.literalArg, consolidated);
-                }, {
-                    argCount: "?",
-                    bang: true,
-                    completer: function (context) completion.help(context, consolidated),
-                    literal: 0
-                });
-        });
-
         commands.add(["loadplugins", "lpl"],
-            "Load all plugins immediately",
+            "Load all or matching plugins",
             function (args) {
                 dactyl.loadPlugins(args.length ? args : null, args.bang);
             },
@@ -1802,7 +1549,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                 bang: true,
                 keepQuotes: true,
                 serialGroup: 10,
-                serialize: function ()  [
+                serialize: function () [
                     {
                         command: this.name,
                         literalArg: options["loadplugins"].join(" ")
@@ -1819,6 +1566,30 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                 literal: 0
             });
 
+        commands.add(["pr[ivate]", "pr0n", "porn"],
+            "Enable privacy features of a command, when applicable, and do not save the invocation in command history",
+            function (args) {
+                dactyl.withSavedValues(["forcePrivate"], function () {
+                    this.forcePrivate = true;
+                    dactyl.execute(args[0], null, true);
+                });
+            }, {
+                argCount: "1",
+                completer: function (context) completion.ex(context),
+                literal: 0,
+                privateData: "never-save",
+                subCommand: 0
+            });
+
+        commands.add(["exit", "x"],
+            "Quit " + config.appName,
+            function (args) {
+                dactyl.quit(false, args.bang);
+            }, {
+                argCount: "0",
+                bang: true
+            });
+
         commands.add(["q[uit]"],
             dactyl.has("tabs") ? "Quit current tab" : "Quit application",
             function (args) {
@@ -1833,53 +1604,72 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                 bang: true
             });
 
+        let startupOptions = [
+            {
+                names: ["+u"],
+                description: "The initialization file to execute at startup",
+                type: CommandOption.STRING
+            },
+            {
+                names: ["++noplugin"],
+                description: "Do not automatically load plugins"
+            },
+            {
+                names: ["++cmd"],
+                description: "Ex commands to execute prior to initialization",
+                type: CommandOption.STRING,
+                multiple: true
+            },
+            {
+                names: ["+c"],
+                description: "Ex commands to execute after initialization",
+                type: CommandOption.STRING,
+                multiple: true
+            },
+            {
+                names: ["+purgecaches"],
+                description: "Purge " + config.appName + " caches at startup",
+                type: CommandOption.NOARG
+            }
+        ];
+
         commands.add(["reh[ash]"],
             "Reload the " + config.appName + " add-on",
             function (args) {
                 if (args.trailing)
-                    JSMLoader.rehashCmd = args.trailing; // Hack.
+                    storage.storeForSession("rehashCmd", args.trailing); // Hack.
                 args.break = true;
-                util.rehash(args);
+
+                if (args["+purgecaches"])
+                    cache.flush();
+
+                util.delay(function () { util.rehash(args) });
             },
             {
-                argCount: "0",
-                options: [
-                    {
-                        names: ["+u"],
-                        description: "The initialization file to execute at startup",
-                        type: CommandOption.STRING
-                    },
-                    {
-                        names: ["++noplugin"],
-                        description: "Do not automatically load plugins"
-                    },
-                    {
-                        names: ["++cmd"],
-                        description: "Ex commands to execute prior to initialization",
-                        type: CommandOption.STRING,
-                        multiple: true
-                    },
-                    {
-                        names: ["+c"],
-                        description: "Ex commands to execute after initialization",
-                        type: CommandOption.STRING,
-                        multiple: true
-                    }
-                ]
+                argCount: "0", // FIXME
+                options: startupOptions
             });
 
         commands.add(["res[tart]"],
-            "Force " + config.appName + " to restart",
-            function () { dactyl.restart(); });
+            "Force " + config.host + " to restart",
+            function (args) {
+                if (args["+purgecaches"])
+                    cache.flush();
 
-        function findToolbar(name) util.evaluateXPath(
-            "//*[@toolbarname=" + util.escapeString(name, "'") + "]",
+                dactyl.restart(args.string);
+            },
+            {
+                argCount: "0",
+                options: startupOptions
+            });
+
+        function findToolbar(name) DOM.XPath(
+            "//*[@toolbarname=" + util.escapeString(name, "'") + " or " +
+                "@toolbarname=" + util.escapeString(name.trim(), "'") + "]",
             document).snapshotItem(0);
 
         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) {
@@ -1888,7 +1678,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                         action(toolbar);
                         events.checkFocus();
                     }, {
-                        argcount: "1",
+                        argCount: "1",
                         completer: function (context) {
                             completion.toolbar(context);
                             if (filter)
@@ -1900,12 +1690,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"],
@@ -1916,9 +1706,9 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                 args = args[0] || "";
 
                 if (args[0] == ":")
-                    var method = function () commands.execute(args, null, true);
+                    var func = function () commands.execute(args, null, false);
                 else
-                    method = dactyl.userFunc(args);
+                    func = dactyl.userFunc(args);
 
                 try {
                     if (count > 1) {
@@ -1927,7 +1717,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
 
                         for (let i in util.interruptibleRange(0, count, 500)) {
                             let now = Date.now();
-                            method();
+                            func();
                             total += Date.now() - now;
                         }
 
@@ -1951,18 +1741,28 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                             totalUnits = "msec";
 
                         commandline.commandOutput(
-                                <table>
-                                    <tr highlight="Title" align="left">
-                                        <th colspan="3">Code execution summary</th>
-                                    </tr>
-                                    <tr><td>&#xa0;&#xa0;Executed:</td><td align="right"><span class="times-executed">{count}</span></td><td>times</td></tr>
-                                    <tr><td>&#xa0;&#xa0;Average time:</td><td align="right"><span class="time-average">{each.toFixed(2)}</span></td><td>{eachUnits}</td></tr>
-                                    <tr><td>&#xa0;&#xa0;Total time:</td><td align="right"><span class="time-total">{total.toFixed(2)}</span></td><td>{totalUnits}</td></tr>
-                                </table>);
+                                ["table", {}
+                                    ["tr", { highlight: "Title", align: "left" },
+                                        ["th", { colspan: "3" }, _("title.Code execution summary")]],
+                                    ["tr", {},
+                                        ["td", {}, _("title.Executed"), ":"],
+                                        ["td", { align: "right" },
+                                            ["span", { class: "times-executed" }, count]],
+                                        ["td", {}, /*L*/"times"]],
+                                    ["tr", {},
+                                        ["td", {}, _("title.Average time"), ":"],
+                                        ["td", { align: "right" },
+                                            ["span", { class: "time-average" }, each.toFixed(2)]],
+                                        ["td", {}, eachUnits]],
+                                    ["tr", {},
+                                        ["td", {}, _("title.Total time"), ":"],
+                                        ["td", { align: "right" },
+                                            ["span", { class: "time-total" }, total.toFixed(2)]],
+                                        ["td", {}, totalUnits]]]);
                     }
                     else {
                         let beforeTime = Date.now();
-                        method();
+                        func();
 
                         if (special)
                             return;
@@ -1979,7 +1779,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                     dactyl.echoerr(e);
                 }
             }, {
-                argCount: "+",
+                argCount: "1",
                 bang: true,
                 completer: function (context) {
                     if (/^:/.test(context.filter))
@@ -2010,7 +1810,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                     vbs.setFrom = setFrom;
                 }
             }, {
-                argCount: "+",
+                argCount: "1",
                 completer: function (context) completion.ex(context),
                 count: true,
                 literal: 0,
@@ -2022,10 +1822,15 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
             function (args) {
                 if (args.bang)
                     dactyl.open("about:");
-                else
-                    commandline.commandOutput(<>
-                        {config.appName} {config.version} running on:<br/>{navigator.userAgent}
-                    </>);
+                else {
+                    let date = config.buildDate;
+                    date = date ? " (" + date + ")" : "";
+
+                    commandline.commandOutput([
+                        ["div", {}, [config.appName, " ", config.version, date, " running on: "].join("")],
+                        ["div", {}, [window.navigator.userAgent].join("")]
+                    ])
+                }
             }, {
                 argCount: "0",
                 bang: true
@@ -2033,34 +1838,29 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
 
     },
 
-    completion: function () {
+    completion: function initCompletion() {
         completion.dialog = function dialog(context) {
             context.title = ["Dialog"];
             context.filters.push(function ({ item }) !item[2] || item[2]());
             context.completions = [[k, v[0], v[2]] for ([k, v] in Iterator(config.dialogs))];
         };
 
-        completion.help = function help(context, consolidated) {
-            dactyl.initHelp();
-            context.title = ["Help"];
-            context.anchored = false;
-            context.completions = services["dactyl:"].HELP_TAGS;
-            if (consolidated)
-                context.keys = { text: 0, description: function () "all" };
-        };
-
         completion.menuItem = function menuItem(context) {
             context.title = ["Menu Path", "Label"];
             context.anchored = false;
-            context.keys = { text: "fullMenuPath", description: function (item) item.getAttribute("label") };
-            context.completions = dactyl.menuItems;
+            context.keys = {
+                text: "dactylPath",
+                description: function (item) item.getAttribute("label"),
+                highlight: function (item) item.disabled ? "Disabled" : ""
+            };
+            context.generate = function () dactyl.menuItems;
         };
 
         var toolbox = document.getElementById("navigator-toolbox");
         completion.toolbar = function toolbar(context) {
             context.title = ["Toolbar"];
             context.keys = { text: function (item) item.getAttribute("toolbarname"), description: function () "" };
-            context.completions = util.evaluateXPath("//*[@toolbarname]", document);
+            context.completions = DOM.XPath("//*[@toolbarname]", document);
         };
 
         completion.window = function window(context) {
@@ -2069,14 +1869,38 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
             context.completions = dactyl.windows;
         };
     },
-    load: function () {
+    load: function initLoad() {
         dactyl.triggerObserver("load");
 
         dactyl.log(_("dactyl.modulesLoaded"), 3);
 
+        userContext.DOM = Class("DOM", DOM, { init: function DOM_(sel, ctxt) DOM(sel, ctxt || buffer.focusedFrame.document) });
+        userContext.$ = modules.userContext.DOM;
+
+        // Hack: disable disabling of Personas in private windows.
+        let root = document.documentElement;
+
+        if (PrivateBrowsingUtils && PrivateBrowsingUtils.isWindowPrivate(window)
+                && root._lightweightTheme
+                && root._lightweightTheme._lastScreenWidth == null) {
+
+            dactyl.withSavedValues.call(PrivateBrowsingUtils,
+                                      ["isWindowPrivate"], function () {
+                PrivateBrowsingUtils.isWindowPrivate = function () false;
+
+                Cu.import("resource://gre/modules/LightweightThemeConsumer.jsm", {})
+                  .LightweightThemeConsumer.call(root._lightweightTheme, document);
+            });
+        }
+
         dactyl.timeout(function () {
             try {
-                var args = JSMLoader.commandlineArgs || services.commandLineHandler.optionValue;
+                var args = config.prefs.get("commandline-args")
+                        || storage.session.commandlineArgs
+                        || services.commandLineHandler.optionValue;
+
+                config.prefs.reset("commandline-args");
+
                 if (isString(args))
                     args = dactyl.parseCommandLine(args);
 
@@ -2094,20 +1918,17 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
 
             dactyl.log(_("dactyl.commandlineOpts", util.objectToString(dactyl.commandLineOptions)), 3);
 
-            // first time intro message
-            const firstTime = "extensions." + config.name + ".firsttime";
-            if (prefs.get(firstTime, true)) {
+            if (config.prefs.get("first-run", true))
                 dactyl.timeout(function () {
-                    this.withSavedValues(["forceNewTab"], function () {
-                        this.forceNewTab = true;
-                        this.help();
-                        prefs.set(firstTime, false);
+                    config.prefs.set("first-run", false);
+                    this.withSavedValues(["forceTarget"], function () {
+                        this.forceTarget = dactyl.NEW_TAB;
+                        help.help();
                     });
                 }, 1000);
-            }
 
             // 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);
@@ -2149,7 +1970,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                 if (dactyl.commandLineOptions.rcFile == "NONE" || dactyl.commandLineOptions.noPlugins)
                     options["loadplugins"] = [];
 
-                if (options["loadplugins"])
+                if (options["loadplugins"].length)
                     dactyl.loadPlugins();
             }
             catch (e) {
@@ -2167,9 +1988,9 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                     dactyl.execute(cmd);
                 });
 
-            if (JSMLoader.rehashCmd)
-                dactyl.execute(JSMLoader.rehashCmd);
-            JSMLoader.rehashCmd = null;
+            if (storage.session.rehashCmd)
+                dactyl.execute(storage.session.rehashCmd);
+            storage.session.rehashCmd = null;
 
             dactyl.fullyInitialized = true;
             dactyl.triggerObserver("enter", null);