]> git.donarmstrong.com Git - dactyl.git/blobdiff - common/modules/config.jsm
Import r6948 from upstream hg supporting Firefox up to 24.*
[dactyl.git] / common / modules / config.jsm
index 9fb101a97746fb4a4b91d4bfdc318f215a6eba48..9e5b7c0c59ea3526d4c79afcd5c0f769de8d2eee 100644 (file)
 // 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.
 "use strict";
 
-try {
-
 let global = this;
-Components.utils.import("resource://dactyl/bootstrap.jsm");
 defineModule("config", {
     exports: ["ConfigBase", "Config", "config"],
-    require: ["services", "storage", "util", "template"],
-    use: ["io", "prefs"]
-}, this);
+    require: ["dom", "io", "protocol", "services", "util", "template"]
+});
+
+lazyRequire("addons", ["AddonManager"]);
+lazyRequire("cache", ["cache"]);
+lazyRequire("highlight", ["highlight"]);
+lazyRequire("messages", ["_"]);
+lazyRequire("prefs", ["localPrefs", "prefs"]);
+lazyRequire("storage", ["storage", "File"]);
+lazyRequire("styles", ["Styles"]);
+
+function AboutHandler() {}
+AboutHandler.prototype = {
+    get classDescription() "About " + config.appName + " Page",
+
+    classID: Components.ID("81495d80-89ee-4c36-a88d-ea7c4e5ac63f"),
+
+    get contractID() services.ABOUT + config.name,
+
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule]),
+
+    newChannel: function (uri) {
+        let channel = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService)
+                          .newChannel("dactyl://content/about.xul", null, null);
+        channel.originalURI = uri;
+        return channel;
+    },
 
+    getURIFlags: function (uri) Ci.nsIAboutModule.ALLOW_SCRIPT
+};
 var ConfigBase = Class("ConfigBase", {
     /**
      * Called on dactyl startup to allow for any arbitrary application-specific
      * initialization code. Must call superclass's init function.
      */
     init: function init() {
-        this.features.push = deprecated("set.add", function push(feature) set.add(this, feature));
-        if (util.haveGecko("2b"))
-            set.add(this.features, "Gecko2");
+        this.loadConfig();
+
+        this.features.push = deprecated("Set.add", function push(feature) Set.add(this, feature));
+        if (this.haveGecko("2b"))
+            Set.add(this.features, "Gecko2");
+
+        JSMLoader.registerFactory(JSMLoader.Factory(AboutHandler));
+        JSMLoader.registerFactory(JSMLoader.Factory(
+            Protocol("dactyl", "{9c8f2530-51c8-4d41-b356-319e0b155c44}",
+                     "resource://dactyl-content/")));
 
         this.timeout(function () {
-            services["dactyl:"].pages.dtd = function () [null,
-                iter(config.dtdExtra,
-                     (["dactyl." + k, v] for ([k, v] in iter(config.dtd))),
-                     (["dactyl." + s, config[s]] for each (s in config.dtdStrings)))
-                  .map(function ([k, v]) ["<!ENTITY ", k, " '", String.replace(v || "null", /'/g, "&apos;"), "'>"].join(""))
-                  .join("\n")]
+            cache.register("config.dtd", function () util.makeDTD(config.dtd));
+        });
+
+        services["dactyl:"].pages["dtd"] = function () [null, cache.get("config.dtd")];
+
+        update(services["dactyl:"].providers, {
+            "locale": function (uri, path) LocaleChannel("dactyl-locale", config.locale, path, uri),
+            "locale-local": function (uri, path) LocaleChannel("dactyl-local-locale", config.locale, path, uri)
         });
     },
 
-    loadStyles: function loadStyles() {
-        const { highlight } = require("highlight");
+    get prefs() localPrefs,
+
+    get has() Set.has(this.features),
+
+    configFiles: [
+        "resource://dactyl-common/config.json",
+        "resource://dactyl-local/config.json"
+    ],
+
+    configs: Class.Memoize(function () this.configFiles.map(function (url) JSON.parse(File.readURL(url)))),
+
+    loadConfig: function loadConfig(documentURL) {
+
+        for each (let config in this.configs) {
+            if (documentURL)
+                config = config.overlays && config.overlays[documentURL] || {};
+
+            for (let [name, value] in Iterator(config)) {
+                let prop = util.camelCase(name);
+
+                if (isArray(this[prop]))
+                    this[prop] = [].concat(this[prop], value);
+                else if (isObject(this[prop])) {
+                    if (isArray(value))
+                        value = Set(value);
+
+                    this[prop] = update({}, this[prop],
+                                        iter([util.camelCase(k), value[k]]
+                                             for (k in value)).toObject());
+                }
+                else
+                    this[prop] = value;
+            }
+        }
+    },
+
+    modules: {
+        global: ["addons",
+                 "base",
+                 "io",
+                 ["bookmarkcache", "bookmarkcache"],
+                 "buffer",
+                 "cache",
+                 "commands",
+                 "completion",
+                 "config",
+                 "contexts",
+                 "dom",
+                 "downloads",
+                 "finder",
+                 "help",
+                 "highlight",
+                 "javascript",
+                 "main",
+                 "messages",
+                 "options",
+                 "overlay",
+                 "prefs",
+                 "protocol",
+                 "sanitizer",
+                 "services",
+                 "storage",
+                 "styles",
+                 "template",
+                 "util"],
+
+        window: ["dactyl",
+                 "modes",
+                 "commandline",
+                 "abbreviations",
+                 "autocommands",
+                 "editor",
+                 "events",
+                 "hints",
+                 "key-processors",
+                 "mappings",
+                 "marks",
+                 "mow",
+                 "statusline"]
+    },
+
+    loadStyles: function loadStyles(force) {
         highlight.styleableChrome = this.styleableChrome;
-        highlight.loadCSS(this.CSS);
-        highlight.loadCSS(this.helpCSS);
-        if (!util.haveGecko("2b"))
-            highlight.loadCSS(<![CDATA[
+
+        highlight.loadCSS(this.CSS.replace(/__MSG_(.*?)__/g, function (m0, m1) _(m1)));
+        highlight.loadCSS(this.helpCSS.replace(/__MSG_(.*?)__/g, function (m0, m1) _(m1)));
+
+        if (!this.haveGecko("2b"))
+            highlight.loadCSS(literal(/*
                 !TabNumber               font-weight: bold; margin: 0px; padding-right: .8ex;
-                !TabIconNumber {
+                !TabIconNumber  {
                     font-weight: bold;
                     color: white;
                     text-align: center;
                     text-shadow: black -1px 0 1px, black 0 1px 1px, black 1px 0 1px, black 0 -1px 1px;
                 }
-            ]]>);
+            */));
+
+        let hl = highlight.set("Find", "");
+        hl.onChange = function () {
+            function hex(val) ("#" + util.regexp.iterate(/\d+/g, val)
+                                         .map(function (num) ("0" + Number(num).toString(16)).slice(-2))
+                                         .join("")
+                              ).slice(0, 7);
+
+            let elem = services.appShell.hiddenDOMWindow.document.createElement("div");
+            elem.style.cssText = this.cssText;
+
+            let keys = iter(Styles.propertyIter(this.cssText)).map(function (p) p.name).toArray();
+            let bg = keys.some(bind("test", /^background/));
+            let fg = keys.indexOf("color") >= 0;
+
+            let style = DOM(elem).style;
+            prefs[bg ? "safeSet" : "safeReset"]("ui.textHighlightBackground", hex(style.backgroundColor));
+            prefs[fg ? "safeSet" : "safeReset"]("ui.textHighlightForeground", hex(style.color));
+        };
     },
 
     get addonID() this.name + "@dactyl.googlecode.com",
-    addon: Class.memoize(function () {
-        let addon;
-        do {
-            addon = (JSMLoader.bootstrap || {}).addon;
-            if (addon && !addon.getResourceURI) {
-                util.reportError(Error(_("addon.unavailable")));
-                yield 10;
-            }
-        }
-        while (addon && !addon.getResourceURI);
 
-        if (!addon)
-            addon = require("addons").AddonManager.getAddonByID(this.addonID);
-        yield addon;
-    }, true),
+    addon: Class.Memoize(function () {
+        return (JSMLoader.bootstrap || {}).addon ||
+                    AddonManager.getAddonByID(this.addonID);
+    }),
+
+    get styleableChrome() Object.keys(this.overlays),
 
     /**
      * The current application locale.
      */
-    appLocale: Class.memoize(function () services.chromeRegistry.getSelectedLocale("global")),
+    appLocale: Class.Memoize(function () services.chromeRegistry.getSelectedLocale("global")),
 
     /**
      * The current dactyl locale.
      */
-    locale: Class.memoize(function () this.bestLocale(this.locales)),
+    locale: Class.Memoize(function () this.bestLocale(this.locales)),
 
     /**
      * The current application locale.
      */
-    locales: Class.memoize(function () {
+    locales: Class.Memoize(function () {
         // TODO: Merge with completion.file code.
         function getDir(str) str.match(/^(?:.*[\/\\])?/)[0];
 
@@ -91,7 +216,8 @@ var ConfigBase = Class("ConfigBase", {
         let jar = io.isJarURL(uri);
         if (jar) {
             let prefix = getDir(jar.JAREntry);
-            var res = iter(s.slice(prefix.length).replace(/\/.*/, "") for (s in io.listJar(jar.JARFile, prefix)))
+            var res = iter(s.slice(prefix.length).replace(/\/.*/, "")
+                           for (s in io.listJar(jar.JARFile, prefix)))
                         .toArray();
         }
         else {
@@ -101,18 +227,10 @@ var ConfigBase = Class("ConfigBase", {
                         if (f.isDirectory())).array;
         }
 
-        function exists(pkg) {
-            try {
-                services["resource:"].getSubstitution(pkg);
-                return true;
-            }
-            catch (e) {
-                return false;
-            }
-        }
+        let exists = function exists(pkg) services["resource:"].hasSubstitution("dactyl-locale-" + pkg);
 
         return array.uniq([this.appLocale, this.appLocale.replace(/-.*/, "")]
-                            .filter(function (locale) exists("dactyl-locale-" + locale))
+                            .filter(exists)
                             .concat(res));
     }),
 
@@ -124,63 +242,180 @@ var ConfigBase = Class("ConfigBase", {
      * @returns {string}
      */
     bestLocale: function (list) {
-        let langs = set(list);
         return values([this.appLocale, this.appLocale.replace(/-.*/, ""),
-                       "en", "en-US", iter(langs).next()])
-            .nth(function (l) set.has(langs, l), 0);
+                       "en", "en-US", list[0]])
+            .nth(Set.has(Set(list)), 0);
+    },
+
+    /**
+     * A list of all known registered chrome and resource packages.
+     */
+    get chromePackages() {
+        // Horrible hack.
+        let res = {};
+        function process(manifest) {
+            for each (let line in manifest.split(/\n+/)) {
+                let match = /^\s*(content|skin|locale|resource)\s+([^\s#]+)\s/.exec(line);
+                if (match)
+                    res[match[2]] = true;
+            }
+        }
+        function processJar(file) {
+            let jar = services.ZipReader(file.file);
+            if (jar)
+                try {
+                    if (jar.hasEntry("chrome.manifest"))
+                        process(File.readStream(jar.getInputStream("chrome.manifest")));
+                }
+                finally {
+                    jar.close();
+                }
+        }
+
+        for each (let dir in ["UChrm", "AChrom"]) {
+            dir = File(services.directory.get(dir, Ci.nsIFile));
+            if (dir.exists() && dir.isDirectory())
+                for (let file in dir.iterDirectory())
+                    if (/\.manifest$/.test(file.leafName))
+                        process(file.read());
+
+            dir = File(dir.parent);
+            if (dir.exists() && dir.isDirectory())
+                for (let file in dir.iterDirectory())
+                    if (/\.jar$/.test(file.leafName))
+                        processJar(file);
+
+            dir = dir.child("extensions");
+            if (dir.exists() && dir.isDirectory())
+                for (let ext in dir.iterDirectory()) {
+                    if (/\.xpi$/.test(ext.leafName))
+                        processJar(ext);
+                    else {
+                        if (ext.isFile())
+                            ext = File(ext.read().replace(/\n*$/, ""));
+                        let mf = ext.child("chrome.manifest");
+                        if (mf.exists())
+                            process(mf.read());
+                    }
+                }
+        }
+        return Object.keys(res).sort();
     },
 
-    haveHg: Class.memoize(function () {
+    /**
+     * Returns true if the current Gecko runtime is of the given version
+     * or greater.
+     *
+     * @param {string} min The minimum required version. @optional
+     * @param {string} max The maximum required version. @optional
+     * @returns {boolean}
+     */
+    haveGecko: function (min, max) let ({ compare } = services.versionCompare,
+                                        { platformVersion } = services.runtime)
+        (min == null || compare(platformVersion, min) >= 0) &&
+        (max == null || compare(platformVersion, max) < 0),
+
+    /** Dactyl's notion of the current operating system platform. */
+    OS: memoize({
+        _arch: services.runtime.OS,
+        /**
+         * @property {string} The normalised name of the OS. This is one of
+         *     "Windows", "Mac OS X" or "Unix".
+         */
+        get name() this.isWindows ? "Windows" : this.isMacOSX ? "Mac OS X" : "Unix",
+        /** @property {boolean} True if the OS is Windows. */
+        get isWindows() this._arch == "WINNT",
+        /** @property {boolean} True if the OS is Mac OS X. */
+        get isMacOSX() this._arch == "Darwin",
+        /** @property {boolean} True if the OS is some other *nix variant. */
+        get isUnix() !this.isWindows,
+        /** @property {RegExp} A RegExp which matches illegal characters in path components. */
+        get illegalCharacters() this.isWindows ? /[<>:"/\\|?*\x00-\x1f]/g : /[\/\x00]/g,
+
+        get pathListSep() this.isWindows ? ";" : ":"
+    }),
+
+    /**
+     * @property {string} The pathname of the VCS repository clone's root
+     *     directory if the application is running from one via an extension
+     *     proxy file.
+     */
+    VCSPath: Class.Memoize(function () {
         if (/pre$/.test(this.addon.version)) {
-            let uri = this.addon.getResourceURI("../.hg");
+            let uri = util.newURI(this.addon.getResourceURI("").spec + "../.hg");
             if (uri instanceof Ci.nsIFileURL &&
-                    uri.QueryInterface(Ci.nsIFileURL).file.exists() &&
+                    uri.file.exists() &&
                     io.pathSearch("hg"))
-                return ["hg", "-R", uri.file.parent.path];
+                return uri.file.parent.path;
         }
         return null;
     }),
 
-    branch: Class.memoize(function () {
-        if (this.haveHg)
-            return io.system(this.haveHg.concat(["branch"])).output;
+    /**
+     * @property {string} The name of the VCS branch that the application is
+     *     running from if using an extension proxy file or was built from if
+     *     installed as an XPI.
+     */
+    branch: Class.Memoize(function () {
+        if (this.VCSPath)
+            return io.system(["hg", "-R", this.VCSPath, "branch"]).output;
         return (/pre-hg\d+-(\S*)/.exec(this.version) || [])[1];
     }),
 
+    /** @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";
+    }),
+
     /** @property {string} The Dactyl version string. */
-    version: Class.memoize(function () {
-        if (/pre$/.test(this.addon.version)) {
-            let uri = this.addon.getResourceURI("../.hg");
-            if (uri instanceof Ci.nsIFileURL &&
-                    uri.QueryInterface(Ci.nsIFileURL).file.exists() &&
-                    io.pathSearch("hg")) {
-                return io.system(["hg", "-R", uri.file.parent.path,
-                                  "log", "-r.",
-                                  "--template=hg{rev}-" + this.branch + " ({date|isodate})"]).output;
-            }
-        }
-        let version = this.addon.version;
+    version: Class.Memoize(function () {
+        if (this.VCSPath)
+            return io.system(["hg", "-R", this.VCSPath, "log", "-r.",
+                              "--template=hg{rev}-{branch}"]).output;
+
+        return this.addon.version;
+    }),
+
+    buildDate: Class.Memoize(function () {
+        if (this.VCSPath)
+            return io.system(["hg", "-R", this.VCSPath, "log", "-r.",
+                              "--template={date|isodate}"]).output;
         if ("@DATE@" !== "@" + "DATE@")
-            version += " (created: @DATE@)";
-        return version;
+            return _("dactyl.created", "@DATE@");
     }),
 
-    get fileExt() this.name.slice(0, -5),
+    get fileExt() this.name.slice(0, -6),
+
+    dtd: Class.Memoize(function ()
+        iter(this.dtdExtra,
+             (["dactyl." + k, v] for ([k, v] in iter(config.dtdDactyl))),
+             (["dactyl." + s, config[s]] for each (s in config.dtdStrings)))
+            .toObject()),
 
-    dtd: memoize({
+    dtdDactyl: memoize({
         get name() config.name,
-        get home() "http://dactyl.sourceforge.net/",
+        get home() "http://5digits.org/",
         get apphome() this.home + this.name,
         code: "http://code.google.com/p/dactyl/",
         get issues() this.home + "bug/" + this.name,
-        get plugins() "http://dactyl.sf.net/" + this.name + "/plugins",
+        get plugins() "http://5digits.org/" + this.name + "/plugins",
         get faq() this.home + this.name + "/faq",
 
-        "list.mailto": Class.memoize(function () config.name + "@googlegroups.com"),
-        "list.href": Class.memoize(function () "http://groups.google.com/group/" + config.name),
+        "list.mailto": Class.Memoize(function () config.name + "@googlegroups.com"),
+        "list.href": Class.Memoize(function () "http://groups.google.com/group/" + config.name),
 
-        "hg.latest": Class.memoize(function () this.code + "source/browse/"), // XXX
-        "irc": "irc://irc.oftc.net/#pentadactyl",
+        "hg.latest": Class.Memoize(function () this.code + "source/browse/"), // XXX
+        "irc": "irc://irc.oftc.net/#pentadactyl"
     }),
 
     dtdExtra: {
@@ -188,8 +423,9 @@ var ConfigBase = Class("ConfigBase", {
         "xmlns.html":   "http://www.w3.org/1999/xhtml",
         "xmlns.xul":    "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
 
-        "tag.command-line": '<link topic="command-line">command line</link>',
-        "tag.status-line":  '<link topic="status-line">status line</link>',
+        "tag.command-line": ["link", { xmlns: "dactyl", topic: "command-line" }, "command line"],
+        "tag.status-line":  ["link", { xmlns: "dactyl", topic: "status-line" }, "status line"],
+        "mode.command-line": ["link", { xmlns: "dactyl", topic: "command-line-mode" }, "Command Line"]
     },
 
     dtdStrings: [
@@ -202,10 +438,9 @@ var ConfigBase = Class("ConfigBase", {
         "version"
     ],
 
-    helpStyles: /^(Help|StatusLine|REPL)|^(Boolean|Indicator|MoreMsg|Number|Object|Logo|Key(word)?|String)$/,
+    helpStyles: /^(Help|StatusLine|REPL)|^(Boolean|Dense|Indicator|MoreMsg|Number|Object|Logo|Key(word)?|String)$/,
     styleHelp: function styleHelp() {
         if (!this.helpStyled) {
-            const { highlight } = require("highlight");
             for (let k in keys(highlight.loaded))
                 if (this.helpStyles.test(k))
                     highlight.loaded[k] = true;
@@ -213,35 +448,45 @@ var ConfigBase = Class("ConfigBase", {
         this.helpCSS = true;
     },
 
-    Local: function Local(dactyl, modules, window) ({
+    Local: function Local(dactyl, modules, { document, window }) ({
         init: function init() {
+            this.loadConfig(document.documentURI);
+
+            let append = [
+                    ["menupopup", { id: "viewSidebarMenu", xmlns: "xul" }],
+                    ["broadcasterset", { id: "mainBroadcasterSet", xmlns: "xul" }]];
 
-            let append = <e4x xmlns={XUL} xmlns:dactyl={NS}>
-                    <menupopup id="viewSidebarMenu"/>
-                    <broadcasterset id="mainBroadcasterSet"/>
-            </e4x>;
             for each (let [id, [name, key, uri]] in Iterator(this.sidebars)) {
-                append.XUL::menupopup[0].* +=
-                        <menuitem observes={"pentadactyl-" + id + "Sidebar"} label={name} accesskey={key} xmlns={XUL}/>
-                append.XUL::broadcasterset[0].* +=
-                        <broadcaster id={"pentadactyl-" + id + "Sidebar"}
-                            autoCheck="false" type="checkbox" group="sidebar"
-                            sidebartitle={name} sidebarurl={uri}
-                            oncommand="toggleSidebar(this.id || this.observes);" xmlns={XUL}/>
+                append[0].push(
+                        ["menuitem", { observes: "pentadactyl-" + id + "Sidebar", label: name,
+                                       accesskey: key }]);
+                append[1].push(
+                        ["broadcaster", { id: "pentadactyl-" + id + "Sidebar", autoCheck: "false",
+                                          type: "checkbox", group: "sidebar", sidebartitle: name,
+                                          sidebarurl: uri,
+                                          oncommand: "toggleSidebar(this.id || this.observes);" }]);
             }
 
-            util.overlayWindow(window, { append: append.elements() });
+            util.overlayWindow(window, { append: append });
         },
 
-        browser: Class.memoize(function () window.gBrowser),
-        tabbrowser: Class.memoize(function () window.gBrowser),
+        get window() window,
+
+        get document() document,
+
+        ids: Class.Update({
+            get commandContainer() document.documentElement.id
+        }),
+
+        browser: Class.Memoize(function () window.gBrowser),
+        tabbrowser: Class.Memoize(function () window.gBrowser),
 
         get browserModes() [modules.modes.NORMAL],
 
         /**
          * @property {string} The ID of the application's main XUL window.
          */
-        mainWindowId: window.document.documentElement.id,
+        mainWindowId: document.documentElement.id,
 
         /**
          * @property {number} The height (px) that is available to the output
@@ -249,7 +494,7 @@ var ConfigBase = Class("ConfigBase", {
          */
         get outputHeight() this.browser.mPanelContainer.boxObject.height,
 
-        tabStrip: Class.memoize(function () window.document.getElementById("TabsToolbar") || this.tabbrowser.mTabContainer),
+        tabStrip: Class.Memoize(function () document.getElementById("TabsToolbar") || this.tabbrowser.mTabContainer)
     }),
 
     /**
@@ -263,54 +508,26 @@ var ConfigBase = Class("ConfigBase", {
      * @property {Object} A map of :command-complete option values to completer
      *     function names.
      */
-    completers: {
-       abbreviation: "abbreviation",
-       altstyle: "alternateStyleSheet",
-       bookmark: "bookmark",
-       buffer: "buffer",
-       charset: "charset",
-       color: "colorScheme",
-       command: "command",
-       dialog: "dialog",
-       dir: "directory",
-       environment: "environment",
-       event: "autocmdEvent",
-       extension: "extension",
-       file: "file",
-       help: "help",
-       highlight: "highlightGroup",
-       history: "history",
-       javascript: "javascript",
-       macro: "macro",
-       mapping: "userMapping",
-       mark: "mark",
-       menu: "menuItem",
-       option: "option",
-       preference: "preference",
-       qmark: "quickmark",
-       runtime: "runtime",
-       search: "search",
-       shellcmd: "shellCommand",
-       toolbar: "toolbar",
-       url: "url",
-       usercommand: "userCommand"
-    },
+    completers: {},
 
     /**
      * @property {Object} Application specific defaults for option values. The
      *     property names must be the options' canonical names, and the values
      *     must be strings as entered via :set.
      */
-    defaults: { guioptions: "rb" },
+    optionDefaults: {},
+
     cleanups: {},
 
     /**
      * @property {Object} A map of dialogs available via the
      *      :dialog command. Property names map dialog names to an array
-     *      as follows:
+     *      with the following elements:
      *  [0] description - A description of the dialog, used in
      *                    command completion results for :dialog.
      *  [1] action - The function executed by :dialog.
+     *  [2] test - Function which returns true if the dialog is available in
+     *      the current window. @optional
      */
     dialogs: {},
 
@@ -330,21 +547,12 @@ var ConfigBase = Class("ConfigBase", {
 
     guioptions: {},
 
-    hasTabbrowser: false,
-
     /**
      * @property {string} The name of the application that hosts the
      *     extension. E.g., "Firefox" or "XULRunner".
      */
     host: null,
 
-    /**
-     * @property {[[]]} An array of application specific mode specifications.
-     *     The values of each mode are passed to modes.addMode during
-     *     dactyl startup.
-     */
-    modes: [],
-
     /**
      * @property {string} The name of the extension.
      *    Required.
@@ -360,398 +568,16 @@ var ConfigBase = Class("ConfigBase", {
 
     sidebars: {},
 
-    /**
-     * @property {string} The leaf name of any temp files created by
-     *     {@link io.createTempFile}.
-     */
-    get tempFile() this.name + ".tmp",
-
     /**
      * @constant
      * @property {string} The default highlighting rules.
      * See {@link Highlights#loadCSS} for details.
      */
-    CSS: UTF8(String.replace(<><![CDATA[
-        // <css>
-        Boolean      color: red;
-        Function     color: navy;
-        Null         color: blue;
-        Number       color: blue;
-        Object       color: maroon;
-        String       color: green; white-space: pre;
-
-        Key          font-weight: bold;
-
-        Enabled      color: blue;
-        Disabled     color: red;
-
-        FontFixed                            font-family: monospace !important;
-        FontCode            font-size: 9pt;  font-family: -mox-fixed, monospace !important;
-        FontProportional    font-size: 10pt; font-family: "Droid Sans", "Helvetica LT Std", Helvetica, "DejaVu Sans", Verdana, sans-serif !important;
-
-        // Hack to give these groups slightly higher precedence
-        // than their unadorned variants.
-        CmdCmdLine;[dactyl|highlight]>*  &#x0d; StatusCmdLine;[dactyl|highlight]>*
-        CmdNormal;[dactyl|highlight]     &#x0d; StatusNormal;[dactyl|highlight]
-        CmdErrorMsg;[dactyl|highlight]   &#x0d; StatusErrorMsg;[dactyl|highlight]
-        CmdInfoMsg;[dactyl|highlight]    &#x0d; StatusInfoMsg;[dactyl|highlight]
-        CmdModeMsg;[dactyl|highlight]    &#x0d; StatusModeMsg;[dactyl|highlight]
-        CmdMoreMsg;[dactyl|highlight]    &#x0d; StatusMoreMsg;[dactyl|highlight]
-        CmdQuestion;[dactyl|highlight]   &#x0d; StatusQuestion;[dactyl|highlight]
-        CmdWarningMsg;[dactyl|highlight] &#x0d; StatusWarningMsg;[dactyl|highlight]
-
-        Normal            color: black   !important; background: white       !important; font-weight: normal !important;
-        StatusNormal      color: inherit !important; background: transparent !important;
-        ErrorMsg          color: white   !important; background: red         !important; font-weight: bold !important;
-        InfoMsg           color: black   !important; background: white       !important;
-        StatusInfoMsg     color: inherit !important; background: transparent !important;
-        LineNr            color: orange  !important; background: white       !important;
-        ModeMsg           color: black   !important; background: white       !important;
-        StatusModeMsg     color: inherit !important; background: transparent !important; padding-right: 1em;
-        MoreMsg           color: green   !important; background: white       !important;
-        StatusMoreMsg                                background: transparent !important;
-        Message           white-space: pre-wrap !important; min-width: 100%; width: 100%; padding-left: 4em; text-indent: -4em; display: block;
-        Message String    white-space: pre-wrap;
-        NonText           color: blue; background: transparent !important;
-        *Preview          color: gray;
-        Question          color: green   !important; background: white       !important; font-weight: bold !important;
-        StatusQuestion    color: green   !important; background: transparent !important;
-        WarningMsg        color: red     !important; background: white       !important;
-        StatusWarningMsg  color: red     !important; background: transparent !important;
-
-        CmdLine;>*;;FontFixed   padding: 1px !important;
-        CmdPrompt;.dactyl-commandline-prompt
-        CmdInput;.dactyl-commandline-command
-        CmdOutput         white-space: pre;
-
-
-        CompGroup
-        CompGroup:not(:first-of-type)  margin-top: .5em;
-        CompGroup:last-of-type         padding-bottom: 1.5ex;
-
-        CompTitle            color: magenta; background: white; font-weight: bold;
-        CompTitle>*          padding: 0 .5ex;
-        CompTitleSep         height: 1px; background: magenta; background: -moz-linear-gradient(60deg, magenta, white);
-
-        CompMsg              font-style: italic; margin-left: 16px;
-
-        CompItem
-        CompItem:nth-child(2n+1)    background: rgba(0, 0, 0, .04);
-        CompItem[selected]   background: yellow;
-        CompItem>*           padding: 0 .5ex;
-
-        CompIcon             width: 16px; min-width: 16px; display: inline-block; margin-right: .5ex;
-        CompIcon>img         max-width: 16px; max-height: 16px; vertical-align: middle;
-
-        CompResult           width: 36%; padding-right: 1%; overflow: hidden;
-        CompDesc             color: gray; width: 62%; padding-left: 1em;
-
-        CompLess             text-align: center; height: 0;    line-height: .5ex; padding-top: 1ex;
-        CompLess::after      content: "⌃";
-
-        CompMore             text-align: center; height: .5ex; line-height: .5ex; margin-bottom: -.5ex;
-        CompMore::after      content: "⌄";
-
-
-        EditorEditing;;*   background: #bbb !important; -moz-user-input: none !important; -moz-user-modify: read-only !important;
-        EditorError;;*     background: red !important;
-        EditorBlink1;;*    background: yellow !important;
-        EditorBlink2;;*
-
-        REPL                overflow: auto; max-height: 40em;
-        REPL-R;;;Question
-        REPL-E              white-space: pre-wrap;
-        REPL-P              white-space: pre-wrap; margin-bottom: 1em;
-
-        Usage               width: 100%;
-        UsageBody
-        UsageHead
-        UsageItem
-        UsageItem:nth-of-type(2n)    background: rgba(0, 0, 0, .04);
-
-        Indicator   color: blue; width: 1.5em; text-align: center;
-        Filter      font-weight: bold;
-
-        Keyword     color: red;
-        Tag         color: blue;
-
-        Link                        position: relative; padding-right: 2em;
-        Link:not(:hover)>LinkInfo   opacity: 0; left: 0; width: 1px; height: 1px; overflow: hidden;
-        LinkInfo                    {
-            color: black;
-            position: absolute;
-            left: 100%;
-            padding: 1ex;
-            margin: -1ex -1em;
-            background: rgba(255, 255, 255, .8);
-            border-radius: 1ex;
-        }
-
-        StatusLine;;;FontFixed  {
-            -moz-appearance: none !important;
-            font-weight: bold;
-            background: transparent !important;
-            border: 0px !important;
-            padding-right: 0px !important;
-            min-height: 18px !important;
-            text-shadow: none !important;
-        }
-        StatusLineNormal;[dactyl|highlight]    color: white !important; background: black   !important;
-        StatusLineBroken;[dactyl|highlight]    color: black !important; background: #FFa0a0 !important; /* light-red */
-        StatusLineSecure;[dactyl|highlight]    color: black !important; background: #a0a0FF !important; /* light-blue */
-        StatusLineExtended;[dactyl|highlight]  color: black !important; background: #a0FFa0 !important; /* light-green */
-
-        TabClose;.tab-close-button
-        TabIcon;.tab-icon       min-width: 16px;
-        TabText;.tab-text
-        TabNumber               font-weight: bold; margin: 0px; padding-right: .8ex; cursor: default;
-        TabIconNumber {
-            cursor: default;
-            width: 16px;
-            margin: 0 2px 0 -18px !important;
-            font-weight: bold;
-            color: white;
-            text-align: center;
-            text-shadow: black -1px 0 1px, black 0 1px 1px, black 1px 0 1px, black 0 -1px 1px;
-        }
-
-        Title       color: magenta; font-weight: bold;
-        URL         text-decoration: none; color: green; background: inherit;
-        URL:hover   text-decoration: underline; cursor: pointer;
-        URLExtra    color: gray;
-
-        FrameIndicator;;* {
-            background-color: red;
-            opacity: 0.5;
-            z-index: 999999;
-            position: fixed;
-            top:      0;
-            bottom:   0;
-            left:     0;
-            right:    0;
-        }
-
-        Bell          background-color: black !important;
-
-        Hint;;* {
-            font:        bold 10px "Droid Sans Mono", monospace !important;
-            margin:      -.2ex;
-            padding:     0 0 0 1px;
-            outline:     1px solid rgba(0, 0, 0, .5);
-            background:  rgba(255, 248, 231, .8);
-            color:       black;
-        }
-        Hint[active];;*  background: rgba(255, 253, 208, .8);
-        Hint::after;;*   content: attr(text) !important;
-        HintElem;;*      background-color: yellow  !important; color: black !important;
-        HintActive;;*    background-color: #88FF00 !important; color: black !important;
-        HintImage;;*     opacity: .5 !important;
-
-        Button                  display: inline-block; font-weight: bold; cursor: pointer; color: black; text-decoration: none;
-        Button:hover            text-decoration: underline;
-        Button[collapsed]       visibility: collapse; width: 0;
-        Button::before          content: "["; color: gray; text-decoration: none !important;
-        Button::after           content: "]"; color: gray; text-decoration: none !important;
-        Button:not([collapsed]) ~ Button:not([collapsed])::before  content: "/[";
-
-        Buttons
-
-        DownloadCell                    display: table-cell; padding: 0 1ex;
-
-        Downloads                       display: table; margin: 0; padding: 0;
-        DownloadHead;;;CompTitle        display: table-row;
-        DownloadHead>*;;;DownloadCell
-
-        Download                        display: table-row;
-        Download:not([active])          color: gray;
-
-        Download>*;;;DownloadCell
-        DownloadButtons
-        DownloadPercent
-        DownloadProgress
-        DownloadProgressHave
-        DownloadProgressTotal
-        DownloadSource
-        DownloadState
-        DownloadTime
-        DownloadTitle
-        DownloadTitle>Link>a         max-width: 48ex; overflow: hidden; display: inline-block;
-
-        AddonCell                    display: table-cell; padding: 0 1ex;
-
-        Addons                       display: table; margin: 0; padding: 0;
-        AddonHead;;;CompTitle        display: table-row;
-        AddonHead>*;;;AddonCell
-
-        Addon                        display: table-row;
-
-        Addon>*;;;AddonCell
-        AddonButtons
-        AddonDescription
-        AddonName                    max-width: 48ex; overflow: hidden;
-        AddonStatus
-        AddonVersion
-
-        // </css>
-    ]]></>, /&#x0d;/g, "\n")),
+    CSS: Class.Memoize(function () File.readURL("resource://dactyl-skin/global-styles.css")),
 
-    helpCSS: UTF8(<><![CDATA[
-        // <css>
-        InlineHelpLink                              font-size: inherit !important; font-family: inherit !important;
-
-        Help;;;FontProportional                     line-height: 1.4em;
-
-        HelpInclude                                 margin: 2em 0;
-
-        HelpArg;;;FontCode                          color: #6A97D4;
-        HelpOptionalArg;;;FontCode                  color: #6A97D4;
-
-        HelpBody                                    display: block; margin: 1em auto; max-width: 100ex; padding-bottom: 1em; margin-bottom: 4em; border-bottom-width: 1px;
-        HelpBorder;*;dactyl://help/*                border-color: silver; border-width: 0px; border-style: solid;
-        HelpCode;;;FontCode                         display: block; white-space: pre; margin-left: 2em;
-        HelpTT;html|tt;dactyl://help/*;FontCode
-
-        HelpDefault;;;FontCode                      display: inline-block; margin: -1px 1ex 0 0; white-space: pre; vertical-align: text-top;
-
-        HelpDescription                             display: block; clear: right;
-        HelpDescription[short]                      clear: none;
-        HelpEm;html|em;dactyl://help/*              font-weight: bold; font-style: normal;
-
-        HelpEx;;;FontCode                           display: inline-block; color: #527BBD;
-
-        HelpExample                                 display: block; margin: 1em 0;
-        HelpExample::before                         content: "Example: "; font-weight: bold;
-
-        HelpInfo                                    display: block; width: 20em; margin-left: auto;
-        HelpInfoLabel                               display: inline-block; width: 6em;  color: magenta; font-weight: bold; vertical-align: text-top;
-        HelpInfoValue                               display: inline-block; width: 14em; text-decoration: none;             vertical-align: text-top;
-
-        HelpItem                                    display: block; margin: 1em 1em 1em 10em; clear: both;
-
-        HelpKey;;;FontCode                          color: #102663;
-        HelpKeyword                                 font-weight: bold; color: navy;
-
-        HelpLink;html|a;dactyl://help/*             text-decoration: none !important;
-        HelpLink[href]:hover                        text-decoration: underline !important;
-        HelpLink[href^="mailto:"]::after            content: "✉"; padding-left: .2em;
-        HelpLink[rel=external] {
-            /* Thanks, Wikipedia */
-            background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAMAAAC67D+PAAAAFVBMVEVmmcwzmcyZzP8AZswAZv////////9E6giVAAAAB3RSTlP///////8AGksDRgAAADhJREFUGFcly0ESAEAEA0Ei6/9P3sEcVB8kmrwFyni0bOeyyDpy9JTLEaOhQq7Ongf5FeMhHS/4AVnsAZubxDVmAAAAAElFTkSuQmCC) no-repeat scroll right center;
-            padding-right: 13px;
-        }
-
-
-        HelpTOC
-        HelpTOC>ol ol                               margin-left: -1em;
-
-        HelpOrderedList;ol;dactyl://help/*                          margin: 1em 0;
-        HelpOrderedList1;ol[level="1"],ol;dactyl://help/*           list-style: outside decimal; display: block;
-        HelpOrderedList2;ol[level="2"],ol ol;dactyl://help/*        list-style: outside upper-alpha;
-        HelpOrderedList3;ol[level="3"],ol ol ol;dactyl://help/*     list-style: outside lower-roman;
-        HelpOrderedList4;ol[level="4"],ol ol ol ol;dactyl://help/*  list-style: outside decimal;
-
-        HelpList;html|ul;dactyl://help/*      display: block; list-style-position: outside; margin: 1em 0;
-        HelpListItem;html|li;dactyl://help/*  display: list-item;
-
-
-        HelpNote                                    color: red; font-weight: bold;
-
-        HelpOpt;;;FontCode                          color: #106326;
-        HelpOptInfo;;;FontCode                      display: block; margin-bottom: 1ex; padding-left: 4em;
-
-        HelpParagraph;html|p;dactyl://help/*        display: block; margin: 1em 0em;
-        HelpParagraph:first-child                   margin-top: 0;
-        HelpParagraph:last-child                    margin-bottom: 0;
-        HelpSpec;;;FontCode                         display: block; margin-left: -10em; float: left; clear: left; color: #527BBD; margin-right: 1em;
-
-        HelpString;;;FontCode                       color: green; font-weight: normal;
-        HelpString::before                          content: '"';
-        HelpString::after                           content: '"';
-        HelpString[delim]::before                   content: attr(delim);
-        HelpString[delim]::after                    content: attr(delim);
-
-        HelpNews        position: relative;
-        HelpNewsOld     opacity: .7;
-        HelpNewsNew     font-style: italic;
-        HelpNewsTag     font-style: normal; position: absolute; left: 100%; padding-left: 1em; color: #527BBD; opacity: .6; white-space: pre;
-
-        HelpHead;html|h1,html|h2,html|h3,html|h4;dactyl://help/* {
-            font-weight: bold;
-            color: #527BBD;
-            clear: both;
-        }
-        HelpHead1;html|h1;dactyl://help/* {
-            margin: 2em 0 1em;
-            padding-bottom: .2ex;
-            border-bottom-width: 1px;
-            font-size: 2em;
-        }
-        HelpHead2;html|h2;dactyl://help/* {
-            margin: 2em 0 1em;
-            padding-bottom: .2ex;
-            border-bottom-width: 1px;
-            font-size: 1.2em;
-        }
-        HelpHead3;html|h3;dactyl://help/* {
-            margin: 1em 0;
-            padding-bottom: .2ex;
-            font-size: 1.1em;
-        }
-        HelpHead4;html|h4;dactyl://help/* {
-        }
-
-
-        HelpTab;html|dl;dactyl://help/* {
-            display: table;
-            width: 100%;
-            margin: 1em 0;
-            border-bottom-width: 1px;
-            border-top-width: 1px;
-            padding: .5ex 0;
-            table-layout: fixed;
-        }
-        HelpTabColumn;html|column;dactyl://help/*   display: table-column;
-        HelpTabColumn:first-child                   width: 25%;
-        HelpTabTitle;html|dt;dactyl://help/*;FontCode  display: table-cell; padding: .1ex 1ex; font-weight: bold;
-        HelpTabDescription;html|dd;dactyl://help/*  display: table-cell; padding: .3ex 1em; text-indent: -1em; border-width: 0px;
-        HelpTabDescription>*;;dactyl://help/*       text-indent: 0;
-        HelpTabRow;html|dl>html|tr;dactyl://help/*  display: table-row;
-
-        HelpTag;;;FontCode                          display: inline-block; color: #527BBD; margin-left: 1ex; font-weight: normal;
-        HelpTags                                    display: block; float: right; clear: right;
-        HelpTopic;;;FontCode                        color: #102663;
-        HelpType;;;FontCode                         margin-right: 2ex;
-
-        HelpWarning                                 color: red; font-weight: bold;
-
-        HelpXML;;;FontCode                          color: #C5F779; background-color: #444444; font-family: Terminus, Fixed, monospace;
-        HelpXMLBlock {                              white-space: pre; color: #C5F779; background-color: #444444;
-            border: 1px dashed #aaaaaa;
-            display: block;
-            margin-left: 2em;
-            font-family: Terminus, Fixed, monospace;
-        }
-        HelpXMLAttribute                            color: #C5F779;
-        HelpXMLAttribute::after                     color: #E5E5E5; content: "=";
-        HelpXMLComment                              color: #444444;
-        HelpXMLComment::before                      content: "<!--";
-        HelpXMLComment::after                       content: "-->";
-        HelpXMLProcessing                           color: #C5F779;
-        HelpXMLProcessing::before                   color: #444444; content: "<?";
-        HelpXMLProcessing::after                    color: #444444; content: "?>";
-        HelpXMLString                               color: #C5F779; white-space: pre;
-        HelpXMLString::before                       content: '"';
-        HelpXMLString::after                        content: '"';
-        HelpXMLNamespace                            color: #FFF796;
-        HelpXMLNamespace::after                     color: #777777; content: ":";
-        HelpXMLTagStart                             color: #FFF796; white-space: normal; display: inline-block; text-indent: -1.5em; padding-left: 1.5em;
-        HelpXMLTagEnd                               color: #71BEBE;
-        HelpXMLText                                 color: #E5E5E5;
-        // </css>
-    ]]></>)
+    helpCSS: Class.Memoize(function () File.readURL("resource://dactyl-skin/help-styles.css"))
 }, {
 });
-
 JSMLoader.loadSubScript("resource://dactyl-local-content/config.js", this);
 
 config.INIT = update(Object.create(config.INIT), config.INIT, {
@@ -761,13 +587,14 @@ config.INIT = update(Object.create(config.INIT), config.INIT, {
         let img = window.Image();
         img.src = this.logo || "resource://dactyl-local-content/logo.png";
         img.onload = util.wrapCallback(function () {
-            const { highlight } = require("highlight");
-            highlight.loadCSS(<>{"!Logo  {"}
+            highlight.loadCSS(literal(/*
+                !Logo  {
                      display:    inline-block;
-                     background: url({img.src});
-                     width:      {img.width}px;
-                     height:     {img.height}px;
-                 {"}"}</>);
+                     background: url({src});
+                     width:      {width}px;
+                     height:     {height}px;
+                }
+            */).replace(/\{(.*?)\}/g, function (m, m1) img[m1]));
             img = null;
         });
     },
@@ -785,6 +612,6 @@ config.INIT = update(Object.create(config.INIT), config.INIT, {
 
 endModule();
 
-} catch(e){ if (typeof e === "string") e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
+// catch(e){ if (typeof e === "string") e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
 
-// vim: set fdm=marker sw=4 sts=4 et ft=javascript:
+// vim: set fdm=marker sw=4 sts=4 ts=8 et ft=javascript: