diff --git a/common/Makefile b/common/Makefile index 827164c0..470ddb33 100644 --- a/common/Makefile +++ b/common/Makefile @@ -25,7 +25,7 @@ CHROME = chrome/ JAR = $(CHROME)$(NAME).jar XPI_BASES = $(JAR_BASES) $(TOP)/.. -XPI_FILES = install.rdf TODO AUTHORS Donors NEWS LICENSE.txt +XPI_FILES = bootstrap.js install.rdf TODO AUTHORS Donors NEWS LICENSE.txt XPI_DIRS = modules components chrome defaults XPI_TEXTS = js jsm $(JAR_TEXTS) XPI_BINS = $(JAR_BINS) diff --git a/common/bootstrap.js b/common/bootstrap.js new file mode 100755 index 00000000..89a63dec --- /dev/null +++ b/common/bootstrap.js @@ -0,0 +1,211 @@ +// https://wiki.mozilla.org/Extension_Manager:Bootstrapped_Extensions + +const NAME = "bootstrap"; +const global = this; + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; +const Cr = Components.results; + +Cu.import("resource://gre/modules/AddonManager.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +const resourceProto = Services.io.getProtocolHandler("resource") + .QueryInterface(Ci.nsIResProtocolHandler); +const categoryManager = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager); +const manager = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); +const storage = Cc["@mozilla.org/fuel/application;1"].getService(Ci.fuelIApplication).storage; + +function httpGet(url) { + let xmlhttp = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest); + xmlhttp.open("GET", url, false); + xmlhttp.send(null); + return xmlhttp; +} + +function writeFile(file, buf) { + let fstream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream); + let stream = Cc["@mozilla.org/intl/converter-output-stream;1"].createInstance(Ci.nsIConverterOutputStream); + + fstream.init(file, 0x02 | 0x08 | 0x20, parseInt("0644", 8), 0); + stream.init(fstream, "UTF-8", 0, "?"); + stream.writeString(buf); + stream.close(); + fstream.close(); +} + +let initialized = false; +let addon = null; +let basePath = null; +let components = {}; +let getURI = null; +storage.set("dactyl.bootstrap", this); +var JSMLoader = storage.get("dactyl.JSMLoader", { get load() Cu.import }); + +function startup(data, reason) { + dump("dactyl: bootstrap: startup " + reasonToString(reason) + "\n"); + basePath = data.installPath; + + if (!initialized) { + initialized = true; + + dump("dactyl: bootstrap: init" + " " + data.id + "\n"); + + addon = data; + AddonManager.getAddonByID(addon.id, function (a) { addon = a }); + + if (basePath.isDirectory()) + getURI = function getURI(path) { + let file = basePath.clone().QueryInterface(Ci.nsILocalFile); + file.appendRelativePath(path); + return (Services.io || services.io).newFileURI(file); + } + else + getURI = function getURI(path) + Services.io.newURI("jar:" + Services.io.newFileURI(file).spec + "!" + path); + try { + init(); + } + catch (e) { + dump("dactyl: bootstrap: " + e + "\n" + e.stack); + Cu.reportError(e); + } + } +} + +function FactoryProxy(url, classID) { + this.url = url; + this.classID = Components.ID(classID); +} +FactoryProxy.prototype = { + QueryInterface: XPCOMUtils.generateQI(Ci.nsIFactory), + register: function () { + dump("dactyl: bootstrap: register: " + this.classID + " " + this.contractID + "\n"); + manager.registerFactory(this.classID, + String(this.classID), + this.contractID, + this); + }, + unregister: function () { + dump("dactyl: bootstrap: unregister: " + this.classID + " " + this.contractID + "\n"); + manager.unregisterFactory(this.classID, this); + }, + get module() { + Object.defineProperty(this, "module", { value: {}, enumerable: true }); + JSMLoader.load(this.url, this.module); + JSMLoader.registerGlobal(this.url, this.module.global); + return this.module; + }, + createInstance: function (iids) { + dump("dactyl: bootstrap: createInstance: " + this.classID + " " + this.contractID + " " + iids + "\n"); + return let (factory = this.module.NSGetFactory(this.classID)) + factory.createInstance.apply(factory, arguments) + } +} + +function init() { + dump("dactyl: bootstrap: init\n"); + + let manifestURI = getURI("chrome.manifest"); + let manifest = httpGet(manifestURI.spec) + .responseText + .replace(/^\s*|\s*$|#.*/g, "") + .replace(/^\s*\n/gm, ""); + + function url(path) getURI(path).spec; + + let result = []; + + for each (let line in manifest.split("\n")) { + let fields = line.split(/\s+/); + switch(fields[0]) { + case "content": + fields[2] = url(fields[2]); + default: + result.push(fields); + break; + + case "locale": + case "skin": + fields[3] = url(fields[3]); + result.push(fields); + break; + + case "category": + categoryManager.addCategoryEntry(fields[1], fields[2], fields[3], false, true); + break; + case "component": + components[fields[1]] = new FactoryProxy(url(fields[2]), fields[1]); + break; + case "contract": + components[fields[2]].contractID = fields[1]; + break; + + case "resource": + resourceProto.setSubstitution(fields[1], getURI(fields[2])); + } + } + + Services.obs.notifyObservers(null, "dactyl-rehash", null); + + JSMLoader.load("resource://dactyl/base.jsm", global); + + for each (let component in components) + component.register(); + + require(global, "prefs"); + require(global, "services"); + + services.subscriptLoader.loadSubScript( + url("defaults/preferences/dactyl.js"), + { + pref: function pref(name, val) { + if (prefs.get(name, null) == null) + prefs.set(name, val); + } + }); + + let manifestText = result.map(function (line) line.join(" ")).join("\n"); + + if (manifestURI instanceof Ci.nsIFileURL) + manager.autoRegister(manifestURI.QueryInterface(Ci.nsIFileURL).file); + else { + var file = basePath.parent; + file.append(addon.id + ".manifest"); + + writeFile(file, manifestText); + manager.autoRegister(file); + file.remove(false); + } + + require(global, "overlay"); +} + +function shutdown(data, reason) { + dump("dactyl: bootstrap: shutdown " + reasonToString(reason) + "\n"); + if (reason != APP_SHUTDOWN) { + if ([ADDON_UPGRADE, ADDON_DOWNGRADE, ADDON_UNINSTALL].indexOf(reason) >= 0) + services.observer.notifyObservers(null, "dactyl-purge", null); + + services.observer.notifyObservers(null, "dactyl-cleanup", null); + services.observer.notifyObservers(null, "dactyl-cleanup-modules", null); + for (let factory in values(components)) + // TODO: Categories; + factory.unregister(); + } +} + +function reasonToString(reason) { + for each (let name in ["disable", "downgrade", "enable", + "install", "shutdown", "startup", + "uninstall", "upgrade"]) + if (reason == global["ADDON_" + name.toUpperCase()] || + reason == global["APP_" + name.toUpperCase()]) + return name; +} + +function install(data, reason) { dump("dactyl: bootstrap: install " + reasonToString(reason) + "\n") } +function uninstall(data, reason) { dump("dactyl: bootstrap: uninstall " + reasonToString(reason) + "\n") } + diff --git a/common/components/protocols.js b/common/components/protocols.js index 40ae3ae0..6b4f9c7f 100644 --- a/common/components/protocols.js +++ b/common/components/protocols.js @@ -21,8 +21,6 @@ var Cu = Components.utils; Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); var ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService); -var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService) - .getBranch("extensions.dactyl."); var systemPrincipal = Cc["@mozilla.org/systemprincipal;1"].getService(Ci.nsIPrincipal); function dataURL(type, data) "data:" + (type || "application/xml;encoding=UTF-8") + "," + escape(data); @@ -101,20 +99,23 @@ function Dactyl() { this.OVERLAY_MAP = {}; this.pages = {}; - for each (let pref in ["appName", "fileExt", "host", "hostbin", "idName", "name"]) - this[pref] = prefs.getComplexValue(pref, Ci.nsISupportsString).data; - this.addonID = this.name + "@dactyl.googlecode.com"; + Cu.import("resource://dactyl/base.jsm"); + require(global, "prefs"); + + ["appName", "fileExt", "host", "hostbin", "idName", "name", "version"].forEach(function (pref) + this.__defineGetter__(pref, function () prefs.get("extensions.dactyl." + pref, "dactyl")), + this); + + memoize(this, "addonID", function () this.name + "@dactyl.googlecode.com"); } Dactyl.prototype = { contractID: "@mozilla.org/network/protocol;1?name=dactyl", classID: Components.ID("{9c8f2530-51c8-4d41-b356-319e0b155c44}"), classDescription: "Dactyl utility protocol", - QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler]), + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsIProtocolHandler]), _xpcom_factory: Factory(Dactyl), - get version() prefs.getComplexValue("version", Ci.nsISupportsString).data, - init: function (obj) { for each (let prop in ["HELP_TAGS", "FILE_MAP", "OVERLAY_MAP"]) { this[prop] = this[prop].constructor(); @@ -162,6 +163,19 @@ Dactyl.prototype = { } catch (e) {} return fakeChannel(uri); + }, + + // FIXME: Belongs elsewhere + _xpcom_categories: [{ + category: "profile-after-change", + entry: "m-dactyl" + }], + + observe: function (subject, topic, data) { + if (topic === "profile-after-change") { + Cu.import("resource://dactyl/base.jsm"); + require(global, "overlay"); + } } }; diff --git a/common/content/buffer.js b/common/content/buffer.js index 0e3f54f2..bd468b65 100644 --- a/common/content/buffer.js +++ b/common/content/buffer.js @@ -539,7 +539,12 @@ var Buffer = Module("buffer", { buffer.lastInputField = elem; } else { - dactyl.focus(elem); + if (isinstance(elem, [HTMLInputElement, XULTextBoxElement])) + var flags = services.focus.FLAG_BYMOUSE; + else + flags = services.focus.FLAG_SHOWRING; + dactyl.focus(elem, flags); + if (elem instanceof Window) { let sel = elem.getSelection(); if (sel && !sel.rangeCount) @@ -1021,11 +1026,10 @@ var Buffer = Module("buffer", { this.callback(file); else { this.file = io.createTempFile(); - var webBrowserPersist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] - .createInstance(Ci.nsIWebBrowserPersist); - webBrowserPersist.persistFlags = webBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES; - webBrowserPersist.progressListener = this; - webBrowserPersist.saveURI(uri, null, null, null, null, this.file); + var persist = services.Persist(); + persist.persistFlags = persist.PERSIST_FLAGS_REPLACE_EXISTING_FILES; + persist.progressListener = this; + persist.saveURI(uri, null, null, null, null, this.file); } return null; }, diff --git a/common/content/buffer.xhtml b/common/content/buffer.xhtml index 089b1563..6e05b7ee 100644 --- a/common/content/buffer.xhtml +++ b/common/content/buffer.xhtml @@ -1,7 +1,7 @@ - + </head> diff --git a/common/content/commandline.js b/common/content/commandline.js index b8123a63..3cf54785 100644 --- a/common/content/commandline.js +++ b/common/content/commandline.js @@ -10,6 +10,76 @@ var CommandWidgets = Class("CommandWidgets", { init: function () { + let _status = "dactyl-statusline-field-"; + + util.overlayWindow(window, { + append: <e4x xmlns={XUL} xmlns:dactyl={NS}> + <window id={document.documentElement.id}> + <popupset> + <menupopup id="dactyl-contextmenu" + onpopupshowing="return (event.target != this || dactyl.modules.commandline.onContext(event));"> + <menuitem id="dactyl-context-copylink" + label="Copy Link Location" dactyl:group="link" + oncommand="goDoCommand('cmd_copyLink');"/> + <menuitem id="dactyl-context-copy" + label="Copy" dactyl:group="selection" + command="cmd_copy"/> + <menuitem id="dactyl-context-selectall" + label="Select All" + command="cmd_selectAll"/> + </menupopup> + </popupset> + </window> + + <vbox id={config.commandContainer}> + + <vbox class="dactyl-container" hidden="false" collapsed="true"> + <iframe id="dactyl-multiline-output" src="chrome://dactyl/content/buffer.xhtml" + flex="1" hidden="false" collapsed="false" + contextmenu="dactyl-contextmenu" + onclick="dactyl.modules.commandline.onMultilineOutputEvent(event)"/> + </vbox> + + <vbox class="dactyl-container" hidden="false" collapsed="true"> + <iframe class="dactyl-completions" id="dactyl-completions-dactyl-commandline" src="chrome://dactyl/content/buffer.xhtml" + contextmenu="dactyl-contextmenu" + flex="1" hidden="false" collapsed="false" + onclick="dactyl.modules.commandline.onMultilineOutputEvent(event)"/> + </vbox> + + <vbox id={"dactyl-completions-" + _status + "commandline-container"} class="dactyl-container" hidden="false" collapsed="true" insertbefore="addon-bar,status-bar"> + <iframe class="dactyl-completions" id={"dactyl-completions-" + _status + "commandline"} src="chrome://dactyl/content/buffer.xhtml" + contextmenu="dactyl-contextmenu" + flex="1" hidden="false" collapsed="false" + onclick="dactyl.modules.commandline.onMultilineOutputEvent(event)"/> + </vbox> + + <stack orient="horizontal" align="stretch" class="dactyl-container" id="dactyl-container" highlight="CmdLine CmdCmdLine"> + <textbox class="plain" id="dactyl-strut" flex="1" crop="end" collapsed="true"/> + <textbox class="plain" id="dactyl-mode" flex="1" crop="end"/> + <textbox class="plain" id="dactyl-message" flex="1" readonly="true"/> + + <hbox id="dactyl-commandline" hidden="false" class="dactyl-container" highlight="Normal CmdNormal" collapsed="true"> + <label id="dactyl-commandline-prompt" class="dactyl-commandline-prompt plain" flex="0" crop="end" value="" collapsed="true"/> + <textbox id="dactyl-commandline-command" class="dactyl-commandline-command plain" flex="1" type="input" timeout="100" + oninput="dactyl.modules.commandline.onEvent(event);" + onkeyup="dactyl.modules.commandline.onEvent(event);" + onfocus="dactyl.modules.commandline.onEvent(event);" + onblur="dactyl.modules.commandline.onEvent(event);"/> + </hbox> + </stack> + + <vbox class="dactyl-container" hidden="false" collapsed="false" highlight="CmdLine"> + <textbox id="dactyl-multiline-input" class="plain" flex="1" rows="1" hidden="false" collapsed="true" multiline="true" + highlight="Normal" + onkeypress="dactyl.modules.commandline.onMultilineInputEvent(event);" + oninput="dactyl.modules.commandline.onMultilineInputEvent(event);" + onblur="dactyl.modules.commandline.onMultilineInputEvent(event);"/> + </vbox> + </vbox> + </e4x>.elements() + }); + this.elements = {}; this.addElement({ name: "container", @@ -97,11 +167,14 @@ var CommandWidgets = Class("CommandWidgets", { addElement: function (obj) { const self = this; this.elements[obj.name] = obj; - function get(id) obj.getElement ? obj.getElement(id) : document.getElementById(id); + + function get(prefix, map, id) (obj.getElement || util.identity)(map[id] || document.getElementById(prefix + id)); + this.active.__defineGetter__(obj.name, function () self.activeGroup[obj.name][obj.name]); this.activeGroup.__defineGetter__(obj.name, function () self.getGroup(obj.name)); - memoize(this.statusbar, obj.name, function () get("dactyl-statusline-field-" + (obj.id || obj.name))); - memoize(this.commandbar, obj.name, function () get("dactyl-" + (obj.id || obj.name))); + + memoize(this.statusbar, obj.name, function () get("dactyl-statusline-field-", statusline.widgets, (obj.id || obj.name))); + memoize(this.commandbar, obj.name, function () get("dactyl-", {}, (obj.id || obj.name))); if (!(obj.noValue || obj.getValue)) Object.defineProperty(this, obj.name, Modes.boundProperty({ @@ -173,11 +246,21 @@ var CommandWidgets = Class("CommandWidgets", { completionList: Class.memoize(function () document.getElementById("dactyl-completions")), completionContainer: Class.memoize(function () this.completionList.parentNode), multilineOutput: Class.memoize(function () { - let elem = document.getElementById("dactyl-multiline-output"); + this.__defineGetter__("multilineOutput", function () { + let elem = document.getElementById("dactyl-multiline-output"); + while (elem.contentDocument.documentURI != elem.getAttribute("src") || + ["viewable", "complete"].indexOf(elem.contentDocument.readyState) < 0) + util.threadYield(); + return elem; + }); + + let elem = this.multilineOutput; elem.contentWindow.addEventListener("unload", function (event) { event.preventDefault(); }, true); + elem.contentDocument.documentElement.id = "dactyl-multiline-output-top"; elem.contentDocument.body.id = "dactyl-multiline-output-content"; elem.__defineGetter__("atEnd", function () !Buffer.isScrollable(elem.contentDocument.documentElement, 1)); + ["copy", "copylink", "selectall"].forEach(function (tail) { // some host apps use "hostPrefixContext-copy" ids let xpath = "//xul:menuitem[contains(@id, '" + "ontext-" + tail + "') and not(starts-with(@id, 'dactyl-'))]"; @@ -189,8 +272,7 @@ var CommandWidgets = Class("CommandWidgets", { multilineInput: Class.memoize(function () document.getElementById("dactyl-multiline-input")), mowContainer: Class.memoize(function () this.multilineOutput.parentNode) }, { - getEditor: function (id) { - let elem = document.getElementById(id); + getEditor: function (elem) { elem.inputField.QueryInterface(Ci.nsIDOMNSEditableElement); return elem; } @@ -455,7 +537,7 @@ var CommandLine = Module("commandline", { set: function (value) { this.widgets.multilineInput.collapsed = !value; } }), multilineOutputVisible: Modes.boundProperty({ - set: function (value) { this.widgets.mowContainer.collapsed = !value; } + set: function (value) { (this.widgets.mowContainer || {}).collapsed = !value; } }), /** @@ -673,9 +755,6 @@ var CommandLine = Module("commandline", { * the MOW. */ echo: function echo(str, highlightGroup, flags) { - if (String(str) == "undefined") - util.dumpStack(); - // dactyl.echo uses different order of flags as it omits the highlight group, change commandline.echo argument order? --mst if (this._silent) return; @@ -1777,6 +1856,7 @@ var ItemList = Class("ItemList", { this._win = iframe.contentWindow; this._container = iframe.parentNode; + this._doc.documentElement.id = id + "-top"; this._doc.body.id = id + "-content"; this._doc.body.className = iframe.className + "-content"; this._doc.body.appendChild(this._doc.createTextNode("")); diff --git a/common/content/configbase.js b/common/content/configbase.js index 92b389f6..d9378581 100644 --- a/common/content/configbase.js +++ b/common/content/configbase.js @@ -67,12 +67,15 @@ var ConfigBase = Class(ModuleBase, { get browserModes() [modes.NORMAL], + commandContainer: "browser-bottombox", + /** * @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" }, + cleanups: {}, /** * @property {[["string", "string", "function"]]} An array of @@ -184,8 +187,8 @@ var ConfigBase = Class(ModuleBase, { CmdQuestion;[dactyl|highlight] StatusQuestion;[dactyl|highlight] CmdWarningMsg;[dactyl|highlight] StatusWarningMsg;[dactyl|highlight] - !Normal color: black !important; background: white !important; font-weight: normal !important; - !StatusNormal color: inherit !important; background: inherit !important; + Normal color: black !important; background: white !important; font-weight: normal !important; + StatusNormal color: inherit !important; background: inherit !important; ErrorMsg color: white !important; background: red !important; font-weight: bold !important; InfoMsg color: black !important; background: white !important; StatusInfoMsg color: inherit !important; background: inherit !important; @@ -195,7 +198,7 @@ var ConfigBase = Class(ModuleBase, { MoreMsg color: green !important; background: white !important; StatusMoreMsg background: inherit !important; Message white-space: pre-wrap !important; min-width: 100%; width: 100%; padding-left: 4em; text-indent: -4em; display: block; - !Message String white-space: pre-wrap; + Message String white-space: pre-wrap; NonText color: blue; background: transparent !important; *Preview color: gray; Question color: green !important; background: white !important; font-weight: bold !important; @@ -203,7 +206,7 @@ var ConfigBase = Class(ModuleBase, { WarningMsg color: red !important; background: white !important; StatusWarningMsg color: red !important; background: inherit !important; - !CmdLine;>* font-family: monospace !important; padding: 1px !important; + CmdLine;>* font-family: monospace !important; padding: 1px !important; CmdPrompt;.dactyl-commandline-prompt CmdInput;.dactyl-commandline-command CmdOutput white-space: pre; @@ -252,8 +255,8 @@ var ConfigBase = Class(ModuleBase, { Usage>LineInfo position: absolute; left: 100%; padding: 1ex; margin: -1ex -1em; background: rgba(255, 255, 255, .8); border-radius: 1ex; Usage:not(:hover)>LineInfo opacity: 0; left: 0; width: 1px; height: 1px; overflow: hidden; - !StatusLine font-weight: bold; font-family: monospace; -moz-appearance: none !important; border: 0px !important; min-height: 18px !important; - !StatusLineNormal color: white !important; background: black !important; + StatusLine font-weight: bold; font-family: monospace; -moz-appearance: none !important; border: 0px !important; min-height: 18px !important; + StatusLineNormal color: white !important; background: black !important; StatusLineBroken color: black !important; background: #FFa0a0 !important /* light-red */ StatusLineSecure color: black !important; background: #a0a0FF !important /* light-blue */ StatusLineExtended color: black !important; background: #a0FFa0 !important /* light-green */ diff --git a/common/content/dactyl-overlay.js b/common/content/dactyl-overlay.js deleted file mode 100644 index a0a51da6..00000000 --- a/common/content/dactyl-overlay.js +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) 2008-2010 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"; - -(function () { - function newContext(proto) { - let sandbox = Components.utils.Sandbox(window, { sandboxPrototype: proto || modules, wantXrays: false }); - // Hack: - sandbox.Object = jsmodules.Object; - sandbox.Math = jsmodules.Math; - sandbox.__proto__ = proto || modules; - return sandbox; - } - const jsmodules = {}; - const modules = { - __proto__: jsmodules, - get content() this.config.browser.contentWindow || window.content, - jsmodules: jsmodules, - newContext: newContext, - window: window - }; - modules.modules = modules; - - const BASE = "chrome://dactyl/content/"; - const loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] - .getService(Components.interfaces.mozIJSSubScriptLoader); - - modules.load = function load(script) { - for (let [i, base] in Iterator(prefix)) { - try { - loader.loadSubScript(base + script + ".js", modules, "UTF-8"); - return; - } - catch (e) { - if (typeof e !== "string") { - dump("dactyl: Trying: " + (base + script + ".js") + ": " + e + "\n" + e.stack + "\n"); - Components.utils.reportError(e); - } - } - } - try { - Components.utils.import("resource://dactyl/" + script + ".jsm", jsmodules); - } - catch (e) { - dump("dactyl: Loading script " + script + ": " + e.result + " " + e + "\n"); - dump(Error().stack + "\n"); - Components.utils.reportError(e); - } - }; - - let prefix = [BASE]; - - modules.load("util"); - modules.load("services"); - prefix.unshift("chrome://" + modules.services["dactyl:"].name + "/content/"); - - ["base", - "modules", - "prefs", - "storage", - "javascript", - "dactyl", - "modes", - "abbreviations", - "autocommands", - "buffer", - "commandline", - "commands", - "completion", - "configbase", - "config", - "editor", - "events", - "finder", - "highlight", - "hints", - "io", - "mappings", - "marks", - "options", - "statusline", - "styles", - "template" - ].forEach(modules.load); - - modules.Config.prototype.scripts.forEach(modules.load); -})(); - -// vim: set fdm=marker sw=4 ts=4 et: diff --git a/common/content/dactyl.js b/common/content/dactyl.js index bb79b679..26ec0c30 100644 --- a/common/content/dactyl.js +++ b/common/content/dactyl.js @@ -30,6 +30,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { this.indices = {}; this.modules = modules; this.observers = {}; + util.addObserver(this); this.commands["dactyl.help"] = function (event) { let elem = event.originalTarget; @@ -50,6 +51,27 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { autocommands.trigger("Leave", {}); }, + observe: { + "dactyl-cleanup": function () { + let modules = dactyl.modules; + + for (let name in values(Object.getOwnPropertyNames(modules).reverse())) { + let mod = Object.getOwnPropertyDescriptor(modules, name).value; + if (mod instanceof ModuleBase) { + if ("cleanup" in mod) + mod.cleanup(); + if ("destroy" in mod) + mod.destroy(); + } + } + for (let name in values(Object.getOwnPropertyNames(modules).reverse())) + try { + delete modules[name]; + } + catch (e) {} + } + }, + /** @property {string} The name of the current user profile. */ profileName: Class.memoize(function () { // NOTE: services.profile.selectedProfile.name doesn't return @@ -140,7 +162,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { template.usage(results, params.format)); }, { - argCount: "*", + argCount: "0", completer: function (context, args) { context.keys.text = util.identity; context.keys.description = function () seen[this.text] + " matching items"; @@ -170,21 +192,30 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { */ beep: function () { if (options["visualbell"]) { - let bell = document.getElementById("dactyl-bell"); - let strut = document.getElementById("dactyl-bell-strut"); - if (!bell) { - bell = document.documentElement.insertBefore( - util.xmlToDom(<hbox xmlns={XUL} style="display: none" highlight="Bell" id="dactyl-bell"/>, document), - document.documentElement.firstChild); - strut = document.documentElement.appendChild( - util.xmlToDom(<hbox xmlns={XUL} style="display: none" highlight="Bell" id="dactyl-bell-strut"/>, document)); + let elems = { + bell: document.getElementById("dactyl-bell"), + strut: document.getElementById("dactyl-bell-strut") } + if (!elems.bell) + util.overlayWindow(window, { + objects: elems, + prepend: <> + <window id={document.documentElement.id} xmlns={XUL}> + <hbox style="display: none" highlight="Bell" id="dactyl-bell" key="bell"/> + </window> + </>, + append: <> + <window id={document.documentElement.id} xmlns={XUL}> + <hbox style="display: none" highlight="Bell" id="dactyl-bell-strut" key="strut"/> + </window> + </> + }, elems); - bell.style.height = window.innerHeight + "px"; - strut.style.marginBottom = -window.innerHeight + "px"; - strut.style.display = bell.style.display = ""; + elems.bell.style.height = window.innerHeight + "px"; + elems.strut.style.marginBottom = -window.innerHeight + "px"; + elems.strut.style.display = elems.bell.style.display = ""; - util.timeout(function () { strut.style.display = bell.style.display = "none"; }, 20); + util.timeout(function () { elems.strut.style.display = elems.bell.style.display = "none"; }, 20); } else { let soundService = Cc["@mozilla.org/sound;1"].getService(Ci.nsISound); @@ -397,7 +428,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { * element. */ focusContent: function focusContent(clearFocusedElement) { - if (window != services.windowWatcher.activeWindow) + if (window != services.focus.activeWindow) return; let win = document.commandDispatcher.focusedWindow; @@ -423,6 +454,8 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { dactyl.focusedElement.blur(); if (win && Editor.getEditor(win)) { win.blur(); + if (win.frameElement) + util.dump("blur(" + util.objectToString(win.frameElement) + ")"); if (win.frameElement) win.frameElement.blur(); } @@ -1152,14 +1185,8 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { * @see Commands#parseArgs */ parseCommandLine: function (cmdline) { - const options = [ - [["+u"], CommandOption.STRING], - [["++noplugin"], CommandOption.NOARG], - [["++cmd"], CommandOption.STRING, null, null, true], - [["+c"], CommandOption.STRING, null, null, true] - ].map(CommandOption.fromArray, CommandOption); try { - return commands.parseArgs(cmdline, { options: options, argCount: "*" }); + return commands.get("rehash").parseArgs(cmdline); } catch (e) { dactyl.reportError(e, true); @@ -1325,6 +1352,12 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { options.add(["guioptions", "go"], "Show or hide certain GUI elements like the menu or toolbar", "charlist", config.defaults.guioptions || "", { + + // FIXME: cleanup + cleanupValue: config.cleanups.guioptions || + "r" + [k for ([k, v] in iter(groups[1].opts)) + if (!document.getElementById(v[1][0]).collapsed)].join(""), + completer: function (context) array(groups).map(function (g) [[k, v[0]] for ([k, v] in Iterator(g.opts))]).flatten(), setter: function (value) { @@ -1414,7 +1447,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { function () { dactyl.quit(true); }); }, - commands: function () { + commands: function (_dactyl, _modules, _window) { commands.add(["addo[ns]"], "Manage available Extensions and Themes", function () { @@ -1658,15 +1691,32 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { filter: function ({ item }) !item.userDisabled, perm: "disable" }, + { + name: "extr[ehash]", + description: "Reload an extension", + action: function (addon) { + dactyl.assert(dactyl.has("Gecko2"), "This command is not useful in this version of " + config.host); + util.timeout(function () { + addon.userDisabled = true; + addon.userDisabled = false; + }); + }, + filter: function ({ item }) !item.userDisabled, + perm: "disable" + }, + { + name: "extt[oggle]", + description: "Toggle an extension's enabled status", + action: function (addon) addon.userDisabled = !addon.userDisabled + }, { name: "extu[pdate]", description: "Update an extension", actions: updateAddons, - filter: function ({ item }) !item.userDisabled, perm: "upgrade" } ].forEach(function (command) { - let perm = AddonManager["PERM_CAN_" + command.perm.toUpperCase()]; + let perm = command.perm && AddonManager["PERM_CAN_" + command.perm.toUpperCase()]; function ok(addon) !perm || addon.permissions & perm; commands.add([command.name], command.description, @@ -1845,6 +1895,34 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { bang: true }); + commands.add(["reh[ash]"], + "Reload the " + config.appName + " add-on", + function (args) { util.rehash(args); }, + { + argCount: "0", + options: [ + { + names: ["+u"], + description: "The initialization file to execute at startup", + type: CommandOption.STRING + }, + { + names: ["++noplugin"], + description: "Do not automatically load plugins" + }, + { + names: ["++cmd"], + description: "Ex commands to execute prior to initialization", + multiple: true + }, + { + names: ["+c"], + description: "Ex commands to execute after initialization", + multiple: true + } + ] + }); + commands.add(["res[tart]"], "Force " + config.appName + " to restart", function () { dactyl.restart(); }); @@ -2062,20 +2140,25 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { dactyl.log("All modules loaded", 3); - AddonManager.getAddonByID(services["dactyl:"].addonID, function (addon) { + AddonManager.getAddonByID(services["dactyl:"].addonID, this.wrapCallback(function (addon) { // @DATE@ token replaced by the Makefile // TODO: Find it automatically prefs.set("extensions.dactyl.version", addon.version); dactyl.version = addon.version + " (created: @DATE@)"; - }); + })); if (!services.commandLineHandler) services.add("commandLineHandler", "@mozilla.org/commandlinehandler/general-startup;1?type=" + config.name); - if (services.commandLineHandler) { - let commandline = services.commandLineHandler.optionValue; - if (commandline) { - let args = dactyl.parseCommandLine(commandline); + try { + if (services.fuel) + var args = services.fuel.storage.get("dactyl.commandlineArgs", null); + if (!args) { + let commandline = services.commandLineHandler.optionValue; + if (commandline) + args = dactyl.parseCommandLine(commandline); + } + if (args) { dactyl.commandLineOptions.rcFile = args["+u"]; dactyl.commandLineOptions.noPlugins = "++noplugin" in args; dactyl.commandLineOptions.postCommands = args["+c"]; @@ -2083,6 +2166,9 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { util.dump("Processing command-line option: " + commandline); } } + catch (e) { + dactyl.echoerr("Parsing command line options: " + e); + } dactyl.log("Command-line options: " + util.objectToString(dactyl.commandLineOptions), 3); diff --git a/common/content/dactyl.xul b/common/content/dactyl.xul deleted file mode 100644 index 73123038..00000000 --- a/common/content/dactyl.xul +++ /dev/null @@ -1,130 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> - -<!-- - Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org> - Copyright (c) 2007-2009 by 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. - --> - -<?xml-stylesheet href="chrome://dactyl/skin/dactyl.css" type="text/css"?> -<!DOCTYPE overlay SYSTEM "dactyl.dtd" [ - <!ENTITY and "&&"> - <!ENTITY dactyl.content "chrome://dactyl/content/"> - <!ENTITY commandline "if (window.dactyl) return dactyl.modules.commandline"> - <!ENTITY events "if (window.dactyl ∧ dactyl.modules.loaded.events) return dactyl.modules.events"> - <!ENTITY status "dactyl-statusline-field-"> -]> - -<overlay id="dactyl" - xmlns:dactyl="http://vimperator.org/namespaces/liberator" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:nc="http://home.netscape.com/NC-rdf#" - xmlns:html="http://www.w3.org/1999/xhtml" - xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> - - <script type="application/x-javascript;version=1.8" src="&dactyl.content;dactyl-overlay.js"/> - - <window id="&dactyl.mainWindow;"> - - <popupset> - <menupopup id="dactyl-contextmenu" - onpopupshowing="return (event.target != this || dactyl.modules.commandline.onContext(event));"> - <menuitem id="dactyl-context-copylink" - label="Copy Link Location" dactyl:group="link" - oncommand="goDoCommand('cmd_copyLink');"/> - <menuitem id="dactyl-context-copy" - label="Copy" dactyl:group="selection" - command="cmd_copy"/> - <menuitem id="dactyl-context-selectall" - label="Select All" - command="cmd_selectAll"/> - </menupopup> - </popupset> - - <!--this notifies us also of focus events in the XUL - from: http://developer.mozilla.org/en/docs/XUL_Tutorial:Updating_Commands !--> - <!-- I don't think we really need this. ––Kris --> - <commandset id="onPentadactylFocus" commandupdater="true" events="focus" - oncommandupdate="&events;.onFocusChange(event);"/> - <commandset id="onPentadactylSelect" commandupdater="true" events="select" - oncommandupdate="&events;.onSelectionChange(event);"/> - </window> - - <vbox id="&dactyl.commandContainer;"> - - <vbox class="dactyl-container" hidden="false" collapsed="true"> - <iframe id="dactyl-multiline-output" src="chrome://dactyl/content/buffer.xhtml" - flex="1" hidden="false" collapsed="false" - contextmenu="dactyl-contextmenu" - onclick="&commandline;.onMultilineOutputEvent(event)"/> - </vbox> - - <vbox class="dactyl-container" hidden="false" collapsed="true"> - <iframe class="dactyl-completions" id="dactyl-completions-dactyl-commandline" src="chrome://dactyl/content/buffer.xhtml" - contextmenu="dactyl-contextmenu" - flex="1" hidden="false" collapsed="false" - onclick="&commandline;.onMultilineOutputEvent(event)"/> - </vbox> - - <vbox id="dactyl-completions-&status;commandline-container" class="dactyl-container" hidden="false" collapsed="true" insertbefore="addon-bar,status-bar"> - <iframe class="dactyl-completions" id="dactyl-completions-&status;commandline" src="chrome://dactyl/content/buffer.xhtml" - contextmenu="dactyl-contextmenu" - flex="1" hidden="false" collapsed="false" - onclick="&commandline;.onMultilineOutputEvent(event)"/> - </vbox> - - <stack orient="horizontal" align="stretch" class="dactyl-container" id="dactyl-container" dactyl:highlight="CmdLine CmdCmdLine"> - <textbox class="plain" id="dactyl-strut" flex="1" crop="end" collapsed="true"/> - <textbox class="plain" id="dactyl-mode" flex="1" crop="end"/> - <textbox class="plain" id="dactyl-message" flex="1" readonly="true"/> - - <hbox id="dactyl-commandline" hidden="false" class="dactyl-container" dactyl:highlight="Normal CmdNormal" collapsed="true"> - <label id="dactyl-commandline-prompt" class="dactyl-commandline-prompt plain" flex="0" crop="end" value="" collapsed="true"/> - <textbox id="dactyl-commandline-command" class="dactyl-commandline-command plain" flex="1" type="input" timeout="100" - oninput="&commandline;.onEvent(event);" onkeyup="&commandline;.onEvent(event);" - onfocus="&commandline;.onEvent(event);" onblur="&commandline;.onEvent(event);"/> - </hbox> - </stack> - - <vbox class="dactyl-container" hidden="false" collapsed="false" dactyl:highlight="CmdLine"> - <textbox id="dactyl-multiline-input" class="plain" flex="1" rows="1" hidden="false" collapsed="true" multiline="true" - dactyl:highlight="Normal" - onkeypress="&commandline;.onMultilineInputEvent(event);" oninput="&commandline;.onMultilineInputEvent(event);" - onblur="&commandline;.onMultilineInputEvent(event);"/> - </vbox> - </vbox> - - <statusbar id="status-bar" dactyl:highlight="StatusLine StatusNormal"> - <hbox insertbefore="&dactyl.statusBefore;" insertafter="&dactyl.statusAfter;" - style="background: inherit;" id="&status;container" flex="1" hidden="false" align="center"> - <stack orient="horizontal" align="stretch" flex="1" class="dactyl-container" dactyl:highlight="CmdLine StatusCmdLine"> - <hbox class="dactyl-container" dactyl:highlight="CmdLine StatusCmdLine"> - <label class="plain" id="&status;mode" crop="end" collapsed="true"/> - <stack flex="1" class="dactyl-container" dactyl:highlight="CmdLine StatusCmdLine"> - <textbox class="plain" id="&status;url" crop="end" flex="1" readonly="true"/> - <textbox class="plain" id="&status;message" crop="end" flex="1" readonly="true" dactyl:highlight="Normal StatusNormal"/> - </stack> - </hbox> - - <hbox id="&status;commandline" hidden="false" class="dactyl-container" dactyl:highlight="Normal StatusNormal" collapsed="true"> - <label id="&status;commandline-prompt" class="dactyl-commandline-prompt plain" flex="0" crop="end" value="" collapsed="true"/> - <textbox id="&status;commandline-command" class="dactyl-commandline-command plain" flex="1" type="text" timeout="100" - oninput="&commandline;.onEvent(event);" onkeyup="&commandline;.onEvent(event);" - onfocus="&commandline;.onEvent(event);" onblur="&commandline;.onEvent(event);"/> - </hbox> - </stack> - <label class="plain" id="&status;inputbuffer" flex="0"/> - <label class="plain" id="&status;progress" flex="0"/> - <label class="plain" id="&status;tabcount" flex="0"/> - <label class="plain" id="&status;bufferposition" flex="0"/> - <label class="plain" id="&status;zoomlevel" flex="0"/> - </hbox> - <!-- just hide them since other elements expect them --> - <statusbarpanel id="statusbar-display" hidden="true"/> - <statusbarpanel id="statusbar-progresspanel" hidden="true"/> - </statusbar> -</overlay> - -<!-- vim: set fdm=marker sw=4 ts=4 et: --> diff --git a/common/content/events.js b/common/content/events.js index d9979569..0c3b5a7a 100644 --- a/common/content/events.js +++ b/common/content/events.js @@ -13,6 +13,20 @@ */ var Events = Module("events", { init: function () { + util.overlayWindow(window, { + append: <e4x xmlns={XUL}> + <window id={document.documentElement.id}> + <!--this notifies us also of focus events in the XUL + from: http://developer.mozilla.org/en/docs/XUL_Tutorial:Updating_Commands !--> + <!-- I don't think we really need this. ––Kris --> + <commandset id="onPentadactylFocus" commandupdater="true" events="focus" + oncommandupdate="dactyl.modules.events.onFocusChange(event);"/> + <commandset id="onPentadactylSelect" commandupdater="true" events="select" + oncommandupdate="dactyl.modules.events.onSelectionChange(event);"/> + </window> + </e4x>.elements() + }); + this._fullscreen = window.fullScreen; this._lastFocus = null; this._currentMacro = ""; @@ -33,19 +47,29 @@ var Events = Module("events", { this._keyTable = { add: ["Plus", "Add"], back_space: ["BS"], + count: ["count"], delete: ["Del"], escape: ["Esc", "Escape"], insert: ["Insert", "Ins"], + leader: ["Leader"], left_shift: ["LT", "<"], + nop: ["Nop"], return: ["Return", "CR", "Enter"], right_shift: [">"], space: ["Space", " "], subtract: ["Minus", "Subtract"] }; + this._pseudoKeys = set(["count", "leader", "nop"]); + + this._key_key = {}; this._code_key = {}; this._key_code = {}; + for (let list in values(this._keyTable)) + for (let v in values(list)) + this._key_key[v.toLowerCase()] = v; + for (let [k, v] in Iterator(KeyEvent)) { k = k.substr(7).toLowerCase(); let names = [k.replace(/(^|_)(.)/g, function (m, n1, n2) n2.toUpperCase()) @@ -53,8 +77,10 @@ var Events = Module("events", { if (k in this._keyTable) names = this._keyTable[k]; this._code_key[v] = names[0]; - for (let [, name] in Iterator(names)) + for (let [, name] in Iterator(names)) { + this._key_key[name.toLowerCase()] = name; this._key_code[name.toLowerCase()] = v; + } } // HACK: as Gecko does not include an event for <, we must add this in manually. @@ -67,6 +93,7 @@ var Events = Module("events", { this._activeMenubar = false; this.addSessionListener(window, "DOMMenuBarActive", this.onDOMMenuBarActive, true); this.addSessionListener(window, "DOMMenuBarInactive", this.onDOMMenuBarInactive, true); + this.addSessionListener(window, "blur", this.onBlur, true); this.addSessionListener(window, "focus", this.onFocus, true); this.addSessionListener(window, "keydown", this.onKeyUpOrDown, true); this.addSessionListener(window, "keypress", this.onKeyPress, true); @@ -422,7 +449,7 @@ var Events = Module("events", { keyname = String.fromCharCode(parseInt(keyname.substr(1), 16)); if (keyname && (unknownOk || keyname.length == 1 || /mouse$/.test(keyname) || - this._key_code[keyname] || keyname == "nop")) { + this._key_code[keyname] || set.has(this._pseudoKeys, keyname))) { evt_obj.ctrlKey = /C-/.test(modifier); evt_obj.altKey = /A-/.test(modifier); evt_obj.shiftKey = /S-/.test(modifier); @@ -437,8 +464,8 @@ var Events = Module("events", { evt_obj.charCode = keyname.charCodeAt(0); } - else if (keyname == "nop") { - evt_obj.dactylString = "<Nop>"; + else if (set.has(this._pseudoKeys)) { + evt_obj.dactylString = "<" + this._key_key[keyname] + ">"; } else if (/mouse$/.test(keyname)) { // mouse events evt_obj.type = (/2-/.test(modifier) ? "dblclick" : "click"); @@ -562,7 +589,7 @@ var Events = Module("events", { } } if (key == null) - key = event.dactylKeyname; + key = this._key_key[event.dactylKeyname] || event.dactylKeyname; if (key == null) return null; } @@ -698,6 +725,15 @@ var Events = Module("events", { } }, + onBlur: function onFocus(event) { + if (event.originalTarget instanceof Window && services.focus.activeWindow == null) + // Deals with circumstances where, after the main window + // blurs while a collapsed frame has focus, re-activating + // the main window does not restore focus and we lose key + // input. + services.focus.clearFocus(window); + }, + // TODO: Merge with onFocusChange onFocus: function onFocus(event) { let elem = event.originalTarget; @@ -1056,9 +1092,9 @@ var Events = Module("events", { const self = this; let key = events.toString(event); - let [, countStr, candidateCommand] = /^((?:[1-9][0-9]*)?)(.*)/.exec(this.buffer + key); + let [, countStr, command] = /^((?:[1-9][0-9]*)?)(.*)/.exec(this.buffer + key); - let map = mappings[event.noremap ? "getDefault" : "get"](this.main, candidateCommand); + let map = mappings[event.noremap ? "getDefault" : "get"](this.main, command); function execute(map) { if (self.preExecute) @@ -1069,16 +1105,16 @@ var Events = Module("events", { return res; } - let candidates = mappings.getCandidates(this.main, candidateCommand); + let candidates = mappings.getCandidates(this.main, command); if (candidates.length == 0 && !map) { - map = this.pendingMap; + [map, command] = this.pendingMap || []; this.pendingMap = null; if (map && map.arg) this.pendingArgMap = map; } // counts must be at the start of a complete mapping (10j -> go 10 lines down) - if (countStr && !candidateCommand) { + if (countStr && !command) { // no count for insert mode mappings if (!this.main.count) return this.append(event); @@ -1088,9 +1124,9 @@ var Events = Module("events", { this.append(event); } else if (this.pendingArgMap) { - let map = this.pendingArgMap; + let [map, command] = this.pendingArgMap; if (!Events.isEscape(key)) - execute(map, null, this.count, key); + execute(map, null, this.count, key, command); return true; } else if (map && !event.skipmap && candidates.length == 0) { @@ -1104,27 +1140,28 @@ var Events = Module("events", { if (map.arg) { this.append(event); - this.pendingArgMap = map; + this.pendingArgMap = [map, command]; } else if (this.pendingMotionMap) { + let [map, command] = this.pendingMotionMap; if (!Events.isEscape(key)) - execute(this.pendingMotionMap, candidateCommand, this.motionCount || this.count, null); + execute(map, command, this.motionCount || this.count, null, command); return true; } else if (map.motion) { this.buffer = ""; - this.pendingMotionMap = map; + this.pendingMotionMap = [map, command]; } else { if (modes.replaying && !this.waitForPageLoad()) return true; - return !execute(map, null, this.count) || !map.route + return !execute(map, null, this.count, null, command) || !map.route } } - else if (mappings.getCandidates(this.main, candidateCommand).length > 0 && !event.skipmap) { + else if (mappings.getCandidates(this.main, command).length > 0 && !event.skipmap) { this.append(event); - this.pendingMap = map; + this.pendingMap = [map, command]; } else { this.append(event); diff --git a/common/content/hints.js b/common/content/hints.js index 2785b09d..0bdd1d83 100644 --- a/common/content/hints.js +++ b/common/content/hints.js @@ -74,6 +74,15 @@ var Hints = Module("hints", { Buffer.isScrollable(elem, 0, true) || Buffer.isScrollable(elem, 0, false); }, + /** + * Clear any timeout which might be active after pressing a number + */ + clearTimeout: function () { + if (this._activeTimeout) + this._activeTimeout.cancel(); + this._activeTimeout = null; + }, + /** * Reset hints, so that they can be cleanly used again. */ @@ -92,10 +101,7 @@ var Hints = Module("hints", { this._pageHints = []; this._validHints = []; this._docs = []; - - if (this._activeTimeout) - this._activeTimeout.cancel(); - this._activeTimeout = null; + this.clearTimeout(); }, __reset: function __reset() { if (!this._usedTabKey) @@ -298,14 +304,12 @@ var Hints = Module("hints", { let mode = this._hintMode; let res = util.evaluateXPath(mode.xpath, doc, null, true); - if (mode.filter) - res = let (orig = res) (e for (e in orig) if (mode.filter(e))); let start = this._pageHints.length; for (let elem in res) { let hint = { elem: elem, showText: false }; - if (!isVisible(elem)) + if (!isVisible(elem) || mode.filter && !mode.filter(elem)) continue; if (elem.hasAttributeNS(NS, "hint")) @@ -582,11 +586,7 @@ var Hints = Module("hints", { _onInput: function _onInput(event) { this.prevInput = "text"; - // clear any timeout which might be active after pressing a number - if (this._activeTimeout) { - this._activeTimeout.cancel(); - this._activeTimeout = null; - } + this.clearTimeout(); this._hintNumber = 0; this._hintString = commandline.command; @@ -896,104 +896,38 @@ var Hints = Module("hints", { */ onEvent: function onEvent(event) { let key = events.toString(event); - let followFirst = false; - // clear any timeout which might be active after pressing a number - if (this._activeTimeout) { - this._activeTimeout.cancel(); - this._activeTimeout = null; - } + this.clearTimeout(); - switch (key) { - case "<Return>": - followFirst = true; - break; + if (!this.escNumbers && this.isHintKey(key)) { + this.prevInput = "number"; - case "<Tab>": - case "<S-Tab>": - this._usedTabKey = true; - if (this._hintNumber == 0) - this._hintNumber = 1; - - let oldId = this._hintNumber; - if (key == "<Tab>") { - if (++this._hintNumber > this._validHints.length) - this._hintNumber = 1; - } - else { - if (--this._hintNumber < 1) - this._hintNumber = this._validHints.length; - } - this._showActiveHint(this._hintNumber, oldId); - this._updateStatusline(); - return false; - - case "<BS>": - if (this.prevInput !== "number") - return true; - - if (this._hintNumber > 0 && !this._usedTabKey) { - this._hintNumber = Math.floor(this._hintNumber / this.hintKeys.length); - if (this._hintNumber == 0) - this.prevInput = "text"; - } - else { - this._usedTabKey = false; + let oldHintNumber = this._hintNumber; + if (this._usedTabKey) { this._hintNumber = 0; - dactyl.beep(); + this._usedTabKey = false; + } + this._hintNumber = this._hintNumber * this.hintKeys.length + + this.hintKeys.indexOf(key); + + this._updateStatusline(); + + if (!this._canUpdate) return; - } - break; - case options["mapleader"]: - hints.escNumbers = !hints.escNumbers; - if (hints.escNumbers && this._usedTabKey) - this._hintNumber = 0; - - this._updateStatusline(); - return false; - - default: - if (!this.escNumbers && this.isHintKey(key)) { - this.prevInput = "number"; - - let oldHintNumber = this._hintNumber; - if (this._usedTabKey) { - this._hintNumber = 0; - this._usedTabKey = false; - } - this._hintNumber = this._hintNumber * this.hintKeys.length + - this.hintKeys.indexOf(key); - - this._updateStatusline(); - - if (!this._canUpdate) - return; - - if (this._docs.length == 0) { - this._generate(); - this._showHints(); - } - this._showActiveHint(this._hintNumber, oldHintNumber || 1); - - dactyl.assert(this._hintNumber != 0); - - this._checkUnique(); - return false; - } - return true; - } - - this._updateStatusline(); - - if (this._canUpdate) { - if (this._docs.length == 0 && this._hintString.length > 0) + if (this._docs.length == 0) { this._generate(); + this._showHints(); + } + this._showActiveHint(this._hintNumber, oldHintNumber || 1); - this._showHints(); - this._processHints(followFirst); + dactyl.assert(this._hintNumber != 0); + + this._checkUnique(); + return false; } - return false; + + return !Events.isEscape(key); } //}}} }, { @@ -1118,6 +1052,84 @@ var Hints = Module("hints", { "Start an extended hint mode and stay there until <Esc> is pressed", function (count) { hints.open("g;", { continue: true, count: count }); }, { count: true }); + + function update(followFirst) { + hints.clearTimeout(); + hints._updateStatusline(); + + if (hints._canUpdate) { + if (hints._docs.length == 0 && hints._hintString.length > 0) + hints._generate(); + + hints._showHints(); + hints._processHints(followFirst); + } + } + + mappings.add(modes.HINTS, ["<Return>"], + "Follow the selected hint", + function () { update(true) }); + + function tab(previous) { + hints.clearTimeout(); + this._usedTabKey = true; + if (this._hintNumber == 0) + this._hintNumber = 1; + + let oldId = this._hintNumber; + if (!previous) { + if (++this._hintNumber > this._validHints.length) + this._hintNumber = 1; + } + else { + if (--this._hintNumber < 1) + this._hintNumber = this._validHints.length; + } + + this._showActiveHint(this._hintNumber, oldId); + this._updateStatusline(); + } + + mappings.add(modes.HINTS, ["<Tab>"], + "Focus the next matching hint", + function () { tab.call(hints, false) }); + + mappings.add(modes.HINTS, ["<S-Tab>"], + "Focus the previous matching hint", + function () { tab.call(hints, true) }); + + mappings.add(modes.HINTS, ["<BS>", "<C-h>"], + "Delete the previous character", + function () { + hints.clearTimeout(); + if (hints.prevInput !== "number") + return true; + + if (hints._hintNumber > 0 && !hints._usedTabKey) { + hints._hintNumber = Math.floor(hints._hintNumber / hints.hintKeys.length); + if (hints._hintNumber == 0) + hints.prevInput = "text"; + update(false); + } + else { + hints._usedTabKey = false; + hints._hintNumber = 0; + dactyl.beep(); + } + return false; + }, + { route: true }); + + mappings.add(modes.HINTS, ["<Leader>"], + "Toggle hint filtering", + function () { + hints.clearTimeout(); + hints.escNumbers = !hints.escNumbers; + if (hints.escNumbers && hints._usedTabKey) + hints._hintNumber = 0; + + hints._updateStatusline(); + }); }, options: function () { const DEFAULT_HINTTAGS = @@ -1129,7 +1141,7 @@ var Hints = Module("hints", { "XPath strings of hintable elements for extended hint modes", "regexpmap", "[iI]:" + xpath(["img"]) + ",[OTivVWy]:" + xpath(["{a,area}[@href]", "{img,iframe}[@src]"]) + - ",[F]:" + xpath(["div", "span", "p", "body", "html"]) + + ",[F]:" + xpath(["body", "code", "div", "html", "p", "pre", "span"]) + ",[S]:" + xpath(["input[not(@type='hidden')]", "textarea", "button", "select"]), { validator: Option.validateXPath }); diff --git a/common/content/mappings.js b/common/content/mappings.js index d3a2a25d..a84bbf92 100644 --- a/common/content/mappings.js +++ b/common/content/mappings.js @@ -90,7 +90,9 @@ var Map = Class("Map", { * @param {string} name The name to query. * @returns {boolean} */ - hasName: function (name) this.names.indexOf(name) >= 0, + hasName: function (name) this.keys.indexOf(name) >= 0, + + keys: Class.memoize(function () this.names.map(mappings._expandLeader)), /** * Execute the action for this mapping. @@ -102,7 +104,7 @@ var Map = Class("Map", { * @param {string} argument The normal argument if accepted by this * mapping. E.g. "a" for "ma" */ - execute: function (motion, count, argument) { + execute: function (motion, count, argument, command) { let args = []; if (this.motion) @@ -111,6 +113,7 @@ var Map = Class("Map", { args.push(count); if (this.arg) args.push(argument); + args.push(command); let self = this; function repeat() self.action.apply(self, args); @@ -153,10 +156,9 @@ var Mappings = Module("mappings", { _getMap: function (mode, cmd, stack) { let maps = stack[mode] || []; - for (let [, map] in Iterator(maps)) { + for (let [, map] in Iterator(maps)) if (map.hasName(cmd)) return map; - } return null; }, @@ -233,7 +235,6 @@ var Mappings = Module("mappings", { * @optional */ addUserMap: function (modes, keys, description, action, extra) { - keys = keys.map(this._expandLeader); extra = extra || {}; extra.user = true; let map = Map(modes, keys, description, action, extra); @@ -631,7 +632,16 @@ var Mappings = Module("mappings", { options: function () { options.add(["mapleader", "ml"], "Define the replacement keys for the <Leader> pseudo-key", - "string", "\\"); + "string", "\\", { + setter: function (value) { + if (this.hasChanged) + for (let hive in values([mappings._user, mappings._main])) + for (let mode in values(hive)) + for (let map in values(mode)) + delete map.keys; + return value; + } + }); } }); diff --git a/common/content/modes.js b/common/content/modes.js index 8f3f0ca1..7cfcf97d 100644 --- a/common/content/modes.js +++ b/common/content/modes.js @@ -51,8 +51,7 @@ var Modes = Module("modes", { } }); - this.addMode("COMMAND_LINE", { char: "c", input: true, - display: function () modes.extended & modes.OUTPUT_MULTILINE ? null : this.disp }); + this.addMode("COMMAND_LINE", { char: "c", input: true }); this.addMode("CARET", {}, { get pref() prefs.get("accessibility.browsewithcaret"), diff --git a/common/content/modules.js b/common/content/modules.js deleted file mode 100644 index df15743b..00000000 --- a/common/content/modules.js +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright (c) 2009-2010 by 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"; - -/** - * @class ModuleBase - * The base class for all modules. - */ -var ModuleBase = Class("ModuleBase", { - /** - * @property {[string]} A list of module prerequisites which - * must be initialized before this module is loaded. - */ - requires: [], - - toString: function () "[module " + this.constructor.className + "]" -}); - -/** - * @constructor Module - * - * Constructs a new ModuleBase class and makes arrangements for its - * initialization. Arguments marked as optional must be either - * entirely elided, or they must have the exact type specified. - * Loading semantics are as follows: - * - * - A module is guaranteed not to be initialized before any of its - * prerequisites as listed in its {@see ModuleBase#requires} member. - * - A module is considered initialized once it's been instantiated, - * its {@see Class#init} method has been called, and its - * instance has been installed into the top-level {@see modules} - * object. - * - Once the module has been initialized, its module-dependent - * initialization functions will be called as described hereafter. - * @param {string} name The module's name as it will appear in the - * top-level {@see modules} object. - * @param {ModuleBase} base The base class for this module. - * @optional - * @param {Object} prototype The prototype for instances of this - * object. The object itself is copied and not used as a prototype - * directly. - * @param {Object} classProperties The class properties for the new - * module constructor. - * @optional - * @param {Object} moduleInit The module initialization functions - * for the new module. Each function is called as soon as the named module - * has been initialized, but after the module itself. The constructors are - * guaranteed to be called in the same order that the dependent modules - * were initialized. - * @optional - * - * @returns {function} The constructor for the resulting module. - */ -function Module(name) { - let args = Array.slice(arguments); - - var base = ModuleBase; - if (callable(args[1])) - base = args.splice(1, 1)[0]; - let [, prototype, classProperties, moduleInit] = args; - const module = Class(name, base, prototype, classProperties); - - module.INIT = moduleInit || {}; - module.prototype.INIT = module.INIT; - module.requires = prototype.requires || []; - Module.list.push(module); - Module.constructors[name] = module; - return module; -} -Module.list = []; -Module.constructors = {}; - -window.addEventListener("load", function onLoad() { - window.removeEventListener("load", onLoad, false); - - Module.list.forEach(function (module) { - modules.__defineGetter__(module.className, function () { - delete modules[module.className]; - return load(module.className, null, Components.stack.caller); - }); - }); - - const start = Date.now(); - const deferredInit = { load: [] }; - const seen = set(); - const loaded = set(["init"]); - modules.loaded = loaded; - - function init(module) { - function init(func, mod) - function () defineModule.time(module.className || module.constructor.className, mod, - func, module, - dactyl, modules, window); - - set.add(loaded, module.constructor.className); - for (let [mod, func] in Iterator(module.INIT)) { - if (mod in loaded) - init(func, mod)(); - else { - deferredInit[mod] = deferredInit[mod] || []; - deferredInit[mod].push(init(func, mod)); - } - } - } - defineModule.modules.map(init); - - function load(module, prereq, frame) { - if (isString(module)) { - if (!Module.constructors.hasOwnProperty(module)) - modules.load(module); - module = Module.constructors[module]; - } - - try { - if (module.className in loaded) - return; - if (module.className in seen) - throw Error("Module dependency loop."); - set.add(seen, module.className); - - for (let dep in values(module.requires)) - load(Module.constructors[dep], module.className); - - defineModule.loadLog.push("Load" + (isString(prereq) ? " " + prereq + " dependency: " : ": ") + module.className); - if (frame && frame.filename) - defineModule.loadLog.push(" from: " + frame.filename + ":" + frame.lineNumber); - - delete modules[module.className]; - modules[module.className] = defineModule.time(module.className, "init", module); - - init(modules[module.className]); - for (let [, fn] in iter(deferredInit[module.className] || [])) - fn(); - } - catch (e) { - util.dump("Loading " + (module && module.className) + ": " + e + "\n" + (e.stack || "")); - } - return modules[module.className]; - } - - Module.list.forEach(load); - deferredInit["load"].forEach(call); - modules.times = update({}, defineModule.times); - - util.dump("Loaded in " + (Date.now() - start) + "ms"); -}, false); - -window.addEventListener("unload", function onUnload() { - window.removeEventListener("unload", onUnload, false); - for (let [, mod] in iter(modules)) - if (mod instanceof ModuleBase && "destroy" in mod) - mod.destroy(); -}, false); - -// vim: set fdm=marker sw=4 ts=4 et: diff --git a/common/content/options.js b/common/content/options.js index 38303c4e..69aca5e9 100644 --- a/common/content/options.js +++ b/common/content/options.js @@ -82,7 +82,7 @@ var Option = Class("Option", { get isDefault() this.stringValue === this.stringDefaultValue, /** @property {value} The option's global value. @see #scope */ - get globalValue() options.store.get(this.name, {}).value, + get globalValue() { try { return options.store.get(this.name, {}).value } catch (e) { util.reportError(e); throw e; } }, set globalValue(val) { options.store.set(this.name, { value: val, time: Date.now() }); }, /** @@ -277,6 +277,8 @@ var Option = Class("Option", { */ description: "", + cleanupValue: null, + /** * @property {function(CompletionContext, Args)} This option's completer. * @see CompletionContext @@ -425,7 +427,8 @@ var Option = Class("Option", { }, parse: { - number: function (value) Number(Option.dequote(value)), + number: function (value) let (val = Option.dequote(value)) + Option.validIf(Number(val) % 1 == 0, "Integer value required") && parseInt(val), boolean: function (value) Option.dequote(value) == "true" || value == true ? true : false, @@ -498,10 +501,9 @@ var Option = Class("Option", { if (invert) values = values[(values.indexOf(String(this.value)) + 1) % values.length] - dactyl.assert(!isNaN(values) && Number(values) == parseInt(values), - "E521: Number required after := " + this.name + "=" + values); - let value = parseInt(values); + dactyl.assert(Number(values) % 1 == 0, + "E521: Number required after =: " + this.name + "=" + values); switch (operator) { case "+": @@ -633,6 +635,12 @@ var Options = Module("options", { }, window); }, + cleanup: function cleanup() { + for (let opt in this) + if (opt.cleanupValue != null) + opt.value = opt.parse(opt.cleanupValue); + }, + /** @property {Iterator(Option)} @private */ __iterator__: function () values(this._options.sort(function (a, b) String.localeCompare(a.name, b.name))), @@ -712,26 +720,25 @@ var Options = Module("options", { * Lists all options in *scope* or only those with changed values if * *onlyNonDefault* is specified. * - * @param {boolean} onlyNonDefault Limit the list to prefs with a - * non-default value. + * @param {function(Option)} filter Limit the list * @param {number} scope Only list options in this scope (see * {@link Option#scope}). */ - list: function (onlyNonDefault, scope) { + list: function (filter, scope) { if (!scope) scope = Option.SCOPE_BOTH; function opts(opt) { for (let opt in Iterator(options)) { let option = { + __proto__: opt, isDefault: opt.isDefault, - name: opt.name, default: opt.stringDefaultValue, pre: "\u00a0\u00a0", // Unicode nonbreaking space. - value: <></> + value: <></>, }; - if (onlyNonDefault && option.isDefault) + if (filter && !filter(opt)) continue; if (!(opt.scope & scope)) continue; @@ -749,7 +756,7 @@ var Options = Module("options", { } }; - commandline.commandOutput(template.options("Options", opts())); + commandline.commandOutput(template.options("Options", opts(), options["verbose"] > 0)); }, /** @@ -766,7 +773,7 @@ var Options = Module("options", { let matches, prefix, postfix; [matches, prefix, ret.name, postfix, ret.valueGiven, ret.operator, ret.value] = - args.match(/^\s*(no|inv)?([a-z_.-]*?)([?&!])?\s*(([-+^]?)=(.*))?\s*$/) || []; + args.match(/^\s*(no|inv)?([^=]+?)([?&!])?\s*(([-+^]?)=(.*))?\s*$/) || []; ret.args = args; ret.onlyNonDefault = false; // used for :set to print non-default options @@ -862,6 +869,17 @@ var Options = Module("options", { if (!args.length) args[0] = ""; + let list = []; + function flushList() { + let names = set(list.map(function (opt) opt.option ? opt.option.name : "")); + if (list.length) + if (list.some(function (opt) opt.all)) + options.list(function (opt) !(list[0].onlyNonDefault && opt.isDefault) , list[0].scope); + else + options.list(function (opt) set.has(names, opt.name), list[0].scope); + list = []; + } + for (let [, arg] in args) { if (bang) { let onlyNonDefault = false; @@ -874,7 +892,7 @@ var Options = Module("options", { } else { var [matches, name, postfix, valueGiven, operator, value] = - arg.match(/^\s*?([a-zA-Z0-9\.\-_{}]+?)([?&!])?\s*(([-+^]?)=(.*))?\s*$/); + arg.match(/^\s*?([^=]+?)([?&!])?\s*(([-+^]?)=(.*))?\s*$/); reset = (postfix == "&"); invertBoolean = (postfix == "!"); } @@ -900,7 +918,7 @@ var Options = Module("options", { value = true; else if (value == "false") value = false; - else if (!isNaN(value) && parseInt(value) === Number(value)) + else if (Number(value) % 1 == 0) value = parseInt(value); else value = Option.dequote(value); @@ -923,6 +941,7 @@ var Options = Module("options", { // reset a variable to its default value if (opt.reset) { + flushList(); if (opt.all) { for (let option in options) option.reset(); @@ -932,25 +951,11 @@ var Options = Module("options", { } } // read access - else if (opt.get) { - if (opt.all) - options.list(opt.onlyNonDefault, opt.scope); - else { - XML.prettyPrinting = false; - XML.ignoreWhitespace = false; - if (option.type == "boolean") - var msg = (opt.optionValue ? " " : "no") + option.name; - else - msg = " " + option.name + "=" + opt.option.stringify(opt.optionValue); - - if (options["verbose"] > 0 && option.setFrom) - msg = <>{msg}<br/> Last set from {template.sourceLink(option.setFrom)}</>; - - dactyl.echo(<span highlight="CmdOutput Message">{msg}</span>); - } - } + else if (opt.get) + list.push(opt); // write access else { + flushList(); if (opt.option.type === "boolean") { dactyl.assert(!opt.valueGiven, "E474: Invalid argument: " + arg); opt.values = !opt.unsetBoolean; @@ -969,6 +974,7 @@ var Options = Module("options", { option.setFrom = commands.getCaller(null); } } + flushList(); } function setCompleter(context, args, modifiers) { diff --git a/common/content/statusline.js b/common/content/statusline.js index 07695b44..baeb4f49 100644 --- a/common/content/statusline.js +++ b/common/content/statusline.js @@ -14,12 +14,6 @@ var StatusLine = Module("statusline", { this.statusBar = document.getElementById("addon-bar") || this._statusLine; this.statusBar.collapsed = true; // it is later restored unless the user sets laststatus=0 - // our status bar fields - this.widgets = array(["container", "url", "inputbuffer", "progress", "tabcount", "bufferposition", "zoomlevel"] - .map(function (field) [field, document.getElementById("dactyl-statusline-field-" + field)])) - .toObject(); - this.widgets.status = this.widgets.container; - if (this.statusBar.localName == "toolbar") { styles.system.add("addon-bar", config.styleableChrome, <css><![CDATA[ #status-bar { margin-top: 0 !important; } @@ -28,10 +22,46 @@ var StatusLine = Module("statusline", { #addon-bar > #addonbar-closebutton { visibility: collapse; } #addon-bar > xul|toolbarspring { visibility: collapse; } ]]></css>); - let parent = this.widgets.status.parentNode; - parent.removeChild(this.widgets.status); - parent.insertBefore(this.widgets.status, parent.firstChild); } + + let _commandline = "if (window.dactyl) return dactyl.modules.commandline"; + let prepend = <e4x xmlns={XUL} xmlns:dactyl={NS}> + <statusbar id="status-bar" highlight="StatusLine StatusLineNormal"> + <!-- insertbefore="dactyl.statusBefore;" insertafter="dactyl.statusAfter;" --> + <hbox style="background: inherit;" key="container" flex="1" hidden="false" align="center"> + <stack orient="horizontal" align="stretch" flex="1" class="dactyl-container" highlight="CmdLine StatusCmdLine"> + <hbox class="dactyl-container" highlight="CmdLine StatusCmdLine"> + <label key="mode" crop="end" class="plain" collapsed="true"/> + <stack flex="1" class="dactyl-container" highlight="CmdLine StatusCmdLine"> + <textbox key="url" crop="end" flex="1" class="plain dactyl-status-field-url" readonly="true"/> + <textbox key="message" crop="end" flex="1" class="plain" highlight="Normal StatusNormal" readonly="true"/> + </stack> + </hbox> + + <hbox key="commandline" hidden="false" class="dactyl-container" highlight="Normal StatusNormal" collapsed="true"> + <label key="commandline-prompt" class="dactyl-commandline-prompt plain" flex="0" crop="end" value="" collapsed="true"/> + <textbox key="commandline-command" class="dactyl-commandline-command plain" flex="1" type="text" timeout="100" + oninput={_commandline + ".onEvent(event);"} onkeyup={_commandline + ".onEvent(event);"} + onfocus={_commandline + ".onEvent(event);"} onblur={_commandline + ".onEvent(event);"}/> + </hbox> + </stack> + <label class="plain" key="inputbuffer" flex="0"/> + <label class="plain" key="progress" flex="0"/> + <label class="plain" key="tabcount" flex="0"/> + <label class="plain" key="bufferposition" flex="0"/> + <label class="plain" key="zoomlevel" flex="0"/> + </hbox> + <!-- just hide them since other elements expect them --> + <statusbarpanel id="statusbar-display" hidden="true"/> + <statusbarpanel id="statusbar-progresspanel" hidden="true"/> + </statusbar> + </e4x>; + + util.dump("statusbar: load overlay"); + util.overlayWindow(window, { + objects: this.widgets = { get status() this.container }, + prepend: prepend.elements() + }); }, get visible() !this.statusBar.collapsed && !this.statusBar.hidden, diff --git a/common/locale/en-US/browsing.xml b/common/locale/en-US/browsing.xml index 2fa669ff..3fc85879 100644 --- a/common/locale/en-US/browsing.xml +++ b/common/locale/en-US/browsing.xml @@ -358,6 +358,28 @@ want to bypass &dactyl.appName;'s key handling and pass keys directly to </description> </item> +<item> + <tags>:reh :rehash</tags> + <spec>:reh<oa>ash</oa> <oa>arg</oa> …</spec> + <description> + <p> + Reload the &dactyl.appName; add-on, including all code, plugins, + and configuration. For users running directly from the development + repository, this is a good way to update to the latest version or + to test your changes. + </p> + <p> + Any arguments supplied are parsed as command line arguments as + specified in <t>startup-options</t>. + </p> + <warning> + Not all plugins are designed to cleanly un-apply during a rehash. + While official plugins are safe, beware of possibility instability + if you rehash while running third-party plugins. + </warning> + </description> +</item> + <item> <tags>:re :reload</tags> <spec>:re<oa>load</oa><oa>!</oa></spec> diff --git a/common/locale/en-US/gui.xml b/common/locale/en-US/gui.xml index 0db9b588..25c34fb6 100644 --- a/common/locale/en-US/gui.xml +++ b/common/locale/en-US/gui.xml @@ -153,10 +153,33 @@ </description> </item> +<item> + <tags>:extr :extrehash</tags> + <spec>:extr<oa>ehash</oa> <a>extension</a></spec> + <spec>:extr<oa>ehash</oa></spec> + <description> + <p> + Toggle an extension's enabled status twice. This is useful for rebooting + a restartless extension. + </p> + </description> +</item> + +<item> + <tags>:extt :exttoggle</tags> + <spec>:extt<oa>oggle</oa> <a>extension</a></spec> + <spec>:extt<oa>oggle</oa></spec> + <description> + <p> + Toggle an extension's enabled status. + </p> + </description> +</item> + <item> <tags>:extu :extupdate</tags> <spec>:extu<oa>pdate</oa> <a>extension</a></spec> - <spec>:extu<oa>pdate</oa>!</spec> + <spec>:extu<oa>pdate</oa><oa>!</oa></spec> <description> <p> Update an extension. When <oa>!</oa> is given, update all diff --git a/common/modules/base.jsm b/common/modules/base.jsm index daee9992..76b99af4 100644 --- a/common/modules/base.jsm +++ b/common/modules/base.jsm @@ -163,7 +163,7 @@ defineModule.dump = function dump_() { msg = util.objectToString(msg); return msg; }).join(", "); - let name = loaded.services ? services["dactyl:"].name : "dactyl"; + let name = loaded.services && loaded.prefs && services["dactyl:"] ? services["dactyl:"].name : "dactyl"; dump(String.replace(msg, /\n?$/, "\n") .replace(/^./gm, name + ": $&")); } @@ -294,7 +294,10 @@ function deprecated(reason, fn) { (obj ? obj + "." : "") + (fn.name || name) + " is deprecated: " + reason); return func.apply(this, arguments); } - deprecatedMethod.seen = { "chrome://dactyl/content/javascript.js": true }; + deprecatedMethod.seen = { + "chrome://dactyl/content/javascript.js": true, + "resource://dactyl/util.jsm": true + }; return callable(fn) ? deprecatedMethod : Class.Property({ get: function () deprecatedMethod, @@ -859,6 +862,8 @@ Class.prototype = { timeout: function (callback, timeout) { const self = this; function notify(timer) { + if (util.rehashing && !isinstance(Cu.getGlobalForObject(callback), ["BackstagePass"])) + return; util.trapErrors(callback, self); } return services.Timer(notify, timeout || 0, services.Timer.TYPE_ONE_SHOT);; @@ -987,6 +992,8 @@ let StructBase = Class("StructBase", Array, { clone: function clone() this.constructor.apply(null, this.slice()), + closure: Class.Property(Object.getOwnPropertyDescriptor(Class.prototype, "closure")), + toString: function () Class.prototype.toString.apply(this, arguments), // Iterator over our named members @@ -1029,6 +1036,9 @@ var Timer = Class("Timer", { }, notify: function (timer) { + if (util.rehashing) + return; + this._timer.cancel(); this.latest = 0; // minInterval is the time between the completion of the command and the next firing diff --git a/common/modules/overlay.jsm b/common/modules/overlay.jsm new file mode 100644 index 00000000..cbf9a750 --- /dev/null +++ b/common/modules/overlay.jsm @@ -0,0 +1,266 @@ +// Copyright (c) 2009-2010 by 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"; + +Components.utils.import("resource://dactyl/base.jsm"); +defineModule("overlay", { + exports: ["ModuleBase"], + require: ["highlight", "sanitizer", "services", "template", "util"], +}); + +/** + * @class ModuleBase + * The base class for all modules. + */ +var ModuleBase = Class("ModuleBase", { + /** + * @property {[string]} A list of module prerequisites which + * must be initialized before this module is loaded. + */ + requires: [], + + toString: function () "[module " + this.constructor.className + "]" +}); + +var Overlay = Module("Overlay", { + init: function () { + util.overlayWindow("chrome://browser/content/browser.xul", function (window) ({ + init: function (document) { + /** + * @constructor Module + * + * Constructs a new ModuleBase class and makes arrangements for its + * initialization. Arguments marked as optional must be either + * entirely elided, or they must have the exact type specified. + * Loading semantics are as follows: + * + * - A module is guaranteed not to be initialized before any of its + * prerequisites as listed in its {@see ModuleBase#requires} member. + * - A module is considered initialized once it's been instantiated, + * its {@see Class#init} method has been called, and its + * instance has been installed into the top-level {@see modules} + * object. + * - Once the module has been initialized, its module-dependent + * initialization functions will be called as described hereafter. + * @param {string} name The module's name as it will appear in the + * top-level {@see modules} object. + * @param {ModuleBase} base The base class for this module. + * @optional + * @param {Object} prototype The prototype for instances of this + * object. The object itself is copied and not used as a prototype + * directly. + * @param {Object} classProperties The class properties for the new + * module constructor. + * @optional + * @param {Object} moduleInit The module initialization functions + * for the new module. Each function is called as soon as the named module + * has been initialized, but after the module itself. The constructors are + * guaranteed to be called in the same order that the dependent modules + * were initialized. + * @optional + * + * @returns {function} The constructor for the resulting module. + */ + function Module(name) { + let args = Array.slice(arguments); + + var base = ModuleBase; + if (callable(args[1])) + base = args.splice(1, 1)[0]; + let [, prototype, classProperties, moduleInit] = args; + const module = Class(name, base, prototype, classProperties); + + module.INIT = moduleInit || {}; + module.prototype.INIT = module.INIT; + module.requires = prototype.requires || []; + Module.list.push(module); + Module.constructors[name] = module; + return module; + } + Module.list = []; + Module.constructors = {}; + + const BASE = "chrome://dactyl/content/"; + + const create = window.Object.create || (function () { + window.__dactyl_eval_string = "(function (proto) ({ __proto__: proto }))"; + services.subscriptLoader.loadSubScript(BASE + "eval.js", window); + + let res = window.__dactyl_eval_result; + delete window.__dactyl_eval_string; + delete window.__dactyl_eval_result; + return res; + })(); + + const jsmodules = {}; + const modules = update(create(jsmodules), { + + jsmodules: jsmodules, + + get content() this.config.browser.contentWindow || window.content, + + window: window, + + Module: Module, + + load: function load(script) { + for (let [i, base] in Iterator(prefix)) { + try { + services.subscriptLoader.loadSubScript(base + script + ".js", modules, "UTF-8"); + return; + } + catch (e) { + if (typeof e !== "string") { + util.dump("Trying: " + (base + script + ".js") + ":"); + util.reportError(e); + } + } + } + try { + Cu.import("resource://dactyl/" + script + ".jsm", jsmodules); + } + catch (e) { + util.dump("Loading script " + script + ":"); + util.reportError(e); + } + }, + + newContext: function newContext(proto) { + let sandbox = Components.utils.Sandbox(window, { sandboxPrototype: proto || modules, wantXrays: false }); + // Hack: + sandbox.Object = jsmodules.Object; + sandbox.Math = jsmodules.Math; + sandbox.__proto__ = proto || modules; + return sandbox; + } + }); + modules.modules = modules; + window.dactyl = { modules: modules }; + + let prefix = [BASE]; + + modules.load("util"); + modules.load("services"); + prefix.unshift("chrome://" + modules.services["dactyl:"].name + "/content/"); + + ["base", + "overlay", + "prefs", + "storage", + "javascript", + "dactyl", + "modes", + "abbreviations", + "autocommands", + "buffer", + "commandline", + "commands", + "completion", + "configbase", + "config", + "editor", + "events", + "finder", + "highlight", + "hints", + "io", + "mappings", + "marks", + "options", + "statusline", + "styles", + "template" + ].forEach(modules.load); + + modules.Config.prototype.scripts.forEach(modules.load); + }, + load: function (document) { + var { modules, Module } = window.dactyl.modules; + delete window.dactyl; + + Module.list.forEach(function (module) { + modules.__defineGetter__(module.className, function () { + delete modules[module.className]; + return load(module.className, null, Components.stack.caller); + }); + }); + + const start = Date.now(); + const deferredInit = { load: [] }; + const seen = set(); + const loaded = set(["init"]); + modules.loaded = loaded; + + function init(module) { + function init(func, mod) + function () defineModule.time(module.className || module.constructor.className, mod, + func, module, + modules.dactyl, modules, window); + + set.add(loaded, module.constructor.className); + for (let [mod, func] in Iterator(module.INIT)) { + if (mod in loaded) + init(func, mod)(); + else { + deferredInit[mod] = deferredInit[mod] || []; + deferredInit[mod].push(init(func, mod)); + } + } + } + defineModule.modules.map(init); + + function load(module, prereq, frame) { + if (isString(module)) { + if (!Module.constructors.hasOwnProperty(module)) + modules.load(module); + module = Module.constructors[module]; + } + + try { + if (module.className in loaded) + return; + if (module.className in seen) + throw Error("Module dependency loop."); + set.add(seen, module.className); + + for (let dep in values(module.requires)) + load(Module.constructors[dep], module.className); + + defineModule.loadLog.push("Load" + (isString(prereq) ? " " + prereq + " dependency: " : ": ") + module.className); + if (frame && frame.filename) + defineModule.loadLog.push(" from: " + frame.filename + ":" + frame.lineNumber); + + delete modules[module.className]; + modules[module.className] = defineModule.time(module.className, "init", module); + + init(modules[module.className]); + for (let [, fn] in iter(deferredInit[module.className] || [])) + fn(); + } + catch (e) { + util.dump("Loading " + (module && module.className) + ":"); + util.reportError(e); + } + return modules[module.className]; + } + + Module.list.forEach(load); + deferredInit["load"].forEach(call); + modules.times = update({}, defineModule.times); + + util.dump("Loaded in " + (Date.now() - start) + "ms"); + + modules.events.addSessionListener(window, "unload", function onUnload() { + window.removeEventListener("unload", onUnload.wrapped, false); + for (let [, mod] in iter(modules)) + if (mod instanceof ModuleBase && "destroy" in mod) + util.trapErrors(mod.destroy, mod); + }, false); + } + })); + } +}); + +// vim: set fdm=marker sw=4 ts=4 et: diff --git a/common/modules/services.jsm b/common/modules/services.jsm index 77060989..c29ecb95 100644 --- a/common/modules/services.jsm +++ b/common/modules/services.jsm @@ -36,7 +36,7 @@ var Services = Module("Services", { this.add("extensionManager", "@mozilla.org/extensions/manager;1", Ci.nsIExtensionManager); this.add("favicon", "@mozilla.org/browser/favicon-service;1", Ci.nsIFaviconService); this.add("focus", "@mozilla.org/focus-manager;1", Ci.nsIFocusManager); - this.add("fuel", "@mozilla.org/fuel/application;1", Ci.fuelIApplication); + this.add("fuel", "@mozilla.org/fuel/application;1", Ci.extIApplication); this.add("history", "@mozilla.org/browser/global-history;2", [Ci.nsIBrowserHistory, Ci.nsIGlobalHistory3, Ci.nsINavHistoryService, Ci.nsPIPlacesDatabase]); this.add("io", "@mozilla.org/network/io-service;1", Ci.nsIIOService); @@ -66,6 +66,7 @@ var Services = Module("Services", { this.addClass("Find", "@mozilla.org/embedcomp/rangefind;1", Ci.nsIFind); this.addClass("HtmlConverter","@mozilla.org/widget/htmlformatconverter;1", Ci.nsIFormatConverter); this.addClass("HtmlEncoder", "@mozilla.org/layout/htmlCopyEncoder;1", Ci.nsIDocumentEncoder); + this.addClass("Persist", "@mozilla.org/embedding/browser/nsWebBrowserPersist;1", Ci.nsIWebBrowserPersist); this.addClass("Process", "@mozilla.org/process/util;1", Ci.nsIProcess, "init"); this.addClass("String", "@mozilla.org/supports-string;1", Ci.nsISupportsString); this.addClass("Timer", "@mozilla.org/timer;1", Ci.nsITimer, "initWithCallback"); @@ -76,6 +77,7 @@ var Services = Module("Services", { if (!this.extensionManager) Components.utils.import("resource://gre/modules/AddonManager.jsm"); }, + reinit: function () {}, _create: function (classes, ifaces, meth, init, args) { try { diff --git a/common/modules/styles.jsm b/common/modules/styles.jsm index bb4f0f03..b31df65a 100644 --- a/common/modules/styles.jsm +++ b/common/modules/styles.jsm @@ -56,6 +56,12 @@ update(Sheet.prototype, { } }, + match: function (uri) { + if (isString(uri)) + uri = util.newURI(uri); + return this.sites.some(function (site) Styles.matchFilter(site, uri)); + }, + get fullCSS() { let filter = this.sites; let css = this.css; @@ -357,31 +363,21 @@ var Styles = Module("Styles", { commands: function (dactyl, modules, window) { const commands = modules.commands; - const queue = []; - const timer = Timer(10, 10, function () { - let args = queue.shift() - let [filter, css] = args; - if ("-append" in args) { - let sheet = styles.user.get(args["-name"]); - if (sheet) { - filter = sheet.sites.concat(filter).join(","); - css = sheet.css + " " + css; - - } - } - styles.user.add(args["-name"], filter, css, args["-agent"]); - - if (queue.length) - timer.tell(); - }); commands.add(["sty[le]"], "Add or list user styles", function (args) { let [filter, css] = args; if (css) { - queue.push(args); - timer.tell(args); + if ("-append" in args) { + let sheet = styles.user.get(args["-name"]); + if (sheet) { + filter = sheet.sites.concat(filter).join(","); + css = sheet.css + " " + css; + + } + } + styles.user.add(args["-name"], filter, css, args["-agent"]); } else { let list = styles.user.sheets.slice() @@ -476,7 +472,7 @@ var Styles = Module("Styles", { let uris = util.visibleURIs(window.content); context.compare = modules.CompletionContext.Sort.number; context.generate = function () styles.user.sheets; - context.keys.active = function (sheet) sheet.sites.some(function (site) uris.some(Styles.matchFilter(site))), + context.keys.active = function (sheet) uris.some(sheet.closure.match); context.keys.description = function (sheet) <>{sheet.formatSites(uris)}: {sheet.css.replace("\n", "\\n")}</> if (cmd.filter) context.filters.push(function ({ item }) cmd.filter(item)); diff --git a/common/modules/template.jsm b/common/modules/template.jsm index 0a4652fc..7e4454eb 100644 --- a/common/modules/template.jsm +++ b/common/modules/template.jsm @@ -235,7 +235,7 @@ var Template = Module("Template", { })(), template[help ? "HelpLink" : "helpLink"]); }, - options: function options(title, opts) { + options: function options(title, opts, verbose) { XML.ignoreWhitespace = false; XML.prettyPrinting = false; // <e4x> return <table> @@ -249,7 +249,9 @@ var Template = Module("Template", { <div highlight="Message" ><span style={opt.isDefault ? "" : "font-weight: bold"}>{opt.pre}{opt.name}</span><span>{opt.value}</span>{ opt.isDefault || opt.default == null ? "" : <span class="extra-info"> (default: {opt.default})</span> - }</div> + }</div>{ + verbose && opt.setFrom ? <div highlight="Message"> Last set from {template.sourceLink(opt.setFrom)}</div> : <></> + } </td> </tr>) } diff --git a/common/modules/util.jsm b/common/modules/util.jsm index e5dde3f3..156158ee 100644 --- a/common/modules/util.jsm +++ b/common/modules/util.jsm @@ -59,6 +59,8 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), } }, + get addon() services.fuel.storage.get("dactyl.bootstrap", null).addon, + // FIXME: Only works for Pentadactyl get activeWindow() services.windowMediator.getMostRecentWindow("navigator:browser"), dactyl: update(function dactyl(obj) { @@ -96,7 +98,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), obj._observe = observers; function register(meth) { - for (let target in set(["dactyl-cleanup", "quit-application"].concat(Object.keys(observers)))) + for (let target in set(["dactyl-cleanup-modules", "quit-application"].concat(Object.keys(observers)))) try { services.observer[meth](obj, target, true); } @@ -106,7 +108,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), Class.replaceProperty(obj, "observe", function (subject, target, data) { try { - if (target == "quit-application" || target == "dactyl-cleanup") + if (target == "quit-application" || target == "dactyl-cleanup-modules") register("removeObserver"); if (observers[target]) observers[target].call(obj, subject, data); @@ -484,7 +486,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), */ dumpStack: function dumpStack(msg, frames) { let stack = util.stackLines(Error().stack); - stack = stack.slice(2, 2 + (frames || stack.length)).join("\n"); + stack = stack.slice(1, 1 + (frames || stack.length)).join("\n").replace(/^/gm, " "); util.dump((arguments.length == 0 ? "Stack" : msg) + "\n" + stack + "\n"); }, @@ -922,6 +924,35 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), }, observe: { + "dactyl-cleanup-modules": function () { + util.dump("dactyl: util: observe: dactyl-cleanup-modules"); + + for (let module in values(defineModule.modules)) + if (module.cleanup) { + util.dump("cleanup: " + module.constructor.className); + util.trapErrors(module.cleanup, module); + } + + services.observer.addObserver(this, "dactyl-rehash", true); + }, + "dactyl-rehash": function () { + services.observer.removeObserver(this, "dactyl-rehash"); + + util.dump("dactyl: util: observe: dactyl-rehash"); + if (this.rehashing) + JSMLoader.purge(); + else + for (let module in values(defineModule.modules)) { + util.dump("dactyl: util: init(" + module + ")"); + if (module.reinit) + module.reinit(); + else + module.init(); + } + }, + "dactyl-purge": function () { + this.rehashing = true; + }, "toplevel-window-ready": function (window, data) { window.addEventListener("DOMContentLoaded", wrapCallback(function listener(event) { if (event.originalTarget === window.document) { @@ -965,7 +996,10 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), doc.dactylOverlayElements.push(n); fn(elem, node); for each (let attr in attr || []) // FIXME: Cleanup... - elem.setAttributeNS(attr.namespace(), attr.localName(), attr); + if (attr.name() != "highlight") + elem.setAttributeNS(attr.namespace(), attr.localName(), String(attr)); + else + highlight.highlightNode(elem, String(attr)); } } } @@ -1156,6 +1190,16 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), getSource: function regexp_getSource(re) re.source.replace(/\\(.)/g, function (m0, m1) m1 === "/" ? "/" : m0) }), + rehash: function (args) { + if (services.fuel) + services.fuel.storage.set("dactyl.commandlineArgs", args); + this.timeout(function () { + this.rehashing = true; + this.addon.userDisabled = true; + this.addon.userDisabled = false; + }); + }, + maxErrors: 15, errors: Class.memoize(function () []), reportError: function (error) { @@ -1404,6 +1448,8 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), */ xmlToDom: function xmlToDom(node, doc, nodes) { XML.prettyPrinting = false; + if (typeof node === "string") // Sandboxes can't currently pass us XML objects. + node = XML(node); if (node.length() != 1) { let domnode = doc.createDocumentFragment(); for each (let child in node) @@ -1418,11 +1464,8 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), for each (let attr in node.@*::*) if (attr.name() != "highlight") domnode.setAttributeNS(attr.namespace(), attr.localName(), String(attr)); - else { - domnode.setAttributeNS(NS.uri, "highlight", String(attr)); - for each (let h in String.split(attr, " ")) - highlight.loaded[h] = true; - } + else + highlight.highlightNode(domnode, String(attr)); for each (let child in node.*::*) domnode.appendChild(xmlToDom(child, doc, nodes)); diff --git a/common/skin/dactyl.css b/common/skin/dactyl.css index b94987f3..73945471 100644 --- a/common/skin/dactyl.css +++ b/common/skin/dactyl.css @@ -1,6 +1,6 @@ @namespace dactyl url("http://vimperator.org/namespaces/liberator"); @namespace html url("http://www.w3.org/1999/xhtml"); -@namespace xul uri("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); +@namespace xul url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); /* Applied to all content */ [dactyl|activeframe] { @@ -89,8 +89,8 @@ window[dactyl|highlight~=Bell] > * { } [dactyl|highlight~=CmdLine] { - background: inherit; - color: inherit; + background: inherit !important; + color: inherit !important; } [dactyl|highlight~=CmdLine], [dactyl|highlight~=CmdLine] > [dactyl|highlight~=CmdLine] { @@ -122,9 +122,9 @@ statusbarpanel { background: transparent; } -#dactyl-statusline-field-url { - background-color: inherit; - color: inherit; +.dactyl-status-field-url { + background-color: inherit !important; + color: inherit !important; } /* no longer at the window's bottom right corner */ @@ -141,8 +141,8 @@ statusbarpanel { padding: 0px; } .dactyl-commandline-command { - background-color: inherit; - color: inherit; + background-color: inherit !important; + color: inherit !important; margin: 0px; } .dactyl-commandline-command html|*:focus { diff --git a/pentadactyl/NEWS b/pentadactyl/NEWS index 6d42bd9b..1bc353fa 100644 --- a/pentadactyl/NEWS +++ b/pentadactyl/NEWS @@ -64,7 +64,7 @@ * :extadd now supports remote URLs as well as local files on Firefox 4. * Added :if/:elseif/:else/:endif conditionals. - Added -keyword, -tags, -title to :delbmarks. - - Added :extupdate command. + - Added :extrehash, :exttoggle, :extupdate, and :rehash commands. - Added :feedkeys command. - Added -sort option to :history. - Added several new options, including -javascript, to :abbrev and :map. diff --git a/pentadactyl/bootstrap.js b/pentadactyl/bootstrap.js new file mode 120000 index 00000000..ff1024d5 --- /dev/null +++ b/pentadactyl/bootstrap.js @@ -0,0 +1 @@ +../common/bootstrap.js \ No newline at end of file diff --git a/pentadactyl/chrome.manifest b/pentadactyl/chrome.manifest index 5b2b12e9..85a3f764 100644 --- a/pentadactyl/chrome.manifest +++ b/pentadactyl/chrome.manifest @@ -10,9 +10,6 @@ skin dactyl classic/1.0 ../common/skin/ override chrome://dactyl/content/dactyl.dtd chrome://pentadactyl/content/dactyl.dtd -overlay chrome://browser/content/browser.xul chrome://dactyl/content/dactyl.xul -overlay chrome://browser/content/browser.xul chrome://pentadactyl/content/pentadactyl.xul - component {16dc34f7-6d22-4aa4-a67f-2921fb5dcb69} components/commandline-handler.js contract @mozilla.org/commandlinehandler/general-startup;1?type=pentadactyl {16dc34f7-6d22-4aa4-a67f-2921fb5dcb69} category command-line-handler m-pentadactyl @mozilla.org/commandlinehandler/general-startup;1?type=pentadactyl diff --git a/pentadactyl/content/config.js b/pentadactyl/content/config.js index e298ffb4..2ab87d56 100644 --- a/pentadactyl/content/config.js +++ b/pentadactyl/content/config.js @@ -7,6 +7,52 @@ "use strict"; const Config = Module("config", ConfigBase, { + init: function init() { + init.superapply(this, arguments); + + util.overlayWindow(window, { + append: <e4x xmlns={XUL} xmlns:dactyl={NS}> + <menupopup id="viewSidebarMenu"> + <menuitem observes="pentadactyl-viewAddonsSidebar" label="Add-ons" accesskey="A"/> + <menuitem observes="pentadactyl-viewConsoleSidebar" label="Console" accesskey="C"/> + <menuitem observes="pentadactyl-viewDownloadsSidebar" label="Downloads" accesskey="D"/> + <menuitem observes="pentadactyl-viewPreferencesSidebar" label="Preferences" accesskey="P"/> + </menupopup> + + <broadcasterset id="mainBroadcasterSet"> + <broadcaster id="pentadactyl-viewAddonsSidebar" + autoCheck="false" + type="checkbox" + group="sidebar" + sidebarurl="chrome://mozapps/content/extensions/extensions.xul" + sidebartitle="Add-ons" + oncommand="toggleSidebar(this.id);"/> + <broadcaster id="pentadactyl-viewConsoleSidebar" + autoCheck="false" + type="checkbox" + group="sidebar" + sidebarurl="chrome://global/content/console.xul" + sidebartitle="Console" + oncommand="toggleSidebar(this.id);"/> + <broadcaster id="pentadactyl-viewDownloadsSidebar" + autoCheck="false" + type="checkbox" + group="sidebar" + sidebarurl="chrome://mozapps/content/downloads/downloads.xul" + sidebartitle="Downloads" + oncommand="toggleSidebar(this.id);"/> + <broadcaster id="pentadactyl-viewPreferencesSidebar" + autoCheck="false" + type="checkbox" + group="sidebar" + sidebarurl="about:config" + sidebartitle="Preferences" + oncommand="toggleSidebar(this.id);"/> + </broadcasterset> + </e4x>.elements() + }); + }, + get visualbellWindow() getBrowser().mPanelContainer, styleableChrome: ["chrome://browser/content/browser.xul"], @@ -177,7 +223,7 @@ const Config = Module("config", ConfigBase, { }, { argCount: "0" }); - commands.add(["sideb[ar]", "sb[ar]", "sbope[n]"], + commands.add(["sideb[ar]", "sb[ar]", "sbop[en]"], "Open the sidebar window", function (args) { function compare(a, b) util.compareIgnoreCase(a, b) == 0 diff --git a/pentadactyl/content/pentadactyl.xul b/pentadactyl/content/pentadactyl.xul deleted file mode 100644 index 719b0e20..00000000 --- a/pentadactyl/content/pentadactyl.xul +++ /dev/null @@ -1,58 +0,0 @@ -<?xml version="1.0"?> - -<!-- ***** BEGIN LICENSE BLOCK ***** {{{ - Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org> - - This work is licensed for reuse under an MIT license. Details are - given in the LICENSE.txt file included with this file. -}}} ***** END LICENSE BLOCK ***** --> - -<!-- <?xml-stylesheet href="chrome://browser/skin/" type="text/css"?> --> - -<overlay id="pentadactyl" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:nc="http://home.netscape.com/NC-rdf#" - xmlns:html="http://www.w3.org/1999/xhtml" - xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> - - <menupopup id="viewSidebarMenu"> - <menuitem observes="pentadactyl-viewAddonsSidebar" label="Add-ons" accesskey="A"/> - <menuitem observes="pentadactyl-viewConsoleSidebar" label="Console" accesskey="C"/> - <menuitem observes="pentadactyl-viewDownloadsSidebar" label="Downloads" accesskey="D"/> - <menuitem observes="pentadactyl-viewPreferencesSidebar" label="Preferences" accesskey="P"/> - </menupopup> - - <broadcasterset id="mainBroadcasterSet"> - <broadcaster id="pentadactyl-viewAddonsSidebar" - autoCheck="false" - type="checkbox" - group="sidebar" - sidebarurl="chrome://mozapps/content/extensions/extensions.xul" - sidebartitle="Add-ons" - oncommand="toggleSidebar('pentadactyl-viewAddonsSidebar');"/> - <broadcaster id="pentadactyl-viewConsoleSidebar" - autoCheck="false" - type="checkbox" - group="sidebar" - sidebarurl="chrome://global/content/console.xul" - sidebartitle="Console" - oncommand="toggleSidebar('pentadactyl-viewConsoleSidebar');"/> - <broadcaster id="pentadactyl-viewDownloadsSidebar" - autoCheck="false" - type="checkbox" - group="sidebar" - sidebarurl="chrome://mozapps/content/downloads/downloads.xul" - sidebartitle="Downloads" - oncommand="toggleSidebar('pentadactyl-viewDownloadsSidebar');"/> - <broadcaster id="pentadactyl-viewPreferencesSidebar" - autoCheck="false" - type="checkbox" - group="sidebar" - sidebarurl="about:config" - sidebartitle="Preferences" - oncommand="toggleSidebar('pentadactyl-viewPreferencesSidebar');"/> - </broadcasterset> - -</overlay> - -<!-- vim: set fdm=marker sw=4 ts=4 et: --> diff --git a/pentadactyl/install.rdf b/pentadactyl/install.rdf index ce5e76b4..7e7429b7 100644 --- a/pentadactyl/install.rdf +++ b/pentadactyl/install.rdf @@ -8,7 +8,8 @@ em:description="Firefox for Vim and Links addicts" em:homepageURL="http://dactyl.sourceforge.net/pentadactyl" em:iconURL="chrome://pentadactyl/skin/icon.png" - em:optionsURL="chrome://dactyl/content/preferences.xul"> + em:optionsURL="chrome://dactyl/content/preferences.xul" + em:bootstrap="true"> <em:creator>Kris Maglione, Doug Kearns</em:creator> <em:developer>Štěpán Němec</em:developer>