]> git.donarmstrong.com Git - dactyl.git/blobdiff - common/modules/contexts.jsm
Import r6948 from upstream hg supporting Firefox up to 24.*
[dactyl.git] / common / modules / contexts.jsm
index 15549e37cede5a2d10de6f1bb818571bd77bed00..ffada2b7635446021f543c06cdc5c2a6e8165f7b 100644 (file)
@@ -1,53 +1,71 @@
-// Copyright (c) 2010-2011 by Kris Maglione <maglione.k@gmail.com>
+// Copyright (c) 2010-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 {
-
-Components.utils.import("resource://dactyl/bootstrap.jsm");
 defineModule("contexts", {
     exports: ["Contexts", "Group", "contexts"],
-    use: ["commands", "messages", "options", "services", "storage", "styles", "template", "util"]
-}, this);
+    require: ["services", "util"]
+});
+
+lazyRequire("commands", ["ArgType", "CommandOption", "commands"]);
+lazyRequire("options", ["Option"]);
+lazyRequire("overlay", ["overlay"]);
+lazyRequire("storage", ["File"]);
+lazyRequire("template", ["template"]);
 
 var Const = function Const(val) Class.Property({ enumerable: true, value: val });
 
 var Group = Class("Group", {
     init: function init(name, description, filter, persist) {
-        const self = this;
-
         this.name = name;
         this.description = description;
         this.filter = filter || this.constructor.defaultFilter;
         this.persist = persist || false;
         this.hives = [];
+        this.children = [];
     },
 
+    get contexts() this.modules.contexts,
+
+    set lastDocument(val) { this._lastDocument = util.weakReference(val); },
+    get lastDocument() this._lastDocument && this._lastDocument.get(),
+
     modifiable: true,
 
-    cleanup: function cleanup() {
+    cleanup: function cleanup(reason) {
         for (let hive in values(this.hives))
             util.trapErrors("cleanup", hive);
 
         this.hives = [];
         for (let hive in keys(this.hiveMap))
             delete this[hive];
+
+        if (reason != "shutdown")
+            this.children.splice(0).forEach(this.contexts.closure.removeGroup);
     },
-    destroy: function destroy() {
+    destroy: function destroy(reason) {
         for (let hive in values(this.hives))
             util.trapErrors("destroy", hive);
+
+        if (reason != "shutdown")
+            this.children.splice(0).forEach(this.contexts.closure.removeGroup);
     },
 
     argsExtra: function argsExtra() ({}),
 
+    makeArgs: function makeArgs(doc, context, args) {
+        let res = update({ doc: doc, context: context }, args);
+        return update(res, this.argsExtra(res), args);
+    },
+
     get toStringParams() [this.name],
 
     get builtin() this.modules.contexts.builtinGroups.indexOf(this) >= 0,
 
 }, {
-    compileFilter: function (patterns, default_) {
+    compileFilter: function (patterns, default_ = false) {
         if (arguments.length < 2)
             default_ = false;
 
@@ -58,19 +76,31 @@ var Group = Class("Group", {
         return update(siteFilter, {
             toString: function () this.filters.join(","),
 
-            toXML: function (modules) let (uri = modules && modules.buffer.uri)
+            toJSONXML: function (modules) let (uri = modules && modules.buffer.uri)
                 template.map(this.filters,
-                             function (f) <span highlight={uri && f(uri) ? "Filter" : ""}>{f}</span>,
-                             <>,</>),
+                             function (f) ["span", { highlight: uri && f(uri) ? "Filter" : "" },
+                                               "toJSONXML" in f ? f.toJSONXML() : String(f)],
+                             ","),
 
             filters: Option.parse.sitelist(patterns)
         });
     },
 
-    defaultFilter: Class.memoize(function () this.compileFilter(["*"]))
+    defaultFilter: Class.Memoize(function () this.compileFilter(["*"]))
 });
 
 var Contexts = Module("contexts", {
+    init: function () {
+        this.pluginModules = {};
+    },
+
+    cleanup: function () {
+        for each (let module in this.pluginModules)
+            util.trapErrors("unload", module);
+
+        this.pluginModules = {};
+    },
+
     Local: function Local(dactyl, modules, window) ({
         init: function () {
             const contexts = this;
@@ -109,19 +139,22 @@ var Contexts = Module("contexts", {
 
                 completer: function (context) modules.completion.group(context)
             });
+
+            memoize(modules, "userContext",  function () contexts.Context(modules.io.getRCFile("~", true), contexts.user, [modules, true]));
+            memoize(modules, "_userContext", function () contexts.Context(modules.io.getRCFile("~", true), contexts.user, [modules.userContext]));
         },
 
         cleanup: function () {
-            for (let hive in values(this.groupList))
-                util.trapErrors("cleanup", hive);
+            for each (let hive in this.groupList.slice())
+                util.trapErrors("cleanup", hive, "shutdown");
         },
 
         destroy: function () {
-            for (let hive in values(this.groupList))
-                util.trapErrors("destroy", hive);
+            for each (let hive in values(this.groupList.slice()))
+                util.trapErrors("destroy", hive, "shutdown");
 
             for (let [name, plugin] in iter(this.modules.plugins.contexts))
-                if (plugin && "onUnload" in plugin)
+                if (plugin && "onUnload" in plugin && callable(plugin.onUnload))
                     util.trapErrors("onUnload", plugin);
         },
 
@@ -136,13 +169,12 @@ var Contexts = Module("contexts", {
         Hives: Class("Hives", Class.Property, {
             init: function init(name, constructor) {
                 const { contexts } = modules;
-                const self = this;
 
                 if (this.Hive)
                     return {
                         enumerable: true,
 
-                        get: function () array(contexts.groups[self.name])
+                        get: () => array(contexts.groups[this.name])
                     };
 
                 this.Hive = constructor;
@@ -159,7 +191,7 @@ var Contexts = Module("contexts", {
                                                                 { _hive: { value: name } })));
 
                 memoize(contexts.groupsProto, name,
-                        function () [group[name] for (group in values(this.groups)) if (set.has(group, name))]);
+                        function () [group[name] for (group in values(this.groups)) if (Set.has(group, name))]);
             },
 
             get toStringParams() [this.name, this.Hive]
@@ -176,28 +208,42 @@ var Contexts = Module("contexts", {
                                   function (dir) dir.contains(file, true),
                                   0);
 
+        let name = isPlugin ? file.getRelativeDescriptor(isPlugin).replace(File.PATH_SEP, "-")
+                            : file.leafName;
+        let id   = util.camelCase(name.replace(/\.[^.]*$/, ""));
+
         let contextPath = file.path;
-        let self = set.has(plugins, contextPath) && plugins.contexts[contextPath];
+        let self = Set.has(plugins, contextPath) && plugins.contexts[contextPath];
+
+        if (!self && isPlugin && false)
+            self = Set.has(plugins, id) && plugins[id];
 
         if (self) {
-            if (set.has(self, "onUnload"))
-                self.onUnload();
+            if (Set.has(self, "onUnload"))
+                util.trapErrors("onUnload", self);
         }
         else {
-            let name = isPlugin ? file.getRelativeDescriptor(isPlugin).replace(File.PATH_SEP, "-")
-                                : file.leafName;
+            let params = Array.slice(args || [userContext]);
+            params[2] = params[2] || File(file).URI.spec;
 
-            self = update(newContext.apply(null, args || [userContext]), {
-                NAME: Const(name.replace(/\.[^.]*$/, "").replace(/-([a-z])/g, function (m, n1) n1.toUpperCase())),
+            self = args && !isArray(args) ? args : newContext.apply(null, params);
+            update(self, {
+                NAME: Const(id),
 
                 PATH: Const(file.path),
 
                 CONTEXT: Const(self),
 
+                set isGlobalModule(val) {
+                    // Hack.
+                    if (val)
+                        throw Contexts;
+                },
+
                 unload: Const(function unload() {
                     if (plugins[this.NAME] === this || plugins[this.PATH] === this)
                         if (this.onUnload)
-                            this.onUnload();
+                            util.trapErrors("onUnload", this);
 
                     if (plugins[this.NAME] === this)
                         delete plugins[this.NAME];
@@ -212,10 +258,12 @@ var Contexts = Module("contexts", {
                         contexts.removeGroup(this.GROUP);
                 })
             });
-            Class.replaceProperty(plugins, file.path, self);
+
+            if (group !== this.user)
+                Class.replaceProperty(plugins, file.path, self);
 
             // This belongs elsewhere
-            if (isPlugin && args)
+            if (isPlugin)
                 Object.defineProperty(plugins, self.NAME, {
                     configurable: true,
                     enumerable: true,
@@ -233,7 +281,7 @@ var Contexts = Module("contexts", {
             group = this.addGroup(commands.nameRegexp
                                           .iterate(name.replace(/\.[^.]*$/, ""))
                                           .join("-").replace(/--+/g, "-"),
-                                  "Script group for " + file.path,
+                                  _("context.scriptGroup", file.path),
                                   null, false);
 
         Class.replaceProperty(self, "GROUP", group);
@@ -246,6 +294,74 @@ var Contexts = Module("contexts", {
         return this.Context(file, group, [this.modules.userContext, true]);
     },
 
+    Module: function Module(uri, isPlugin) {
+        const { io, plugins } = this.modules;
+
+        let canonical = uri.spec;
+        if (uri.scheme == "resource")
+            canonical = services["resource:"].resolveURI(uri);
+
+        if (uri instanceof Ci.nsIFileURL)
+            var file = File(uri.file);
+
+        let isPlugin = array.nth(io.getRuntimeDirectories("plugins"),
+                                 function (dir) dir.contains(file, true),
+                                 0);
+
+        let name = isPlugin && file && file.getRelativeDescriptor(isPlugin)
+                                           .replace(File.PATH_SEP, "-");
+        let id   = util.camelCase(name.replace(/\.[^.]*$/, ""));
+
+        let self = Set.has(this.pluginModules, canonical) && this.pluginModules[canonical];
+
+        if (!self) {
+            self = Object.create(jsmodules);
+
+            update(self, {
+                NAME: Const(id),
+
+                PATH: Const(file && file.path),
+
+                CONTEXT: Const(self),
+
+                get isGlobalModule() true,
+                set isGlobalModule(val) {
+                    util.assert(val, "Loading non-global module as global",
+                                false);
+                },
+
+                unload: Const(function unload() {
+                    if (contexts.pluginModules[canonical] == this) {
+                        if (this.onUnload)
+                            util.trapErrors("onUnload", this);
+
+                        delete contexts.pluginModules[canonical];
+                    }
+
+                    for each (let { plugins } in overlay.modules)
+                        if (plugins[this.NAME] == this)
+                            delete plugins[this.name];
+                })
+            });
+
+            JSMLoader.loadSubScript(uri.spec, self, File.defaultEncoding);
+            this.pluginModules[canonical] = self;
+        }
+
+        // This belongs elsewhere
+        if (isPlugin)
+            Object.defineProperty(plugins, self.NAME, {
+                configurable: true,
+                enumerable: true,
+                get: function () self,
+                set: function (val) {
+                    util.dactyl(val).reportError(FailedAssertion(_("plugin.notReplacingContext", self.NAME), 3, false), true);
+                }
+            });
+
+        return self;
+    },
+
     context: null,
 
     /**
@@ -259,26 +375,35 @@ var Contexts = Module("contexts", {
            return {
                 __proto__: frame,
                 filename: this.context.file[0] == "[" ? this.context.file
-                                                      : services.io.newFileURI(File(this.context.file)).spec,
+                                                      : File(this.context.file).URI.spec,
                 lineNumber: this.context.line
             };
         return frame;
     },
 
-    groups: Class.memoize(function () this.matchingGroups(this.modules.buffer.uri)),
+    groups: Class.Memoize(function () this.matchingGroups()),
 
-    allGroups: Class.memoize(function () Object.create(this.groupsProto, {
+    allGroups: Class.Memoize(function () Object.create(this.groupsProto, {
         groups: { value: this.initializedGroups() }
     })),
 
     matchingGroups: function (uri) Object.create(this.groupsProto, {
-        groups: { value: this.activeGroups(uri) },
+        groups: { value: this.activeGroups(uri) }
     }),
 
-    activeGroups: function (uri, doc) {
+    activeGroups: function (uri) {
+        if (uri instanceof Ci.nsIDOMDocument)
+            var [doc, uri] = [uri, uri.documentURIObject || util.newURI(uri.documentURI)];
+
         if (!uri)
-            ({ uri, doc }) = this.modules.buffer;
-        return this.initializedGroups().filter(function (g) uri && g.filter(uri, doc));
+            var { uri, doc } = this.modules.buffer;
+
+        return this.initializedGroups().filter(function (g) {
+            let res = uri && g.filter(uri, doc);
+            if (doc)
+                g.lastDocument = res && doc;
+            return res;
+        });
     },
 
     flush: function flush() {
@@ -288,7 +413,7 @@ var Contexts = Module("contexts", {
 
     initializedGroups: function (hive)
         let (need = hive ? [hive] : Object.keys(this.hives))
-            this.groupList.filter(function (group) need.some(set.has(group))),
+            this.groupList.filter(function (group) need.some(Set.has(group))),
 
     addGroup: function addGroup(name, description, filter, persist, replace) {
         let group = this.getGroup(name);
@@ -304,10 +429,11 @@ var Contexts = Module("contexts", {
 
         if (replace) {
             util.trapErrors("cleanup", group);
+
             if (description)
                 group.description = description;
             if (filter)
-                group.filter = filter
+                group.filter = filter;
             group.persist = persist;
         }
 
@@ -315,7 +441,7 @@ var Contexts = Module("contexts", {
         return group;
     },
 
-    removeGroup: function removeGroup(name, filter) {
+    removeGroup: function removeGroup(name) {
         if (isObject(name)) {
             if (this.groupList.indexOf(name) === -1)
                 return;
@@ -344,7 +470,7 @@ var Contexts = Module("contexts", {
     getGroup: function getGroup(name, hive) {
         if (name === "default")
             var group = this.context && this.context.context && this.context.context.GROUP;
-        else if (set.has(this.groupMap, name))
+        else if (Set.has(this.groupMap, name))
             group = this.groupMap[name];
 
         if (group && hive)
@@ -352,15 +478,37 @@ var Contexts = Module("contexts", {
         return group;
     },
 
+    getDocs: function getDocs(context) {
+        try {
+            if (isinstance(context, ["Sandbox"])) {
+                let info = "INFO" in context && Cu.evalInSandbox("this.INFO instanceof XML ? INFO.toXMLString() : this.INFO", context);
+                return /^</.test(info) ? XML(info) : info;
+            }
+            if (DOM.isJSONXML(context.INFO))
+                return context.INFO;
+            if (typeof context.INFO == "xml" && config.haveGecko(null, "14.*"))
+                return context.INFO;
+        }
+        catch (e) {}
+        return null;
+    },
+
     bindMacro: function (args, default_, params) {
         const { dactyl, events, modules } = this.modules;
 
+        function Proxy(obj, key) Class.Property({
+            configurable: true,
+            enumerable: true,
+            get: function Proxy_get() process(obj[key]),
+            set: function Proxy_set(val) obj[key] = val
+        })
+
         let process = util.identity;
 
         if (callable(params))
             var makeParams = function makeParams(self, args)
-                iter.toObject([k, process(v)]
-                               for ([k, v] in iter(params.apply(self, args))));
+                let (obj = params.apply(self, args))
+                    iter.toObject([k, Proxy(obj, k)] for (k in properties(obj)));
         else if (params)
             makeParams = function makeParams(self, args)
                 iter.toObject([name, process(args[i])]
@@ -368,19 +516,21 @@ var Contexts = Module("contexts", {
 
         let rhs = args.literalArg;
         let type = ["-builtin", "-ex", "-javascript", "-keys"].reduce(function (a, b) args[b] ? b : a, default_);
+
         switch (type) {
         case "-builtin":
             let noremap = true;
             /* fallthrough */
         case "-keys":
             let silent = args["-silent"];
-            rhs = events.canonicalKeys(rhs, true);
+            rhs = DOM.Event.canonicalKeys(rhs, true);
             var action = function action() {
                 events.feedkeys(action.macro(makeParams(this, arguments)),
                                 noremap, silent);
-            }
+            };
             action.macro = util.compileMacro(rhs, true);
             break;
+
         case "-ex":
             action = function action() modules.commands
                                               .execute(action.macro, makeParams(this, arguments),
@@ -388,16 +538,18 @@ var Contexts = Module("contexts", {
             action.macro = util.compileMacro(rhs, true);
             action.context = this.context && update({}, this.context);
             break;
+
         case "-javascript":
             if (callable(params))
                 action = dactyl.userEval("(function action() { with (action.makeParams(this, arguments)) {" + args.literalArg + "} })");
             else
-                action = dactyl.userFunc.apply(dactyl, params.concat(args.literalArg).array);
+                action = dactyl.userFunc.apply(dactyl, params.concat(args.literalArg));
             process = function (param) isObject(param) && param.valueOf ? param.valueOf() : param;
             action.params = params;
             action.makeParams = makeParams;
             break;
         }
+
         action.toString = function toString() (type === default_ ? "" : type + " ") + rhs;
         args = null;
         return action;
@@ -420,6 +572,7 @@ var Contexts = Module("contexts", {
         get modifiable() this.group.modifiable,
 
         get argsExtra() this.group.argsExtra,
+        get makeArgs() this.group.makeArgs,
         get builtin() this.group.builtin,
 
         get name() this.group.name,
@@ -434,7 +587,7 @@ var Contexts = Module("contexts", {
         get persist() this.group.persist,
         set persist(val) this.group.persist = val,
 
-        prefix: Class.memoize(function () this.name === "builtin" ? "" : this.name + ":"),
+        prefix: Class.Memoize(function () this.name === "builtin" ? "" : this.name + ":"),
 
         get toStringParams() [this.name]
     })
@@ -466,9 +619,9 @@ var Contexts = Module("contexts", {
                     if (args.has("-locations"))
                         group.filter = filter;
                     if (args.has("-description"))
-                        group.description = args["-description"]
+                        group.description = args["-description"];
                     if (args.has("-nopersist"))
-                        group.persist = !args["-nopersist"]
+                        group.persist = !args["-nopersist"];
                 }
 
                 if (!group.builtin && args.has("-args")) {
@@ -477,12 +630,23 @@ var Contexts = Module("contexts", {
                     group.args = args["-args"];
                 }
 
-                if (args.context)
+                if (args.context) {
                     args.context.group = group;
+                    if (args.context.context) {
+                        args.context.context.group = group;
+
+                        let parent = args.context.context.GROUP;
+                        if (parent && parent != group) {
+                            group.parent = parent;
+                            if (!~parent.children.indexOf(group))
+                                parent.children.push(group);
+                        }
+                    }
+                }
 
                 util.assert(!group.builtin ||
                                 !["-description", "-locations", "-nopersist"]
-                                    .some(set.has(args.explicitOpts)),
+                                    .some(Set.has(args.explicitOpts)),
                             _("group.cantModifyBuiltin"));
             },
             {
@@ -502,12 +666,12 @@ var Contexts = Module("contexts", {
                     {
                         names: ["-description", "-desc", "-d"],
                         description: "A description of this group",
-                        default: ["User-defined group"],
+                        default: "User-defined group",
                         type: CommandOption.STRING
                     },
                     {
                         names: ["-locations", "-locs", "-loc", "-l"],
-                        description: ["The URLs for which this group should be active"],
+                        description: "The URLs for which this group should be active",
                         default: ["*"],
                         type: CommandOption.LIST
                     },
@@ -540,14 +704,23 @@ var Contexts = Module("contexts", {
         commands.add(["delg[roup]"],
             "Delete a group",
             function (args) {
-                util.assert(contexts.getGroup(args[0]), _("group.noSuch", args[0]));
-                contexts.removeGroup(args[0]);
+                util.assert(args.bang ^ !!args[0], _("error.argumentOrBang"));
+
+                if (args.bang)
+                    contexts.groupList = contexts.groupList.filter(function (g) g.builtin);
+                else {
+                    util.assert(contexts.getGroup(args[0]), _("group.noSuch", args[0]));
+                    contexts.removeGroup(args[0]);
+                }
             },
             {
-                argCount: "1",
+                argCount: "?",
+                bang: true,
                 completer: function (context, args) {
-                    modules.completion.group(context);
+                    if (args.bang)
+                        return;
                     context.filters.push(function ({ item }) !item.builtin);
+                    modules.completion.group(context);
                 }
             });
 
@@ -624,7 +797,7 @@ var Contexts = Module("contexts", {
             context.keys = {
                 active: function (group) group.filter(uri),
                 text: "name",
-                description: function (g) <>{g.filter.toXML ? g.filter.toXML(modules) + <>&#xa0;</> : ""}{g.description || ""}</>
+                description: function (g) ["", g.filter.toJSONXML ? g.filter.toJSONXML(modules).concat("\u00a0") : "", g.description || ""]
             };
             context.completions = (active === undefined ? contexts.groupList : contexts.initializedGroups(active))
                                     .slice(0, -1);
@@ -641,6 +814,6 @@ var Contexts = Module("contexts", {
 
 endModule();
 
-} catch(e){ if (!e.stack) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
+// catch(e){ if (!e.stack) e = Error(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: