]> git.donarmstrong.com Git - dactyl.git/blobdiff - common/content/dactyl.js
Import 1.0b7.1 supporting Firefox up to 8.*
[dactyl.git] / common / content / dactyl.js
index 6296b9ac6d26a5db35ccee446d4ecd3c606de94d..1a244ff641412240d89ec6c7c4726bdcd993529e 100644 (file)
@@ -12,9 +12,6 @@ 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";
@@ -41,9 +38,25 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
         };
 
         styles.registerSheet("resource://dactyl-skin/dactyl.css");
+
+        this.cleanups = [];
+        this.cleanups.push(util.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;
 
@@ -57,21 +70,36 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
         autocommands.trigger("Leave", {});
     },
 
+    // initially hide all GUI elements, they are later restored unless the user
+    // has :set go= or something similar in his config
+    hideGUI: function () {
+        let guioptions = config.guioptions;
+        for (let option in guioptions) {
+            guioptions[option].forEach(function (elem) {
+                try {
+                    document.getElementById(elem).collapsed = true;
+                }
+                catch (e) {}
+            });
+        }
+    },
+
+
     observers: {
-        "dactyl-cleanup": function dactyl_cleanup() {
+        "dactyl-cleanup": function dactyl_cleanup(subject, reason) {
             let modules = dactyl.modules;
 
             for (let mod in values(modules.moduleList.reverse())) {
                 mod.stale = true;
                 if ("cleanup" in mod)
-                    this.trapErrors("cleanup", mod);
+                    this.trapErrors("cleanup", mod, reason);
                 if ("destroy" in mod)
-                    this.trapErrors("destroy", mod);
+                    this.trapErrors("destroy", mod, reason);
             }
 
             for (let mod in values(modules.ownPropertyValues.reverse()))
                 if (mod instanceof Class && "INIT" in mod && "cleanup" in mod.INIT)
-                    this.trapErrors(mod.cleanup, mod, dactyl, modules, window);
+                    this.trapErrors(mod.cleanup, mod, dactyl, modules, window, reason);
 
             for (let name in values(Object.getOwnPropertyNames(modules).reverse()))
                 try {
@@ -98,7 +126,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
     }),
 
     /**
-     * @property {number} The current main mode.
+     * @property {Modes.Mode} The current main mode.
      * @see modes#mainModes
      */
     mode: deprecated("modes.main", {
@@ -106,7 +134,37 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
         set: function mode(val) modes.main = val
     }),
 
-    get menuItems() Dactyl.getMenuItems(),
+    get menuItems() {
+        function dispatch(node, name) {
+            let event = node.ownerDocument.createEvent("Events");
+            event.initEvent(name, false, false);
+            node.dispatchEvent(event);
+        }
+
+        function addChildren(node, parent) {
+            if (~["menu", "menupopup"].indexOf(node.localName) && node.children.length)
+                dispatch(node, "popupshowing");
+
+            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");
+                    items.push(item);
+                }
+                else {
+                    let path = parent;
+                    if (item.localName == "menu")
+                        path += item.getAttribute("label") + ".";
+                    addChildren(item, path);
+                }
+            }
+        }
+
+        let items = [];
+        addChildren(document.getElementById(config.guioptions["m"][1]), "");
+        return items;
+    },
 
     // Global constants
     CURRENT_TAB: "here",
@@ -184,14 +242,16 @@ 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) util.regexp("\\b" + util.regexp.escape(arg) + "\\b", "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,9 +260,9 @@ 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";
                     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))
                         .map(function (k) {
@@ -221,7 +281,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                 let tags = services["dactyl:"].HELP_TAGS;
                 for (let obj in values(results)) {
                     let res = dactyl.generateHelp(obj, null, null, true);
-                    if (!set.has(tags, obj.helpTag))
+                    if (!Set.has(tags, obj.helpTag))
                         res[1].@tag = obj.helpTag;
 
                     yield res;
@@ -311,7 +371,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
         clipboardHelper.copyString(str);
 
         if (verbose) {
-            let message = { message: "Yanked " + str };
+            let message = { message: _("dactyl.yank", str) };
             try {
                 message.domains = [util.newURI(str).host];
             }
@@ -346,7 +406,7 @@ 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"]) || isinstance(str, ["XPCWrappedNative_NoHelper"]) && /^\[Exception/.test(str))
             dactyl.reportError(str);
         if (isObject(str) && "echoerr" in str)
             str = str.echoerr;
@@ -414,7 +474,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                 ({ file: fileName, line: lineNumber, context: ctxt }) = info;
 
         if (!context && fileName && fileName[0] !== "[")
-            context = _userContext || ctxt;
+            context = ctxt || _userContext;
 
         if (isinstance(context, ["Sandbox"]))
             return Cu.evalInSandbox(str, context, "1.8", fileName, lineNumber);
@@ -559,7 +619,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),
+    has: function (feature) Set.has(config.features, feature),
 
     /**
      * Returns the URL of the specified help *topic* if it exists.
@@ -608,6 +668,10 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
      * Initialize the help system.
      */
     initHelp: function (force) {
+        // Waits for the add-on to become available, if necessary.
+        config.addon;
+        config.version;
+
         if (force || !this.helpInitialized) {
             if ("noscriptOverlay" in window) {
                 noscriptOverlay.safeAllow("chrome-data:", true, false);
@@ -665,34 +729,38 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
 
             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));
+                try {
+                    let info = contexts.getDocs(context);
+                    if (info instanceof XML) {
+                        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);
+                            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];
+                            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={info.@name + '-plugin'}>{info.@summary}</h2> +
+                            info;
                     }
-                    body += <h2 xmlns={NS.uri} tag={context.INFO.@name + '-plugin'}>{context.INFO.@summary}</h2> +
-                        context.INFO;
+                }
+                catch (e) {
+                    util.reportError(e);
                 }
 
             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>
+                    <h1 tag="using-plugins">{_("help.title.Using Plugins")}</h1>
                     <toc start="2"/>
 
                     {body}
-                </document>.toXMLString()));
+                </document>.toXMLString();
             fileMap["plugins"] = function () ['text/xml;charset=UTF-8', help];
 
             fileMap["versions"] = function () {
@@ -725,6 +793,8 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
 
                 default xml namespace = NS;
                 function rec(text, level, li) {
+                    XML.ignoreWhitespace = XML.prettyPrinting = false;
+
                     let res = <></>;
                     let list, space, i = 0;
 
@@ -735,7 +805,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                             if (!list)
                                 res += list = <ul/>;
                             let li = <li/>;
-                            li.* += rec(match.content.replace(RegExp("^" + match.space, "gm"), ""), level + 1, li)
+                            li.* += rec(match.content.replace(RegExp("^" + match.space, "gm"), ""), level + 1, li);
                             list.* += li;
                         }
                         else if (match.par) {
@@ -751,8 +821,10 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                             }
 
                             list = null;
-                            if (level == 0 && /^.*:\n$/.test(match.par))
-                                res += <h2>{template.linkifyHelp(par.slice(0, -1), true)}</h2>;
+                            if (level == 0 && /^.*:\n$/.test(match.par)) {
+                                let text = par.slice(0, -1);
+                                res += <h2 tag={"news-" + text}>{template.linkifyHelp(text, true)}</h2>;
+                            }
                             else {
                                 let [, a, b] = /^(IMPORTANT:?)?([^]*)/.exec(par);
                                 res += <p highlight={group + " HelpNews"}>{
@@ -774,6 +846,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                     return res;
                 }
 
+                XML.ignoreWhitespace = XML.prettyPrinting = false;
                 let body = rec(NEWS, 0);
                 for each (let li in body..li) {
                     let list = li..li.(@NS::highlight == "HelpNewsOld");
@@ -784,19 +857,17 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                     }
                 }
 
-                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()))
+                    </document>.toXMLString()
                 ];
             }
             addTags("versions", util.httpGet("dactyl://help/versions").responseXML);
@@ -806,16 +877,27 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
 
             overlayMap["index"] = ['text/xml;charset=UTF-8',
                 '<?xml version="1.0"?>\n' +
-                '<overlay xmlns="' + NS + '">\n' +
-                unescape(encodeURI( // UTF-8 handling hack.
+                <overlay xmlns={NS}>{
                 template.map(dactyl.indices, function ([name, iter])
                     <dl insertafter={name + "-index"}>{
                         template.map(iter(), util.identity)
-                    }</dl>, <>{"\n\n"}</>))) +
-                '\n</overlay>'];
-
+                    }</dl>, <>{"\n\n"}</>)
+                }</overlay>];
             addTags("index", util.httpGet("dactyl://help-overlay/index").responseXML);
 
+            overlayMap["gui"] = ['text/xml;charset=UTF-8',
+                '<?xml version="1.0"?>\n' +
+                <overlay xmlns={NS}>
+                    <dl insertafter="dialog-list">{
+                    template.map(config.dialogs, function ([name, val])
+                        (!val[2] || val[2]())
+                            ? <><dt>{name}</dt><dd>{val[0]}</dd></>
+                            : undefined,
+                        <>{"\n"}</>)
+                    }</dl>
+                </overlay>];
+
+
             this.helpInitialized = true;
         }
     },
@@ -850,64 +932,68 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                 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"
+        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(/.*\//, "");
+            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(),
+                              " xmlns:dactyl=" + NS.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 || value;
+                        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);
                         }
-                        data.push(" ");
-                        data.push(name);
-                        data.push('="');
-                        data.push(<>{value}</>.toXMLString());
-                        data.push('"');
+                        if (!/^#|[\/](#|$)|^[a-z]+:/.test(value))
+                            value = value.replace(/(#|$)/, ".xhtml$1");
                     }
-                    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(">");
+                    if (name == "src" && value.indexOf(":") > 0) {
+                        chromeFiles[value] = value.replace(/.*\//, "");
+                        value = value.replace(/.*\//, "");
                     }
-                    break;
-                case Node.TEXT_NODE:
-                    data.push(<>{node.textContent}</>.toXMLString());
+
+                    data.push(" ", name, '="',
+                              <>{value}</>.toXMLString().replace(/"/g, "&quot;"),
+                              '"');
+                }
+                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("</", node.localName, ">");
+                }
+                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 = [
+            let url = "dactyl://help/" + file;
+            dactyl.open(url);
+            util.waitFor(function () content.location.href == url && buffer.loaded
+                            && content.document.documentElement instanceof HTMLHtmlElement,
+                         15000);
+            events.waitForPageLoad();
+            var 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'
@@ -916,7 +1002,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
             addDataEntry(file + ".xhtml", data.join(""));
         }
 
-        let data = [h for (h in highlight) if (set.has(styles, h.class) || /^Help/.test(h.class))]
+        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 + "}")
@@ -954,7 +1040,13 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
         if (obj instanceof Command) {
             link = function (cmd) <ex>{cmd}</ex>;
             args = obj.parseArgs("", CompletionContext(str || ""));
-            spec = function (cmd) cmd + (obj.bang ? <oa>!</oa> : <></>);
+            spec = function (cmd) <>{
+                    obj.count ? <oa>count</oa> : <></>
+                }{
+                    cmd
+                }{
+                    obj.bang ? <oa>!</oa> : <></>
+                }</>;
         }
         else if (obj instanceof Map) {
             spec = function (map) obj.count ? <><oa>count</oa>{map}</> : <>{map}</>;
@@ -969,7 +1061,9 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
             };
         }
         else if (obj instanceof Option) {
+            tag = spec = function (name) <>'{name}'</>;
             link = function (opt, name) <o>{name}</o>;
+            args = { value: "", values: [] };
         }
 
         XML.prettyPrinting = false;
@@ -1001,7 +1095,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                 <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> : "" }
+                        !(extraHelp || obj.description) ? br + <p><!--L-->Sorry, no help available.</p> : "" }
                 </description>
             </item></>;
 
@@ -1014,7 +1108,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
         }
 
         if (obj.completer)
-            add(completion._runCompleter(obj.completer, "", null, args).items
+            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))
@@ -1064,7 +1158,7 @@ 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
     }),
 
@@ -1076,13 +1170,14 @@ 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.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);
@@ -1151,7 +1246,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
     onExecute: function onExecute(event) {
         let cmd = event.originalTarget.getAttribute("dactyl-execute");
         commands.execute(cmd, null, false, null,
-                         { file: "[Command Line]", line: 1 });
+                         { file: /*L*/"[Command Line]", line: 1 });
     },
 
     /**
@@ -1187,7 +1282,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
             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);
@@ -1270,7 +1365,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,7 +1378,7 @@ 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) || util.OS.isWindows && /^[a-z]:/i.test(url)) {
                 try {
                     // Try to find a matching file.
                     let file = io.File(url);
@@ -1296,7 +1391,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
             // 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, "");
+                return url;
 
             // Check for a matching search keyword.
             let searchURL = this.has("bookmarks") && bookmarks.getSearchURL(url, false);
@@ -1470,48 +1565,13 @@ 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) {}
-            });
-        }
-    },
-
-    // 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);
-                }
-                else {
-                    let path = parent;
-                    if (item.localName == "menu")
-                        path += item.getAttribute("label") + ".";
-                    addChildren(item, path);
-                }
-            }
-        }
-
-        let items = [];
-        addChildren(document.getElementById(config.guioptions["m"][1]), "");
-        return items;
-    }
+    toolbarHidden: function hidden(elem) (elem.getAttribute("autohide") || elem.getAttribute("collapsed")) == "true"
 }, {
     events: function () {
         events.listen(window, "click", dactyl.closure.onClick, true);
@@ -1542,7 +1602,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 +1638,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"))
@@ -1616,7 +1676,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                 // FIXME: cleanup
                 cleanupValue: config.cleanups.guioptions ||
                     "r" + [k for ([k, v] in iter(groups[1].opts))
-                           if (!document.getElementById(v[1][0]).collapsed)].join(""),
+                           if (!Dactyl.toolbarHidden(document.getElementById(v[1][0])))].join(""),
 
                 values: array(groups).map(function (g) [[k, v[0]] for ([k, v] in Iterator(g.opts))]).flatten(),
 
@@ -1670,7 +1730,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,18 +1744,18 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
             {
                 setter: function (value) {
                     prefs.safeSet("accessibility.typeaheadfind.enablesound", !value,
-                                  "See 'visualbell' option");
+                                  _("option.visualbell.safeSet"));
                     return value;
                 }
             });
     },
 
     mappings: function () {
-        mappings.add([modes.MAIN], ["<F1>"],
+        mappings.add([modes.MAIN], ["<open-help>", "<F1>"],
             "Open the introductory help page",
             function () { dactyl.help(); });
 
-        mappings.add([modes.MAIN], ["<A-F1>"],
+        mappings.add([modes.MAIN], ["<open-single-help>", "<A-F1>"],
             "Open the single, consolidated help page",
             function () { ex.helpall(); });
 
@@ -1727,7 +1787,6 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                 }
             }, {
                 argCount: "1",
-                bang: true,
                 completer: function (context) {
                     context.ignoreCase = true;
                     completion.dialog(context);
@@ -1738,14 +1797,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.menuItems;
 
-                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",
@@ -1793,7 +1854,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
         });
 
         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 +1863,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 +1880,15 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                 literal: 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) {
@@ -1837,12 +1907,12 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
             "Reload the " + config.appName + " add-on",
             function (args) {
                 if (args.trailing)
-                    JSMLoader.rehashCmd = args.trailing; // Hack.
+                    storage.session.rehashCmd = args.trailing; // Hack.
                 args.break = true;
                 util.rehash(args);
             },
             {
-                argCount: "0",
+                argCount: "0", // FIXME
                 options: [
                     {
                         names: ["+u"],
@@ -1870,16 +1940,16 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
 
         commands.add(["res[tart]"],
             "Force " + config.appName + " to restart",
-            function () { dactyl.restart(); });
+            function () { dactyl.restart(); },
+            { argCount: "0" });
 
         function findToolbar(name) util.evaluateXPath(
-            "//*[@toolbarname=" + util.escapeString(name, "'") + "]",
+            "//*[@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 +1958,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 +1970,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 +1986,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 +1997,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;
                         }
 
@@ -1953,16 +2023,16 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
                         commandline.commandOutput(
                                 <table>
                                     <tr highlight="Title" align="left">
-                                        <th colspan="3">Code execution summary</th>
+                                        <th colspan="3">{_("title.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>
+                                    <tr><td>&#xa0;&#xa0;{_("title.Executed")}:</td><td align="right"><span class="times-executed">{count}</span></td><td><!--L-->times</td></tr>
+                                    <tr><td>&#xa0;&#xa0;{_("title.Average time")}:</td><td align="right"><span class="time-average">{each.toFixed(2)}</span></td><td>{eachUnits}</td></tr>
+                                    <tr><td>&#xa0;&#xa0;{_("title.Total time")}:</td><td align="right"><span class="time-total">{total.toFixed(2)}</span></td><td>{totalUnits}</td></tr>
                                 </table>);
                     }
                     else {
                         let beforeTime = Date.now();
-                        method();
+                        func();
 
                         if (special)
                             return;
@@ -1979,7 +2049,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 +2080,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,
@@ -2052,8 +2122,12 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
         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");
@@ -2076,7 +2150,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
 
         dactyl.timeout(function () {
             try {
-                var args = JSMLoader.commandlineArgs || services.commandLineHandler.optionValue;
+                var args = storage.session.commandlineArgs || services.commandLineHandler.optionValue;
                 if (isString(args))
                     args = dactyl.parseCommandLine(args);
 
@@ -2107,7 +2181,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
             }
 
             // TODO: we should have some class where all this guioptions stuff fits well
-            // Dactyl.hideGUI();
+            // dactyl.hideGUI();
 
             if (dactyl.userEval("typeof document", null, "test.js") === "undefined")
                 jsmodules.__proto__ = XPCSafeJSObjectWrapper(window);
@@ -2167,9 +2241,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);