]> git.donarmstrong.com Git - dactyl.git/blobdiff - common/modules/config.jsm
Import 1.0rc1 supporting Firefox up to 11.*
[dactyl.git] / common / modules / config.jsm
index 4da5a88d6f50249a4fdba6cfaa07d3945cf0186c..c9c1420fba6396ed0edbd3597b141da15a483ecf 100644 (file)
 //
 // 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 {
+/* use strict */
 
 let global = this;
 Components.utils.import("resource://dactyl/bootstrap.jsm");
 defineModule("config", {
     exports: ["ConfigBase", "Config", "config"],
-    require: ["services", "storage", "util", "template"],
-    use: ["io", "messages", "prefs", "styles"]
+    require: ["dom", "io", "protocol", "services", "util", "template"]
 }, this);
 
+this.lazyRequire("addons", ["AddonManager"]);
+this.lazyRequire("cache", ["cache"]);
+this.lazyRequire("highlight", ["highlight"]);
+this.lazyRequire("messages", ["_"]);
+this.lazyRequire("prefs", ["localPrefs", "prefs"]);
+this.lazyRequire("storage", ["storage", "File"]);
+
+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.loadConfig();
+
         this.features.push = deprecated("Set.add", function push(feature) Set.add(this, feature));
-        if (util.haveGecko("2b"))
+        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, util.makeDTD(config.dtd)];
+            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(force) {
-        const { highlight } = require("highlight");
-        const { _ } = require("messages");
+    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",
+                 "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.replace(/__MSG_(.*?)__/g, function (m0, m1) _(m1)));
         highlight.loadCSS(this.helpCSS.replace(/__MSG_(.*?)__/g, function (m0, m1) _(m1)));
 
-        if (!util.haveGecko("2b"))
+        if (!this.haveGecko("2b"))
             highlight.loadCSS(<![CDATA[
                 !TabNumber               font-weight: bold; margin: 0px; padding-right: .8ex;
-                !TabIconNumber {
+                !TabIconNumber  {
                     font-weight: bold;
                     color: white;
                     text-align: center;
@@ -60,48 +174,40 @@ var ConfigBase = Class("ConfigBase", {
 
             let elem = services.appShell.hiddenDOMWindow.document.createElement("div");
             elem.style.cssText = this.cssText;
-            let style = util.computedStyle(elem);
 
             let keys = iter(Styles.propertyIter(this.cssText)).map(function (p) p.name).toArray();
-            let bg = keys.some(function (k) /^background/.test(k));
+            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];
 
@@ -119,18 +225,10 @@ var ConfigBase = Class("ConfigBase", {
                         if (f.isDirectory())).array;
         }
 
-        function exists(pkg) {
-            try {
-                services["resource:"].getSubstitution(pkg);
-                return true;
-            }
-            catch (e) {
-                return false;
-            }
-        }
+        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));
     }),
 
@@ -142,18 +240,105 @@ 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);
+            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();
     },
 
+    /**
+     * 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 () {
+    VCSPath: Class.Memoize(function () {
         if (/pre$/.test(this.addon.version)) {
             let uri = util.newURI(this.addon.getResourceURI("").spec + "../.hg");
             if (uri instanceof Ci.nsIFileURL &&
@@ -169,26 +354,47 @@ var ConfigBase = Class("ConfigBase", {
      *     running from if using an extension proxy file or was built from if
      *     installed as an XPI.
      */
-    branch: Class.memoize(function () {
+    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 () {
+    version: Class.Memoize(function () {
         if (this.VCSPath)
             return io.system(["hg", "-R", this.VCSPath, "log", "-r.",
-                              "--template=hg{rev}-" + this.branch + " ({date|isodate})"]).output;
-        let version = this.addon.version;
+                              "--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 += " " + _("dactyl.created", "@DATE@");
-        return version;
+            return _("dactyl.created", "@DATE@");
     }),
 
     get fileExt() this.name.slice(0, -6),
 
-    dtd: Class.memoize(function ()
+    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)))
@@ -203,10 +409,10 @@ var ConfigBase = Class("ConfigBase", {
         get plugins() "http://dactyl.sf.net/" + 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
+        "hg.latest": Class.Memoize(function () this.code + "source/browse/"), // XXX
         "irc": "irc://irc.oftc.net/#pentadactyl",
     }),
 
@@ -233,7 +439,6 @@ var ConfigBase = Class("ConfigBase", {
     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;
@@ -241,8 +446,9 @@ 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 = <e4x xmlns={XUL} xmlns:dactyl={NS}>
                     <menupopup id="viewSidebarMenu"/>
@@ -261,15 +467,23 @@ var ConfigBase = Class("ConfigBase", {
             util.overlayWindow(window, { append: append.elements() });
         },
 
-        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
@@ -277,7 +491,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),
     }),
 
     /**
@@ -291,45 +505,15 @@ 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: {},
 
     /**
@@ -360,21 +544,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.
@@ -394,525 +569,18 @@ var ConfigBase = Class("ConfigBase", {
      * @property {string} The leaf name of any temp files created by
      *     {@link io.createTempFile}.
      */
-    get tempFile() this.name + ".tmp",
+    get tempFile() this.name + ".txt",
 
     /**
      * @constant
      * @property {string} The default highlighting rules.
      * See {@link Highlights#loadCSS} for details.
      */
-    CSS: UTF8(String.replace(<><![CDATA[
-        // <css>
-        Boolean      /* JavaScript booleans */       color: red;
-        Function     /* JavaScript functions */      color: navy;
-        Null         /* JavaScript null values */    color: blue;
-        Number       /* JavaScript numbers */        color: blue;
-        Object       /* JavaScript objects */        color: maroon;
-        String       /* String values */             color: green; white-space: pre;
-        Comment      /* JavaScriptor CSS comments */ color: gray;
-
-        Key          /* Keywords */                  font-weight: bold;
-
-        Enabled      /* Enabled item indicator text */  color: blue;
-        Disabled     /* Disabled item indicator text */ color: red;
-
-        FontFixed           /* The font used for fixed-width text */ \
-                                             font-family: monospace !important;
-        FontCode            /* The font used for code listings */ \
-                            font-size: 9pt;  font-family: monospace !important;
-        FontProportional    /* The font used for proportionally spaced text */ \
-                            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            /* Normal text */ \
-                          color: black   !important; background: white       !important; font-weight: normal !important;
-        StatusNormal      /* Normal text in the status line */ \
-                          color: inherit !important; background: transparent !important;
-        ErrorMsg          /* Error messages */ \
-                          color: white   !important; background: red         !important; font-weight: bold !important;
-        InfoMsg           /* Information messages */ \
-                          color: black   !important; background: white       !important;
-        StatusInfoMsg     /* Information messages in the status line */ \
-                          color: inherit !important; background: transparent !important;
-        LineNr            /* The line number of an error */ \
-                          color: orange  !important; background: white       !important;
-        ModeMsg           /* The mode indicator */ \
-                          color: black   !important; background: white       !important;
-        StatusModeMsg     /* The mode indicator in the status line */ \
-                          color: inherit !important; background: transparent !important; padding-right: 1em;
-        MoreMsg           /* The indicator that there is more text to view */ \
-                          color: green   !important; background: white       !important;
-        StatusMoreMsg                                background: transparent !important;
-        Message           /* A message as displayed in <ex>:messages</ex> */ \
-                          white-space: pre-wrap !important; min-width: 100%; width: 100%; padding-left: 4em; text-indent: -4em; display: block;
-        Message String    /* A message as displayed in <ex>:messages</ex> */ \
-                          white-space: pre-wrap;
-        NonText           /* The <em>~</em> indicators which mark blank lines in the completion list */ \
-                          color: blue; background: transparent !important;
-        *Preview          /* The completion preview displayed in the &tag.command-line; */ \
-                          color: gray;
-        Question          /* A prompt for a decision */ \
-                          color: green   !important; background: white       !important; font-weight: bold !important;
-        StatusQuestion    /* A prompt for a decision in the status line */ \
-                          color: green   !important; background: transparent !important;
-        WarningMsg        /* A warning message */ \
-                          color: red     !important; background: white       !important;
-        StatusWarningMsg  /* A warning message in the status line */ \
-                          color: red     !important; background: transparent !important;
-        Disabled          /* Disabled items */ \
-                          color: gray    !important;
-
-        CmdLine;>*;;FontFixed   /* The command line */ \
-                                padding: 1px !important;
-        CmdPrompt;.dactyl-commandline-prompt  /* The default styling form the command prompt */
-        CmdInput;.dactyl-commandline-command
-        CmdOutput         /* The output of commands executed by <ex>:run</ex> */ \
-                          white-space: pre;
-
-        CompGroup                      /* Item group in completion output */
-        CompGroup:not(:first-of-type)  margin-top: .5em;
-        CompGroup:last-of-type         padding-bottom: 1.5ex;
-
-        CompTitle            /* Completion row titles */ \
-                             color: magenta; background: white; font-weight: bold;
-        CompTitle>*          padding: 0 .5ex;
-        CompTitleSep         /* The element which separates the completion title from its results */ \
-                             height: 1px; background: magenta; background: -moz-linear-gradient(60deg, magenta, white);
-
-        CompMsg              /* The message which may appear at the top of a group of completion results */ \
-                             font-style: italic; margin-left: 16px;
-
-        CompItem             /* A single row of output in the completion list */
-        CompItem:nth-child(2n+1)    background: rgba(0, 0, 0, .04);
-        CompItem[selected]   /* A selected row of completion list */ \
-                             background: yellow;
-        CompItem>*           padding: 0 .5ex;
-
-        CompIcon             /* The favicon of a completion row */ \
-                             width: 16px; min-width: 16px; display: inline-block; margin-right: .5ex;
-        CompIcon>img         max-width: 16px; max-height: 16px; vertical-align: middle;
-
-        CompResult           /* The result column of the completion list */ \
-                             width: 36%; padding-right: 1%; overflow: hidden;
-        CompDesc             /* The description column of the completion list */ \
-                             color: gray; width: 62%; padding-left: 1em;
-
-        CompLess             /* The indicator shown when completions may be scrolled up */ \
-                             text-align: center; height: 0;    line-height: .5ex; padding-top: 1ex;
-        CompLess::after      /* The character of indicator shown when completions may be scrolled up */ \
-                             content: "⌃";
-
-        CompMore             /* The indicator shown when completions may be scrolled down */ \
-                             text-align: center; height: .5ex; line-height: .5ex; margin-bottom: -.5ex;
-        CompMore::after      /* The character of indicator shown when completions may be scrolled down */ \
-                             content: "⌄";
-
-        Dense              /* Arbitrary elements which should be packed densely together */\
-                           margin-top: 0; margin-bottom: 0;
-
-        EditorEditing;;*   /* Text fields for which an external editor is open */ \
-                           background-color: #bbb !important; -moz-user-input: none !important; -moz-user-modify: read-only !important;
-        EditorError;;*     /* Text fields briefly after an error has occurred running the external editor */ \
-                           background: red !important;
-        EditorBlink1;;*    /* Text fields briefly after successfully running the external editor, alternated with EditorBlink2 */ \
-                           background: yellow !important;
-        EditorBlink2;;*    /* Text fields briefly after successfully running the external editor, alternated with EditorBlink1 */
-
-        REPL                /* Read-Eval-Print-Loop output */ \
-                            overflow: auto; max-height: 40em;
-        REPL-R;;;Question   /* Prompts in REPL mode */
-        REPL-E              /* Evaled input in REPL mode */ \
-                            white-space: pre-wrap;
-        REPL-P              /* Evaled output in REPL mode */ \
-                            white-space: pre-wrap; margin-bottom: 1em;
-
-        Usage               /* Output from the :*usage commands */ \
-                            width: 100%;
-        UsageHead           /* Headings in output from the :*usage commands */
-        UsageBody           /* The body of listings in output from the :*usage commands */
-        UsageItem           /* Individual items in output from the :*usage commands */
-        UsageItem:nth-of-type(2n)    background: rgba(0, 0, 0, .04);
-
-        Indicator   /* The <em>#</em> and  <em>%</em> in the <ex>:buffers</ex> list */ \
-                    color: blue; width: 1.5em; text-align: center;
-        Filter      /* The matching text in a completion list */ \
-                    font-weight: bold;
-
-        Keyword     /* A bookmark keyword for a URL */ \
-                    color: red;
-        Tag         /* A bookmark tag for a URL */ \
-                    color: blue;
-
-        Link                        /* A link with additional information shown on hover */ \
-                                    position: relative; padding-right: 2em;
-        Link:not(:hover)>LinkInfo   opacity: 0; left: 0; width: 1px; height: 1px; overflow: hidden;
-        LinkInfo                    {
-            /* Information shown when hovering over a link */
-            color: black;
-            position: absolute;
-            left: 100%;
-            padding: 1ex;
-            margin: -1ex -1em;
-            background: rgba(255, 255, 255, .8);
-            border-radius: 1ex;
-        }
+    CSS: Class.Memoize(function () File.readURL("resource://dactyl-skin/global-styles.css")),
 
-        StatusLine;;;FontFixed  {
-            /* The status bar */
-            -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]    /* The status bar for an ordinary web page */ \
-                                               color: white !important; background: black   !important;
-        StatusLineBroken;[dactyl|highlight]    /* The status bar for a broken web page */ \
-                                               color: black !important; background: #FFa0a0 !important; /* light-red */
-        StatusLineSecure;[dactyl|highlight]    /* The status bar for a secure web page */ \
-                                               color: black !important; background: #a0a0FF !important; /* light-blue */
-        StatusLineExtended;[dactyl|highlight]  /* The status bar for a secure web page with an Extended Validation (EV) certificate */ \
-                                               color: black !important; background: #a0FFa0 !important; /* light-green */
-
-        !TabClose;.tab-close-button            /* The close button of a browser tab */ \
-                                               /* The close button of a browser tab */
-        !TabIcon;.tab-icon,.tab-icon-image     /* The icon of a browser tab */ \
-                                               /* The icon of a browser tab */
-        !TabText;.tab-text                     /* The text of a browser tab */
-        TabNumber                              /* The number of a browser tab, next to its icon */ \
-                                               font-weight: bold; margin: 0px; padding-right: .8ex; cursor: default;
-        TabIconNumber  {
-            /* The number of a browser tab, over its icon */
-            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       /* The title of a listing, including <ex>:pageinfo</ex>, <ex>:jumps</ex> */ \
-                    color: magenta; font-weight: bold;
-        URL         /* A URL */ \
-                    text-decoration: none; color: green; background: inherit;
-        URL:hover   text-decoration: underline; cursor: pointer;
-        URLExtra    /* Extra information about a URL */ \
-                    color: gray;
-
-        FrameIndicator;;* {
-            /* The styling applied to briefly indicate the active frame */
-            background-color: red;
-            opacity: 0.5;
-            z-index: 999999;
-            position: fixed;
-            top:      0;
-            bottom:   0;
-            left:     0;
-            right:    0;
-        }
-
-        Bell          /* &dactyl.appName;’s visual bell */ \
-                      background-color: black !important;
-
-        Hint;;* {
-            /* A hint indicator. See <ex>:help hints</ex> */
-            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;;*      /* The hintable element */ \
-                         background-color: yellow  !important; color: black !important;
-        HintActive;;*    /* The hint element of link which will be followed by <k name="CR"/> */ \
-                         background-color: #88FF00 !important; color: black !important;
-        HintImage;;*     /* The indicator which floats above hinted images */ \
-                         opacity: .5 !important;
-
-        Button                  /* A button widget */ \
-                                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                 /* A group of buttons */
-
-        DownloadCell                    /* A table cell in the :downloads manager */ \
-                                        display: table-cell; padding: 0 1ex;
-
-        Downloads                       /* The :downloads manager */ \
-                                        display: table; margin: 0; padding: 0;
-        DownloadHead;;;CompTitle        /* A heading in the :downloads manager */ \
-                                        display: table-row;
-        DownloadHead>*;;;DownloadCell
-
-        Download                        /* A download in the :downloads manager */ \
-                                        display: table-row;
-        Download:not([active])          color: gray;
-        Download:nth-child(2n+1)        background: rgba(0, 0, 0, .04);
-
-        Download>*;;;DownloadCell
-        DownloadButtons                 /* A button group in the :downloads manager */
-        DownloadPercent                 /* The percentage column for a download */
-        DownloadProgress                /* The progress column for a download */
-        DownloadProgressHave            /* The completed portion of the progress column */
-        DownloadProgressTotal           /* The remaining portion of the progress column */
-        DownloadSource                  /* The download source column for a download */
-        DownloadState                   /* The download state column for a download */
-        DownloadTime                    /* The time remaining column for a download */
-        DownloadTitle                   /* The title column for a download */
-        DownloadTitle>Link>a         max-width: 48ex; overflow: hidden; display: inline-block;
-
-        AddonCell                    /* A cell in tell :addons manager */ \
-                                     display: table-cell; padding: 0 1ex;
-
-        Addons                       /* The :addons manager */ \
-                                     display: table; margin: 0; padding: 0;
-        AddonHead;;;CompTitle        /* A heading in the :addons manager */ \
-                                     display: table-row;
-        AddonHead>*;;;AddonCell
-
-        Addon                        /* An add-on in the :addons manager */ \
-                                     display: table-row;
-        Addon:nth-child(2n+1)        background: rgba(0, 0, 0, .04);
-
-        Addon>*;;;AddonCell
-        AddonButtons
-        AddonDescription
-        AddonName                    max-width: 48ex; overflow: hidden;
-        AddonStatus
-        AddonVersion
-
-        // </css>
-    ]]></>, /&#x0d;/g, "\n")),
-
-    helpCSS: UTF8(<><![CDATA[
-        // <css>
-        InlineHelpLink                              /* A help link shown in the command line or multi-line output area */ \
-                                                    font-size: inherit !important; font-family: inherit !important;
-
-        Help;;;FontProportional                     /* A help page */ \
-                                                    line-height: 1.4em;
-
-        HelpInclude                                 /* A help page included in the consolidated help listing */ \
-                                                    margin: 2em 0;
-
-        HelpArg;;;FontCode                          /* A required command argument indicator */ \
-                                                    color: #6A97D4;
-        HelpOptionalArg;;;FontCode                  /* An optional command argument indicator */ \
-                                                    color: #6A97D4;
-
-        HelpBody                                    /* The body of a help page */ \
-                                                    display: block; margin: 1em auto; max-width: 100ex; padding-bottom: 1em; margin-bottom: 4em; border-bottom-width: 1px;
-        HelpBorder;*;dactyl://help/*                /* The styling of bordered elements */ \
-                                                    border-color: silver; border-width: 0px; border-style: solid;
-        HelpCode;;;FontCode                         /* Code listings */ \
-                                                    display: block; white-space: pre; margin-left: 2em;
-        HelpTT;html|tt;dactyl://help/*;FontCode     /* Teletype text */
-
-        HelpDefault;;;FontCode                      /* The default value of a help item */ \
-                                                    display: inline-block; margin: -1px 1ex 0 0; white-space: pre; vertical-align: text-top;
-
-        HelpDescription                             /* The description of a help item */ \
-                                                    display: block; clear: right;
-        HelpDescription[short]                      clear: none;
-        HelpEm;html|em;dactyl://help/*              /* Emphasized text */ \
-                                                    font-weight: bold; font-style: normal;
-
-        HelpEx;;;FontCode                           /* An Ex command */ \
-                                                    display: inline-block; color: #527BBD;
-
-        HelpExample                                 /* An example */ \
-                                                    display: block; margin: 1em 0;
-        HelpExample::before                         content: "__MSG_help.Example__: "; font-weight: bold;
-
-        HelpInfo                                    /* Arbitrary information about a help item */ \
-                                                    display: block; width: 20em; margin-left: auto;
-        HelpInfoLabel                               /* The label for a HelpInfo item */ \
-                                                    display: inline-block; width: 6em;  color: magenta; font-weight: bold; vertical-align: text-top;
-        HelpInfoValue                               /* The details for a HelpInfo item */ \
-                                                    display: inline-block; width: 14em; text-decoration: none;             vertical-align: text-top;
-
-        HelpItem                                    /* A help item */ \
-                                                    display: block; margin: 1em 1em 1em 10em; clear: both;
-
-        HelpKey;;;FontCode                          /* A keyboard key specification */ \
-                                                    color: #102663;
-        HelpKeyword                                 /* A keyword */ \
-                                                    font-weight: bold; color: navy;
-
-        HelpLink;html|a;dactyl://help/*             /* A hyperlink */ \
-                                                    text-decoration: none !important;
-        HelpLink[href]:hover                        text-decoration: underline !important;
-        HelpLink[href^="mailto:"]::after            content: "✉"; padding-left: .2em;
-        HelpLink[rel=external] {
-            /* A hyperlink to an external resource */
-            /* Thanks, Wikipedia */
-            background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAMAAAC67D+PAAAAFVBMVEVmmcwzmcyZzP8AZswAZv////////9E6giVAAAAB3RSTlP///////8AGksDRgAAADhJREFUGFcly0ESAEAEA0Ei6/9P3sEcVB8kmrwFyni0bOeyyDpy9JTLEaOhQq7Ongf5FeMhHS/4AVnsAZubxDVmAAAAAElFTkSuQmCC) no-repeat scroll right center;
-            padding-right: 13px;
-        }
-
-        ErrorMsg HelpEx       color: inherit; background: inherit; text-decoration: underline;
-        ErrorMsg HelpKey      color: inherit; background: inherit; text-decoration: underline;
-        ErrorMsg HelpOption   color: inherit; background: inherit; text-decoration: underline;
-        ErrorMsg HelpTopic    color: inherit; background: inherit; text-decoration: underline;
-
-        HelpTOC               /* The Table of Contents for a help page */
-        HelpTOC>ol ol         margin-left: -1em;
-
-        HelpOrderedList;ol;dactyl://help/*                          /* Any ordered list */ \
-                                                                    margin: 1em 0;
-        HelpOrderedList1;ol[level="1"],ol;dactyl://help/*           /* A first-level ordered list */ \
-                                                                    list-style: outside decimal; display: block;
-        HelpOrderedList2;ol[level="2"],ol ol;dactyl://help/*        /* A second-level ordered list */ \
-                                                                    list-style: outside upper-alpha;
-        HelpOrderedList3;ol[level="3"],ol ol ol;dactyl://help/*     /* A third-level ordered list */ \
-                                                                    list-style: outside lower-roman;
-        HelpOrderedList4;ol[level="4"],ol ol ol ol;dactyl://help/*  /* A fourth-level ordered list */ \
-                                                                    list-style: outside decimal;
-
-        HelpList;html|ul;dactyl://help/*      /* An unordered list */ \
-                                              display: block; list-style-position: outside; margin: 1em 0;
-        HelpListItem;html|li;dactyl://help/*  /* A list item, ordered or unordered */ \
-                                              display: list-item;
-
-        HelpNote                                    /* The indicator for a note */ \
-                                                    color: red; font-weight: bold;
-
-        HelpOpt;;;FontCode                          /* An option name */ \
-                                                    color: #106326;
-        HelpOptInfo;;;FontCode                      /* Information about the type and default values for an option entry */ \
-                                                    display: block; margin-bottom: 1ex; padding-left: 4em;
-
-        HelpParagraph;html|p;dactyl://help/*        /* An ordinary paragraph */ \
-                                                    display: block; margin: 1em 0em;
-        HelpParagraph:first-child                   margin-top: 0;
-        HelpParagraph:last-child                    margin-bottom: 0;
-        HelpSpec;;;FontCode                         /* The specification for a help entry */ \
-                                                    display: block; margin-left: -10em; float: left; clear: left; color: #527BBD; margin-right: 1em;
-
-        HelpString;;;FontCode                       /* A quoted string */ \
-                                                    color: green; font-weight: normal;
-        HelpString::before                          content: '"';
-        HelpString::after                           content: '"';
-        HelpString[delim]::before                   content: attr(delim);
-        HelpString[delim]::after                    content: attr(delim);
-
-        HelpNews        /* A news item */           position: relative;
-        HelpNewsOld     /* An old news item */      opacity: .7;
-        HelpNewsNew     /* A new news item */       font-style: italic;
-        HelpNewsTag     /* The version tag for a news item */ \
-                        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/* {
-            /* Any help heading */
-            font-weight: bold;
-            color: #527BBD;
-            clear: both;
-        }
-        HelpHead1;html|h1;dactyl://help/* {
-            /* A first-level help heading */
-            margin: 2em 0 1em;
-            padding-bottom: .2ex;
-            border-bottom-width: 1px;
-            font-size: 2em;
-        }
-        HelpHead2;html|h2;dactyl://help/* {
-            /* A second-level help heading */
-            margin: 2em 0 1em;
-            padding-bottom: .2ex;
-            border-bottom-width: 1px;
-            font-size: 1.2em;
-        }
-        HelpHead3;html|h3;dactyl://help/* {
-            /* A third-level help heading */
-            margin: 1em 0;
-            padding-bottom: .2ex;
-            font-size: 1.1em;
-        }
-        HelpHead4;html|h4;dactyl://help/* {
-            /* A fourth-level help heading */
-        }
-
-        HelpTab;html|dl;dactyl://help/* {
-            /* A description table */
-            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  /* The title column of description tables */ \
-                                                    display: table-cell; padding: .1ex 1ex; font-weight: bold;
-        HelpTabDescription;html|dd;dactyl://help/*  /* The description column of description tables */ \
-                                                    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/*  /* Entire rows in description tables */ \
-                                                    display: table-row;
-
-        HelpTag;;;FontCode                          /* A help tag */ \
-                                                    display: inline-block; color: #527BBD; margin-left: 1ex; font-weight: normal;
-        HelpTags                                    /* A group of help tags */ \
-                                                    display: block; float: right; clear: right;
-        HelpTopic;;;FontCode                        /* A link to a help topic */ \
-                                                    color: #102663;
-        HelpType;;;FontCode                         /* An option type */ \
-                                                    color: #102663 !important; margin-right: 2ex;
-
-        HelpWarning                                 /* The indicator for a warning */ \
-                                                    color: red; font-weight: bold;
-
-        HelpXML;;;FontCode                          /* Highlighted XML */ \
-                                                    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, {
@@ -922,7 +590,6 @@ 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  {"}
                      display:    inline-block;
                      background: url({img.src});
@@ -946,6 +613,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: