summaryrefslogtreecommitdiff
path: root/common
diff options
context:
space:
mode:
authorKris Maglione <maglione.k@gmail.com>2011-08-14 23:30:05 -0400
committerKris Maglione <maglione.k@gmail.com>2011-08-14 23:30:05 -0400
commit681af3e618eed5d24c581c3ea5dee1907e404e7d (patch)
tree88f49f14cb4fc76484eec56e4684f444c98c5e15 /common
parent236a894c8913d8da6e2297dca1cacf363bca8a51 (diff)
downloadpentadactyl-681af3e618eed5d24c581c3ea5dee1907e404e7d.tar.gz
Move DOM and config properties from the util namespace to the DOM and config namespaces, respectively.
Diffstat (limited to 'common')
-rw-r--r--common/content/bookmarks.js8
-rw-r--r--common/content/buffer.js60
-rw-r--r--common/content/commandline.js16
-rw-r--r--common/content/dactyl.js31
-rw-r--r--common/content/editor.js15
-rw-r--r--common/content/events.js29
-rw-r--r--common/content/hints.js34
-rw-r--r--common/content/marks.js6
-rw-r--r--common/content/mow.js18
-rw-r--r--common/content/statusline.js2
-rw-r--r--common/content/tabs.js12
-rw-r--r--common/modules/addons.jsm2
-rw-r--r--common/modules/base.jsm4
-rw-r--r--common/modules/config.jsm87
-rw-r--r--common/modules/finder.jsm6
-rw-r--r--common/modules/io.jsm36
-rw-r--r--common/modules/overlay.jsm26
-rw-r--r--common/modules/styles.jsm2
-rw-r--r--common/modules/util.jsm919
19 files changed, 719 insertions, 594 deletions
diff --git a/common/content/bookmarks.js b/common/content/bookmarks.js
index 388b8f20..97c2eaad 100644
--- a/common/content/bookmarks.js
+++ b/common/content/bookmarks.js
@@ -116,13 +116,13 @@ var Bookmarks = Module("bookmarks", {
*/
addSearchKeyword: function addSearchKeyword(elem) {
if (elem instanceof HTMLFormElement || elem.form)
- var [url, post, charset] = util.parseForm(elem);
+ var { url, postData, charset } = DOM(elem).formData;
else
- var [url, post, charset] = [elem.href || elem.src, null, elem.ownerDocument.characterSet];
+ var [url, postData, charset] = [elem.href || elem.src, null, elem.ownerDocument.characterSet];
let options = { "-title": "Search " + elem.ownerDocument.title };
- if (post != null)
- options["-post"] = post;
+ if (postData != null)
+ options["-postData"] = postData;
if (charset != null && charset !== "UTF-8")
options["-charset"] = charset;
diff --git a/common/content/buffer.js b/common/content/buffer.js
index 3345a814..78dcd824 100644
--- a/common/content/buffer.js
+++ b/common/content/buffer.js
@@ -16,7 +16,6 @@
*/
var Buffer = Module("buffer", {
init: function init() {
- this.evaluateXPath = util.evaluateXPath;
this.pageInfo = {};
this.addPageInfoSection("e", "Search Engines", function (verbose) {
@@ -24,8 +23,8 @@ var Buffer = Module("buffer", {
let n = 1;
let nEngines = 0;
for (let { document: doc } in values(buffer.allFrames())) {
- let engines = util.evaluateXPath(["link[@href and @rel='search' and @type='application/opensearchdescription+xml']"], doc);
- nEngines += engines.snapshotLength;
+ let engines = DOM("link[href][rel=search][type='application/opensearchdescription+xml']", doc);
+ nEngines += engines.length;
if (verbose)
for (let link in engines)
@@ -79,7 +78,7 @@ var Buffer = Module("buffer", {
for (let [i, win] in Iterator(buffer.allFrames())) {
let doc = win.document;
- for (let link in util.evaluateXPath(["link[@href and (@rel='feed' or (@rel='alternate' and @type))]"], doc)) {
+ for (let link in DOM("link[href][rel=feed], link[href][rel=alternate][type]", doc)) {
let rel = link.rel.toLowerCase();
let feed = { title: link.title, href: link.href, type: link.type || "" };
if (isValidFeed(feed, doc.nodePrincipal, rel == "feed")) {
@@ -612,7 +611,7 @@ var Buffer = Module("buffer", {
};
DOM(elem).mousedown(params).mouseup(params);
- if (!util.haveGecko("2b"))
+ if (!config.haveGecko("2b"))
DOM(elem).click(params);
let sel = util.selectionController(win);
@@ -855,7 +854,7 @@ var Buffer = Module("buffer", {
dactyl.assert(idx in elems);
let elem = elems[idx][0];
- elem.scrollIntoView(true);
+ DOM(elem).scrollIntoView();
let sel = elem.ownerDocument.defaultView.getSelection();
sel.removeAllRanges();
@@ -897,7 +896,7 @@ var Buffer = Module("buffer", {
// focus next frame and scroll into view
dactyl.focus(frames[next]);
if (frames[next] != content)
- frames[next].frameElement.scrollIntoView(false);
+ DOM(frames[next].frameElement).scrollIntoView();
// add the frame indicator
let doc = frames[next].document;
@@ -1207,7 +1206,7 @@ var Buffer = Module("buffer", {
selection.removeAllRanges();
selection.addRange(range);
}
- return util.domToString(range);
+ return DOM.stringify(range);
},
getDefaultNames: function getDefaultNames(node) {
@@ -1242,7 +1241,7 @@ var Buffer = Module("buffer", {
names.push([decodeURIComponent(url.replace(/.*?([^\/]*)\/*$/, "$1")), "File Name"]);
return names.filter(function ([leaf, title]) leaf)
- .map(function ([leaf, title]) [leaf.replace(util.OS.illegalCharacters, encodeURIComponent)
+ .map(function ([leaf, title]) [leaf.replace(config.OS.illegalCharacters, encodeURIComponent)
.replace(re, ext), title]);
},
@@ -1256,7 +1255,7 @@ var Buffer = Module("buffer", {
pos = "scrollLeft", size = "clientWidth", max = "scrollWidth", layoutSize = "offsetWidth",
overflow = "overflowX", border1 = "borderLeftWidth", border2 = "borderRightWidth";
- let style = util.computedStyle(elem);
+ let style = DOM(elem).style;
let borderSize = Math.round(parseFloat(style[border1]) + parseFloat(style[border2]));
let realSize = elem[size];
// Stupid Gecko eccentricities. May fail for quirks mode documents.
@@ -1288,7 +1287,7 @@ var Buffer = Module("buffer", {
if (top != null)
elem.scrollTop = top;
- if (util.haveGecko("2.0") && !util.haveGecko("7.*"))
+ if (config.haveGecko("2.0") && !util.haveGecko("7.*"))
elem.ownerDocument.defaultView
.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils)
.redraw();
@@ -1307,7 +1306,7 @@ var Buffer = Module("buffer", {
* given direction.
*/
scrollHorizontal: function scrollHorizontal(elem, unit, number) {
- let fontSize = parseInt(util.computedStyle(elem).fontSize);
+ let fontSize = parseInt(DOM(elem).style.fontSize);
let increment;
if (unit == "columns")
increment = fontSize; // Good enough, I suppose.
@@ -1337,7 +1336,7 @@ var Buffer = Module("buffer", {
* given direction.
*/
scrollVertical: function scrollVertical(elem, unit, number) {
- let fontSize = parseInt(util.computedStyle(elem).lineHeight);
+ let fontSize = parseInt(DOM(elem).style.lineHeight);
let increment;
if (unit == "lines")
increment = fontSize;
@@ -1385,7 +1384,7 @@ var Buffer = Module("buffer", {
* column ordinal to scroll to.
*/
scrollToPosition: function scrollToPosition(elem, horizontal, vertical) {
- let style = util.computedStyle(elem);
+ let style = DOM(elem).style;
Buffer.scrollTo(elem,
horizontal == null ? null :
horizontal == 0 ? 0 : this._exWidth(elem) * horizontal,
@@ -1399,7 +1398,7 @@ var Buffer = Module("buffer", {
* @param {Element} elem The element to scroll.
*/
getScrollPosition: function getPosition(elem) {
- let style = util.computedStyle(elem);
+ let style = DOM(elem).style;
return {
x: elem.scrollLeft && elem.scrollLeft / this._exWidth(elem),
y: elem.scrollTop / parseFloat(style.lineHeight)
@@ -1407,14 +1406,13 @@ var Buffer = Module("buffer", {
},
_exWidth: function _exWidth(elem) {
- let div = elem.appendChild(
- util.xmlToDom(<elem style="width: 1ex !important; position: absolute !important; padding: 0 !important; display: block;"/>,
- elem.ownerDocument));
+ let div = DOM(<elem style="width: 1ex !important; position: absolute !important; padding: 0 !important; display: block;"/>,
+ elem.ownerDocument).appendTo(elem);
try {
- return parseFloat(util.computedStyle(div).width);
+ return parseFloat(div.style.width);
}
finally {
- div.parentNode.removeChild(div);
+ div.remove();
}
},
@@ -1443,7 +1441,7 @@ var Buffer = Module("buffer", {
let arg = args[0];
// FIXME: arg handling is a bit of a mess, check for filename
- dactyl.assert(!arg || arg[0] == ">" && !util.OS.isWindows,
+ dactyl.assert(!arg || arg[0] == ">" && !config.OS.isWindows,
_("error.trailingCharacters"));
const PRINTER = "PostScript/default";
@@ -1913,18 +1911,20 @@ var Buffer = Module("buffer", {
let frames = buffer.allFrames(null, true);
- let elements = array.flatten(frames.map(function (win) [m for (m in util.evaluateXPath(xpath, win.document))]))
+ let elements = array.flatten(frames.map(function (win) [m for (m in DOM.XPath(xpath, win.document))]))
.filter(function (elem) {
if (isinstance(elem, [HTMLFrameElement, HTMLIFrameElement]))
return Editor.getEditor(elem.contentWindow);
- if (elem.readOnly || elem instanceof HTMLInputElement && !Set.has(util.editableInputs, elem.type))
+ elem = DOM(elem);
+
+ if (elem[0].readOnly || !DOM(elem).isEditable)
return false;
- let computedStyle = util.computedStyle(elem);
- let rect = elem.getBoundingClientRect();
- return computedStyle.visibility != "hidden" && computedStyle.display != "none" &&
- (elem instanceof Ci.nsIDOMXULTextBoxElement || computedStyle.MozUserFocus != "ignore") &&
+ let style = elem.style;
+ let rect = elem.rect;
+ return elem.isVisible &&
+ (elem[0] instanceof Ci.nsIDOMXULTextBoxElement || style.MozUserFocus != "ignore") &&
rect.width && rect.height;
});
@@ -1932,7 +1932,7 @@ var Buffer = Module("buffer", {
elem = elements[Math.constrain(args.count, 1, elements.length) - 1];
}
buffer.focusElement(elem);
- util.scrollIntoView(elem);
+ DOM(elem).scrollIntoView();
},
{ count: true });
@@ -2086,10 +2086,10 @@ var Buffer = Module("buffer", {
keepQuotes: true,
setter: function (vals) {
for (let [k, v] in Iterator(vals))
- vals[k] = update(new String(v), { matcher: util.compileMatcher(Option.splitList(v)) });
+ vals[k] = update(new String(v), { matcher: DOM.compileMatcher(Option.splitList(v)) });
return vals;
},
- validator: function (value) util.validateMatcher.call(this, value)
+ validator: function (value) DOM.validateMatcher.call(this, value)
&& Object.keys(value).every(function (v) v.length == 1)
});
diff --git a/common/content/commandline.js b/common/content/commandline.js
index ca5b00d6..0675153b 100644
--- a/common/content/commandline.js
+++ b/common/content/commandline.js
@@ -279,7 +279,7 @@ var CommandWidgets = Class("CommandWidgets", {
// some host apps use "hostPrefixContext-copy" ids
let xpath = "//xul:menuitem[contains(@id, '" + "ontext-" + tail + "') and not(starts-with(@id, 'dactyl-'))]";
document.getElementById("dactyl-context-" + tail).style.listStyleImage =
- util.computedStyle(util.evaluateXPath(xpath, document).snapshotItem(0)).listStyleImage;
+ DOM(DOM.XPath(xpath, document).snapshotItem(0)).style.listStyleImage;
});
return document.getElementById("dactyl-contextmenu");
}),
@@ -899,7 +899,7 @@ var CommandLine = Module("commandline", {
this.savingOutput = true;
dactyl.trapErrors.apply(dactyl, [fn, self].concat(Array.slice(arguments, 2)));
this.savingOutput = false;
- return output.map(function (elem) elem instanceof Node ? util.domToString(elem) : elem)
+ return output.map(function (elem) elem instanceof Node ? DOM.stringify(elem) : elem)
.join("\n");
}
}, {
@@ -1641,8 +1641,10 @@ var ItemList = Class("ItemList", {
_init: function _init() {
this._div = this._dom(
<div class="ex-command-output" highlight="Normal" style="white-space: nowrap">
- <div highlight="Completions" key="noCompletions"><span highlight="Title">{_("completion.noCompletions")}</span></div>
- <div key="completions"/>
+ <div key="wrapper">
+ <div highlight="Completions" key="noCompletions"><span highlight="Title">{_("completion.noCompletions")}</span></div>
+ <div key="completions"/>
+ </div>
<div highlight="Completions">
{
template.map(util.range(0, options["maxitems"] * 2), function (i)
@@ -1653,7 +1655,7 @@ var ItemList = Class("ItemList", {
</div>
</div>, this._divNodes);
this._doc.body.replaceChild(this._div, this._doc.body.firstChild);
- util.scrollIntoView(this._div, true);
+ DOM(this._divNodes.wrapper).scrollIntoView(true);
this._items.contextList.forEach(function init_eachContext(context) {
delete context.cache.nodes;
@@ -1758,7 +1760,7 @@ var ItemList = Class("ItemList", {
this._divNodes.noCompletions.style.display = haveCompletions ? "none" : "block";
- this._completionElements = util.evaluateXPath("//xhtml:div[@dactyl:highlight='CompItem']", this._doc);
+ this._completionElements = DOM.XPath("//xhtml:div[@dactyl:highlight='CompItem']", this._doc);
return true;
},
@@ -1828,7 +1830,7 @@ var ItemList = Class("ItemList", {
if (index >= 0) {
this._getCompletion(index).setAttribute("selected", "true");
if (this._container.height != 0)
- util.scrollIntoView(this._getCompletion(index));
+ DOM(this._getCompletion(index)).scrollIntoView();
}
//if (index == 0)
diff --git a/common/content/dactyl.js b/common/content/dactyl.js
index 8e0301d7..b9395f84 100644
--- a/common/content/dactyl.js
+++ b/common/content/dactyl.js
@@ -698,7 +698,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
};
// Find the tags in the document.
let addTags = function addTags(file, doc) {
- for (let elem in util.evaluateXPath("//@tag|//dactyl:tags/text()|//dactyl:tag/text()", doc))
+ for (let elem in DOM.XPath("//@tag|//dactyl:tags/text()|//dactyl:tag/text()", doc))
for (let tag in values((elem.value || elem.textContent).split(/\s+/)))
tagMap[tag] = file;
};
@@ -716,7 +716,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
tagMap["all"] = tagMap["all.xml"] = "all";
tagMap["versions"] = tagMap["versions.xml"] = "versions";
let files = findHelpFile("all").map(function (doc)
- [f.value for (f in util.evaluateXPath("//dactyl:include/@href", doc))]);
+ [f.value for (f in DOM.XPath("//dactyl:include/@href", doc))]);
// Scrape the tags from the rest of the help files.
array.flatten(files).forEach(function (file) {
@@ -1255,10 +1255,11 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
* Opens one or more URLs. Returns true when load was initiated, or
* false on error.
*
- * @param {string|Array} urls A representation of the URLs to open. May be
+ * @param {string|object|Array} urls A representation of the URLs to open. May be
* either a string, which will be passed to
- * {@see Dactyl#parseURLs}, or an array in the same format as
- * would be returned by the same.
+ * {@link Dactyl#parseURLs}, an array in the same format as
+ * would be returned by the same, or an object as returned by
+ * {@link DOM#formData}.
* @param {object} params A set of parameters specifying how to open the
* URLs. The following properties are recognized:
*
@@ -1312,21 +1313,23 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
return;
let browser = config.tabbrowser;
- function open(urls, where) {
+ function open(loc, where) {
try {
- let url = Array.concat(urls)[0];
- let postdata = Array.concat(urls)[1];
+ if (isArray(loc))
+ loc = { url: loc[0], postData: loc[1] };
+ else if (isString(loc))
+ loc = { url: loc };
// decide where to load the first url
switch (where) {
case dactyl.NEW_TAB:
if (!dactyl.has("tabs"))
- return open(urls, dactyl.NEW_WINDOW);
+ return open(loc, dactyl.NEW_WINDOW);
return prefs.withContext(function () {
prefs.set("browser.tabs.loadInBackground", true);
- return browser.loadOneTab(url, null, null, postdata, background).linkedBrowser.contentDocument;
+ return browser.loadOneTab(loc.url, null, null, loc.postData, background).linkedBrowser.contentDocument;
});
case dactyl.NEW_WINDOW:
@@ -1335,7 +1338,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
browser = win.dactyl && win.dactyl.modules.config.tabbrowser || win.getBrowser();
// FALLTHROUGH
case dactyl.CURRENT_TAB:
- browser.loadURIWithFlags(url, flags, null, null, postdata);
+ browser.loadURIWithFlags(loc.url, flags, null, null, loc.postData);
return browser.contentWindow;
}
}
@@ -1380,7 +1383,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
return urls.map(function (url) {
url = url.trim();
- if (/^(\.{0,2}|~)(\/|$)/.test(url) || util.OS.isWindows && /^[a-z]:/i.test(url)) {
+ if (/^(\.{0,2}|~)(\/|$)/.test(url) || config.OS.isWindows && /^[a-z]:/i.test(url)) {
try {
// Try to find a matching file.
let file = io.File(url);
@@ -1945,7 +1948,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
function () { dactyl.restart(); },
{ argCount: "0" });
- function findToolbar(name) util.evaluateXPath(
+ function findToolbar(name) DOM.XPath(
"//*[@toolbarname=" + util.escapeString(name, "'") + " or " +
"@toolbarname=" + util.escapeString(name.trim(), "'") + "]",
document).snapshotItem(0);
@@ -2136,7 +2139,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
completion.toolbar = function toolbar(context) {
context.title = ["Toolbar"];
context.keys = { text: function (item) item.getAttribute("toolbarname"), description: function () "" };
- context.completions = util.evaluateXPath("//*[@toolbarname]", document);
+ context.completions = DOM.XPath("//*[@toolbarname]", document);
};
completion.window = function window(context) {
diff --git a/common/content/editor.js b/common/content/editor.js
index 1c50dac9..cc60c721 100644
--- a/common/content/editor.js
+++ b/common/content/editor.js
@@ -209,7 +209,7 @@ var Editor = Module("editor", {
var editor_ = window.GetCurrentEditor ? GetCurrentEditor()
: Editor.getEditor(document.commandDispatcher.focusedWindow);
dactyl.assert(editor_);
- text = Array.map(editor_.rootElement.childNodes, function (e) util.domToString(e, true)).join("");
+ text = Array.map(editor_.rootElement.childNodes, function (e) DOM.stringify(e, true)).join("");
}
let origGroup = textBox && textBox.getAttributeNS(NS, "highlight") || "";
@@ -348,16 +348,7 @@ var Editor = Module("editor", {
elem = dactyl.focusedElement || document.commandDispatcher.focusedWindow;
dactyl.assert(elem);
- try {
- if (elem instanceof Element)
- return elem.QueryInterface(Ci.nsIDOMNSEditableElement).editor;
- return elem.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation)
- .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIEditingSession)
- .getEditorForWindow(elem);
- }
- catch (e) {
- return null;
- }
+ return DOM(elem).editor;
},
getController: function (cmd) {
@@ -582,7 +573,7 @@ var Editor = Module("editor", {
addMotionMap("d", "Delete motion", true, function (editor) { editor.cut(); });
addMotionMap("c", "Change motion", true, function (editor) { editor.cut(); }, modes.INSERT);
- addMotionMap("y", "Yank motion", false, function (editor, range) { dactyl.clipboardWrite(util.domToString(range)) });
+ addMotionMap("y", "Yank motion", false, function (editor, range) { dactyl.clipboardWrite(DOM.stringify(range)) });
let bind = function bind(names, description, action, params)
mappings.add([modes.INPUT], names, description,
diff --git a/common/content/events.js b/common/content/events.js
index 1b3a3d80..6f3090df 100644
--- a/common/content/events.js
+++ b/common/content/events.js
@@ -495,12 +495,6 @@ var Events = Module("events", {
this._activeMenubar = false;
this.listen(window, this, "events");
-
- util.windows = [window].concat(util.windows);
- },
-
- destroy: function destroy() {
- util.windows = util.windows.filter(function (w) w != window);
},
signals: {
@@ -922,14 +916,14 @@ var Events = Module("events", {
// https://bugzilla.mozilla.org/show_bug.cgi?id=432951
// ---
//
- // The following fixes are only activated if util.OS.isMacOSX.
+ // The following fixes are only activated if config.OS.isMacOSX.
// Technically, they prevent mappings from <C-Esc> (and
// <C-C-]> if your fancy keyboard permits such things<?>), but
// these <C-control> mappings are probably pathological (<C-Esc>
// certainly is on Windows), and so it is probably
- // harmless to remove the util.OS.isMacOSX if desired.
+ // harmless to remove the config.OS.isMacOSX if desired.
//
- else if (util.OS.isMacOSX && event.ctrlKey && charCode >= 27 && charCode <= 31) {
+ else if (config.OS.isMacOSX && event.ctrlKey && charCode >= 27 && charCode <= 31) {
if (charCode == 27) { // [Ctrl-Bug 1/5] the <C-[> bug
key = "Esc";
modifier = modifier.replace("C-", "");
@@ -1019,7 +1013,7 @@ var Events = Module("events", {
["key", key.toLowerCase()]);
}
- let accel = util.OS.isMacOSX ? "metaKey" : "ctrlKey";
+ let accel = config.OS.isMacOSX ? "metaKey" : "ctrlKey";
let access = iter({ 1: "shiftKey", 2: "ctrlKey", 4: "altKey", 8: "metaKey" })
.filter(function ([k, v]) this & k, prefs.get("ui.key.chromeAccess"))
@@ -1160,7 +1154,7 @@ var Events = Module("events", {
let elem = event.originalTarget;
if (elem == window)
- util.windows = [window].concat(util.windows.filter(function (w) w != window));
+ overlay.activeWindow = window;
elem.dactylHadFocus = true;
if (event.target instanceof Ci.nsIDOMXULTextBoxElement)
@@ -1444,7 +1438,7 @@ var Events = Module("events", {
let haveInput = modes.stack.some(function (m) m.main.input);
if (elem instanceof HTMLTextAreaElement
- || elem instanceof Element && util.computedStyle(elem).MozUserModify === "read-write"
+ || elem instanceof Element && DOM(elem).style.MozUserModify === "read-write"
|| elem == null && win && Editor.getEditor(win)) {
if (modes.main == modes.VISUAL && elem.selectionEnd == elem.selectionStart)
@@ -1525,7 +1519,7 @@ var Events = Module("events", {
key === "<Esc>" || key === "<C-[>",
isHidden: function isHidden(elem, aggressive) {
- if (util.computedStyle(elem).visibility !== "visible")
+ if (DOM(elem).style.visibility !== "visible")
return true;
if (aggressive)
@@ -1539,12 +1533,9 @@ var Events = Module("events", {
},
isInputElement: function isInputElement(elem) {
- return elem instanceof HTMLInputElement && Set.has(util.editableInputs, elem.type) ||
- isinstance(elem, [HTMLEmbedElement,
- HTMLObjectElement, HTMLSelectElement,
- HTMLTextAreaElement,
- Ci.nsIDOMXULTextBoxElement]) ||
- elem instanceof Window && Editor.getEditor(elem);
+ return DOM(elem).isEditable ||
+ isinstance(elem, [HTMLEmbedElement, HTMLObjectElement,
+ HTMLSelectElement])
},
kill: function kill(event) {
diff --git a/common/content/hints.js b/common/content/hints.js
index 459ac88a..2c4b29ed 100644
--- a/common/content/hints.js
+++ b/common/content/hints.js
@@ -255,7 +255,7 @@ var HintSession = Class("HintSession", CommandMode, {
getContainerOffsets: function _getContainerOffsets(doc) {
let body = doc.body || doc.documentElement;
// TODO: getComputedStyle returns null for Facebook channel_iframe doc - probable Gecko bug.
- let style = util.computedStyle(body);
+ let style = DOM(body).style;
if (style && /^(absolute|fixed|relative)$/.test(style.position)) {
let rect = body.getClientRects()[0];
@@ -298,7 +298,7 @@ var HintSession = Class("HintSession", CommandMode, {
return false;
if (!rect.width || !rect.height)
- if (!Array.some(elem.childNodes, function (elem) elem instanceof Element && util.computedStyle(elem).float != "none" && isVisible(elem)))
+ if (!Array.some(elem.childNodes, function (elem) elem instanceof Element && DOM(elem).style.float != "none" && isVisible(elem)))
return false;
let computedStyle = doc.defaultView.getComputedStyle(elem, null);
@@ -309,12 +309,11 @@ var HintSession = Class("HintSession", CommandMode, {
let body = doc.body || doc.querySelector("body");
if (body) {
- let fragment = util.xmlToDom(<div highlight="hints"/>, doc);
- body.appendChild(fragment);
- util.computedStyle(fragment).height; // Force application of binding.
- let container = doc.getAnonymousElementByAttribute(fragment, "anonid", "hints") || fragment;
+ let fragment = DOM(<div highlight="hints"/>, doc).appendTo(body);
+ fragment.style.height; // Force application of binding.
+ let container = doc.getAnonymousElementByAttribute(fragment[0], "anonid", "hints") || fragment[0];
- let baseNodeAbsolute = util.xmlToDom(<span highlight="Hint" style="display: none;"/>, doc);
+ let baseNode = DOM(<span highlight="Hint" style="display: none;"/>, doc)[0];
let mode = this.hintMode;
let res = mode.matcher(doc);
@@ -342,7 +341,7 @@ var HintSession = Class("HintSession", CommandMode, {
else
hint.text = elem.textContent.toLowerCase();
- hint.span = baseNodeAbsolute.cloneNode(false);
+ hint.span = baseNode.cloneNode(false);
let leftPos = Math.max((rect.left + offsetX), offsetX);
let topPos = Math.max((rect.top + offsetY), offsetY);
@@ -532,7 +531,7 @@ var HintSession = Class("HintSession", CommandMode, {
// Goddamn stupid fucking Gecko 1.x security manager bullshit.
try { delete doc.dactylLabels; } catch (e) { doc.dactylLabels = undefined; }
- for (let elem in util.evaluateXPath("//*[@dactyl:highlight='hints']", doc))
+ for (let elem in DOM.XPath("//*[@dactyl:highlight='hints']", doc))
elem.parentNode.removeChild(elem);
for (let i in util.range(start, end + 1)) {
this.pageHints[i].ambiguous = false;
@@ -824,7 +823,7 @@ var Hints = Module("hints", {
let type = elem.type;
- if (elem instanceof HTMLInputElement && Set.has(util.editableInputs, elem.type))
+ if (DOM(elem).isInput)
return [elem.value, false];
else {
for (let [, option] in Iterator(options["hintinputs"])) {
@@ -1080,7 +1079,7 @@ var Hints = Module("hints", {
isVisible: function isVisible(elem, offScreen) {
let rect = elem.getBoundingClientRect();
if (!rect.width || !rect.height)
- if (!Array.some(elem.childNodes, function (elem) elem instanceof Element && util.computedStyle(elem).float != "none" && isVisible(elem)))
+ if (!Array.some(elem.childNodes, function (elem) elem instanceof Element && DOM(elem).style.float != "none" && isVisible(elem)))
return false;
let win = elem.ownerDocument.defaultView;
@@ -1089,8 +1088,7 @@ var Hints = Module("hints", {
rect.right + win.scrollX > win.scrolMaxX + win.innerWidth))
return false;
- let computedStyle = util.computedStyle(elem, null);
- if (computedStyle.visibility != "visible" || computedStyle.display == "none")
+ if (!DOM(elem).isVisible)
return false;
return true;
},
@@ -1249,8 +1247,6 @@ var Hints = Module("hints", {
function ({ self }) { self.escapeNumbers = !self.escapeNumbers; });
},
options: function () {
- function xpath(arg) util.makeXPath(arg);
-
options.add(["extendedhinttags", "eht"],
"XPath or CSS selector strings of hintable elements for extended hint modes",
"regexpmap", {
@@ -1267,10 +1263,10 @@ var Hints = Module("hints", {
res ? res.matcher : default_,
setter: function (vals) {
for (let value in values(vals))
- value.matcher = util.compileMatcher(Option.splitList(value.result));
+ value.matcher = DOM.compileMatcher(Option.splitList(value.result));
return vals;
},
- validator: util.validateMatcher
+ validator: DOM.validateMatcher
});
options.add(["hinttags", "ht"],
@@ -1280,10 +1276,10 @@ var Hints = Module("hints", {
"[tabindex],[role=link],[role=button],[contenteditable=true]",
{
setter: function (values) {
- this.matcher = util.compileMatcher(values);
+ this.matcher = DOM.compileMatcher(values);
return values;
},
- validator: util.validateMatcher
+ validator: DOM.validateMatcher
});
options.add(["hintkeys", "hk"],
diff --git a/common/content/marks.js b/common/content/marks.js
index e6bbb9b4..13355261 100644
--- a/common/content/marks.js
+++ b/common/content/marks.js
@@ -42,7 +42,7 @@ var Marks = Module("marks", {
params.location = doc.documentURI,
params.offset = buffer.scrollPosition;
- params.path = util.generateXPath(buffer.findScrollable(0, params.offset.x));
+ params.path = DOM(buffer.findScrollable(0, params.offset.x)).xpath;
params.timestamp = Date.now() * 1000;
params.equals = function (m) this.location == m.location
&& this.offset.x == m.offset.x
@@ -227,11 +227,11 @@ var Marks = Module("marks", {
if (!mark.xpath)
var node = buffer.findScrollable(0, (mark.offset || mark.position).x)
else
- for (node in util.evaluateXPath(mark.xpath, buffer.focusedFrame.document))
+ for (node in DOM.XPath(mark.xpath, buffer.focusedFrame.document))
break;
util.assert(node);
- util.scrollIntoView(node);
+ DOM(node).scrollIntoView();
if (mark.offset)
Buffer.scrollToPosition(node, mark.offset.x, mark.offset.y);
diff --git a/common/content/mow.js b/common/content/mow.js
index bd055746..4254cb9c 100644
--- a/common/content/mow.js
+++ b/common/content/mow.js
@@ -24,7 +24,7 @@ var MOW = Module("mow", {
if (options["more"] && this.isScrollable(1)) {
// start the last executed command's output at the top of the screen
let elements = this.document.getElementsByClassName("ex-command-output");
- elements[elements.length - 1].scrollIntoView(true);
+ DOM(elements[elements.length - 1]).scrollIntoView(true);
}
else
this.body.scrollTop = this.body.scrollHeight;
@@ -37,7 +37,7 @@ var MOW = Module("mow", {
events.listen(window, this, "windowEvents");
modules.mow = this;
- let fontSize = util.computedStyle(document.documentElement).fontSize;
+ let fontSize = DOM(document.documentElement).style.fontSize;
styles.system.add("font-size", "dactyl://content/buffer.xhtml",
"body { font-size: " + fontSize + "; } \
html|html > xul|scrollbar { visibility: collapse !important; }",
@@ -94,7 +94,7 @@ var MOW = Module("mow", {
* @param {string} highlightGroup
*/
echo: function echo(data, highlightGroup, silent) {
- let body = this.document.body;
+ let body = DOM(this.document.body);
this.widgets.message = null;
if (!commandline.commandVisible)
@@ -122,11 +122,11 @@ var MOW = Module("mow", {
if (isObject(data) && !isinstance(data, _)) {
this.lastOutput = null;
- var output = util.xmlToDom(<div class="ex-command-output" style="white-space: nowrap" highlight={highlightGroup}/>,
- this.document);
+ var output = DOM(<div class="ex-command-output" style="white-space: nowrap" highlight={highlightGroup}/>,
+ this.document);
data.document = this.document;
try {
- output.appendChild(data.message);
+ output.append(data.message);
}
catch (e) {
util.reportError(e);
@@ -138,17 +138,17 @@ var MOW = Module("mow", {
let style = isString(data) ? "pre-wrap" : "nowrap";
this.lastOutput = <div class="ex-command-output" style={"white-space: " + style} highlight={highlightGroup}>{data}</div>;
- var output = util.xmlToDom(this.lastOutput, this.document);
+ var output = DOM(this.lastOutput, this.document);
}
// FIXME: need to make sure an open MOW is closed when commands
// that don't generate output are executed
if (!this.visible) {
this.body.scrollTop = 0;
- body.textContent = "";
+ body.empty();
}
- body.appendChild(output);
+ body.append(output);
let str = typeof data !== "xml" && data.message || data;
if (!silent)
diff --git a/common/content/statusline.js b/common/content/statusline.js
index b5dbee70..2d93e512 100644
--- a/common/content/statusline.js
+++ b/common/content/statusline.js
@@ -42,7 +42,7 @@ var StatusLine = Module("statusline", {
color: inherit !important;
}
AddonButton:not(:hover) background: transparent;
- ]]>)({ padding: util.OS.isMacOSX ? "padding-right: 10px !important;" : "" }));
+ ]]>)({ padding: config.OS.isMacOSX ? "padding-right: 10px !important;" : "" }));
if (document.getElementById("appmenu-button"))
highlight.loadCSS(<![CDATA[
diff --git a/common/content/tabs.js b/common/content/tabs.js
index 3d9e6c7f..c5c156c0 100644
--- a/common/content/tabs.js
+++ b/common/content/tabs.js
@@ -37,7 +37,7 @@ var Tabs = Module("tabs", {
this.tabBinding = styles.system.add("tab-binding", "chrome://browser/content/browser.xul", String.replace(<><![CDATA[
xul|tab { -moz-binding: url(chrome://dactyl/content/bindings.xml#tab) !important; }
- ]]></>, /tab-./g, function (m) util.OS.isMacOSX ? "tab-mac" : m),
+ ]]></>, /tab-./g, function (m) config.OS.isMacOSX ? "tab-mac" : m),
false, true);
this.timeout(function () {
@@ -75,14 +75,12 @@ var Tabs = Module("tabs", {
if (!node("dactyl-tab-number")) {
let img = node("tab-icon-image");
if (img) {
- let nodes = {};
- let dom = util.xmlToDom(<xul xmlns:xul={XUL} xmlns:html={XHTML}
+ let dom = DOM(<xul xmlns:xul={XUL} xmlns:html={XHTML}
><xul:hbox highlight="tab-number"><xul:label key="icon" align="center" highlight="TabIconNumber" class="dactyl-tab-icon-number"/></xul:hbox
><xul:hbox highlight="tab-number"><html:div key="label" highlight="TabNumber" class="dactyl-tab-number"/></xul:hbox
- ></xul>.*, document, nodes);
- img.parentNode.appendChild(dom);
- tab.__defineGetter__("dactylOrdinal", function () Number(nodes.icon.value));
- tab.__defineSetter__("dactylOrdinal", function (i) nodes.icon.value = nodes.label.textContent = i);
+ ></xul>.*, document).appendTo(img.parentNode);
+ tab.__defineGetter__("dactylOrdinal", function () Number(dom.nodes.icon.value));
+ tab.__defineSetter__("dactylOrdinal", function (i) dom.nodes.icon.value = dom.nodes.label.textContent = i);
}
}
}
diff --git a/common/modules/addons.jsm b/common/modules/addons.jsm
index 6f43950c..160b3178 100644
--- a/common/modules/addons.jsm
+++ b/common/modules/addons.jsm
@@ -111,7 +111,7 @@ var actions = {
name: "extr[ehash]",
description: "Reload an extension",
action: function (addon) {
- util.assert(util.haveGecko("2b"), _("command.notUseful", config.host));
+ util.assert(config.haveGecko("2b"), _("command.notUseful", config.host));
util.timeout(function () {
addon.userDisabled = true;
addon.userDisabled = false;
diff --git a/common/modules/base.jsm b/common/modules/base.jsm
index 0c8c82f4..8cde5ce2 100644
--- a/common/modules/base.jsm
+++ b/common/modules/base.jsm
@@ -144,6 +144,7 @@ function defineModule(name, params, module) {
use[mod] = use[mod] || [];
use[mod].push(module);
}
+ module._lastModule = currentModule;
currentModule = module;
module.startTime = Date.now();
}
@@ -190,6 +191,7 @@ function endModule() {
require(mod, currentModule.NAME, "use");
loaded[currentModule.NAME] = 1;
+ currentModule = currentModule._lastModule;
}
function require(obj, name, from) {
@@ -732,7 +734,7 @@ function Class() {
if (callable(args[0]))
superclass = args.shift();
- if (loaded.util && util.haveGecko("6.0a1")) // Bug 657418.
+ if (loaded.config && config.haveGecko("6.0a1")) // Bug 657418.
var Constructor = function Constructor() {
var self = Object.create(Constructor.prototype, {
constructor: { value: Constructor },
diff --git a/common/modules/config.jsm b/common/modules/config.jsm
index 4da5a88d..2623b84b 100644
--- a/common/modules/config.jsm
+++ b/common/modules/config.jsm
@@ -23,7 +23,7 @@ var ConfigBase = Class("ConfigBase", {
*/
init: function init() {
this.features.push = deprecated("Set.add", function push(feature) Set.add(this, feature));
- if (util.haveGecko("2b"))
+ if (this.haveGecko("2b"))
Set.add(this.features, "Gecko2");
this.timeout(function () {
@@ -40,7 +40,7 @@ var ConfigBase = Class("ConfigBase", {
highlight.loadCSS(this.CSS.replace(/__MSG_(.*?)__/g, function (m0, m1) _(m1)));
highlight.loadCSS(this.helpCSS.replace(/__MSG_(.*?)__/g, function (m0, m1) _(m1)));
- if (!util.haveGecko("2b"))
+ if (!this.haveGecko("2b"))
highlight.loadCSS(<![CDATA[
!TabNumber font-weight: bold; margin: 0px; padding-right: .8ex;
!TabIconNumber {
@@ -60,12 +60,12 @@ var ConfigBase = Class("ConfigBase", {
let elem = services.appShell.hiddenDOMWindow.document.createElement("div");
elem.style.cssText = this.cssText;
- let style = util.computedStyle(elem);
let keys = iter(Styles.propertyIter(this.cssText)).map(function (p) p.name).toArray();
let bg = keys.some(function (k) /^background/.test(k));
let fg = keys.indexOf("color") >= 0;
+ let style = DOM(elem).style;
prefs[bg ? "safeSet" : "safeReset"]("ui.textHighlightBackground", hex(style.backgroundColor));
prefs[fg ? "safeSet" : "safeReset"]("ui.textHighlightForeground", hex(style.color));
};
@@ -149,6 +149,87 @@ var ConfigBase = Class("ConfigBase", {
},
/**
+ * A list of all known registered chrome and resource packages.
+ */
+ get chromePackages() {
+ // Horrible hack.
+ let res = {};
+ function process(manifest) {
+ for each (let line in manifest.split(/\n+/)) {
+ let match = /^\s*(content|skin|locale|resource)\s+([^\s#]+)\s/.exec(line);
+ if (match)
+ res[match[2]] = true;
+ }
+ }
+ function processJar(file) {
+ let jar = services.ZipReader(file);
+ if (jar) {
+ if (jar.hasEntry("chrome.manifest"))
+ process(File.readStream(jar.getInputStream("chrome.manifest")));
+ jar.close();
+ }
+ }
+
+ for each (let dir in ["UChrm", "AChrom"]) {
+ dir = File(services.directory.get(dir, Ci.nsIFile));
+ if (dir.exists() && dir.isDirectory())
+ for (let file in dir.iterDirectory())
+ if (/\.manifest$/.test(file.leafName))
+ process(file.read());
+
+ dir = File(dir.parent);
+ if (dir.exists() && dir.isDirectory())
+ for (let file in dir.iterDirectory())
+ if (/\.jar$/.test(file.leafName))
+ processJar(file);
+
+ dir = dir.child("extensions");
+ if (dir.exists() && dir.isDirectory())
+ for (let ext in dir.iterDirectory()) {
+ if (/\.xpi$/.test(ext.leafName))
+ processJar(ext);
+ else {
+ if (ext.isFile())
+ ext = File(ext.read().replace(/\n*$/, ""));
+ let mf = ext.child("chrome.manifest");
+ if (mf.exists())
+ process(mf.read());
+ }
+ }
+ }
+ return Object.keys(res).sort();
+ },
+
+ /**
+ * Returns true if the current Gecko runtime is of the given version
+ * or greater.
+ *
+ * @param {string} ver The required version.
+ * @returns {boolean}
+ */
+ haveGecko: function (ver) services.versionCompare.compare(services.runtime.platformVersion, ver) >= 0,
+
+ /** Dactyl's notion of the current operating system platform. */
+ OS: memoize({
+ _arch: services.runtime.OS,
+ /**
+ * @property {string} The normalised name of the OS. This is one of
+ * "Windows", "Mac OS X" or "Unix".
+ */
+ get name() this.isWindows ? "Windows" : this.isMacOSX ? "Mac OS X" : "Unix",
+ /** @property {boolean} True if the OS is Windows. */
+ get isWindows() this._arch == "WINNT",
+ /** @property {boolean} True if the OS is Mac OS X. */
+ get isMacOSX() this._arch == "Darwin",
+ /** @property {boolean} True if the OS is some other *nix variant. */
+ get isUnix() !this.isWindows && !this.isMacOSX,
+ /** @property {RegExp} A RegExp which matches illegal characters in path components. */
+ get illegalCharacters() this.isWindows ? /[<>:"/\\|?*\x00-\x1f]/g : /\//g,
+
+ get pathListSep() this.isWindows ? ";" : ":"
+ }),
+
+ /**
* @property {string} The pathname of the VCS repository clone's root
* directory if the application is running from one via an extension
* proxy file.
diff --git a/common/modules/finder.jsm b/common/modules/finder.jsm
index 6ff2caee..94f9e48a 100644
--- a/common/modules/finder.jsm
+++ b/common/modules/finder.jsm
@@ -403,8 +403,8 @@ var RangeFind = Class("RangeFind", {
focus: function focus() {
if (this.lastRange)
- var node = util.evaluateXPath(RangeFind.selectNodePath,
- this.lastRange.commonAncestorContainer).snapshotItem(0);
+ var node = DOM.XPath(RangeFind.selectNodePath,
+ this.lastRange.commonAncestorContainer).snapshotItem(0);
if (node) {
node.focus();
// Re-highlight collapsed selection
@@ -517,7 +517,7 @@ var RangeFind = Class("RangeFind", {
for (let frame in array.iterValues(win.frames)) {
let range = doc.createRange();
- if (util.computedStyle(frame.frameElement).visibility == "visible") {
+ if (DOM(frame.frameElement).style.visibility == "visible") {
range.selectNode(frame.frameElement);
pushRange(pageStart, RangeFind.endpoint(range, true));
pageStart = RangeFind.endpoint(range, false);
diff --git a/common/modules/io.jsm b/common/modules/io.jsm
index 59eb8d98..aeead9b3 100644
--- a/common/modules/io.jsm
+++ b/common/modules/io.jsm
@@ -299,7 +299,7 @@ var IO = Module("io", {
let rcFile1 = dir.child("." + config.name + "rc");
let rcFile2 = dir.child("_" + config.name + "rc");
- if (util.OS.isWindows)
+ if (config.OS.isWindows)
[rcFile1, rcFile2] = [rcFile2, rcFile1];
if (rcFile1.exists() && rcFile1.isFile())
@@ -393,9 +393,9 @@ var IO = Module("io", {
if (bin instanceof File || File.isAbsolutePath(bin))
return this.File(bin);
- let dirs = services.environment.get("PATH").split(util.OS.isWindows ? ";" : ":");
+ let dirs = services.environment.get("PATH").split(config.OS.isWindows ? ";" : ":");
// Windows tries the CWD first TODO: desirable?
- if (util.OS.isWindows)
+ if (config.OS.isWindows)
dirs = [io.cwd].concat(dirs);
for (let [, dir] in Iterator(dirs))
@@ -408,7 +408,7 @@ var IO = Module("io", {
// TODO: couldn't we just palm this off to the start command?
// automatically try to add the executable path extensions on windows
- if (util.OS.isWindows) {
+ if (config.OS.isWindows) {
let extensions = services.environment.get("PATHEXT").split(";");
for (let [, extension] in Iterator(extensions)) {
file = dir.child(bin + extension);
@@ -478,7 +478,7 @@ var IO = Module("io", {
system: function (command, input, callback) {
util.dactyl.echomsg(_("io.callingShell", command), 4);
- function escape(str) '"' + String.replace(str, /[\\"$]/g, "\\$&") + '"';
+ let { shellEscape } = util.closure;
return this.withTempFiles(function (stdin, stdout, cmd) {
if (input instanceof File)
@@ -505,17 +505,17 @@ var IO = Module("io", {
util.assert(shell, _("error.invalid", "'shell'"));
if (isArray(command))
- command = command.map(escape).join(" ");
+ command = command.map(shellEscape).join(" ");
// TODO: implement 'shellredir'
- if (util.OS.isWindows && !/sh/.test(shell.leafName)) {
+ if (config.OS.isWindows && !/sh/.test(shell.leafName)) {
command = "cd /D " + this.cwd.path + " && " + command + " > " + stdout.path + " 2>&1" + " < " + stdin.path;
var res = this.run(shell, shcf.split(/\s+/).concat(command), callback ? async : true);
}
else {
- cmd.write("cd " + escape(this.cwd.path) + "\n" +
- ["exec", ">" + escape(stdout.path), "2>&1", "<" + escape(stdin.path),
- escape(shell.path), shcf, escape(command)].join(" "));
+ cmd.write("cd " + shellEscape(this.cwd.path) + "\n" +
+ ["exec", ">" + shellEscape(stdout.path), "2>&1", "<" + shellEscape(stdin.path),
+ shellEscape(shell.path), shcf, shellEscape(command)].join(" "));
res = this.run("/bin/sh", ["-e", cmd.path], callback ? async : true);
}
@@ -556,7 +556,7 @@ var IO = Module("io", {
const rtpvar = config.idName + "_RUNTIME";
let rtp = services.environment.get(rtpvar);
if (!rtp) {
- rtp = "~/" + (util.OS.isWindows ? "" : ".") + config.name;
+ rtp = "~/" + (config.OS.isWindows ? "" : ".") + config.name;
services.environment.set(rtpvar, rtp);
}
return rtp;
@@ -644,7 +644,7 @@ var IO = Module("io", {
commands.add(["mks[yntax]"],
"Generate a Vim syntax file",
function (args) {
- let runtime = util.OS.isWindows ? "~/vimfiles/" : "~/.vim/";
+ let runtime = config.OS.isWindows ? "~/vimfiles/" : "~/.vim/";
let file = io.File(runtime + "syntax/" + config.name + ".vim");
if (args.length)
file = io.File(args[0]);
@@ -886,7 +886,7 @@ unlet s:cpo_save
completion.environment = function environment(context) {
context.title = ["Environment Variable", "Value"];
context.generate = function ()
- io.system(util.OS.isWindows ? "set" : "env")
+ io.system(config.OS.isWindows ? "set" : "env")
.output.split("\n")
.filter(function (line) line.indexOf("=") > 0)
.map(function (line) line.match(/([^=]+)=(.*)/).slice(1));
@@ -956,7 +956,7 @@ unlet s:cpo_save
completion.shellCommand = function shellCommand(context) {
context.title = ["Shell Command", "Path"];
context.generate = function () {
- let dirNames = services.environment.get("PATH").split(util.OS.isWindows ? ";" : ":");
+ let dirNames = services.environment.get("PATH").split(config.OS.pathListSep);
let commands = [];
for (let [, dirName] in Iterator(dirNames)) {
@@ -987,7 +987,7 @@ unlet s:cpo_save
if (!match.path) {
context.key = match.proto;
context.advance(match.proto.length);
- context.generate = function () util.chromePackages.map(function (p) [p, match.proto + p + "/"]);
+ context.generate = function () config.chromePackages.map(function (p) [p, match.proto + p + "/"]);
}
else if (match.scheme === "chrome") {
context.key = match.prefix;
@@ -1001,7 +1001,7 @@ unlet s:cpo_save
}
if (!match || match.scheme === "resource" && match.path)
if (/^(\.{0,2}|~)\/|^file:/.test(context.filter)
- || util.OS.isWindows && /^[a-z]:/i.test(context.filter)
+ || config.OS.isWindows && /^[a-z]:/i.test(context.filter)
|| util.getFile(context.filter)
|| io.isJarURL(context.filter))
completion.file(context, full);
@@ -1030,7 +1030,7 @@ unlet s:cpo_save
const { completion, options } = modules;
var shell, shellcmdflag;
- if (util.OS.isWindows) {
+ if (config.OS.isWindows) {
shell = "cmd.exe";
shellcmdflag = "/c";
}
@@ -1077,7 +1077,7 @@ unlet s:cpo_save
"string", shellcmdflag,
{
getter: function (value) {
- if (this.hasChanged || !util.OS.isWindows)
+ if (this.hasChanged || !config.OS.isWindows)
return value;
return /sh/.test(options["shell"]) ? "-c" : "/c";
}
diff --git a/common/modules/overlay.jsm b/common/modules/overlay.jsm
index 08c2829d..ec329f05 100644
--- a/common/modules/overlay.jsm
+++ b/common/modules/overlay.jsm
@@ -8,7 +8,7 @@ try {
Components.utils.import("resource://dactyl/bootstrap.jsm");
defineModule("overlay", {
- exports: ["ModuleBase"],
+ exports: ["ModuleBase", "overlay"],
require: ["config", "io", "services", "util"]
}, this);
@@ -28,11 +28,13 @@ var ModuleBase = Class("ModuleBase", {
var Overlay = Module("Overlay", {
init: function init() {
+ let overlay = this;
+
services["dactyl:"]; // Hack. Force module initialization.
config.loadStyles();
- util.overlayWindow(config.overlayChrome, function overlay(window) ({
+ util.overlayWindow(config.overlayChrome, function _overlay(window) ({
init: function onInit(document) {
/**
* @constructor Module
@@ -308,9 +310,15 @@ var Overlay = Module("Overlay", {
defineModule.loadLog.push("Loaded in " + (Date.now() - start) + "ms");
+ util.dump(overlay);
+
+ overlay.windows = array.uniq(overlay.windows.concat(window), true);
+
modules.events.listen(window, "unload", function onUnload() {
window.removeEventListener("unload", onUnload.wrapped, false);
+ overlay.windows = overlay.windows.filter(function (w) w != window);
+
for each (let mod in modules.moduleList.reverse()) {
mod.stale = true;
@@ -320,7 +328,19 @@ var Overlay = Module("Overlay", {
}, false);
}
}));
- }
+ },
+
+ /**
+ * The most recently active dactyl window.
+ */
+ get activeWindow() this.windows[0],
+
+ set activeWindow(win) this.windows = [win].concat(this.windows.filter(function (w) w != win)),
+
+ /**
+ * A list of extant dactyl windows.
+ */
+ windows: Class.memoize(function () [])
});
endModule();
diff --git a/common/modules/styles.jsm b/common/modules/styles.jsm
index 65c32ec2..d164c92e 100644
--- a/common/modules/styles.jsm
+++ b/common/modules/styles.jsm
@@ -702,7 +702,7 @@ var Styles = Module("Styles", {
}));
},
completion: function (dactyl, modules, window) {
- const names = Array.slice(util.computedStyle(window.document.createElement("div")));
+ const names = Array.slice(DOM(<div/>, window.document).style);
modules.completion.css = function (context) {
context.title = ["CSS Property"];
context.keys = { text: function (p) p + ":", description: function () "" };
diff --git a/common/modules/util.jsm b/common/modules/util.jsm
index 59ba36bd..eeae6adf 100644
--- a/common/modules/util.jsm
+++ b/common/modules/util.jsm
@@ -13,7 +13,7 @@ Components.utils.import("resource://dactyl/bootstrap.jsm");
defineModule("util", {
exports: ["$", "DOM", "FailedAssertion", "Math", "NS", "Point", "Util", "XBL", "XHTML", "XUL", "util"],
require: ["services"],
- use: ["commands", "config", "highlight", "messages", "storage", "template"]
+ use: ["commands", "config", "highlight", "messages", "overlay", "storage", "template"]
}, this);
var XBL = Namespace("xbl", "http://www.mozilla.org/xbl");
@@ -85,7 +85,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
}
},
- get activeWindow() this.windows[0],
+ activeWindow: deprecated("overlay.activeWindow", { get: function activeWindow() overlay.activeWindow }),
dactyl: update(function dactyl(obj) {
if (obj)
@@ -93,7 +93,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
return {
__noSuchMethod__: function (meth, args) {
- let win = util.activeWindow;
+ let win = overlay.activeWindow;
var dactyl = global && global.dactyl || win && win.dactyl;
if (!dactyl)
@@ -204,55 +204,6 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
return RegExp("[" + util.regexp.escape(list) + "]");
},
- get chromePackages() {
- // Horrible hack.
- let res = {};
- function process(manifest) {
- for each (let line in manifest.split(/\n+/)) {
- let match = /^\s*(content|skin|locale|resource)\s+([^\s#]+)\s/.exec(line);
- if (match)
- res[match[2]] = true;
- }
- }
- function processJar(file) {
- let jar = services.ZipReader(file);
- if (jar) {
- if (jar.hasEntry("chrome.manifest"))
- process(File.readStream(jar.getInputStream("chrome.manifest")));
- jar.close();
- }
- }
-
- for each (let dir in ["UChrm", "AChrom"]) {
- dir = File(services.directory.get(dir, Ci.nsIFile));
- if (dir.exists() && dir.isDirectory())
- for (let file in dir.iterDirectory())
- if (/\.manifest$/.test(file.leafName))
- process(file.read());
-
- dir = File(dir.parent);
- if (dir.exists() && dir.isDirectory())
- for (let file in dir.iterDirectory())
- if (/\.jar$/.test(file.leafName))
- processJar(file);
-
- dir = dir.child("extensions");
- if (dir.exists() && dir.isDirectory())
- for (let ext in dir.iterDirectory()) {
- if (/\.xpi$/.test(ext.leafName))
- processJar(ext);
- else {
- if (ext.isFile())
- ext = File(ext.read().replace(/\n*$/, ""));
- let mf = ext.child("chrome.manifest");
- if (mf.exists())
- process(mf.read());
- }
- }
- }
- return Object.keys(res).sort();
- },
-
/**
* Returns a shallow copy of *obj*.
*
@@ -451,79 +402,24 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
return stack.top;
},
- /**
- * 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);
+ compileMatcher: deprecated("DOM.compileMatcher", { get: function compileMatcher() DOM.compileMatcher }),
+ computedStyle: deprecated("DOM#style", function computedStyle(elem) DOM(elem).style),
+ domToString: deprecated("DOM.stringify", { get: function domToString() DOM.stringify }),
+ editableInputs: deprecated("DOM.editableInputs", { get: function editableInputs(elem) DOM.editableInputs }),
+ escapeHTML: deprecated("DOM.escapeHTML", { get: function escapeHTML(elem) DOM.escapeHTML }),
+ evaluateXPath: deprecated("DOM.XPath",
+ function evaluateXPath(path, elem, asIterator) DOM.XPath(path, elem || util.activeWindow.content.document, asIterator)),
+ isVisible: deprecated("DOM#isVisible", function isVisible(elem) DOM(elem).isVisible),
+ makeXPath: deprecated("DOM.makeXPath", { get: function makeXPath(elem) DOM.makeXPath }),
+ namespaces: deprecated("DOM.namespaces", { get: function namespaces(elem) DOM.namespaces }),
+ namespaceNames: deprecated("DOM.namespaceNames", { get: function namespaceNames(elem) DOM.namespaceNames }),
+ parseForm: deprecated("DOM#formData", function parseForm(elem) values(DOM(elem).formData).toArray()),
+ scrollIntoView: deprecated("DOM#scrollIntoView", function scrollIntoView(elem, alignWithTop) DOM(elem).scrollIntoView(alignWithTop)),
+ validateMatcher: deprecated("DOM.validateMatcher", { get: function validateMatcher() DOM.validateMatcher }),
- return update(
- function matcher(node) {
- if (matcher.xpath)
- for (let elem in util.evaluateXPath(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), util.evaluateXPath.resolver);
- else
- node.querySelector(value);
- return true;
- });
- },
-
- /**
- * Returns an object representing a Node's computed CSS style.
- *
- * @param {Node} node
- * @returns {Object}
- */
- computedStyle: function computedStyle(node) {
- while (!(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;
- },
+ chromePackages: deprecated("config.chromePackages", { get: function chromePackages() config.chromePackages }),
+ haveGecko: deprecated("config.haveGecko", { get: function haveGecko() config.closure.haveGecko }),
+ OS: deprecated("config.OS", { get: function OS() config.OS }),
/**
* Converts any arbitrary string into an URI object. Returns null on
@@ -614,44 +510,6 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
pattern.replace(/\\(.)/, function (m0, m1) chars.indexOf(m1) >= 0 ? m1 : m0),
/**
- * 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.
- */
- domToString: function (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;
- },
-
- /**
* Prints a message to the console. If *msg* is an object it is pretty
* printed.
*
@@ -688,25 +546,6 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
},
/**
- * 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 HTML special characters in *str* to the equivalent HTML
- * entities.
- *
- * @param {string} str
- * @returns {string}
- */
- escapeHTML: function escapeHTML(str) {
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;");
- },
-
- /**
* Escapes quotes, newline and tab characters in *str*. The returned string
* is delimited by *delimiter* or " if *delimiter* is not specified.
* {@see String#quote}.
@@ -721,92 +560,6 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
return delimiter + str.replace(/([\\'"])/g, "\\$1").replace("\n", "\\n", "g").replace("\t", "\\t", "g") + delimiter;
},
- /**
- * 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.
- * @default The current document.
- * @param {boolean} asIterator Whether to return the results as an
- * XPath iterator.
- * @returns {Object} Iterable result of the evaluation.
- */
- evaluateXPath: update(
- function evaluateXPath(expression, elem, asIterator) {
- try {
- if (!elem)
- elem = util.activeWindow.content.document;
- let doc = elem.ownerDocument || elem;
- if (isArray(expression))
- expression = util.makeXPath(expression);
-
- let result = doc.evaluate(expression, elem,
- evaluateXPath.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) (util.namespaces[prefix] || null)
- }),
-
- /**
- * Generates an XPath expression for the given element.
- *
- * @param {Element} elem The element for which to generate an XPath.
- * @returns {string}
- */
- generateXPath: function generateXPath(elem) {
- function quote(val) "'" + val.replace(/[\\']/g, "\\$&") + "'";
-
- let res = [];
- let doc = elem.ownerDocument;
- for (;; 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 this.namespaceNames)
- name = this.namespaceNames[elem.namespaceURI] + ":" + name;
- else
- name = "*[local-name()=" + quote(name) + " and namespace-uri()=" + quote(elem.namespaceURI) + "]";
-
- res.push(name + "[" + (1 + iter(this.evaluateXPath("./" + name, elem.parentNode)).indexOf(elem)) + "]");
- continue;
- }
- break;
- }
-
- return res.reverse().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()),
-
extend: function extend(dest) {
Array.slice(arguments, 1).filter(util.identity).forEach(function (src) {
for (let [k, v] in Iterator(src)) {
@@ -925,15 +678,6 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
},
/**
- * Returns true if the current Gecko runtime is of the given version
- * or greater.
- *
- * @param {string} ver The required version.
- * @returns {boolean}
- */
- haveGecko: function (ver) services.versionCompare.compare(services.runtime.platformVersion, ver) >= 0,
-
- /**
* Sends a synchronous or asynchronous HTTP request to *url* and returns
* the XMLHttpRequest object. If *callback* is specified the request is
* asynchronous and the *callback* is invoked with the object as its
@@ -1017,24 +761,6 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
*/
isDomainURL: function isDomainURL(url, domain) util.isSubdomain(util.getHost(url), domain),
- /** Dactyl's notion of the current operating system platform. */
- OS: memoize({
- _arch: services.runtime.OS,
- /**
- * @property {string} The normalised name of the OS. This is one of
- * "Windows", "Mac OS X" or "Unix".
- */
- get name() this.isWindows ? "Windows" : this.isMacOSX ? "Mac OS X" : "Unix",
- /** @property {boolean} True if the OS is Windows. */
- get isWindows() this._arch == "WINNT",
- /** @property {boolean} True if the OS is Mac OS X. */
- get isMacOSX() this._arch == "Darwin",
- /** @property {boolean} True if the OS is some other *nix variant. */
- get isUnix() !this.isWindows && !this.isMacOSX,
- /** @property {RegExp} A RegExp which matches illegal characters in path components. */
- get illegalCharacters() this.isWindows ? /[<>:"/\\|?*\x00-\x1f]/g : /\//g
- }),
-
/**
* Returns true if *host* is a subdomain of *domain*.
*
@@ -1050,17 +776,6 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
},
/**
- * Returns true if the given DOM node is currently visible.
- *
- * @param {Node} node
- * @returns {boolean}
- */
- isVisible: function (node) {
- let style = util.computedStyle(node);
- return style.visibility == "visible" && style.display != "none";
- },
-
- /**
* Iterates over all currently open documents, including all
* top-level window and sub-frames thereof.
*/
@@ -1110,20 +825,6 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
},
/**
- * 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 Buffer#evaluateXPath}.
- *
- * @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(" | ");
- },
-
- /**
* Creates a DTD fragment from the given object. Each property of
* the object is converted to an ENTITY declaration. SGML special
* characters other than ' and % are left intact.
@@ -1150,7 +851,6 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
* @param {string} uri
* @returns {nsIURI}
*/
- // FIXME: createURI needed too?
newURI: function newURI(uri, charset, base) this.withProperErrors("newURI", services.io, uri, charset, base),
/**
@@ -1181,43 +881,12 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
if (!isObject(object))
return String(object);
- function namespaced(node) {
- var ns = util.namespaceNames[node.namespaceURI] || /^(?:(.*?):)?/.exec(node.name)[0];
- if (!ns)
- return node.localName;
- if (color)
- return <><span highlight="HelpXMLNamespace">{ns}</span>{node.localName}</>
- return ns + ":" + node.localName;
- }
-
if (object instanceof Ci.nsIDOMElement) {
let elem = object;
if (elem.nodeType == elem.TEXT_NODE)
return elem.data;
- try {
- let hasChildren = elem.firstChild && (!/^\s*$/.test(elem.firstChild) || elem.firstChild.nextSibling)
- if (color)
- return <span highlight="HelpXMLBlock"><span highlight="HelpXMLTagStart">&lt;{
- namespaced(elem)} {
- template.map(array.iterValues(elem.attributes),
- function (attr)
- <span highlight="HelpXMLAttribute">{namespaced(attr)}</span> +
- <span highlight="HelpXMLString">{attr.value}</span>,
- <> </>)
- }{ !hasChildren ? "/>" : ">"
- }</span>{ !hasChildren ? "" : <>...</> +
- <span highlight="HtmlTagEnd">&lt;{namespaced(elem)}></span>
- }</span>;
-
- let tag = "<" + [namespaced(elem)].concat(
- [namespaced(a) + "=" + template.highlight(a.value, true)
- for ([i, a] in array.iterItems(elem.attributes))]).join(" ");
- return tag + (!hasChildren ? "/>" : ">...</" + namespaced(elem) + ">");
- }
- catch (e) {
- return {}.toString.call(elem);
- }
+ return DOM(elem).repr(color);
}
try { // for window.JSON
@@ -1487,68 +1156,6 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
},
/**
- * Parses the fields of a form and returns a URL/POST-data pair
- * that is the equivalent of submitting the form.
- *
- * @param {nsINode} field One of the fields of the given form.
- * @returns {array}
- */
- // Nuances gleaned from browser.jar/content/browser/browser.js
- parseForm: function parseForm(field) {
- function encode(name, value, param) {
- param = param ? "%s" : "";
- if (post)
- return name + "=" + encodeComponent(value + param);
- return encodeComponent(name) + "=" + encodeComponent(value) + param;
- }
-
- 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 (Set.has(util.editableInputs, elem.type)
- || /^(?: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, elems.join('&'), charset, elems];
- return [url + "?" + elems.join('&'), null, charset, elems];
- },
-
- /**
* A generator that returns the values between *start* and *end*, in *step*
* increments.
*
@@ -1790,19 +1397,6 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
},
/**
- * Scrolls an element into view if and only if it's not already
- * fully visible.
- *
- * @param {Node} elem The element to make visible.
- */
- scrollIntoView: function scrollIntoView(elem, alignWithTop) {
- let win = elem.ownerDocument.defaultView;
- let rect = elem.getBoundingClientRect();
- if (!(rect && rect.bottom <= win.innerHeight && rect.top >= 0 && rect.left < win.innerWidth && rect.right > 0))
- elem.scrollIntoView(arguments.length > 1 ? alignWithTop : Math.abs(rect.top) < Math.abs(win.innerHeight - rect.bottom));
- },
-
- /**
* Returns the selection controller for the given window.
*
* @param {Window} window
@@ -1814,6 +1408,12 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
.QueryInterface(Ci.nsISelectionController),
/**
+ * Escapes a string against shell meta-characters and argument
+ * separators.
+ */
+ shellEscape: function shellEscape(str) '"' + String.replace(str, /[\\"$]/g, "\\$&") + '"',
+
+ /**
* Suspend execution for at least *delay* milliseconds. Functions by
* yielding execution to the next item in the main event queue, and
* so may lead to unexpected call graphs, and long delays if another
@@ -2139,6 +1739,16 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
Array: array
});
+
+/**
+ * @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;
@@ -2153,12 +1763,15 @@ var DOM = Class("DOM", {
if (val == null)
;
else if (typeof val == "xml")
- this[length++] = util.xmlToDom(val, context);
- else if (val instanceof Ci.nsIDOMNode)
+ this[length++] = util.xmlToDom(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;
@@ -2171,6 +1784,8 @@ var DOM = Class("DOM", {
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);
@@ -2234,10 +1849,10 @@ var DOM = Class("DOM", {
if (callable(val))
return this.each(function (elem, i) {
- fn.call(this, munge(val.call(this, elem, i)), elem, i);
+ util.withProperErrors(fn, this, munge(val.call(this, elem, i)), elem, i);
}, self || this);
- fn.call(self || this, munge(val), this[0], 0);
+ util.withProperErrors(fn, self || this, munge(val), this[0], 0);
return this;
},
@@ -2276,8 +1891,16 @@ var DOM = Class("DOM", {
let res = this.Empty();
this.each(function (elem) {
- while((elem = fn.call(this, elem)) instanceof Ci.nsIDOMElement)
- res[res.length++] = 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;
},
@@ -2288,10 +1911,10 @@ var DOM = Class("DOM", {
for (let i = 0; i < this.length; i++) {
let tmp = fn.call(self || update(obj, [this[i]]), this[i], i);
- if ("length" in tmp)
+ if (tmp && "length" in tmp)
for (let j = 0; j < tmp.length; j++)
res[res.length++] = tmp[j];
- else
+ else if (tmp !== undefined)
res[res.length++] = tmp;
}
@@ -2311,6 +1934,15 @@ var DOM = Class("DOM", {
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,
@@ -2354,6 +1986,7 @@ var DOM = Class("DOM", {
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(" "));
}),
@@ -2371,7 +2004,213 @@ var DOM = Class("DOM", {
get rect() this[0].getBoundingClientRect(),
- get style() util.computedStyle(this[0]),
+ 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 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 <><span highlight="HelpXMLNamespace">{ns}</span>{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(<span highlight="HelpXMLBlock"><span highlight="HelpXMLTagStart">&lt;{
+ namespaced(elem)} {
+ template.map(array.iterValues(elem.attributes),
+ function (attr)
+ <span highlight="HelpXMLAttribute">{namespaced(attr)}</span> +
+ <span highlight="HelpXMLString">{attr.value}</span>,
+ <> </>)
+ }{ !hasChildren ? "/>" : ">"
+ }</span>{ !hasChildren ? "" : <>...</> +
+ <span highlight="HtmlTagEnd">&lt;{namespaced(elem)}></span>
+ }</span>);
+ 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 ? "/>" : ">...</" + namespaced(elem) + ">"));
+ }
+ }
+ catch (e) {
+ res.push({}.toString.call(elem));
+ }
+ }, this);
+ return template.map(res, util.identity, <>,</>);
+ },
attr: function attr(key, val) {
return this.attrNS("", key, val);
@@ -2574,7 +2413,32 @@ var DOM = Class("DOM", {
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.
@@ -2643,7 +2507,7 @@ var DOM = Class("DOM", {
* @param {Event} event The event to dispatch.
*/
dispatch: Class.memoize(function ()
- util.haveGecko("2b")
+ config.haveGecko("2b")
? function dispatch(target, event, extra) {
try {
this.feedingEvent = extra;
@@ -2674,7 +2538,184 @@ var DOM = Class("DOM", {
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 = { "'": "&apos;", '"': "&quot;", "%": "&#x25;", "&": "&amp;", "<": "&lt;", ">": "&gt;" };
+ 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) {