]> git.donarmstrong.com Git - dactyl.git/blobdiff - common/modules/styles.jsm
Import r6948 from upstream hg supporting Firefox up to 24.*
[dactyl.git] / common / modules / styles.jsm
index adfc725c8269780495c182289689e672b9c1af67..ffa4c696fc0b3e2d5f548c78967d0aa935855f20 100644 (file)
@@ -1,20 +1,21 @@
-// Copyright (c) 2008-2011 by Kris Maglione <maglione.k at Gmail>
+// Copyright (c) 2008-2012 Kris Maglione <maglione.k at Gmail>
 //
 // This work is licensed for reuse under an MIT license. Details are
 // given in the LICENSE.txt file included with this file.
 "use strict";
 
-Components.utils.import("resource://dactyl/bootstrap.jsm");
 defineModule("styles", {
     exports: ["Style", "Styles", "styles"],
-    require: ["services", "util"],
-    use: ["contexts", "messages", "template"]
-}, this);
+    require: ["services", "util"]
+});
+
+lazyRequire("contexts", ["Contexts"]);
+lazyRequire("template", ["template"]);
 
 function cssUri(css) "chrome-data:text/css," + encodeURI(css);
-var namespace = "@namespace html " + XHTML.uri.quote() + ";\n" +
-                "@namespace xul " + XUL.uri.quote() + ";\n" +
-                "@namespace dactyl " + NS.uri.quote() + ";\n";
+var namespace = "@namespace html " + XHTML.quote() + ";\n" +
+                "@namespace xul " + XUL.quote() + ";\n" +
+                "@namespace dactyl " + NS.quote() + ";\n";
 
 var Sheet = Struct("name", "id", "sites", "css", "hive", "agent");
 Sheet.liveProperty = function (name) {
@@ -28,15 +29,15 @@ Sheet.liveProperty = function (name) {
         this[i] = val;
         this.enabled = this.enabled;
     });
-}
+};
 Sheet.liveProperty("agent");
 Sheet.liveProperty("css");
 Sheet.liveProperty("sites");
 update(Sheet.prototype, {
     formatSites: function (uris)
           template.map(this.sites,
-                       function (filter) <span highlight={uris.some(Styles.matchFilter(filter)) ? "Filter" : ""}>{filter}</span>,
-                       <>,</>),
+                       function (filter) ["span", { highlight: uris.some(Styles.matchFilter(filter)) ? "Filter" : "" }, filter],
+                       ","),
 
     remove: function () { this.hive.remove(this); },
 
@@ -68,17 +69,20 @@ update(Sheet.prototype, {
     get fullCSS() {
         let filter = this.sites;
         let css = this.css;
+
+        let preamble = "/* " + this.uri + (this.agent ? " (agent)" : "") + " */\n\n" + namespace + "\n";
         if (filter[0] == "*")
-            return namespace + css;
+            return preamble + css;
 
         let selectors = filter.map(function (part)
-                                    (/[*]$/.test(part)   ? "url-prefix" :
-                                     /[\/:]/.test(part)  ? "url"
-                                                         : "domain")
-                                    + '("' + part.replace(/"/g, "%22").replace(/\*$/, "") + '")')
+                                    !/^(?:[a-z-]+[:*]|[a-z-.]+$)/i.test(part) ? "regexp(" + Styles.quote(".*(?:" + part + ").*") + ")" :
+                                       (/[*]$/.test(part)   ? "url-prefix" :
+                                        /[\/:]/.test(part)  ? "url"
+                                                            : "domain")
+                                       + '(' + Styles.quote(part.replace(/\*$/, "")) + ')')
                               .join(",\n               ");
-        return "/* " + this.uri + (this.agent ? " (agent)" : "") + " */\n\n"
-             + namespace + "\n@-moz-document " + selectors + " {\n\n" + css + "\n\n}\n";
+
+        return preamble + "@-moz-document " + selectors + " {\n\n" + css + "\n\n}\n";
     }
 });
 
@@ -94,7 +98,7 @@ var Hive = Class("Hive", {
     get modifiable() this.name !== "system",
 
     addRef: function (obj) {
-        this.refs.push(Cu.getWeakReference(obj));
+        this.refs.push(util.weakReference(obj));
         this.dropRef(null);
     },
     dropRef: function (obj) {
@@ -199,7 +203,6 @@ var Hive = Class("Hive", {
      * @param {number} index
      */
     remove: function remove(name, filter, css, index) {
-        let self = this;
         if (arguments.length == 1) {
             var matches = [name];
             name = null;
@@ -207,7 +210,7 @@ var Hive = Class("Hive", {
 
         if (filter && filter.indexOf(",") > -1)
             return filter.split(",").reduce(
-                function (n, f) n + self.removeSheet(name, f, index), 0);
+                (n, f) => n + this.removeSheet(name, f, index), 0);
 
         if (filter == undefined)
             filter = "";
@@ -251,12 +254,14 @@ var Styles = Module("Styles", {
         this.cleanup();
         this.allSheets = {};
 
-        services["dactyl:"].providers["style"] = function styleProvider(uri) {
-            let id = /^\/(\d*)/.exec(uri.path)[1];
-            if (set.has(styles.allSheets, id))
-                return ["text/css", unescape(encodeURI(styles.allSheets[id].fullCSS))];
-            return null;
-        };
+        update(services["dactyl:"].providers, {
+            "style": function styleProvider(uri, path) {
+                let id = parseInt(path);
+                if (Set.has(styles.allSheets, id))
+                    return ["text/css", styles.allSheets[id].fullCSS];
+                return null;
+            }
+        });
     },
 
     cleanup: function cleanup() {
@@ -296,50 +301,48 @@ var Styles = Module("Styles", {
     systemNames: Class.Property({ get: deprecated("Styles#system.names", function systemNames() this.system.names) }),
     sites: Class.Property({ get: deprecated("Styles#user.sites", function sites() this.user.sites) }),
 
-    list: function list(content, filter, name, hives) {
+    list: function list(content, sites, name, hives) {
         const { commandline, dactyl } = this.modules;
 
         hives = hives || styles.hives.filter(function (h) h.modifiable && h.sheets.length);
 
         function sheets(group)
             group.sheets.slice()
+                 .filter(function (sheet) (!name || sheet.name === name) &&
+                                          (!sites || sites.every(function (s) sheet.sites.indexOf(s) >= 0)))
                  .sort(function (a, b) a.name && b.name ? String.localeCompare(a.name, b.name)
                                                         : !!b.name - !!a.name || a.id - b.id);
 
         let uris = util.visibleURIs(content);
 
-        let list = <table>
-                <tr highlight="Title">
-                    <td/>
-                    <td/>
-                    <td style="padding-right: 1em;">Name</td>
-                    <td style="padding-right: 1em;">Filter</td>
-                    <td style="padding-right: 1em;">CSS</td>
-                </tr>
-                <col style="min-width: 4em; padding-right: 1em;"/>
-                <col style="min-width: 1em; text-align: center; color: red; font-weight: bold;"/>
-                <col style="padding: 0 1em 0 1ex; vertical-align: top;"/>
-                <col style="padding: 0 1em 0 0; vertical-align: top;"/>
-                {
-                    template.map(hives, function (hive) let (i = 0)
-                        <tr style="height: .5ex;"/> +
-                        template.map(sheets(hive), function (sheet)
-                            <tr>
-                                <td highlight="Title">{!i++ ? hive.name : ""}</td>
-                                <td>{sheet.enabled ? "" : UTF8("×")}</td>
-                                <td>{sheet.name || hive.sheets.indexOf(sheet)}</td>
-                                <td>{sheet.formatSites(uris)}</td>
-                                <td>{sheet.css}</td>
-                            </tr>) +
-                        <tr style="height: .5ex;"/>)
-                }
-                </table>;
-
-        // TODO: Move this to an ItemList to show this automatically
-        if (list.*.length() === list.text().length() + 5)
-            dactyl.echomsg(_("style.none"));
-        else
-            commandline.commandOutput(list);
+        let list = ["table", {},
+                ["tr", { highlight: "Title" },
+                    ["td"],
+                    ["td"],
+                    ["td", { style: "padding-right: 1em;" }, _("title.Name")],
+                    ["td", { style: "padding-right: 1em;" }, _("title.Filter")],
+                    ["td", { style: "padding-right: 1em;" }, _("title.CSS")]],
+                ["col", { style: "min-width: 4em; padding-right: 1em;" }],
+                ["col", { style: "min-width: 1em; text-align: center; color: red; font-weight: bold;" }],
+                ["col", { style: "padding: 0 1em 0 1ex; vertical-align: top;" }],
+                ["col", { style: "padding: 0 1em 0 0; vertical-align: top;" }],
+                template.map(hives, function (hive) let (i = 0) [
+                    ["tr", { style: "height: .5ex;" }],
+                    template.map(sheets(hive), function (sheet)
+                        ["tr", {},
+                            ["td", { highlight: "Title" }, !i++ ? hive.name : ""],
+                            ["td", {}, sheet.enabled ? "" : UTF8("×")],
+                            ["td", {}, sheet.name || hive.sheets.indexOf(sheet)],
+                            ["td", {}, sheet.formatSites(uris)],
+                            ["td", {}, sheet.css]]),
+                    ["tr", { style: "height: .5ex;" }]])];
+
+        // E4X-FIXME
+        // // TODO: Move this to an ItemList to show this automatically
+        // if (list.*.length() === list.text().length() + 5)
+        //     dactyl.echomsg(_("style.none"));
+        // else
+        commandline.commandOutput(list);
     },
 
     registerSheet: function registerSheet(url, agent, reload) {
@@ -365,9 +368,13 @@ var Styles = Module("Styles", {
             for (let prop in Styles.propertyIter(str))
                 props[prop.name] = prop.value;
 
-        return Object.keys(props)[sort ? "sort" : "slice"]()
-                     .map(function (prop) prop + ": " + props[prop] + ";")
-                     .join(" ");
+        let val = Object.keys(props)[sort ? "sort" : "slice"]()
+                        .map(function (prop) prop + ": " + props[prop] + ";")
+                        .join(" ");
+
+        if (/^\s*(\/\*.*?\*\/)/.exec(src))
+            val = RegExp.$1 + " " + val;
+        return val;
     },
 
     completeSite: function (context, content, group) {
@@ -377,8 +384,8 @@ var Styles = Module("Styles", {
             context.fork("current", 0, this, function (context) {
                 context.title = ["Current Site"];
                 context.completions = [
-                    [content.location.host, "Current Host"],
-                    [content.location.href, "Current URL"]
+                    [content.location.host, /*L*/"Current Host"],
+                    [content.location.href, /*L*/"Current URL"]
                 ];
             });
         }
@@ -389,7 +396,7 @@ var Styles = Module("Styles", {
         context.generate = function () values(group.sites);
 
         context.keys.text = util.identity;
-        context.keys.description = function (site) this.sheets.length + " sheet" + (this.sheets.length == 1 ? "" : "s") + ": " +
+        context.keys.description = function (site) this.sheets.length + /*L*/" sheet" + (this.sheets.length == 1 ? "" : "s") + ": " +
             array.compact(this.sheets.map(function (s) s.name)).join(", ");
         context.keys.sheets = function (site) group.sheets.filter(function (s) s.sites.indexOf(site) >= 0);
         context.keys.active = function (site) uris.some(Styles.matchFilter(site));
@@ -410,6 +417,8 @@ var Styles = Module("Styles", {
      * @returns {nsIURI -> boolean}
      */
     matchFilter: function (filter) {
+        filter = filter.trim();
+
         if (filter === "*")
             var test = function test(uri) true;
         else if (!/^(?:[a-z-]+:|[a-z-.]+$)/.test(filter)) {
@@ -425,6 +434,7 @@ var Styles = Module("Styles", {
         else
             test = function test(uri) { try { return util.isSubdomain(uri.host, filter); } catch (e) { return false; } };
         test.toString = function toString() filter;
+        test.key = filter;
         if (arguments.length < 2)
             return test;
         return test(arguments[1]);
@@ -434,7 +444,7 @@ var Styles = Module("Styles", {
         for (let item in Iterator({ Active: true, Inactive: false })) {
             let [name, active] = item;
             context.split(name, null, function (context) {
-                context.title[0] = name + " " + (title || "Sheets");
+                context.title[0] = /*L*/name + " " + (title || "Sheets");
                 context.filters.push(function (item) !!item.active == active);
             });
         }
@@ -450,7 +460,7 @@ var Styles = Module("Styles", {
         }
     },
 
-    propertyPattern: util.regexp(<![CDATA[
+    propertyPattern: util.regexp(literal(/*
             (?:
                 (?P<preSpace> <space>*)
                 (?P<name> [-a-z]*)
@@ -472,14 +482,14 @@ var Styles = Module("Styles", {
                 )?
             )
             (?P<postSpace> <space>* (?: ; | $) )
-        ]]>, "gix",
+        */), "gix",
         {
             space: /(?: \s | \/\* .*? \*\/ )/,
             string: /(?:" (?:[^\\"]|\\.)* (?:"|$) | '(?:[^\\']|\\.)* (?:'|$) )/
         }),
 
     patterns: memoize({
-        get property() util.regexp(<![CDATA[
+        get property() util.regexp(literal(/*
                 (?:
                     (?P<preSpace> <space>*)
                     (?P<name> [-a-z]*)
@@ -490,26 +500,26 @@ var Styles = Module("Styles", {
                     )?
                 )
                 (?P<postSpace> <space>* (?: ; | $) )
-            ]]>, "gix", this),
+            */), "gix", this),
 
-        get function() util.regexp(<![CDATA[
+        get function() util.regexp(literal(/*
                 (?P<function>
                     \s* \( \s*
                         (?: <string> | [^)]*  )
                     \s* (?: \) | $)
                 )
-            ]]>, "gx", this),
+            */), "gx", this),
 
         space: /(?: \s | \/\* .*? \*\/ )/,
 
-        get string() util.regexp(<![CDATA[
+        get string() util.regexp(literal(/*
                 (?P<string>
                     " (?:[^\\"]|\\.)* (?:"|$) |
                     ' (?:[^\\']|\\.)* (?:'|$)
                 )
-            ]]>, "gx", this),
+            */), "gx", this),
 
-        get token() util.regexp(<![CDATA[
+        get token() util.regexp(literal(/*
             (?P<token>
                 (?P<word> [-\w]+)
                 <function>?
@@ -519,10 +529,20 @@ var Styles = Module("Styles", {
                 | <space>+
                 | [^;}\s]+
             )
-        ]]>, "gix", this)
-    })
+        */), "gix", this)
+    }),
+
+    /**
+     * Quotes a string for use in CSS stylesheets.
+     *
+     * @param {string} str
+     * @returns {string}
+     */
+    quote: function quote(str) {
+        return '"' + str.replace(/([\\"])/g, "\\$1").replace(/\n/g, "\\00000a") + '"';
+    },
 }, {
-    commands: function (dactyl, modules, window) {
+    commands: function initCommands(dactyl, modules, window) {
         const { commands, contexts, styles } = modules;
 
         function sheets(context, args, filter) {
@@ -530,7 +550,7 @@ var Styles = Module("Styles", {
             context.compare = modules.CompletionContext.Sort.number;
             context.generate = function () args["-group"].sheets;
             context.keys.active = function (sheet) uris.some(sheet.closure.match);
-            context.keys.description = function (sheet) <>{sheet.formatSites(uris)}: {sheet.css.replace("\n", "\\n")}</>
+            context.keys.description = function (sheet) [sheet.formatSites(uris), ": ", sheet.css.replace("\n", "\\n")];
             if (filter)
                 context.filters.push(function ({ item }) filter(item));
             Styles.splitContext(context);
@@ -553,17 +573,16 @@ var Styles = Module("Styles", {
                 let [filter, css] = args;
 
                 if (!css)
-                    styles.list(window.content, filter, args["-name"], args.explicitOpts["-group"] ? [args["-group"]] : null);
+                    styles.list(window.content, filter ? filter.split(",") : null, args["-name"], args.explicitOpts["-group"] ? [args["-group"]] : null);
                 else {
                     util.assert(args["-group"].modifiable && args["-group"].hive.modifiable,
-                                "Cannot modify styles in the builtin group");
+                                _("group.cantChangeBuiltin", _("style.styles")));
 
                     if (args["-append"]) {
                         let sheet = args["-group"].get(args["-name"]);
                         if (sheet) {
-                            filter = sheet.sites.concat(filter).join(",");
+                            filter = array(sheet.sites).concat(filter).uniq().join(",");
                             css = sheet.css + " " + css;
-
                         }
                     }
                     let style = args["-group"].add(args["-name"], filter, css, args["-agent"]);
@@ -573,7 +592,6 @@ var Styles = Module("Styles", {
                 }
             },
             {
-                bang: true,
                 completer: function (context, args) {
                     let compl = [];
                     let sheet = args["-group"].get(args["-name"]);
@@ -584,7 +602,9 @@ var Styles = Module("Styles", {
                     }
                     else if (args.completeArg == 1) {
                         if (sheet)
-                            context.completions = [[sheet.css, "Current Value"]];
+                            context.completions = [
+                                [sheet.css, _("option.currentValue")]
+                            ];
                         context.fork("css", 0, modules.completion, "css");
                     }
                 },
@@ -607,10 +627,10 @@ var Styles = Module("Styles", {
                                     command: "style",
                                     arguments: [style.sites.join(",")],
                                     literalArg: style.css,
-                                    options: update({
-                                            "-group": hive.name,
-                                        },
-                                        style.name ? { "-name": style.name } : {})
+                                    options: {
+                                        "-group": hive.name == "user" ? undefined : hive.name,
+                                        "-name": style.name || undefined
+                                    }
                                 })))
                         .flatten().array
             });
@@ -642,7 +662,7 @@ var Styles = Module("Styles", {
             commands.add(cmd.name, cmd.desc,
                 function (args) {
                     dactyl.assert(args.bang ^ !!(args[0] || args[1] || args["-name"] || args["-index"]),
-                                  "Argument or ! required");
+                                  _("error.argumentOrBang"));
 
                     args["-group"].find(args["-name"], args[0], args.literalArg, args["-index"])
                                   .forEach(cmd.action);
@@ -671,7 +691,7 @@ var Styles = Module("Styles", {
                 });
         });
     },
-    contexts: function (dactyl, modules, window) {
+    contexts: function initContexts(dactyl, modules, window) {
         modules.contexts.Hives("styles",
             Class("LocalHive", Contexts.Hive, {
                 init: function init(group) {
@@ -692,8 +712,8 @@ var Styles = Module("Styles", {
                 }
             }));
     },
-    completion: function (dactyl, modules, window) {
-        const names = Array.slice(util.computedStyle(window.document.createElement("div")));
+    completion: function initCompletion(dactyl, modules, window) {
+        const names = Array.slice(DOM(["div"], window.document).style);
         modules.completion.css = function (context) {
             context.title = ["CSS Property"];
             context.keys = { text: function (p) p + ":", description: function () "" };
@@ -707,8 +727,8 @@ var Styles = Module("Styles", {
             }
         };
     },
-    javascript: function (dactyl, modules, window) {
-        modules.JavaScript.setCompleter(["get", "add", "remove", "find"].map(function (m) styles.user[m]),
+    javascript: function initJavascript(dactyl, modules, window) {
+        modules.JavaScript.setCompleter(["get", "add", "remove", "find"].map(function (m) Hive.prototype[m]),
             [ // Prototype: (name, filter, css, index)
                 function (context, obj, args) this.names,
                 function (context, obj, args) Styles.completeSite(context, window.content),
@@ -716,37 +736,40 @@ var Styles = Module("Styles", {
                 function (context, obj, args) this.sheets
             ]);
     },
-    template: function () {
+    template: function initTemplate() {
         let patterns = Styles.patterns;
 
         template.highlightCSS = function highlightCSS(css) {
-            XML.prettyPrinting = XML.ignoreWhitespace = false;
-
-            return this.highlightRegexp(css, patterns.property, function (match) <>{
-                match.preSpace}{template.filter(match.name)}: {
+            return this.highlightRegexp(css, patterns.property, function (match) {
+                if (!match.length)
+                    return [];
+                return ["", match.preSpace, template.filter(match.name), ": ",
 
                     template.highlightRegexp(match.value, patterns.token, function (match) {
                         if (match.function)
-                            return <>{template.filter(match.word)}{
+                            return ["", template.filter(match.word),
                                 template.highlightRegexp(match.function, patterns.string,
-                                    function (match) <span highlight="String">{match.string}</span>)
-                            }</>;
+                                    function (match) ["span", { highlight: "String" }, match.string])
+                            ];
                         if (match.important == "!important")
-                            return <span highlight="String">{match.important}</span>;
+                            return ["span", { highlight: "String" }, match.important];
                         if (match.string)
-                            return <span highlight="String">{match.string}</span>;
-                        return template.highlightRegexp(match.wholeMatch, /^(\d+)(em|ex|px|in|cm|mm|pt|pc)?/g,
-                                                        function (m, n, u) <><span highlight="Number">{n}</span><span highlight="Object">{u || ""}</span></>);
-                    })
-
-                }{ match.postSpace }</>
-            )
-        }
-    },
+                            return ["span", { highlight: "String" }, match.string];
+                        return template._highlightRegexp(match.wholeMatch, /^(\d+)(em|ex|px|in|cm|mm|pt|pc)?/g,
+                                                         function (m, n, u) [
+                                                             ["span", { highlight: "Number" }, n],
+                                                             ["span", { highlight: "Object" }, u || ""]
+                                                         ]);
+                    }),
+                    match.postSpace
+                ];
+            });
+        };
+    }
 });
 
 endModule();
 
 // catch(e){dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack);}
 
-// vim: set fdm=marker sw=4 ts=4 et ft=javascript:
+// vim: set fdm=marker sw=4 sts=4 ts=8 et ft=javascript: