diff --git a/common/bootstrap.js b/common/bootstrap.js index 28cc607d..e4f7d561 100755 --- a/common/bootstrap.js +++ b/common/bootstrap.js @@ -34,11 +34,15 @@ const BOOTSTRAP_JSM = "resource://dactyl/bootstrap.jsm"; const BOOTSTRAP_CONTRACT = "@dactyl.googlecode.com/base/bootstrap"; var JSMLoader = BOOTSTRAP_CONTRACT in Cc && Cc[BOOTSTRAP_CONTRACT].getService().wrappedJSObject.loader; +var name = "dactyl"; function reportError(e) { - dump("\ndactyl: bootstrap: " + e + "\n" + (e.stack || Error().stack) + "\n"); + dump("\n" + name + ": bootstrap: " + e + "\n" + (e.stack || Error().stack) + "\n"); Cu.reportError(e); } +function debug(msg) { + dump(name + ": " + msg + "\n"); +} function httpGet(url) { let xmlhttp = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest); @@ -86,16 +90,17 @@ function updateVersion() { } function startup(data, reason) { - dump("dactyl: bootstrap: startup " + reasonToString(reason) + "\n"); + debug("bootstrap: startup " + reasonToString(reason)); basePath = data.installPath; if (!initialized) { initialized = true; - dump("dactyl: bootstrap: init" + " " + data.id + "\n"); + debug("bootstrap: init" + " " + data.id); addonData = data; addon = data; + name = data.id.replace(/@.*/, ""); AddonManager.getAddonByID(addon.id, function (a) { addon = a; updateVersion(); @@ -138,12 +143,12 @@ function FactoryProxy(url, classID) { FactoryProxy.prototype = { QueryInterface: XPCOMUtils.generateQI(Ci.nsIFactory), register: function () { - dump("dactyl: bootstrap: register: " + this.classID + " " + this.contractID + "\n"); + debug("bootstrap: register: " + this.classID + " " + this.contractID); JSMLoader.registerFactory(this); }, get module() { - dump("dactyl: bootstrap: create module: " + this.contractID + "\n"); + debug("bootstrap: create module: " + this.contractID); Object.defineProperty(this, "module", { value: {}, enumerable: true }); JSMLoader.load(this.url, this.module); @@ -156,7 +161,7 @@ FactoryProxy.prototype = { } function init() { - dump("dactyl: bootstrap: init\n"); + debug("bootstrap: init"); let manifestURI = getURI("chrome.manifest"); let manifest = httpGet(manifestURI.spec) @@ -227,6 +232,7 @@ function init() { if (!JSMLoader || JSMLoader.bump !== 6 || Cu.unload) Cu.import(BOOTSTRAP_JSM, global); + JSMLoader.name = name; JSMLoader.bootstrap = this; JSMLoader.load(BOOTSTRAP_JSM, global); @@ -262,7 +268,7 @@ function init() { } function shutdown(data, reason) { - dump("dactyl: bootstrap: shutdown " + reasonToString(reason) + "\n"); + debug("bootstrap: shutdown " + reasonToString(reason)); if (reason != APP_SHUTDOWN) { try { module("resource://dactyl-content/disable-acr.jsm").cleanup(); @@ -286,7 +292,7 @@ function shutdown(data, reason) { } function uninstall(data, reason) { - dump("dactyl: bootstrap: uninstall " + reasonToString(reason) + "\n"); + debug("bootstrap: uninstall " + reasonToString(reason)); if (reason == ADDON_UNINSTALL) Services.prefs.deleteBranch("extensions.dactyl."); } @@ -300,6 +306,6 @@ function reasonToString(reason) { return name; } -function install(data, reason) { dump("dactyl: bootstrap: install " + reasonToString(reason) + "\n"); } +function install(data, reason) { debug("bootstrap: install " + reasonToString(reason)); } // vim: set fdm=marker sw=4 ts=4 et: diff --git a/common/content/autocommands.js b/common/content/autocommands.js index 89029a39..befe3c63 100644 --- a/common/content/autocommands.js +++ b/common/content/autocommands.js @@ -10,7 +10,7 @@ var AutoCommand = Struct("event", "filter", "command"); update(AutoCommand.prototype, { - eventName: Class.memoize(function () this.event.toLowerCase()), + eventName: Class.Memoize(function () this.event.toLowerCase()), match: function (event, pattern) { return (!event || this.eventName == event.toLowerCase()) && (!pattern || String(this.filter) === String(pattern)); diff --git a/common/content/bookmarks.js b/common/content/bookmarks.js index 97c2eaad..93584786 100644 --- a/common/content/bookmarks.js +++ b/common/content/bookmarks.js @@ -432,7 +432,7 @@ var Bookmarks = Module("bookmarks", { return bookmarks.get(args.join(" "), args["-tags"], null, { keyword: context.filter, title: args["-title"] }); }, type: CommandOption.STRING, - validator: function (arg) /^\S+$/.test(arg) + validator: bind("test", /^\S+$/) }; commands.add(["bma[rk]"], diff --git a/common/content/buffer.js b/common/content/buffer.js index c9838bb2..39cc8bea 100644 --- a/common/content/buffer.js +++ b/common/content/buffer.js @@ -1184,8 +1184,8 @@ var Buffer = Module("buffer", { PageInfo: Struct("PageInfo", "name", "title", "action") .localize("title"), - ZOOM_MIN: Class.memoize(function () prefs.get("zoom.minPercent")), - ZOOM_MAX: Class.memoize(function () prefs.get("zoom.maxPercent")), + ZOOM_MIN: Class.Memoize(function () prefs.get("zoom.minPercent")), + ZOOM_MAX: Class.Memoize(function () prefs.get("zoom.maxPercent")), setZoom: deprecated("buffer.setZoom", function setZoom() buffer.setZoom.apply(buffer, arguments)), bumpZoomLevel: deprecated("buffer.bumpZoomLevel", function bumpZoomLevel() buffer.bumpZoomLevel.apply(buffer, arguments)), diff --git a/common/content/commandline.js b/common/content/commandline.js index ddc6dbd4..1dc22c99 100644 --- a/common/content/commandline.js +++ b/common/content/commandline.js @@ -252,10 +252,10 @@ var CommandWidgets = Class("CommandWidgets", { [this.commandbar.container, this.statusbar.container].forEach(check); }, - active: Class.memoize(Object), - activeGroup: Class.memoize(Object), - commandbar: Class.memoize(function () ({ group: "Cmd" })), - statusbar: Class.memoize(function () ({ group: "Status" })), + active: Class.Memoize(Object), + activeGroup: Class.Memoize(Object), + commandbar: Class.Memoize(function () ({ group: "Cmd" })), + statusbar: Class.Memoize(function () ({ group: "Status" })), _ready: function _ready(elem) { return elem.contentDocument.documentURI === elem.getAttribute("src") && @@ -272,9 +272,9 @@ var CommandWidgets = Class("CommandWidgets", { yield elem; }, - completionContainer: Class.memoize(function () this.completionList.parentNode), + completionContainer: Class.Memoize(function () this.completionList.parentNode), - contextMenu: Class.memoize(function () { + contextMenu: Class.Memoize(function () { ["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-'))]"; @@ -284,15 +284,15 @@ var CommandWidgets = Class("CommandWidgets", { return document.getElementById("dactyl-contextmenu"); }), - multilineOutput: Class.memoize(function () this._whenReady("dactyl-multiline-output", function (elem) { + multilineOutput: Class.Memoize(function () this._whenReady("dactyl-multiline-output", function (elem) { 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"; }), true), - multilineInput: Class.memoize(function () document.getElementById("dactyl-multiline-input")), + multilineInput: Class.Memoize(function () document.getElementById("dactyl-multiline-input")), - mowContainer: Class.memoize(function () document.getElementById("dactyl-multiline-output-container")) + mowContainer: Class.Memoize(function () document.getElementById("dactyl-multiline-output-container")) }, { getEditor: function getEditor(elem) { elem.inputField.QueryInterface(Ci.nsIDOMNSEditableElement); @@ -584,7 +584,7 @@ var CommandLine = Module("commandline", { }, this); }, - widgets: Class.memoize(function () CommandWidgets()), + widgets: Class.Memoize(function () CommandWidgets()), runSilently: function runSilently(func, self) { this.withSavedValues(["silent"], function () { diff --git a/common/content/dactyl.js b/common/content/dactyl.js index 17fed031..ae5f2216 100644 --- a/common/content/dactyl.js +++ b/common/content/dactyl.js @@ -112,7 +112,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { }, /** @property {string} The name of the current user profile. */ - profileName: Class.memoize(function () { + profileName: Class.Memoize(function () { // NOTE: services.profile.selectedProfile.name doesn't return // what you might expect. It returns the last _actively_ selected // profile (i.e. via the Profile Manager or -P option) rather than the @@ -1221,7 +1221,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { }, this); }, stringToURLArray: deprecated("dactyl.parseURLs", "parseURLs"), - urlish: Class.memoize(function () util.regexp(+ (:\d+)? (/ .*) | + (:\d+) | diff --git a/common/content/events.js b/common/content/events.js index 999685f8..b8489697 100644 --- a/common/content/events.js +++ b/common/content/events.js @@ -48,7 +48,7 @@ var ProcessorStack = Class("ProcessorStack", { this.processors.unshift(KeyProcessor(modes.BASE, hive)); }, - passUnknown: Class.memoize(function () options.get("passunknown").getKey(this.modes)), + passUnknown: Class.Memoize(function () options.get("passunknown").getKey(this.modes)), notify: function () { events.dbg("NOTIFY()"); diff --git a/common/content/hints.js b/common/content/hints.js index 8c2c7efe..2bdd7552 100644 --- a/common/content/hints.js +++ b/common/content/hints.js @@ -1103,7 +1103,7 @@ var Hints = Module("hints", { return true; }, - translitTable: Class.memoize(function () { + translitTable: Class.Memoize(function () { const table = {}; [ [0x00c0, 0x00c6, ["A"]], [0x00c7, 0x00c7, ["C"]], diff --git a/common/content/history.js b/common/content/history.js index 5218a81d..b3f16a38 100644 --- a/common/content/history.js +++ b/common/content/history.js @@ -60,7 +60,7 @@ var History = Module("history", { for (let item in iter(sh.SHistoryEnumerator, Ci.nsIHistoryEntry)) obj.push(update(Object.create(item), { index: obj.length, - icon: Class.memoize(function () services.favicon.getFaviconImageForPage(this.URI).spec) + icon: Class.Memoize(function () services.favicon.getFaviconImageForPage(this.URI).spec) })); return obj; }, diff --git a/common/content/mappings.js b/common/content/mappings.js index b1336161..1275d40c 100644 --- a/common/content/mappings.js +++ b/common/content/mappings.js @@ -45,10 +45,10 @@ var Map = Class("Map", { } }, - name: Class.memoize(function () this.names[0]), + name: Class.Memoize(function () this.names[0]), /** @property {[string]} All of this mapping's names (key sequences). */ - names: Class.memoize(function () this._keys.map(function (k) events.canonicalKeys(k))), + names: Class.Memoize(function () this._keys.map(function (k) events.canonicalKeys(k))), get toStringParams() [this.modes.map(function (m) m.name), this.names.map(String.quote)], @@ -293,7 +293,7 @@ var MapHive = Class("MapHive", Contexts.Hive, { delete this.states; }, - states: Class.memoize(function () { + states: Class.Memoize(function () { var states = { candidates: {}, mappings: {} @@ -329,7 +329,7 @@ var Mappings = Module("mappings", { expandLeader: function expandLeader(keyString) keyString.replace(//i, function () options["mapleader"]), - prefixes: Class.memoize(function () { + prefixes: Class.Memoize(function () { let list = Array.map("CASM", function (s) s + "-"); return iter(util.range(0, 1 << list.length)).map(function (mask) diff --git a/common/content/marks.js b/common/content/marks.js index 13355261..9ca6dd25 100644 --- a/common/content/marks.js +++ b/common/content/marks.js @@ -297,9 +297,9 @@ var Marks = Module("marks", { }, - isLocalMark: function isLocalMark(mark) /^[a-z`']$/.test(mark), + isLocalMark: bind("test", /^[a-z`']$/), - isURLMark: function isURLMark(mark) /^[A-Z]$/.test(mark) + isURLMark: bind("test", /^[A-Z]$/) }, { events: function () { let appContent = document.getElementById("appcontent"); diff --git a/common/content/modes.js b/common/content/modes.js index e3887b22..3edabf2e 100644 --- a/common/content/modes.js +++ b/common/content/modes.js @@ -508,12 +508,12 @@ var Modes = Module("modes", { description: Messages.Localized(""), - displayName: Class.memoize(function () this.name.split("_").map(util.capitalize).join(" ")), + displayName: Class.Memoize(function () this.name.split("_").map(util.capitalize).join(" ")), isinstance: function isinstance(obj) this.allBases.indexOf(obj) >= 0 || callable(obj) && this instanceof obj, - allBases: Class.memoize(function () { + allBases: Class.Memoize(function () { let seen = {}, res = [], queue = [this].concat(this.bases); for (let mode in array.iterValues(queue)) if (!Set.add(seen, mode)) { @@ -527,7 +527,7 @@ var Modes = Module("modes", { get count() !this.insert, - _display: Class.memoize(function _display() this.name.replace("_", " ", "g")), + _display: Class.Memoize(function _display() this.name.replace("_", " ", "g")), display: function display() this._display, @@ -535,15 +535,15 @@ var Modes = Module("modes", { hidden: false, - input: Class.memoize(function input() this.insert || this.bases.length && this.bases.some(function (b) b.input)), + input: Class.Memoize(function input() this.insert || this.bases.length && this.bases.some(function (b) b.input)), - insert: Class.memoize(function insert() this.bases.length && this.bases.some(function (b) b.insert)), + insert: Class.Memoize(function insert() this.bases.length && this.bases.some(function (b) b.insert)), - ownsFocus: Class.memoize(function ownsFocus() this.bases.length && this.bases.some(function (b) b.ownsFocus)), + ownsFocus: Class.Memoize(function ownsFocus() this.bases.length && this.bases.some(function (b) b.ownsFocus)), passEvent: function passEvent(event) this.input && event.charCode && !(event.ctrlKey || event.altKey || event.metaKey), - passUnknown: Class.memoize(function () options.get("passunknown").getKey(this.name)), + passUnknown: Class.Memoize(function () options.get("passunknown").getKey(this.name)), get mask() this, diff --git a/common/content/mow.js b/common/content/mow.js index 8fdd65c6..d2c769be 100644 --- a/common/content/mow.js +++ b/common/content/mow.js @@ -81,9 +81,9 @@ var MOW = Module("mow", { __noSuchMethod__: function (meth, args) Buffer[meth].apply(Buffer, [this.body].concat(args)), get widget() this.widgets.multilineOutput, - widgets: Class.memoize(function widgets() commandline.widgets), + widgets: Class.Memoize(function widgets() commandline.widgets), - body: Class.memoize(function body() this.widget.contentDocument.documentElement), + body: Class.Memoize(function body() this.widget.contentDocument.documentElement), get document() this.widget.contentDocument, get window() this.widget.contentWindow, diff --git a/common/content/quickmarks.js b/common/content/quickmarks.js index 74a34c72..23c8f6bd 100644 --- a/common/content/quickmarks.js +++ b/common/content/quickmarks.js @@ -99,9 +99,9 @@ var QuickMarks = Module("quickmarks", { */ list: function list(filter) { let marks = [k for ([k, v] in this._qmarks)]; - let lowercaseMarks = marks.filter(function (x) /[a-z]/.test(x)).sort(); - let uppercaseMarks = marks.filter(function (x) /[A-Z]/.test(x)).sort(); - let numberMarks = marks.filter(function (x) /[0-9]/.test(x)).sort(); + let lowercaseMarks = marks.filter(bind("test", /[a-z]/)).sort(); + let uppercaseMarks = marks.filter(bind("test", /[A-Z]/)).sort(); + let numberMarks = marks.filter(bind("test", /[0-9]/)).sort(); marks = Array.concat(lowercaseMarks, uppercaseMarks, numberMarks); diff --git a/common/content/tabs.js b/common/content/tabs.js index ddd22fd6..7ba354ba 100644 --- a/common/content/tabs.js +++ b/common/content/tabs.js @@ -64,7 +64,7 @@ var Tabs = Module("tabs", { _mappingCount: 0, - _alternates: Class.memoize(function () [config.tabbrowser.mCurrentTab, null]), + _alternates: Class.Memoize(function () [config.tabbrowser.mCurrentTab, null]), cleanup: function cleanup() { for (let [i, tab] in Iterator(this.allTabs)) { diff --git a/common/modules/addons.jsm b/common/modules/addons.jsm index 48f3fd1a..dfb59413 100644 --- a/common/modules/addons.jsm +++ b/common/modules/addons.jsm @@ -59,7 +59,6 @@ var updateAddons = Class("UpgradeListener", AddonListener, { }, onUpdateAvailable: function (addon, install) { - util.dump("onUpdateAvailable"); this.upgrade.push(addon); install.addListener(this); install.install(); @@ -278,7 +277,7 @@ var AddonList = Class("AddonList", { this.update(); }, - message: Class.memoize(function () { + message: Class.Memoize(function () { XML.ignoreWhitespace = true; util.xmlToDom( @@ -348,7 +347,7 @@ var AddonList = Class("AddonList", { }); var Addons = Module("addons", { - errors: Class.memoize(function () + errors: Class.Memoize(function () array(["ERROR_NETWORK_FAILURE", "ERROR_INCORRECT_HASH", "ERROR_CORRUPT_FILE", "ERROR_FILE_ACCESS"]) .map(function (e) [AddonManager[e], _("AddonManager." + e)]) @@ -539,7 +538,7 @@ else return ""; }, - installLocation: Class.memoize(function () services.extensionManager.getInstallLocation(this.id)), + installLocation: Class.Memoize(function () services.extensionManager.getInstallLocation(this.id)), getResourceURI: function getResourceURI(path) { let file = this.installLocation.getItemFile(this.id, path); return services.io.newFileURI(file); diff --git a/common/modules/base.jsm b/common/modules/base.jsm index adcde383..44e630ad 100644 --- a/common/modules/base.jsm +++ b/common/modules/base.jsm @@ -6,6 +6,7 @@ var { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components; +Cu.import("resource://dactyl/bootstrap.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); try { var ctypes; @@ -14,7 +15,8 @@ try { catch (e) {} let objproto = Object.prototype; -let { __lookupGetter__, __lookupSetter__, hasOwnProperty, propertyIsEnumerable } = objproto; +let { __lookupGetter__, __lookupSetter__, __defineGetter__, __defineSetter__, + hasOwnProperty, propertyIsEnumerable } = objproto; if (typeof XPCSafeJSObjectWrapper === "undefined") this.XPCSafeJSObjectWrapper = XPCNativeWrapper; @@ -44,15 +46,15 @@ if (!Object.defineProperty) } catch (e if e instanceof TypeError) {} else { - objproto.__defineGetter__.call(obj, prop, function () value); + __defineGetter__.call(obj, prop, function () value); if (desc.writable) - objproto.__defineSetter__.call(obj, prop, function (val) { value = val; }); + __defineSetter__.call(obj, prop, function (val) { value = val; }); } if ("get" in desc) - objproto.__defineGetter__.call(obj, prop, desc.get); + __defineGetter__.call(obj, prop, desc.get); if ("set" in desc) - objproto.__defineSetter__.call(obj, prop, desc.set); + __defineSetter__.call(obj, prop, desc.set); } catch (e) { throw e.stack ? e : Error(e); @@ -163,9 +165,8 @@ defineModule.dump = function dump_() { msg = util.objectToString(msg); return msg; }).join(", "); - let name = loaded.config ? config.name : "dactyl"; dump(String.replace(msg, /\n?$/, "\n") - .replace(/^./gm, name + ": $&")); + .replace(/^./gm, JSMLoader.name + ": $&")); } defineModule.modules = []; defineModule.time = function time(major, minor, func, self) { @@ -851,7 +852,7 @@ Class.extend = function extend(subclass, superclass, overrides) { * property's value. * @returns {Class.Property} */ -Class.Memoize = Class.memoize = function Memoize(getter, wait) +Class.Memoize = Class.Memoize = function Memoize(getter, wait) Class.Property({ configurable: true, enumerable: true, @@ -1265,7 +1266,7 @@ var Timer = Class("Timer", { } catch (e) { if (typeof util === "undefined") - dump("dactyl: " + e + "\n" + (e.stack || Error().stack)); + dump(JSMLoader.name + ": " + e + "\n" + (e.stack || Error().stack)); else util.reportError(e); } diff --git a/common/modules/bookmarkcache.jsm b/common/modules/bookmarkcache.jsm index e85a7c6c..f030e750 100644 --- a/common/modules/bookmarkcache.jsm +++ b/common/modules/bookmarkcache.jsm @@ -65,7 +65,7 @@ var BookmarkCache = Module("BookmarkCache", XPCOM(Ci.nsINavBookmarkObserver), { get bookmarks() Class.replaceProperty(this, "bookmarks", this.load()), - keywords: Class.memoize(function () array.toObject([[b.keyword, b] for (b in this) if (b.keyword)])), + keywords: Class.Memoize(function () array.toObject([[b.keyword, b] for (b in this) if (b.keyword)])), rootFolders: ["toolbarFolder", "bookmarksMenuFolder", "unfiledBookmarksFolder"] .map(function (s) services.bookmarks[s]), diff --git a/common/modules/bootstrap.jsm b/common/modules/bootstrap.jsm index ca5eaa15..006c7933 100644 --- a/common/modules/bootstrap.jsm +++ b/common/modules/bootstrap.jsm @@ -26,6 +26,8 @@ else factories: [], + name: "dactyl", + global: this, globals: JSMLoader ? JSMLoader.globals : {}, diff --git a/common/modules/commands.jsm b/common/modules/commands.jsm index a837ef33..ecc55582 100644 --- a/common/modules/commands.jsm +++ b/common/modules/commands.jsm @@ -215,7 +215,7 @@ var Command = Class("Command", { extra: extra }), - complained: Class.memoize(function () ({})), + complained: Class.Memoize(function () ({})), /** * @property {[string]} All of this command's name specs. e.g., "com[mand]" @@ -286,7 +286,7 @@ var Command = Class("Command", { */ options: [], - optionMap: Class.memoize(function () array(this.options) + optionMap: Class.Memoize(function () array(this.options) .map(function (opt) opt.names.map(function (name) [name, opt])) .flatten().toObject()), @@ -297,19 +297,19 @@ var Command = Class("Command", { return res; }, - argsPrototype: Class.memoize(function argsPrototype() { + argsPrototype: Class.Memoize(function argsPrototype() { let res = update([], { __iterator__: function AP__iterator__() array.iterItems(this), command: this, - explicitOpts: Class.memoize(function () ({})), + explicitOpts: Class.Memoize(function () ({})), has: function AP_has(opt) Set.has(this.explicitOpts, opt) || typeof opt === "number" && Set.has(this, opt), get literalArg() this.command.literal != null && this[this.command.literal] || "", - // TODO: string: Class.memoize(function () { ... }), + // TODO: string: Class.Memoize(function () { ... }), verify: function verify() { if (this.command.argCount) { @@ -1175,9 +1175,9 @@ var Commands = Module("commands", { ]]>, /U/g, "\\u"), "x") }), - validName: Class.memoize(function validName() util.regexp("^" + this.nameRegexp.source + "$")), + validName: Class.Memoize(function validName() util.regexp("^" + this.nameRegexp.source + "$")), - commandRegexp: Class.memoize(function commandRegexp() util.regexp( (?P [:\s]*) @@ -1514,7 +1514,7 @@ var Commands = Module("commands", { ["+", "One or more arguments are allowed"]], default: "0", type: CommandOption.STRING, - validator: function (arg) /^[01*?+]$/.test(arg) + validator: bind("test", /^[01*?+]$/) }, { names: ["-nopersist", "-n"], diff --git a/common/modules/completion.jsm b/common/modules/completion.jsm index 72147810..6155b423 100644 --- a/common/modules/completion.jsm +++ b/common/modules/completion.jsm @@ -405,7 +405,7 @@ var CompletionContext = Class("CompletionContext", { this.noUpdate = false; }, - ignoreCase: Class.memoize(function () { + ignoreCase: Class.Memoize(function () { let mode = this.wildcase; if (mode == "match") return false; diff --git a/common/modules/config.jsm b/common/modules/config.jsm index 048cafc9..313b17fa 100644 --- a/common/modules/config.jsm +++ b/common/modules/config.jsm @@ -10,7 +10,7 @@ let global = this; Components.utils.import("resource://dactyl/bootstrap.jsm"); defineModule("config", { exports: ["ConfigBase", "Config", "config"], - require: ["protocol", "services", "storage", "util", "template"], + require: ["dom", "protocol", "services", "storage", "util", "template"], use: ["io", "messages", "prefs", "styles"] }, this); @@ -67,6 +67,7 @@ var ConfigBase = Class("ConfigBase", { "completion", "config", "contexts", + "dom", "downloads", "finder", "help", @@ -130,7 +131,7 @@ var ConfigBase = Class("ConfigBase", { elem.style.cssText = this.cssText; let keys = iter(Styles.propertyIter(this.cssText)).map(function (p) p.name).toArray(); - let bg = keys.some(function (k) /^background/.test(k)); + let bg = keys.some(bind("test", /^background/)); let fg = keys.indexOf("color") >= 0; let style = DOM(elem).style; @@ -141,7 +142,7 @@ var ConfigBase = Class("ConfigBase", { get addonID() this.name + "@dactyl.googlecode.com", - addon: Class.memoize(function () { + addon: Class.Memoize(function () { return (JSMLoader.bootstrap || {}).addon || require("addons").AddonManager.getAddonByID(this.addonID); }), @@ -149,17 +150,17 @@ var ConfigBase = Class("ConfigBase", { /** * The current application locale. */ - appLocale: Class.memoize(function () services.chromeRegistry.getSelectedLocale("global")), + appLocale: Class.Memoize(function () services.chromeRegistry.getSelectedLocale("global")), /** * The current dactyl locale. */ - locale: Class.memoize(function () this.bestLocale(this.locales)), + locale: Class.Memoize(function () this.bestLocale(this.locales)), /** * The current application locale. */ - locales: Class.memoize(function () { + locales: Class.Memoize(function () { // TODO: Merge with completion.file code. function getDir(str) str.match(/^(?:.*[\/\\])?/)[0]; @@ -283,7 +284,7 @@ var ConfigBase = Class("ConfigBase", { * directory if the application is running from one via an extension * proxy file. */ - VCSPath: Class.memoize(function () { + VCSPath: Class.Memoize(function () { if (/pre$/.test(this.addon.version)) { let uri = util.newURI(this.addon.getResourceURI("").spec + "../.hg"); if (uri instanceof Ci.nsIFileURL && @@ -299,14 +300,14 @@ var ConfigBase = Class("ConfigBase", { * running from if using an extension proxy file or was built from if * installed as an XPI. */ - branch: Class.memoize(function () { + branch: Class.Memoize(function () { if (this.VCSPath) return io.system(["hg", "-R", this.VCSPath, "branch"]).output; return (/pre-hg\d+-(\S*)/.exec(this.version) || [])[1]; }), /** @property {string} The Dactyl version string. */ - version: Class.memoize(function () { + version: Class.Memoize(function () { if (this.VCSPath) return io.system(["hg", "-R", this.VCSPath, "log", "-r.", "--template=hg{rev}-{branch}"]).output; @@ -314,7 +315,7 @@ var ConfigBase = Class("ConfigBase", { return this.addon.version; }), - buildDate: Class.memoize(function () { + buildDate: Class.Memoize(function () { if (this.VCSPath) return io.system(["hg", "-R", this.VCSPath, "log", "-r.", "--template={date|isodate}"]).output; @@ -324,7 +325,7 @@ var ConfigBase = Class("ConfigBase", { get fileExt() this.name.slice(0, -6), - dtd: Class.memoize(function () + dtd: Class.Memoize(function () iter(this.dtdExtra, (["dactyl." + k, v] for ([k, v] in iter(config.dtdDactyl))), (["dactyl." + s, config[s]] for each (s in config.dtdStrings))) @@ -339,10 +340,10 @@ var ConfigBase = Class("ConfigBase", { get plugins() "http://dactyl.sf.net/" + this.name + "/plugins", get faq() this.home + this.name + "/faq", - "list.mailto": Class.memoize(function () config.name + "@googlegroups.com"), - "list.href": Class.memoize(function () "http://groups.google.com/group/" + config.name), + "list.mailto": Class.Memoize(function () config.name + "@googlegroups.com"), + "list.href": Class.Memoize(function () "http://groups.google.com/group/" + config.name), - "hg.latest": Class.memoize(function () this.code + "source/browse/"), // XXX + "hg.latest": Class.Memoize(function () this.code + "source/browse/"), // XXX "irc": "irc://irc.oftc.net/#pentadactyl", }), @@ -397,8 +398,8 @@ var ConfigBase = Class("ConfigBase", { util.overlayWindow(window, { append: append.elements() }); }, - browser: Class.memoize(function () window.gBrowser), - tabbrowser: Class.memoize(function () window.gBrowser), + browser: Class.Memoize(function () window.gBrowser), + tabbrowser: Class.Memoize(function () window.gBrowser), get browserModes() [modules.modes.NORMAL], @@ -413,7 +414,7 @@ var ConfigBase = Class("ConfigBase", { */ get outputHeight() this.browser.mPanelContainer.boxObject.height, - tabStrip: Class.memoize(function () window.document.getElementById("TabsToolbar") || this.tabbrowser.mTabContainer), + tabStrip: Class.Memoize(function () window.document.getElementById("TabsToolbar") || this.tabbrowser.mTabContainer), }), /** diff --git a/common/modules/contexts.jsm b/common/modules/contexts.jsm index b41bb9fc..c71b7ee8 100644 --- a/common/modules/contexts.jsm +++ b/common/modules/contexts.jsm @@ -75,7 +75,7 @@ var Group = Class("Group", { }); }, - defaultFilter: Class.memoize(function () this.compileFilter(["*"])) + defaultFilter: Class.Memoize(function () this.compileFilter(["*"])) }); var Contexts = Module("contexts", { @@ -283,9 +283,9 @@ var Contexts = Module("contexts", { return frame; }, - groups: Class.memoize(function () this.matchingGroups()), + groups: Class.Memoize(function () this.matchingGroups()), - allGroups: Class.memoize(function () Object.create(this.groupsProto, { + allGroups: Class.Memoize(function () Object.create(this.groupsProto, { groups: { value: this.initializedGroups() } })), @@ -486,7 +486,7 @@ var Contexts = Module("contexts", { get persist() this.group.persist, set persist(val) this.group.persist = val, - prefix: Class.memoize(function () this.name === "builtin" ? "" : this.name + ":"), + prefix: Class.Memoize(function () this.name === "builtin" ? "" : this.name + ":"), get toStringParams() [this.name] }) diff --git a/common/modules/dom.jsm b/common/modules/dom.jsm new file mode 100644 index 00000000..9193dd34 --- /dev/null +++ b/common/modules/dom.jsm @@ -0,0 +1,1079 @@ +// Copyright (c) 2007-2011 by Doug Kearns +// Copyright (c) 2008-2011 by Kris Maglione +// +// This work is licensed for reuse under an MIT license. Details are +// given in the LICENSE.txt file included with this file. +"use strict"; + +Components.utils.import("resource://dactyl/bootstrap.jsm"); +defineModule("dom", { + exports: ["$", "DOM", "NS", "XBL", "XHTML", "XUL"], + use: ["config", "highlight", "template", "util"], +}, this); + +var XBL = Namespace("xbl", "http://www.mozilla.org/xbl"); +var XHTML = Namespace("html", "http://www.w3.org/1999/xhtml"); +var XUL = Namespace("xul", "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); +var NS = Namespace("dactyl", "http://vimperator.org/namespaces/liberator"); +default xml namespace = XHTML; + +/** + * @class + * + * A jQuery-inspired DOM utility framework. + * + * Please note that while this currently implements an Array-like + * interface, this is *not a defined interface* and is very likely to + * change in the near future. + */ +var DOM = Class("DOM", { + init: function init(val, context) { + let self; + let length = 0; + + if (context instanceof Ci.nsIDOMDocument) + this.document = context; + + if (typeof val == "string") + val = context.querySelectorAll(val); + + if (val == null) + ; + else if (typeof val == "xml") + this[length++] = DOM.fromXML(val, context, this.nodes); + else if (val instanceof Ci.nsIDOMNode || val instanceof Ci.nsIDOMWindow) + this[length++] = val; + else if ("length" in val) + for (let i = 0; i < val.length; i++) + this[length++] = val[i]; + else if ("__iterator__" in val) + for (let elem in val) + this[length++] = elem; + + this.length = length; + return self || this; + }, + + __iterator__: function __iterator__() { + for (let i = 0; i < this.length; i++) + yield this[i]; + }, + + Empty: function Empty() this.constructor(null, this.document), + + nodes: Class.Memoize(function () ({})), + + get items() { + for (let i = 0; i < this.length; i++) + yield this.eq(i); + }, + + get document() this._document || this[0].ownerDocument || this[0].document || this[0], + set document(val) this._document = val, + + attrHooks: array.toObject([ + ["", { + href: { get: function (elem) elem.href || elem.getAttribute("href") }, + src: { get: function (elem) elem.src || elem.getAttribute("src") } + }] + ]), + + matcher: function matcher(sel) { + let res; + + if (/^([a-z0-9_-]+)$/i.exec(sel)) + res = function (elem) elem.localName == val; + else if (/^#([a-z0-9:_-]+)$/i.exec(sel)) + res = function (elem) elem.id == val; + else if (/^\.([a-z0-9:_-]+)$/i.exec(sel)) + res = function (elem) elem.classList.contains(val); + else if (/^\[([a-z0-9:_-]+)\]$/i.exec(sel)) + res = function (elem) elem.hasAttribute(val); + else + res = function (elem) ~Array.indexOf(elem.parentNode.querySelectorAll(sel), + elem); + + let val = RegExp.$1; + return res; + }, + + each: function each(fn, self) { + let obj = self || this.Empty(); + for (let i = 0; i < this.length; i++) + fn.call(self || update(obj, [this[i]]), this[i], i); + return this; + }, + + eachDOM: function eachDOM(val, fn, self) { + if (typeof val == "xml") + return this.each(function (elem, i) { + fn.call(this, DOM.fromXML(val, elem.ownerDocument), elem, i); + }, self || this); + + let dom = this; + function munge(val) { + if (typeof val == "xml") + val = dom.constructor(val, dom.document); + + if (isObject(val) && "length" in val) { + let frag = dom.document.createDocumentFragment(); + for (let i = 0; i < val.length; i++) + frag.appendChild(val[i]); + return frag; + } + return val; + } + + if (callable(val)) + return this.each(function (elem, i) { + util.withProperErrors(fn, this, munge(val.call(this, elem, i)), elem, i); + }, self || this); + + util.withProperErrors(fn, self || this, munge(val), this[0], 0); + return this; + }, + + eq: function eq(idx) { + return this.constructor(this[idx >= 0 ? idx : this.length + idx]); + }, + + find: function find(val) { + return this.map(function (elem) elem.querySelectorAll(val)); + }, + + filter: function filter(val, self) { + let res = this.Empty(); + + if (!callable(val)) + val = this.matcher(val); + + this.constructor(Array.filter(this, val, self || this)); + for (let i = 0; i < this.length; i++) + if (val.call(self, this[i], i)) + res[res.length++] = this[i]; + + return res; + }, + + is: function is(val) { + return this.some(this.matcher(val)); + }, + + reverse: function reverse() { + Array.reverse(this); + return this; + }, + + all: function all(fn, self) { + let res = this.Empty(); + + this.each(function (elem) { + while(true) { + elem = fn.call(this, elem) + if (elem instanceof Ci.nsIDOMElement) + res[res.length++] = elem; + else if (elem && "length" in elem) + for (let i = 0; i < tmp.length; i++) + res[res.length++] = tmp[j]; + else + break; + } + }, self || this); + return res; + }, + + map: function map(fn, self) { + let res = this.Empty(); + let obj = self || this.Empty(); + + for (let i = 0; i < this.length; i++) { + let tmp = fn.call(self || update(obj, [this[i]]), this[i], i); + if (isObject(tmp) && "length" in tmp) + for (let j = 0; j < tmp.length; j++) + res[res.length++] = tmp[j]; + else if (tmp !== undefined) + res[res.length++] = tmp; + } + + return res; + }, + + slice: function eq(start, end) { + return this.constructor(Array.slice(this, start, end)); + }, + + some: function some(fn, self) { + for (let i = 0; i < this.length; i++) + if (fn.call(self || this, this[i], i)) + return true; + return false; + }, + + get parent() this.map(function (elem) elem.parentNode, this), + + get offsetParent() this.map(function (elem) { + do { + var parent = elem.offsetParent; + if (parent instanceof Ci.nsIDOMElement && DOM(parent).position != "static") + return parent; + } + while (parent); + }, this), + + get ancestors() this.all(function (elem) elem.parentNode), + + get children() this.map(function (elem) Array.filter(elem.childNodes, + function (e) e instanceof Ci.nsIDOMElement), + this), + + get contents() this.map(function (elem) elem.childNodes, this), + + get siblings() this.map(function (elem) Array.filter(elem.parentNode.childNodes, + function (e) e != elem && e instanceof Ci.nsIDOMElement), + this), + + get siblingsBefore() this.all(function (elem) elem.previousElementSibling), + get siblingsAfter() this.all(function (elem) elem.nextElementSibling), + + get class() let (self = this) ({ + toString: function () self[0].className, + + get list() Array.slice(self[0].classList), + set list(val) self.attr("class", val.join(" ")), + + each: function each(meth, arg) { + return self.each(function (elem) { + elem.classList[meth](arg); + }) + }, + + add: function add(cls) this.each("add", cls), + remove: function remove(cls) this.each("remove", cls), + toggle: function toggle(cls) this.each("toggle", cls), + + has: function has(cls) this[0].classList.has(cls) + }), + + get highlight() let (self = this) ({ + toString: function () self.attrNS(NS, "highlight") || "", + + get list() this.toString().trim().split(/\s+/), + set list(val) self.attrNS(NS, "highlight", val.join(" ")), + + has: function has(hl) ~this.list.indexOf(hl), + + add: function add(hl) self.each(function () { + highlight.loaded[hl] = true; + this.attrNS(NS, "highlight", + array.uniq(this.highlight.list.concat(hl)).join(" ")); + }), + + remove: function remove(hl) self.each(function () { + this.attrNS(NS, "highlight", + this.highlight.list.filter(function (h) h != hl)); + }), + + toggle: function toggle(hl) self.each(function () { + let { highlight } = this; + highlight[highlight.has(hl) ? "remove" : "add"](hl) + }), + }), + + get rect() this[0].getBoundingClientRect(), + + get viewport() { + let r = this.rect; + return { + width: this[0].clientWidth, + height: this[0].clientHeight, + top: r.top + this[0].clientTop, + get bottom() this.top + this.height, + left: r.left + this[0].clientLeft, + get right() this.left + this.width + } + }, + + /** + * Returns true if the given DOM node is currently visible. + * @returns {boolean} + */ + get isVisible() { + let style = this.style; + return style.visibility == "visible" && style.display != "none"; + }, + + get editor() { + this[0] instanceof Ci.nsIDOMNSEditableElement; + if (this[0].editor instanceof Ci.nsIEditor) + return this[0].editor; + + try { + return this[0].QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIEditingSession) + .getEditorForWindow(this[0]); + } + catch (e) {} + + return null; + }, + + get isEditable() !!this.editor, + + get isInput() this[0] instanceof Ci.nsIDOMHTMLInputElement && this.isEditable, + + /** + * Returns an object representing a Node's computed CSS style. + * @returns {Object} + */ + get style() { + let node = this[0]; + while (node && !(node instanceof Ci.nsIDOMElement) && node.parentNode) + node = node.parentNode; + + try { + var res = node.ownerDocument.defaultView.getComputedStyle(node, null); + } + catch (e) {} + + if (res == null) { + util.dumpStack(_("error.nullComputedStyle", node)); + Cu.reportError(Error(_("error.nullComputedStyle", node))); + return {}; + } + return res; + }, + + /** + * Parses the fields of a form and returns a URL/POST-data pair + * that is the equivalent of submitting the form. + * + * @returns {object} An object with the following elements: + * url: The URL the form points to. + * postData: A string containing URL-encoded post data, if this + * form is to be POSTed + * charset: The character set of the GET or POST data. + * elements: The key=value pairs used to generate query information. + */ + // Nuances gleaned from browser.jar/content/browser/browser.js + get formData() { + function encode(name, value, param) { + param = param ? "%s" : ""; + if (post) + return name + "=" + encodeComponent(value + param); + return encodeComponent(name) + "=" + encodeComponent(value) + param; + } + + let field = this[0]; + let form = field.form; + let doc = form.ownerDocument; + + let charset = doc.characterSet; + let converter = services.CharsetConv(charset); + for each (let cs in form.acceptCharset.split(/\s*,\s*|\s+/)) { + let c = services.CharsetConv(cs); + if (c) { + converter = services.CharsetConv(cs); + charset = cs; + } + } + + let uri = util.newURI(doc.baseURI.replace(/\?.*/, ""), charset); + let url = util.newURI(form.action, charset, uri).spec; + + let post = form.method.toUpperCase() == "POST"; + + let encodeComponent = encodeURIComponent; + if (charset !== "UTF-8") + encodeComponent = function encodeComponent(str) + escape(converter.ConvertFromUnicode(str) + converter.Finish()); + + let elems = []; + if (field instanceof Ci.nsIDOMHTMLInputElement && field.type == "submit") + elems.push(encode(field.name, field.value)); + + for (let [, elem] in iter(form.elements)) + if (elem.name && !elem.disabled) { + if (DOM(elem).isInput + || /^(?:hidden|textarea)$/.test(elem.type) + || elem.type == "submit" && elem == field + || elem.checked && /^(?:checkbox|radio)$/.test(elem.type)) + elems.push(encode(elem.name, elem.value, elem === field)); + else if (elem instanceof Ci.nsIDOMHTMLSelectElement) { + for (let [, opt] in Iterator(elem.options)) + if (opt.selected) + elems.push(encode(elem.name, opt.value)); + } + } + + if (post) + return { url: url, postData: elems.join('&'), charset: charset, elements: elems }; + return { url: url + "?" + elems.join('&'), postData: null, charset: charset, elements: elems }; + }, + + /** + * Generates an XPath expression for the given element. + * + * @returns {string} + */ + get xpath() { + function quote(val) "'" + val.replace(/[\\']/g, "\\$&") + "'"; + + let res = []; + let doc = this.document; + for (let elem = this[0];; elem = elem.parentNode) { + if (!(elem instanceof Ci.nsIDOMElement)) + res.push(""); + else if (elem.id) + res.push("id(" + quote(elem.id) + ")"); + else { + let name = elem.localName; + if (elem.namespaceURI && (elem.namespaceURI != XHTML || doc.xmlVersion)) + if (elem.namespaceURI in DOM.namespaceNames) + name = DOM.namespaceNames[elem.namespaceURI] + ":" + name; + else + name = "*[local-name()=" + quote(name) + " and namespace-uri()=" + quote(elem.namespaceURI) + "]"; + + res.push(name + "[" + (1 + iter(DOM.XPath("./" + name, elem.parentNode)).indexOf(elem)) + "]"); + continue; + } + break; + } + + return res.reverse().join("/"); + }, + + /** + * Returns a string or XML representation of this node. + * + * @param {boolean} color If true, return a colored, XML + * representation of this node. + */ + repr: function repr(color) { + function namespaced(node) { + var ns = DOM.namespaceNames[node.namespaceURI] || /^(?:(.*?):)?/.exec(node.name)[0]; + if (!ns) + return node.localName; + if (color) + return <>{ns}{node.localName} + return ns + ":" + node.localName; + } + + let res = []; + this.each(function (elem) { + try { + let hasChildren = elem.firstChild && (!/^\s*$/.test(elem.firstChild) || elem.firstChild.nextSibling) + if (color) + res.push(<{ + namespaced(elem)} { + template.map(array.iterValues(elem.attributes), + function (attr) + {namespaced(attr)} + + {attr.value}, + <> ) + }{ !hasChildren ? "/>" : ">" + }{ !hasChildren ? "" : <>... + + <{namespaced(elem)}> + }); + else { + let tag = "<" + [namespaced(elem)].concat( + [namespaced(a) + "=" + template.highlight(a.value, true) + for ([i, a] in array.iterItems(elem.attributes))]).join(" "); + + res.push(tag + (!hasChildren ? "/>" : ">...")); + } + } + catch (e) { + res.push({}.toString.call(elem)); + } + }, this); + return template.map(res, util.identity, <>,); + }, + + attr: function attr(key, val) { + return this.attrNS("", key, val); + }, + + attrNS: function attrNS(ns, key, val) { + if (val !== undefined) + key = array.toObject([[key, val]]); + + let hooks = this.attrHooks[ns] || {}; + + if (isObject(key)) + return this.each(function (elem) { + for (let [k, v] in Iterator(key)) + if (Set.has(hooks, k) && hooks[k].set) + hooks[k].set.call(this, elem, v); + else if (v == null) + elem.removeAttributeNS(ns, k); + else + elem.setAttributeNS(ns, k, v); + }); + + if (Set.has(hooks, key) && hooks[key].get) + return hooks[key].get.call(this, this[0]); + + if (!this[0].hasAttributeNS(ns, key)) + return null; + + return this[0].getAttributeNS(ns, key); + }, + + css: update(function css(key, val) { + if (val !== undefined) + key = array.toObject([[key, val]]); + + if (isObject(key)) + return this.each(function (elem) { + for (let [k, v] in Iterator(key)) + elem.style[css.property(k)] = v; + }); + + return this[0].style[css.property(key)]; + }, { + name: function (property) property.replace(/[A-Z]/g, function (m0) "-" + m0.toLowerCase()), + + property: function (name) name.replace(/-(.)/g, function (m0, m1) m1.toUpperCase()) + }), + + append: function append(val) { + return this.eachDOM(val, function (elem, target) { + target.appendChild(elem); + }); + }, + + prepend: function prepend(val) { + return this.eachDOM(val, function (elem, target) { + target.insertBefore(elem, target.firstChild); + }); + }, + + before: function before(val) { + return this.eachDOM(val, function (elem, target) { + target.parentNode.insertBefore(elem, target); + }); + }, + + after: function after(val) { + return this.eachDOM(val, function (elem, target) { + target.parentNode.insertBefore(elem, target.nextSibling); + }); + }, + + appendTo: function appendTo(elem) { + if (!(elem instanceof this.constructor)) + elem = this.constructor(elem, this.document); + elem.append(this); + return this; + }, + + prependTo: function appendTo(elem) { + if (!(elem instanceof this.constructor)) + elem = this.constructor(elem, this.document); + elem.prepend(this); + return this; + }, + + insertBefore: function insertBefore(elem) { + if (!(elem instanceof this.constructor)) + elem = this.constructor(elem, this.document); + elem.before(this); + return this; + }, + + insertAfter: function insertAfter(elem) { + if (!(elem instanceof this.constructor)) + elem = this.constructor(elem, this.document); + elem.after(this); + return this; + }, + + remove: function remove() { + return this.each(function (elem) { + if (elem.parentNode) + elem.parentNode.removeChild(elem); + }, this); + }, + + empty: function empty() { + return this.each(function (elem) { + while (elem.firstChild) + elem.removeChild(elem.firstChild); + }, this); + }, + + toggle: function toggle(val, self) { + if (callable(val)) + return this.each(function (elem, i) { + this[val.call(self || this, elem, i) ? "show" : "hide"](); + }); + + if (arguments.length) + return this[val ? "show" : "hide"](); + + let hidden = this.map(function (elem) elem.style.display == "none"); + return this.each(function (elem, i) { + this[hidden[i] ? "show" : "hide"](); + }); + }, + hide: function hide() { + return this.each(function (elem) { elem.style.display = "none"; }, this); + }, + show: function show() { + for (let i = 0; i < this.length; i++) + if (!this[i].dactylDefaultDisplay && this[i].style.display) + this[i].style.display = ""; + + this.each(function (elem) { + if (!elem.dactylDefaultDisplay) + elem.dactylDefaultDisplay = this.style.display; + }); + + return this.each(function (elem) { + elem.style.display = elem.dactylDefaultDisplay == "none" ? "block" : ""; + }, this); + }, + + getSet: function getSet(args, get, set) { + if (!args.length) + return get.call(this, this[0]); + + let [fn, self] = args; + if (!callable(fn)) + fn = function () args[0]; + + return this.each(function (elem, i) { + set.call(this, elem, fn.call(self || this, elem, i)); + }, this); + }, + + html: function html(txt, self) { + return this.getSet(arguments, + function (elem) elem.innerHTML, + function (elem, val) { elem.innerHTML = val }); + }, + + text: function text(txt, self) { + return this.getSet(arguments, + function (elem) elem.textContent, + function (elem, val) { elem.textContent = val }); + }, + + val: function val(txt) { + return this.getSet(arguments, + function (elem) elem.value, + function (elem, val) { elem.value = val }); + }, + + listen: function listen(event, listener, capture) { + if (isObject(event)) + capture = listener; + else + event = array.toObject([[event, listener]]); + + for (let [k, v] in Iterator(event)) + event[k] = util.wrapCallback(v); + + return this.each(function (elem) { + for (let [k, v] in Iterator(event)) + elem.addEventListener(k, v, capture); + }); + }, + unlisten: function unlisten(event, listener, capture) { + if (isObject(event)) + capture = listener; + else + event = array.toObject([[key, val]]); + + return this.each(function (elem) { + for (let [k, v] in Iterator(event)) + elem.removeEventListener(k, v.wrapper || v, capture); + }); + }, + + dispatch: function dispatch(event, params, extraProps) { + this.canceled = false; + return this.each(function (elem) { + let evt = DOM.Event(this.document, event, params); + if (!DOM.Event.dispatch(elem, evt, extraProps)) + this.canceled = true; + }, this); + }, + + focus: function focus(arg, extra) { + if (callable(arg)) + return this.listen("focus", arg, extra); + services.focus.setFocus(this[0], extra || services.focus.FLAG_BYMOUSE); + return this; + }, + blur: function blur(arg, extra) { + if (callable(arg)) + return this.listen("blur", arg, extra); + return this.each(function (elem) { elem.blur(); }, this); + }, + + /** + * Scrolls an element into view if and only if it's not already + * fully visible. + */ + scrollIntoView: function scrollIntoView(alignWithTop) { + return this.each(function (elem) { + let rect = this.rect; + + let force = false; + if (rect) + for (let parent in this.ancestors.items) { + let isect = util.intersection(rect, parent.viewport); + force = isect.width != rect.width || isect.height != rect.height; + if (force) + break; + } + + let win = this.document.defaultView; + + if (force || !(rect && rect.bottom <= win.innerHeight && rect.top >= 0 && rect.left < win.innerWidth && rect.right > 0)) + elem.scrollIntoView(arguments.length ? alignWithTop + : Math.abs(rect.top) < Math.abs(win.innerHeight - rect.bottom)); + }); + }, +}, { + /** + * Creates an actual event from a pseudo-event object. + * + * The pseudo-event object (such as may be retrieved from events.fromString) + * should have any properties you want the event to have. + * + * @param {Document} doc The DOM document to associate this event with + * @param {Type} type The type of event (keypress, click, etc.) + * @param {Object} opts The pseudo-event. @optional + */ + Event: Class("Event", { + init: function Event(doc, type, opts) { + const DEFAULTS = { + HTML: { + type: type, bubbles: true, cancelable: false + }, + Key: { + type: type, + bubbles: true, cancelable: true, + view: doc.defaultView, + ctrlKey: false, altKey: false, shiftKey: false, metaKey: false, + keyCode: 0, charCode: 0 + }, + Mouse: { + type: type, + bubbles: true, cancelable: true, + view: doc.defaultView, + detail: 1, + screenX: 0, screenY: 0, + clientX: 0, clientY: 0, + ctrlKey: false, altKey: false, shiftKey: false, metaKey: false, + button: 0, + relatedTarget: null + } + }; + + opts = opts || {}; + var t = this.constructor.types[type]; + var evt = doc.createEvent((t || "HTML") + "Events"); + + let defaults = DEFAULTS[t || "HTML"]; + update(defaults, this.constructor.defaults[type]); + + let args = Object.keys(defaults) + .map(function (k) k in opts ? opts[k] : defaults[k]) + + evt["init" + t + "Event"].apply(evt, args); + return evt; + } + }, { + defaults: { + load: { bubbles: false }, + submit: { cancelable: true } + }, + + types: Class.Memoize(function () iter( + { + Mouse: "click mousedown mouseout mouseover mouseup", + Key: "keydown keypress keyup", + "": "change dactyl-input input submit " + + "load unload pageshow pagehide DOMContentLoaded" + } + ).map(function ([k, v]) v.split(" ").map(function (v) [v, k])) + .flatten() + .toObject()), + + /** + * Dispatches an event to an element as if it were a native event. + * + * @param {Node} target The DOM node to which to dispatch the event. + * @param {Event} event The event to dispatch. + */ + dispatch: Class.Memoize(function () + config.haveGecko("2b") + ? function dispatch(target, event, extra) { + try { + this.feedingEvent = extra; + + if (target instanceof Ci.nsIDOMElement) + // This causes a crash on Gecko<2.0, it seems. + return (target.ownerDocument || target.document || target).defaultView + .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils) + .dispatchDOMEventViaPresShell(target, event, true); + else { + target.dispatchEvent(event); + return !event.getPreventDefault(); + } + } + catch (e) { + util.reportError(e); + } + finally { + this.feedingEvent = null; + } + } + : function dispatch(target, event, extra) { + try { + this.feedingEvent = extra; + target.dispatchEvent(update(event, extra)); + } + finally { + this.feedingEvent = null; + } + }) + }), + + /** + * The set of input element type attribute values that mark the element as + * an editable field. + */ + editableInputs: Set(["date", "datetime", "datetime-local", "email", "file", + "month", "number", "password", "range", "search", + "tel", "text", "time", "url", "week"]), + + /** + * Converts a given DOM Node, Range, or Selection to a string. If + * *html* is true, the output is HTML, otherwise it is presentation + * text. + * + * @param {nsIDOMNode | nsIDOMRange | nsISelection} node The node to + * stringify. + * @param {boolean} html Whether the output should be HTML rather + * than presentation text. + */ + stringify: function stringify(node, html) { + if (node instanceof Ci.nsISelection && node.isCollapsed) + return ""; + + if (node instanceof Ci.nsIDOMNode) { + let range = node.ownerDocument.createRange(); + range.selectNode(node); + node = range; + } + let doc = (node.getRangeAt ? node.getRangeAt(0) : node).startContainer; + doc = doc.ownerDocument || doc; + + let encoder = services.HtmlEncoder(); + encoder.init(doc, "text/unicode", encoder.OutputRaw|encoder.OutputPreformatted); + if (node instanceof Ci.nsISelection) + encoder.setSelection(node); + else if (node instanceof Ci.nsIDOMRange) + encoder.setRange(node); + + let str = services.String(encoder.encodeToString()); + if (html) + return str.data; + + let [result, length] = [{}, {}]; + services.HtmlConverter().convert("text/html", str, str.data.length*2, "text/unicode", result, length); + return result.value.QueryInterface(Ci.nsISupportsString).data; + }, + + /** + * Compiles a CSS spec and XPath pattern matcher based on the given + * list. List elements prefixed with "xpath:" are parsed as XPath + * patterns, while other elements are parsed as CSS specs. The + * returned function will, given a node, return an iterator of all + * descendants of that node which match the given specs. + * + * @param {[string]} list The list of patterns to match. + * @returns {function(Node)} + */ + compileMatcher: function compileMatcher(list) { + let xpath = [], css = []; + for (let elem in values(list)) + if (/^xpath:/.test(elem)) + xpath.push(elem.substr(6)); + else + css.push(elem); + + return update( + function matcher(node) { + if (matcher.xpath) + for (let elem in DOM.XPath(matcher.xpath, node)) + yield elem; + + if (matcher.css) + for (let [, elem] in iter(node.querySelectorAll(matcher.css))) + yield elem; + }, { + css: css.join(", "), + xpath: xpath.join(" | ") + }); + }, + + /** + * Validates a list as input for {@link #compileMatcher}. Returns + * true if and only if every element of the list is a valid XPath or + * CSS selector. + * + * @param {[string]} list The list of patterns to test + * @returns {boolean} True when the patterns are all valid. + */ + validateMatcher: function validateMatcher(list) { + let evaluator = services.XPathEvaluator(); + let node = services.XMLDocument(); + return this.testValues(list, function (value) { + if (/^xpath:/.test(value)) + evaluator.createExpression(value.substr(6), DOM.XPath.resolver); + else + node.querySelector(value); + return true; + }); + }, + + /** + * Converts HTML special characters in *str* to the equivalent HTML + * entities. + * + * @param {string} str + * @returns {string} + */ + escapeHTML: function escapeHTML(str) { + let map = { "'": "'", '"': """, "%": "%", "&": "&", "<": "<", ">": ">" }; + return str.replace(/['"&<>]/g, function (m) map[m]); + }, + + /** + * Converts an E4X XML literal to a DOM node. Any attribute named + * highlight is present, it is transformed into dactyl:highlight, + * and the named highlight groups are guaranteed to be loaded. + * + * @param {Node} node + * @param {Document} doc + * @param {Object} nodes If present, nodes with the "key" attribute are + * stored here, keyed to the value thereof. + * @returns {Node} + */ + fromXML: function fromXML(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) + domnode.appendChild(fromXML(child, doc, nodes)); + return domnode; + } + + switch (node.nodeKind()) { + case "text": + return doc.createTextNode(String(node)); + case "element": + let domnode = doc.createElementNS(node.namespace(), node.localName()); + + for each (let attr in node.@*::*) + if (attr.name() != "highlight") + domnode.setAttributeNS(attr.namespace(), attr.localName(), String(attr)); + + for each (let child in node.*::*) + domnode.appendChild(fromXML(child, doc, nodes)); + if (nodes && node.@key) + nodes[node.@key] = domnode; + + if ("@highlight" in node) + highlight.highlightNode(domnode, String(node.@highlight), nodes || true); + return domnode; + default: + return null; + } + }, + + /** + * Evaluates an XPath expression in the current or provided + * document. It provides the xhtml, xhtml2 and dactyl XML + * namespaces. The result may be used as an iterator. + * + * @param {string} expression The XPath expression to evaluate. + * @param {Node} elem The context element. + * @param {boolean} asIterator Whether to return the results as an + * XPath iterator. + * @returns {Object} Iterable result of the evaluation. + */ + XPath: update( + function XPath(expression, elem, asIterator) { + try { + let doc = elem.ownerDocument || elem; + + if (isArray(expression)) + expression = DOM.makeXPath(expression); + + let result = doc.evaluate(expression, elem, + XPath.resolver, + asIterator ? Ci.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE : Ci.nsIDOMXPathResult.ORDERED_NODE_SNAPSHOT_TYPE, + null + ); + + return Object.create(result, { + __iterator__: { + value: asIterator ? function () { let elem; while ((elem = this.iterateNext())) yield elem; } + : function () { for (let i = 0; i < this.snapshotLength; i++) yield this.snapshotItem(i); } + } + }); + } + catch (e) { + throw e.stack ? e : Error(e); + } + }, + { + resolver: function lookupNamespaceURI(prefix) (DOM.namespaces[prefix] || null) + }), + + /** + * Returns an XPath union expression constructed from the specified node + * tests. An expression is built with node tests for both the null and + * XHTML namespaces. See {@link DOM.XPath}. + * + * @param nodes {Array(string)} + * @returns {string} + */ + makeXPath: function makeXPath(nodes) { + return array(nodes).map(util.debrace).flatten() + .map(function (node) /^[a-z]+:/.test(node) ? node : [node, "xhtml:" + node]).flatten() + .map(function (node) "//" + node).join(" | "); + }, + + namespaces: { + xul: XUL.uri, + xhtml: XHTML.uri, + html: XHTML.uri, + xhtml2: "http://www.w3.org/2002/06/xhtml2", + dactyl: NS.uri + }, + + namespaceNames: Class.Memoize(function () + iter(this.namespaces).map(function ([k, v]) [v, k]).toObject()), +}); + +Object.keys(DOM.Event.types).forEach(function (event) { + DOM.prototype[event.replace(/-(.)/g, function (m, m1) m1.toUpperCase())] = + function _event(arg, extra) { + return this[callable(arg) ? "listen" : "dispatch"](event, arg, extra); + }; +}); + +var $ = DOM; + +endModule(); + diff --git a/common/modules/downloads.jsm b/common/modules/downloads.jsm index faee6e22..ac311027 100644 --- a/common/modules/downloads.jsm +++ b/common/modules/downloads.jsm @@ -74,7 +74,7 @@ var Download = Class("Download", { get alive() this.inState(["downloading", "notstarted", "paused", "queued", "scanning"]), - allowedCommands: Class.memoize(function () let (self = this) ({ + allowedCommands: Class.Memoize(function () let (self = this) ({ get cancel() self.cancelable && self.inState(["downloading", "paused", "starting"]), get delete() !this.cancel && self.targetFile.exists(), get launch() self.targetFile.exists() && self.inState(["finished"]), @@ -213,7 +213,7 @@ var DownloadList = Class("DownloadList", services.downloadManager.removeListener(this); }, - message: Class.memoize(function () { + message: Class.Memoize(function () { util.xmlToDom(
@@ -280,7 +280,7 @@ var DownloadList = Class("DownloadList", this.cleanup(); }, - allowedCommands: Class.memoize(function () let (self = this) ({ + allowedCommands: Class.Memoize(function () let (self = this) ({ get clear() values(self.downloads).some(function (dl) dl.allowedCommands.remove) })), diff --git a/common/modules/finder.jsm b/common/modules/finder.jsm index 94f9e48a..f5dde18d 100644 --- a/common/modules/finder.jsm +++ b/common/modules/finder.jsm @@ -8,7 +8,7 @@ Components.utils.import("resource://dactyl/bootstrap.jsm"); defineModule("finder", { exports: ["RangeFind", "RangeFinder", "rangefinder"], require: ["prefs"], - use: ["messages", "services", "util"] + use: ["dom", "messages", "services", "util"] }, this); function equals(a, b) XPCNativeWrapper(a) == XPCNativeWrapper(b); diff --git a/common/modules/help.jsm b/common/modules/help.jsm index c6dfd445..cc4ee5c7 100644 --- a/common/modules/help.jsm +++ b/common/modules/help.jsm @@ -7,7 +7,7 @@ Components.utils.import("resource://dactyl/bootstrap.jsm"); defineModule("help", { exports: ["help"], - require: ["protocol", "services", "util"], + require: ["dom", "protocol", "services", "util"], use: ["config", "highlight", "messages", "template"] }, this); @@ -21,7 +21,8 @@ var Help = Module("Help", { function Loop(fn) function (uri, path) { if (!help.initialized) - return RedirectChannel(uri.spec, uri, 1); + return RedirectChannel(uri.spec, uri, 2, + "Initializing. Please wait..."); return fn.apply(this, arguments); } diff --git a/common/modules/io.jsm b/common/modules/io.jsm index 20bbc3d9..a5477b21 100644 --- a/common/modules/io.jsm +++ b/common/modules/io.jsm @@ -269,7 +269,7 @@ var IO = Module("io", { * @property {function} File class. * @final */ - File: Class.memoize(function () let (io = this) + File: Class.Memoize(function () let (io = this) Class("File", File, { init: function init(path, checkCWD) init.supercall(this, path, (arguments.length < 2 || checkCWD) && io.cwd) @@ -826,12 +826,12 @@ unlet s:cpo_save if (args.bang) arg = "!" + arg; - // NOTE: Vim doesn't replace ! preceded by 2 or more backslashes and documents it - desirable? - // pass through a raw bang when escaped or substitute the last command - - // This is an asinine and irritating feature when we have searchable + // This is an asinine and irritating "feature" when we have searchable // command-line history. --Kris if (modules.options["banghist"]) { + // NOTE: Vim doesn't replace ! preceded by 2 or more backslashes and documents it - desirable? + // pass through a raw bang when escaped or substitute the last command + // replaceable bang and no previous command? dactyl.assert(!/((^|[^\\])(\\\\)*)!/.test(arg) || io._lastRunCommand, _("command.run.noPrevious")); @@ -1041,7 +1041,7 @@ unlet s:cpo_save options.add(["banghist", "bh"], "Replace occurrences of ! with the previous command when executing external commands", - "boolean", true); + "boolean", false); options.add(["fileencoding", "fenc"], "The character encoding used when reading and writing files", diff --git a/common/modules/javascript.jsm b/common/modules/javascript.jsm index e85b8ca4..9326ff73 100644 --- a/common/modules/javascript.jsm +++ b/common/modules/javascript.jsm @@ -44,13 +44,13 @@ var JavaScript = Module("javascript", { } }), - globals: Class.memoize(function () [ + globals: Class.Memoize(function () [ [this.modules.userContext, /*L*/"Global Variables"], [this.modules, "modules"], [this.window, "window"] ]), - toplevel: Class.memoize(function () this.modules.jsmodules), + toplevel: Class.Memoize(function () this.modules.jsmodules), lazyInit: true, @@ -612,13 +612,13 @@ var JavaScript = Module("javascript", { return null; }, - magicalNames: Class.memoize(function () Object.getOwnPropertyNames(Cu.Sandbox(this.window), true).sort()), + magicalNames: Class.Memoize(function () Object.getOwnPropertyNames(Cu.Sandbox(this.window), true).sort()), /** * A list of properties of the global object which are not * enumerable by any standard method. */ - globalNames: Class.memoize(function () let (self = this) array.uniq([ + globalNames: Class.Memoize(function () let (self = this) array.uniq([ "Array", "ArrayBuffer", "AttributeName", "Boolean", "Components", "CSSFontFaceStyleDecl", "CSSGroupRuleRuleList", "CSSNameSpaceRule", "CSSRGBColor", "CSSRect", "ComputedCSSStyleDeclaration", "Date", @@ -700,7 +700,7 @@ var JavaScript = Module("javascript", { modes.addMode("REPL", { description: "JavaScript Read Eval Print Loop", bases: [modes.COMMAND_LINE], - displayName: Class.memoize(function () this.name) + displayName: Class.Memoize(function () this.name) }); }, commandline: function initCommandLine(dactyl, modules, window) { @@ -746,7 +746,7 @@ var JavaScript = Module("javascript", { count: 0, - message: Class.memoize(function () { + message: Class.Memoize(function () { default xml namespace = XHTML; util.xmlToDom(
, this.document, this); diff --git a/common/modules/messages.jsm b/common/modules/messages.jsm index f1f4de13..43177bfe 100644 --- a/common/modules/messages.jsm +++ b/common/modules/messages.jsm @@ -30,7 +30,7 @@ var Messages = Module("messages", { init: function _(message) { this.args = arguments; }, - message: Class.memoize(function () { + message: Class.Memoize(function () { let message = this.args[0]; if (this.args.length > 1) { diff --git a/common/modules/options.jsm b/common/modules/options.jsm index 54b500df..7b8b4ded 100644 --- a/common/modules/options.jsm +++ b/common/modules/options.jsm @@ -928,7 +928,7 @@ var Options = Module("options", { setPref: deprecated("prefs.set", function setPref() prefs.set.apply(prefs, arguments)), withContext: deprecated("prefs.withContext", function withContext() prefs.withContext.apply(prefs, arguments)), - cleanupPrefs: Class.memoize(function () localPrefs.Branch("cleanup.option.")), + cleanupPrefs: Class.Memoize(function () localPrefs.Branch("cleanup.option.")), cleanup: function cleanup(reason) { if (~["disable", "uninstall"].indexOf(reason)) diff --git a/common/modules/overlay.jsm b/common/modules/overlay.jsm index 5d6f36d3..fc841df5 100644 --- a/common/modules/overlay.jsm +++ b/common/modules/overlay.jsm @@ -539,7 +539,7 @@ var Overlay = Module("Overlay", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReferen /** * A list of extant dactyl windows. */ - windows: Class.memoize(function () []) + windows: Class.Memoize(function () []) }); endModule(); diff --git a/common/modules/protocol.jsm b/common/modules/protocol.jsm index e8ddea05..cf9f905d 100644 --- a/common/modules/protocol.jsm +++ b/common/modules/protocol.jsm @@ -50,8 +50,9 @@ function NetError(orig, error) { open: function () { throw error || Cr.NS_ERROR_FILE_NOT_FOUND } }).data.QueryInterface(Ci.nsIChannel); } -function RedirectChannel(to, orig, time) { - let html = .toXMLString(); +function RedirectChannel(to, orig, time, message) { + let html = +

{message || ""}

.toXMLString(); return StringChannel(html, "text/html", services.io.newURI(to, null, null)); } diff --git a/common/modules/sanitizer.jsm b/common/modules/sanitizer.jsm index 0d602110..1b4fd099 100644 --- a/common/modules/sanitizer.jsm +++ b/common/modules/sanitizer.jsm @@ -348,7 +348,7 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef session: 8 }, - UNPERMS: Class.memoize(function () iter(this.PERMS).map(Array.reverse).toObject()), + UNPERMS: Class.Memoize(function () iter(this.PERMS).map(Array.reverse).toObject()), COMMANDS: { unset: /*L*/"Unset", @@ -650,7 +650,7 @@ var Sanitizer = Module("sanitizer", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakRef "1d": "Past day", "1w": "Past week" }, - validator: function (value) /^(a(ll)?|s(ession)|\d+[mhdw])$/.test(value) + validator: bind("test", /^(a(ll)?|s(ession)|\d+[mhdw])$/) }); options.add(["cookies", "ck"], diff --git a/common/modules/storage.jsm b/common/modules/storage.jsm index 896daf44..6697060f 100644 --- a/common/modules/storage.jsm +++ b/common/modules/storage.jsm @@ -510,13 +510,13 @@ var File = Class("File", { /** * @property {string} The current platform's path separator. */ - PATH_SEP: Class.memoize(function () { + PATH_SEP: Class.Memoize(function () { let f = services.directory.get("CurProcD", Ci.nsIFile); f.append("foo"); return f.path.substr(f.parent.path.length, 1); }), - pathSplit: Class.memoize(function () util.regexp("(?:/|" + util.regexp.escape(this.PATH_SEP) + ")", "g")), + pathSplit: Class.Memoize(function () util.regexp("(?:/|" + util.regexp.escape(this.PATH_SEP) + ")", "g")), DoesNotExist: function (path, error) ({ path: path, diff --git a/common/modules/styles.jsm b/common/modules/styles.jsm index 4ebce7d2..9c444621 100644 --- a/common/modules/styles.jsm +++ b/common/modules/styles.jsm @@ -8,7 +8,7 @@ Components.utils.import("resource://dactyl/bootstrap.jsm"); defineModule("styles", { exports: ["Style", "Styles", "styles"], require: ["services", "util"], - use: ["contexts", "messages", "template"] + use: ["contexts", "dom", "messages", "template"] }, this); function cssUri(css) "chrome-data:text/css," + encodeURI(css); diff --git a/common/modules/template.jsm b/common/modules/template.jsm index 9fb99f3e..e5fa86e4 100644 --- a/common/modules/template.jsm +++ b/common/modules/template.jsm @@ -57,7 +57,7 @@ var Binding = Class("Binding", { } }, - events: Class.memoize(function () { + events: Class.Memoize(function () { let res = []; for (let obj in this.bindings) if (Object.getOwnPropertyDescriptor(obj, "events")) @@ -66,7 +66,7 @@ var Binding = Class("Binding", { return res; }), - properties: Class.memoize(function () { + properties: Class.Memoize(function () { let res = {}; for (let obj in this.bindings) for (let prop in properties(obj)) { diff --git a/common/modules/util.jsm b/common/modules/util.jsm index 7f49a39a..8f478e99 100644 --- a/common/modules/util.jsm +++ b/common/modules/util.jsm @@ -10,17 +10,11 @@ try { Components.utils.import("resource://dactyl/bootstrap.jsm"); defineModule("util", { - exports: ["$", "DOM", "FailedAssertion", "Math", "NS", "Point", "Util", "XBL", "XHTML", "XUL", "util"], - require: ["services"], + exports: ["DOM", "$", "FailedAssertion", "Math", "NS", "Point", "Util", "XBL", "XHTML", "XUL", "util"], + require: ["dom", "services"], use: ["commands", "config", "highlight", "messages", "overlay", "storage", "template"] }, this); -var XBL = Namespace("xbl", "http://www.mozilla.org/xbl"); -var XHTML = Namespace("html", "http://www.w3.org/1999/xhtml"); -var XUL = Namespace("xul", "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); -var NS = Namespace("dactyl", "http://vimperator.org/namespaces/liberator"); -default xml namespace = XHTML; - var FailedAssertion = Class("FailedAssertion", ErrorBase, { init: function init(message, level, noTrace) { if (noTrace !== undefined) @@ -721,7 +715,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), * @param {nsIStackFrame} frame * @returns {boolean} */ - isDactyl: Class.memoize(function () { + isDactyl: Class.Memoize(function () { let base = util.regexp.escape(Components.stack.filename.replace(/[^\/]+$/, "")); let re = RegExp("^(?:.* -> )?(?:resource://dactyl(?!-content/eval.js)|" + base + ")\\S+$"); return function isDactyl(frame) re.test(frame.filename); @@ -771,7 +765,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), }, // ripped from Firefox; modified - unsafeURI: Class.memoize(function () util.regexp(String.replace(= 0 ? idx : this.length + idx]); - }, - - find: function find(val) { - return this.map(function (elem) elem.querySelectorAll(val)); - }, - - filter: function filter(val, self) { - let res = this.Empty(); - - if (!callable(val)) - val = this.matcher(val); - - this.constructor(Array.filter(this, val, self || this)); - for (let i = 0; i < this.length; i++) - if (val.call(self, this[i], i)) - res[res.length++] = this[i]; - - return res; - }, - - is: function is(val) { - return this.some(this.matcher(val)); - }, - - reverse: function reverse() { - Array.reverse(this); - return this; - }, - - all: function all(fn, self) { - let res = this.Empty(); - - this.each(function (elem) { - while(true) { - elem = fn.call(this, elem) - if (elem instanceof Ci.nsIDOMElement) - res[res.length++] = elem; - else if (elem && "length" in elem) - for (let i = 0; i < tmp.length; i++) - res[res.length++] = tmp[j]; - else - break; - } - }, self || this); - return res; - }, - - map: function map(fn, self) { - let res = this.Empty(); - let obj = self || this.Empty(); - - for (let i = 0; i < this.length; i++) { - let tmp = fn.call(self || update(obj, [this[i]]), this[i], i); - if (isObject(tmp) && "length" in tmp) - for (let j = 0; j < tmp.length; j++) - res[res.length++] = tmp[j]; - else if (tmp !== undefined) - res[res.length++] = tmp; - } - - return res; - }, - - slice: function eq(start, end) { - return this.constructor(Array.slice(this, start, end)); - }, - - some: function some(fn, self) { - for (let i = 0; i < this.length; i++) - if (fn.call(self || this, this[i], i)) - return true; - return false; - }, - - get parent() this.map(function (elem) elem.parentNode, this), - - get offsetParent() this.map(function (elem) { - do { - var parent = elem.offsetParent; - if (parent instanceof Ci.nsIDOMElement && DOM(parent).position != "static") - return parent; - } - while (parent); - }, this), - - get ancestors() this.all(function (elem) elem.parentNode), - - get children() this.map(function (elem) Array.filter(elem.childNodes, - function (e) e instanceof Ci.nsIDOMElement), - this), - - get contents() this.map(function (elem) elem.childNodes, this), - - get siblings() this.map(function (elem) Array.filter(elem.parentNode.childNodes, - function (e) e != elem && e instanceof Ci.nsIDOMElement), - this), - - get siblingsBefore() this.all(function (elem) elem.previousElementSibling), - get siblingsAfter() this.all(function (elem) elem.nextElementSibling), - - get class() let (self = this) ({ - toString: function () self[0].className, - - get list() Array.slice(self[0].classList), - set list(val) self.attr("class", val.join(" ")), - - each: function each(meth, arg) { - return self.each(function (elem) { - elem.classList[meth](arg); - }) - }, - - add: function add(cls) this.each("add", cls), - remove: function remove(cls) this.each("remove", cls), - toggle: function toggle(cls) this.each("toggle", cls), - - has: function has(cls) this[0].classList.has(cls) - }), - - get highlight() let (self = this) ({ - toString: function () self.attrNS(NS, "highlight") || "", - - get list() this.toString().trim().split(/\s+/), - set list(val) self.attrNS(NS, "highlight", val.join(" ")), - - has: function has(hl) ~this.list.indexOf(hl), - - add: function add(hl) self.each(function () { - highlight.loaded[hl] = true; - this.attrNS(NS, "highlight", - array.uniq(this.highlight.list.concat(hl)).join(" ")); - }), - - remove: function remove(hl) self.each(function () { - this.attrNS(NS, "highlight", - this.highlight.list.filter(function (h) h != hl)); - }), - - toggle: function toggle(hl) self.each(function () { - let { highlight } = this; - highlight[highlight.has(hl) ? "remove" : "add"](hl) - }), - }), - - get rect() this[0].getBoundingClientRect(), - - get viewport() { - let r = this.rect; - return { - width: this[0].clientWidth, - height: this[0].clientHeight, - top: r.top + this[0].clientTop, - get bottom() this.top + this.height, - left: r.left + this[0].clientLeft, - get right() this.left + this.width - } - }, - - /** - * Returns true if the given DOM node is currently visible. - * @returns {boolean} - */ - get isVisible() { - let style = this.style; - return style.visibility == "visible" && style.display != "none"; - }, - - get editor() { - this[0] instanceof Ci.nsIDOMNSEditableElement; - if (this[0].editor instanceof Ci.nsIEditor) - return this[0].editor; - - try { - return this[0].QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIEditingSession) - .getEditorForWindow(this[0]); - } - catch (e) {} - - return null; - }, - - get isEditable() !!this.editor, - - get isInput() this[0] instanceof Ci.nsIDOMHTMLInputElement && this.isEditable, - - /** - * Returns an object representing a Node's computed CSS style. - * @returns {Object} - */ - get style() { - let node = this[0]; - while (node && !(node instanceof Ci.nsIDOMElement) && node.parentNode) - node = node.parentNode; - - try { - var res = node.ownerDocument.defaultView.getComputedStyle(node, null); - } - catch (e) {} - - if (res == null) { - util.dumpStack(_("error.nullComputedStyle", node)); - Cu.reportError(Error(_("error.nullComputedStyle", node))); - return {}; - } - return res; - }, - - /** - * Parses the fields of a form and returns a URL/POST-data pair - * that is the equivalent of submitting the form. - * - * @returns {object} An object with the following elements: - * url: The URL the form points to. - * postData: A string containing URL-encoded post data, if this - * form is to be POSTed - * charset: The character set of the GET or POST data. - * elements: The key=value pairs used to generate query information. - */ - // Nuances gleaned from browser.jar/content/browser/browser.js - get formData() { - function encode(name, value, param) { - param = param ? "%s" : ""; - if (post) - return name + "=" + encodeComponent(value + param); - return encodeComponent(name) + "=" + encodeComponent(value) + param; - } - - let field = this[0]; - let form = field.form; - let doc = form.ownerDocument; - - let charset = doc.characterSet; - let converter = services.CharsetConv(charset); - for each (let cs in form.acceptCharset.split(/\s*,\s*|\s+/)) { - let c = services.CharsetConv(cs); - if (c) { - converter = services.CharsetConv(cs); - charset = cs; - } - } - - let uri = util.newURI(doc.baseURI.replace(/\?.*/, ""), charset); - let url = util.newURI(form.action, charset, uri).spec; - - let post = form.method.toUpperCase() == "POST"; - - let encodeComponent = encodeURIComponent; - if (charset !== "UTF-8") - encodeComponent = function encodeComponent(str) - escape(converter.ConvertFromUnicode(str) + converter.Finish()); - - let elems = []; - if (field instanceof Ci.nsIDOMHTMLInputElement && field.type == "submit") - elems.push(encode(field.name, field.value)); - - for (let [, elem] in iter(form.elements)) - if (elem.name && !elem.disabled) { - if (DOM(elem).isInput - || /^(?:hidden|textarea)$/.test(elem.type) - || elem.type == "submit" && elem == field - || elem.checked && /^(?:checkbox|radio)$/.test(elem.type)) - elems.push(encode(elem.name, elem.value, elem === field)); - else if (elem instanceof Ci.nsIDOMHTMLSelectElement) { - for (let [, opt] in Iterator(elem.options)) - if (opt.selected) - elems.push(encode(elem.name, opt.value)); - } - } - - if (post) - return { url: url, postData: elems.join('&'), charset: charset, elements: elems }; - return { url: url + "?" + elems.join('&'), postData: null, charset: charset, elements: elems }; - }, - - /** - * Generates an XPath expression for the given element. - * - * @returns {string} - */ - get xpath() { - function quote(val) "'" + val.replace(/[\\']/g, "\\$&") + "'"; - - let res = []; - let doc = this.document; - for (let elem = this[0];; elem = elem.parentNode) { - if (!(elem instanceof Ci.nsIDOMElement)) - res.push(""); - else if (elem.id) - res.push("id(" + quote(elem.id) + ")"); - else { - let name = elem.localName; - if (elem.namespaceURI && (elem.namespaceURI != XHTML || doc.xmlVersion)) - if (elem.namespaceURI in DOM.namespaceNames) - name = DOM.namespaceNames[elem.namespaceURI] + ":" + name; - else - name = "*[local-name()=" + quote(name) + " and namespace-uri()=" + quote(elem.namespaceURI) + "]"; - - res.push(name + "[" + (1 + iter(DOM.XPath("./" + name, elem.parentNode)).indexOf(elem)) + "]"); - continue; - } - break; - } - - return res.reverse().join("/"); - }, - - /** - * Returns a string or XML representation of this node. - * - * @param {boolean} color If true, return a colored, XML - * representation of this node. - */ - repr: function repr(color) { - function namespaced(node) { - var ns = DOM.namespaceNames[node.namespaceURI] || /^(?:(.*?):)?/.exec(node.name)[0]; - if (!ns) - return node.localName; - if (color) - return <>{ns}{node.localName} - return ns + ":" + node.localName; - } - - let res = []; - this.each(function (elem) { - try { - let hasChildren = elem.firstChild && (!/^\s*$/.test(elem.firstChild) || elem.firstChild.nextSibling) - if (color) - res.push(<{ - namespaced(elem)} { - template.map(array.iterValues(elem.attributes), - function (attr) - {namespaced(attr)} + - {attr.value}, - <> ) - }{ !hasChildren ? "/>" : ">" - }{ !hasChildren ? "" : <>... + - <{namespaced(elem)}> - }); - else { - let tag = "<" + [namespaced(elem)].concat( - [namespaced(a) + "=" + template.highlight(a.value, true) - for ([i, a] in array.iterItems(elem.attributes))]).join(" "); - - res.push(tag + (!hasChildren ? "/>" : ">...")); - } - } - catch (e) { - res.push({}.toString.call(elem)); - } - }, this); - return template.map(res, util.identity, <>,); - }, - - attr: function attr(key, val) { - return this.attrNS("", key, val); - }, - - attrNS: function attrNS(ns, key, val) { - if (val !== undefined) - key = array.toObject([[key, val]]); - - let hooks = this.attrHooks[ns] || {}; - - if (isObject(key)) - return this.each(function (elem) { - for (let [k, v] in Iterator(key)) - if (Set.has(hooks, k) && hooks[k].set) - hooks[k].set.call(this, elem, v); - else if (v == null) - elem.removeAttributeNS(ns, k); - else - elem.setAttributeNS(ns, k, v); - }); - - if (Set.has(hooks, key) && hooks[key].get) - return hooks[key].get.call(this, this[0]); - - if (!this[0].hasAttributeNS(ns, key)) - return null; - - return this[0].getAttributeNS(ns, key); - }, - - css: update(function css(key, val) { - if (val !== undefined) - key = array.toObject([[key, val]]); - - if (isObject(key)) - return this.each(function (elem) { - for (let [k, v] in Iterator(key)) - elem.style[css.property(k)] = v; - }); - - return this[0].style[css.property(key)]; - }, { - name: function (property) property.replace(/[A-Z]/g, function (m0) "-" + m0.toLowerCase()), - - property: function (name) name.replace(/-(.)/g, function (m0, m1) m1.toUpperCase()) - }), - - append: function append(val) { - return this.eachDOM(val, function (elem, target) { - target.appendChild(elem); - }); - }, - - prepend: function prepend(val) { - return this.eachDOM(val, function (elem, target) { - target.insertBefore(elem, target.firstChild); - }); - }, - - before: function before(val) { - return this.eachDOM(val, function (elem, target) { - target.parentNode.insertBefore(elem, target); - }); - }, - - after: function after(val) { - return this.eachDOM(val, function (elem, target) { - target.parentNode.insertBefore(elem, target.nextSibling); - }); - }, - - appendTo: function appendTo(elem) { - if (!(elem instanceof this.constructor)) - elem = this.constructor(elem, this.document); - elem.append(this); - return this; - }, - - prependTo: function appendTo(elem) { - if (!(elem instanceof this.constructor)) - elem = this.constructor(elem, this.document); - elem.prepend(this); - return this; - }, - - insertBefore: function insertBefore(elem) { - if (!(elem instanceof this.constructor)) - elem = this.constructor(elem, this.document); - elem.before(this); - return this; - }, - - insertAfter: function insertAfter(elem) { - if (!(elem instanceof this.constructor)) - elem = this.constructor(elem, this.document); - elem.after(this); - return this; - }, - - remove: function remove() { - return this.each(function (elem) { - if (elem.parentNode) - elem.parentNode.removeChild(elem); - }, this); - }, - - empty: function empty() { - return this.each(function (elem) { - while (elem.firstChild) - elem.removeChild(elem.firstChild); - }, this); - }, - - toggle: function toggle(val, self) { - if (callable(val)) - return this.each(function (elem, i) { - this[val.call(self || this, elem, i) ? "show" : "hide"](); - }); - - if (arguments.length) - return this[val ? "show" : "hide"](); - - let hidden = this.map(function (elem) elem.style.display == "none"); - return this.each(function (elem, i) { - this[hidden[i] ? "show" : "hide"](); - }); - }, - hide: function hide() { - return this.each(function (elem) { elem.style.display = "none"; }, this); - }, - show: function show() { - for (let i = 0; i < this.length; i++) - if (!this[i].dactylDefaultDisplay && this[i].style.display) - this[i].style.display = ""; - - this.each(function (elem) { - if (!elem.dactylDefaultDisplay) - elem.dactylDefaultDisplay = this.style.display; - }); - - return this.each(function (elem) { - elem.style.display = elem.dactylDefaultDisplay == "none" ? "block" : ""; - }, this); - }, - - getSet: function getSet(args, get, set) { - if (!args.length) - return get.call(this, this[0]); - - let [fn, self] = args; - if (!callable(fn)) - fn = function () args[0]; - - return this.each(function (elem, i) { - set.call(this, elem, fn.call(self || this, elem, i)); - }, this); - }, - - html: function html(txt, self) { - return this.getSet(arguments, - function (elem) elem.innerHTML, - function (elem, val) { elem.innerHTML = val }); - }, - - text: function text(txt, self) { - return this.getSet(arguments, - function (elem) elem.textContent, - function (elem, val) { elem.textContent = val }); - }, - - val: function val(txt) { - return this.getSet(arguments, - function (elem) elem.value, - function (elem, val) { elem.value = val }); - }, - - listen: function listen(event, listener, capture) { - if (isObject(event)) - capture = listener; - else - event = array.toObject([[event, listener]]); - - for (let [k, v] in Iterator(event)) - event[k] = util.wrapCallback(v); - - return this.each(function (elem) { - for (let [k, v] in Iterator(event)) - elem.addEventListener(k, v, capture); - }); - }, - unlisten: function unlisten(event, listener, capture) { - if (isObject(event)) - capture = listener; - else - event = array.toObject([[key, val]]); - - return this.each(function (elem) { - for (let [k, v] in Iterator(event)) - elem.removeEventListener(k, v.wrapper || v, capture); - }); - }, - - dispatch: function dispatch(event, params, extraProps) { - this.canceled = false; - return this.each(function (elem) { - let evt = DOM.Event(this.document, event, params); - if (!DOM.Event.dispatch(elem, evt, extraProps)) - this.canceled = true; - }, this); - }, - - focus: function focus(arg, extra) { - if (callable(arg)) - return this.listen("focus", arg, extra); - services.focus.setFocus(this[0], extra || services.focus.FLAG_BYMOUSE); - return this; - }, - blur: function blur(arg, extra) { - if (callable(arg)) - return this.listen("blur", arg, extra); - return this.each(function (elem) { elem.blur(); }, this); - }, - - /** - * Scrolls an element into view if and only if it's not already - * fully visible. - */ - scrollIntoView: function scrollIntoView(alignWithTop) { - return this.each(function (elem) { - let rect = this.rect; - - let force = false; - if (rect) - for (let parent in this.ancestors.items) { - let isect = util.intersection(rect, parent.viewport); - force = isect.width != rect.width || isect.height != rect.height; - if (force) - break; - } - - let win = this.document.defaultView; - - if (force || !(rect && rect.bottom <= win.innerHeight && rect.top >= 0 && rect.left < win.innerWidth && rect.right > 0)) - elem.scrollIntoView(arguments.length ? alignWithTop - : Math.abs(rect.top) < Math.abs(win.innerHeight - rect.bottom)); - }); - }, -}, { - /** - * Creates an actual event from a pseudo-event object. - * - * The pseudo-event object (such as may be retrieved from events.fromString) - * should have any properties you want the event to have. - * - * @param {Document} doc The DOM document to associate this event with - * @param {Type} type The type of event (keypress, click, etc.) - * @param {Object} opts The pseudo-event. @optional - */ - Event: Class("Event", { - init: function Event(doc, type, opts) { - const DEFAULTS = { - HTML: { - type: type, bubbles: true, cancelable: false - }, - Key: { - type: type, - bubbles: true, cancelable: true, - view: doc.defaultView, - ctrlKey: false, altKey: false, shiftKey: false, metaKey: false, - keyCode: 0, charCode: 0 - }, - Mouse: { - type: type, - bubbles: true, cancelable: true, - view: doc.defaultView, - detail: 1, - screenX: 0, screenY: 0, - clientX: 0, clientY: 0, - ctrlKey: false, altKey: false, shiftKey: false, metaKey: false, - button: 0, - relatedTarget: null - } - }; - - opts = opts || {}; - var t = this.constructor.types[type]; - var evt = doc.createEvent((t || "HTML") + "Events"); - - let defaults = DEFAULTS[t || "HTML"]; - update(defaults, this.constructor.defaults[type]); - - let args = Object.keys(defaults) - .map(function (k) k in opts ? opts[k] : defaults[k]) - - evt["init" + t + "Event"].apply(evt, args); - return evt; - } - }, { - defaults: { - load: { bubbles: false }, - submit: { cancelable: true } - }, - - types: Class.memoize(function () iter( - { - Mouse: "click mousedown mouseout mouseover mouseup", - Key: "keydown keypress keyup", - "": "change dactyl-input input submit " + - "load unload pageshow pagehide DOMContentLoaded" - } - ).map(function ([k, v]) v.split(" ").map(function (v) [v, k])) - .flatten() - .toObject()), - - /** - * Dispatches an event to an element as if it were a native event. - * - * @param {Node} target The DOM node to which to dispatch the event. - * @param {Event} event The event to dispatch. - */ - dispatch: Class.memoize(function () - config.haveGecko("2b") - ? function dispatch(target, event, extra) { - try { - this.feedingEvent = extra; - - if (target instanceof Ci.nsIDOMElement) - // This causes a crash on Gecko<2.0, it seems. - return (target.ownerDocument || target.document || target).defaultView - .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils) - .dispatchDOMEventViaPresShell(target, event, true); - else { - target.dispatchEvent(event); - return !event.getPreventDefault(); - } - } - catch (e) { - util.reportError(e); - } - finally { - this.feedingEvent = null; - } - } - : function dispatch(target, event, extra) { - try { - this.feedingEvent = extra; - target.dispatchEvent(update(event, extra)); - } - finally { - this.feedingEvent = null; - } - }) - }), - - /** - * The set of input element type attribute values that mark the element as - * an editable field. - */ - editableInputs: Set(["date", "datetime", "datetime-local", "email", "file", - "month", "number", "password", "range", "search", - "tel", "text", "time", "url", "week"]), - - /** - * Converts a given DOM Node, Range, or Selection to a string. If - * *html* is true, the output is HTML, otherwise it is presentation - * text. - * - * @param {nsIDOMNode | nsIDOMRange | nsISelection} node The node to - * stringify. - * @param {boolean} html Whether the output should be HTML rather - * than presentation text. - */ - stringify: function stringify(node, html) { - if (node instanceof Ci.nsISelection && node.isCollapsed) - return ""; - - if (node instanceof Ci.nsIDOMNode) { - let range = node.ownerDocument.createRange(); - range.selectNode(node); - node = range; - } - let doc = (node.getRangeAt ? node.getRangeAt(0) : node).startContainer; - doc = doc.ownerDocument || doc; - - let encoder = services.HtmlEncoder(); - encoder.init(doc, "text/unicode", encoder.OutputRaw|encoder.OutputPreformatted); - if (node instanceof Ci.nsISelection) - encoder.setSelection(node); - else if (node instanceof Ci.nsIDOMRange) - encoder.setRange(node); - - let str = services.String(encoder.encodeToString()); - if (html) - return str.data; - - let [result, length] = [{}, {}]; - services.HtmlConverter().convert("text/html", str, str.data.length*2, "text/unicode", result, length); - return result.value.QueryInterface(Ci.nsISupportsString).data; - }, - - /** - * Compiles a CSS spec and XPath pattern matcher based on the given - * list. List elements prefixed with "xpath:" are parsed as XPath - * patterns, while other elements are parsed as CSS specs. The - * returned function will, given a node, return an iterator of all - * descendants of that node which match the given specs. - * - * @param {[string]} list The list of patterns to match. - * @returns {function(Node)} - */ - compileMatcher: function compileMatcher(list) { - let xpath = [], css = []; - for (let elem in values(list)) - if (/^xpath:/.test(elem)) - xpath.push(elem.substr(6)); - else - css.push(elem); - - return update( - function matcher(node) { - if (matcher.xpath) - for (let elem in DOM.XPath(matcher.xpath, node)) - yield elem; - - if (matcher.css) - for (let [, elem] in iter(node.querySelectorAll(matcher.css))) - yield elem; - }, { - css: css.join(", "), - xpath: xpath.join(" | ") - }); - }, - - /** - * Validates a list as input for {@link #compileMatcher}. Returns - * true if and only if every element of the list is a valid XPath or - * CSS selector. - * - * @param {[string]} list The list of patterns to test - * @returns {boolean} True when the patterns are all valid. - */ - validateMatcher: function validateMatcher(list) { - let evaluator = services.XPathEvaluator(); - let node = services.XMLDocument(); - return this.testValues(list, function (value) { - if (/^xpath:/.test(value)) - evaluator.createExpression(value.substr(6), DOM.XPath.resolver); - else - node.querySelector(value); - return true; - }); - }, - - /** - * Converts HTML special characters in *str* to the equivalent HTML - * entities. - * - * @param {string} str - * @returns {string} - */ - escapeHTML: function escapeHTML(str) { - let map = { "'": "'", '"': """, "%": "%", "&": "&", "<": "<", ">": ">" }; - return str.replace(/['"&<>]/g, function (m) map[m]); - }, - - - /** - * Evaluates an XPath expression in the current or provided - * document. It provides the xhtml, xhtml2 and dactyl XML - * namespaces. The result may be used as an iterator. - * - * @param {string} expression The XPath expression to evaluate. - * @param {Node} elem The context element. - * @param {boolean} asIterator Whether to return the results as an - * XPath iterator. - * @returns {Object} Iterable result of the evaluation. - */ - XPath: update( - function XPath(expression, elem, asIterator) { - try { - let doc = elem.ownerDocument || elem; - - if (isArray(expression)) - expression = DOM.makeXPath(expression); - - let result = doc.evaluate(expression, elem, - XPath.resolver, - asIterator ? Ci.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE : Ci.nsIDOMXPathResult.ORDERED_NODE_SNAPSHOT_TYPE, - null - ); - - return Object.create(result, { - __iterator__: { - value: asIterator ? function () { let elem; while ((elem = this.iterateNext())) yield elem; } - : function () { for (let i = 0; i < this.snapshotLength; i++) yield this.snapshotItem(i); } - } - }); - } - catch (e) { - throw e.stack ? e : Error(e); - } - }, - { - resolver: function lookupNamespaceURI(prefix) (DOM.namespaces[prefix] || null) - }), - - /** - * Returns an XPath union expression constructed from the specified node - * tests. An expression is built with node tests for both the null and - * XHTML namespaces. See {@link DOM.XPath}. - * - * @param nodes {Array(string)} - * @returns {string} - */ - makeXPath: function makeXPath(nodes) { - return array(nodes).map(util.debrace).flatten() - .map(function (node) /^[a-z]+:/.test(node) ? node : [node, "xhtml:" + node]).flatten() - .map(function (node) "//" + node).join(" | "); - }, - - namespaces: { - xul: XUL.uri, - xhtml: XHTML.uri, - html: XHTML.uri, - xhtml2: "http://www.w3.org/2002/06/xhtml2", - dactyl: NS.uri - }, - - namespaceNames: Class.memoize(function () - iter(this.namespaces).map(function ([k, v]) [v, k]).toObject()), -}); - -Object.keys(DOM.Event.types).forEach(function (event) { - DOM.prototype[util.camelCase(event)] = function _event(arg, extra) { - return this[callable(arg) ? "listen" : "dispatch"](event, arg, extra); - }; -}); - -var $ = DOM; - /** * Math utility methods. * @singleton diff --git a/teledactyl/content/config.js b/teledactyl/content/config.js index 3a883ae9..fe80540a 100644 --- a/teledactyl/content/config.js +++ b/teledactyl/content/config.js @@ -83,7 +83,7 @@ var Config = Module("config", ConfigBase, { dactyl.beep(); }, - completers: Class.memoize(function () update({ mailfolder: "mailFolder" }, this.__proto__.completers)), + completers: Class.Memoize(function () update({ mailfolder: "mailFolder" }, this.__proto__.completers)), dialogs: { about: ["About Thunderbird", @@ -143,7 +143,7 @@ var Config = Module("config", ConfigBase, { }, /*** optional options, there are checked for existence and a fallback provided ***/ - features: Class.memoize(function () Set( + features: Class.Memoize(function () Set( this.isComposeWindow ? ["addressbook"] : ["hints", "mail", "marks", "addressbook", "tabs"])),