// Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
// Copyright (c) 2007-2011 by Doug Kearns <dougkearns@gmail.com>
-// Copyright (c) 2008-2011 by Kris Maglione <maglione.k@gmail.com>
+// Copyright (c) 2008-2014 Kris Maglione <maglione.k@gmail.com>
//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
try {
-Components.utils.import("resource://dactyl/bootstrap.jsm");
- let frag=1;
defineModule("util", {
- exports: ["frag", "FailedAssertion", "Math", "NS", "Point", "Util", "XBL", "XHTML", "XUL", "util"],
- require: ["services"],
- use: ["commands", "config", "highlight", "messages", "storage", "template"]
-}, this);
+ exports: ["DOM", "$", "FailedAssertion", "Math", "NS", "Point", "Util", "XBL", "XHTML", "XUL", "util"],
+ require: ["dom", "promises", "services"]
+});
+
+lazyRequire("overlay", ["overlay"]);
+lazyRequire("storage", ["File", "storage"]);
+lazyRequire("template", ["template"]);
+
+var Magic = Class("Magic", {
+ init: function init(str) {
+ this.str = str;
+ },
+
+ get message() this.str,
-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;
+ toString: function () this.str
+});
var FailedAssertion = Class("FailedAssertion", ErrorBase, {
init: function init(message, level, noTrace) {
noTrace: true
});
-var Point = Struct("x", "y");
+var Point = Struct("Point", "x", "y");
-var wrapCallback = function wrapCallback(fn) {
- fn.wrapper = function wrappedCallback () {
- try {
- return fn.apply(this, arguments);
- }
- catch (e) {
- util.reportError(e);
- return undefined;
- }
- };
+var wrapCallback = function wrapCallback(fn, isEvent) {
+ if (!fn.wrapper)
+ fn.wrapper = function wrappedCallback() {
+ try {
+ let res = fn.apply(this, arguments);
+ if (isEvent && res === false) {
+ arguments[0].preventDefault();
+ arguments[0].stopPropagation();
+ }
+ return res;
+ }
+ catch (e) {
+ util.reportError(e);
+ return undefined;
+ }
+ };
fn.wrapper.wrapped = fn;
return fn.wrapper;
}
-var getAttr = function getAttr(elem, ns, name)
- elem.hasAttributeNS(ns, name) ? elem.getAttributeNS(ns, name) : null;
-var setAttr = function setAttr(elem, ns, name, val) {
- if (val == null)
- elem.removeAttributeNS(ns, name);
- else
- elem.setAttributeNS(ns, name, val);
-}
-
var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), {
- init: function () {
+ Magic: Magic,
+
+ init: function init() {
this.Array = array;
this.addObserver(this);
- this.overlays = {};
+ this.windows = [];
},
- cleanup: function cleanup() {
- for (let { document: doc } in iter(services.windowMediator.getEnumerator(null))) {
- for (let elem in values(doc.dactylOverlayElements || []))
- if (elem.parentNode)
- elem.parentNode.removeChild(elem);
+ activeWindow: deprecated("overlay.activeWindow", { get: function activeWindow() overlay.activeWindow }),
+ overlayObject: deprecated("overlay.overlayObject", { get: function overlayObject() overlay.bound.overlayObject }),
+ overlayWindow: deprecated("overlay.overlayWindow", { get: function overlayWindow() overlay.bound.overlayWindow }),
+
+ 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 }),
- for (let [elem, ns, name, orig, value] in values(doc.dactylOverlayAttributes || []))
- if (getAttr(elem, ns, name) === value)
- setAttr(elem, ns, name, orig);
+ map: deprecated("iter.map", function map(obj, fn, self) iter(obj).map(fn, self).toArray()),
+ writeToClipboard: deprecated("dactyl.clipboardWrite", function writeToClipboard(str, verbose) util.dactyl.clipboardWrite(str, verbose)),
+ readFromClipboard: deprecated("dactyl.clipboardRead", function readFromClipboard() util.dactyl.clipboardRead(false)),
- delete doc.dactylOverlayElements;
- delete doc.dactylOverlayAttributes;
- delete doc.dactylOverlays;
- }
- },
+ chromePackages: deprecated("config.chromePackages", { get: function chromePackages() config.chromePackages }),
+ haveGecko: deprecated("config.haveGecko", { get: function haveGecko() config.bound.haveGecko }),
+ OS: deprecated("config.OS", { get: function OS() config.OS }),
- // FIXME: Only works for Pentadactyl
- get activeWindow() services.windowMediator.getMostRecentWindow("navigator:browser"),
dactyl: update(function dactyl(obj) {
if (obj)
var global = Class.objectGlobal(obj);
+
return {
- __noSuchMethod__: function (meth, args) {
- let win = util.activeWindow;
+ __noSuchMethod__: function __noSuchMethod__(meth, args) {
+ let win = overlay.activeWindow;
+
var dactyl = global && global.dactyl || win && win.dactyl;
if (!dactyl)
return null;
}
};
}, {
- __noSuchMethod__: function () this().__noSuchMethod__.apply(null, arguments)
+ __noSuchMethod__: function __noSuchMethod__() this().__noSuchMethod__.apply(null, arguments)
}),
/**
if (!obj.observers)
obj.observers = obj.observe;
+ let cleanup = ["dactyl-cleanup-modules", "quit-application"];
+
function register(meth) {
- for (let target in Set(["dactyl-cleanup-modules", "quit-application"].concat(Object.keys(obj.observers))))
+ for (let target of RealSet(cleanup.concat(Object.keys(obj.observers))))
try {
services.observer[meth](obj, target, true);
}
Class.replaceProperty(obj, "observe",
function (subject, target, data) {
try {
- if (target == "quit-application" || target == "dactyl-cleanup-modules")
+ if (~cleanup.indexOf(target))
register("removeObserver");
if (obj.observers[target])
obj.observers[target].call(obj, subject, data);
}
});
- obj.observe.unregister = function () register("removeObserver");
+ obj.observe.unregister = () => register("removeObserver");
register("addObserver");
}, { dump: dump, Error: Error }),
* @param {string} message The message to present to the
* user on failure.
*/
- assert: function (condition, message, quiet) {
+ assert: function assert(condition, message, quiet) {
if (!condition)
throw FailedAssertion(message, 1, quiet === undefined ? true : quiet);
return condition;
},
+ /**
+ * CamelCases a -non-camel-cased identifier name.
+ *
+ * @param {string} name The name to mangle.
+ * @returns {string} The mangled name.
+ */
+ camelCase: function camelCase(name) String.replace(name, /-(.)/g,
+ (m, m1) => m1.toUpperCase()),
+
/**
* Capitalizes the first character of the given string.
* @param {string} str The string to capitalize
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*.
*
function frame() update(
function _frame(obj)
- _frame === stack.top || _frame.valid(obj) ?
- _frame.elements.map(function (e) callable(e) ? e(obj) : e).join("") : "",
+ _frame === stack.top || _frame.valid(obj)
+ ? _frame.elements.map(e => callable(e) ? e(obj) : e)
+ .join("")
+ : "",
{
elements: [],
seen: {},
- valid: function (obj) this.elements.every(function (e) !e.test || e.test(obj))
+ valid: function valid(obj) this.elements.every(e => !e.test || e.test(obj))
});
let end = 0;
char = char.toLowerCase();
stack.top.elements.push(update(
- function (obj) obj[char] != null ? quote(obj, char) : "",
- { test: function (obj) obj[char] != null }));
+ function (obj) obj[char] != null ? quote(obj, char)
+ : "",
+ { test: function test(obj) obj[char] != null }));
for (let elem in array.iterValues(stack))
elem.seen[char] = true;
let unknown = util.identity;
if (!keepUnknown)
- unknown = function () "";
+ unknown = () => "";
function frame() update(
function _frame(obj)
- _frame === stack.top || _frame.valid(obj) ?
- _frame.elements.map(function (e) callable(e) ? e(obj) : e).join("") : "",
+ _frame === stack.top || _frame.valid(obj)
+ ? _frame.elements.map(e => callable(e) ? e(obj) : e)
+ .join("")
+ : "",
{
elements: [],
- seen: {},
- valid: function (obj) this.elements.every(function (e) !e.test || e.test(obj))
+ seen: RealSet(),
+ valid: function valid(obj) this.elements.every(e => (!e.test || e.test(obj)))
});
let defaults = { lt: "<", gt: ">" };
- let re = util.regexp(<![CDATA[
+ let re = util.regexp(literal(/*
([^]*?) // 1
(?:
(<\{) | // 2
(< ((?:[a-z]-)?[a-z-]+?) (?:\[([0-9]+)\])? >) | // 3 4 5
(\}>) // 6
)
- ]]>, "gixy");
+ */), "gixy");
macro = String(macro);
let end = 0;
for (let match in re.iterate(macro)) {
}
else if (close) {
stack.pop();
- util.assert(stack.length, /*L*/"Unmatched %] in macro");
+ util.assert(stack.length, /*L*/"Unmatched }> in macro");
}
else {
let [, flags, name] = /^((?:[a-z]-)*)(.*)/.exec(macro);
- flags = Set(flags);
+ flags = RealSet(flags);
let quote = util.identity;
- if (flags.q)
+ if (flags.has("q"))
quote = function quote(obj) typeof obj === "number" ? obj : String.quote(obj);
- if (flags.e)
+ if (flags.has("e"))
quote = function quote(obj) "";
- if (Set.has(defaults, name))
+ if (hasOwnProperty(defaults, name))
stack.top.elements.push(quote(defaults[name]));
else {
+ let index = idx;
if (idx) {
idx = Number(idx) - 1;
stack.top.elements.push(update(
- function (obj) obj[name] != null && idx in obj[name] ? quote(obj[name][idx]) : Set.has(obj, name) ? "" : unknown(full),
- { test: function (obj) obj[name] != null && idx in obj[name] && obj[name][idx] !== false && (!flags.e || obj[name][idx] != "") }));
+ obj => obj[name] != null && idx in obj[name] ? quote(obj[name][idx])
+ : hasOwnProperty(obj, name) ? "" : unknown(full),
+ {
+ test: function test(obj) obj[name] != null && idx in obj[name]
+ && obj[name][idx] !== false
+ && (!flags.e || obj[name][idx] != "")
+ }));
}
else {
stack.top.elements.push(update(
- function (obj) obj[name] != null ? quote(obj[name]) : Set.has(obj, name) ? "" : unknown(full),
- { test: function (obj) obj[name] != null && obj[name] !== false && (!flags.e || obj[name] != "") }));
+ obj => obj[name] != null ? quote(obj[name])
+ : hasOwnProperty(obj, name) ? "" : unknown(full),
+ {
+ test: function test(obj) obj[name] != null
+ && obj[name] !== false
+ && (!flags.e || obj[name] != "")
+ }));
}
for (let elem in array.iterValues(stack))
- elem.seen[name] = true;
+ elem.seen.add(name);
}
}
}
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);
-
- 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;
- },
-
/**
* Converts any arbitrary string into an URI object. Returns null on
* failure.
*/
createURI: function createURI(str) {
try {
- return services.urifixup.createFixupURI(str, services.urifixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP);
+ let uri = services.urifixup.createFixupURI(str, services.urifixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP);
+ uri instanceof Ci.nsIURL;
+ return uri;
}
catch (e) {
return null;
* @returns [string] The resulting strings.
*/
debrace: function debrace(pattern) {
- if (isArray(pattern)) {
+ try {
+ if (isArray(pattern)) {
+ // JƤgermonkey hates us.
+ let obj = ({
+ res: [],
+ rec: function rec(acc) {
+ let vals;
+
+ while (isString(vals = pattern[acc.length]))
+ acc.push(vals);
+
+ if (acc.length == pattern.length)
+ this.res.push(acc.join(""));
+ else
+ for (let val in values(vals))
+ this.rec(acc.concat(val));
+ }
+ });
+ obj.rec([]);
+ return obj.res;
+ }
+
+ if (!pattern.contains("{"))
+ return [pattern];
+
let res = [];
- let rec = function rec(acc) {
- let vals;
- while (isString(vals = pattern[acc.length]))
- acc.push(vals);
+ let split = function split(pattern, re, fn, dequote) {
+ let end = 0, match, res = [];
+ while (match = re.exec(pattern)) {
+ end = match.index + match[0].length;
+ res.push(match[1]);
+ if (fn)
+ fn(match);
+ }
+ res.push(pattern.substr(end));
+ return res.map(s => util.dequote(s, dequote));
+ };
+
+ let patterns = [];
+ let substrings = split(pattern, /((?:[^\\{]|\\.)*)\{((?:[^\\}]|\\.)*)\}/gy,
+ function (match) {
+ patterns.push(split(match[2], /((?:[^\\,]|\\.)*),/gy,
+ null, ",{}"));
+ }, "{}");
- if (acc.length == pattern.length)
- res.push(acc.join(""))
+ let rec = function rec(acc) {
+ if (acc.length == patterns.length)
+ res.push(array(substrings).zip(acc).flatten().join(""));
else
- for (let val in values(vals))
- rec(acc.concat(val));
- }
+ for (let [, pattern] in Iterator(patterns[acc.length]))
+ rec(acc.concat(pattern));
+ };
rec([]);
return res;
}
-
- if (pattern.indexOf("{") == -1)
- return [pattern];
-
- function split(pattern, re, fn, dequote) {
- let end = 0, match, res = [];
- while (match = re.exec(pattern)) {
- end = match.index + match[0].length;
- res.push(match[1]);
- if (fn)
- fn(match);
- }
- res.push(pattern.substr(end));
- return res.map(function (s) util.dequote(s, dequote));
+ catch (e if e.message && e.message.contains("res is undefined")) {
+ // prefs.safeSet() would be reset on :rehash
+ prefs.set("javascript.options.methodjit.chrome", false);
+ util.dactyl.warn(_(UTF8("error.damnYouJƤgermonkey")));
+ return [];
}
- let patterns = [];
- let substrings = split(pattern, /((?:[^\\{]|\\.)*)\{((?:[^\\}]|\\.)*)\}/gy,
- function (match) {
- patterns.push(split(match[2], /((?:[^\\,]|\\.)*),/gy,
- null, ",{}"));
- }, "{}");
+ },
- let res = [];
- function rec(acc) {
- if (acc.length == patterns.length)
- res.push(array(substrings).zip(acc).flatten().join(""));
- else
- for (let [, pattern] in Iterator(patterns[acc.length]))
- rec(acc.concat(pattern));
- }
- rec([]);
- return res;
+ /**
+ * Briefly delay the execution of the passed function.
+ *
+ * @param {function} callback The function to delay.
+ */
+ delay: function delay(callback) {
+ let { mainThread } = services.threading;
+ mainThread.dispatch(callback,
+ mainThread.DISPATCH_NORMAL);
},
/**
* @returns {string}
*/
dequote: function dequote(pattern, chars)
- pattern.replace(/\\(.)/, function (m0, m1) chars.indexOf(m1) >= 0 ? m1 : m0),
+ pattern.replace(/\\(.)/, (m0, m1) => chars.contains(m1) ? 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.
+ * Returns the nsIDocShell for the given window.
+ *
+ * @param {Window} win The window for which to get the docShell.
+ * @returns {nsIDocShell}
*/
- 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;
- },
+ docShell: function docShell(win)
+ win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell),
/**
* Prints a message to the console. If *msg* is an object it is pretty
* @param {string} stack The stack trace from an Error.
* @returns {[string]} The stack frames.
*/
- stackLines: function (stack) {
+ stackLines: function stackLines(stack) {
let lines = [];
let match, re = /([^]*?)@([^@\n]*)(?:\n|$)/g;
while (match = re.exec(stack))
* @param {string} msg The trace message.
* @param {number} frames The number of frames to print.
*/
- dumpStack: function dumpStack(msg, frames) {
+ dumpStack: function dumpStack(msg="Stack", frames=null) {
let stack = util.stackLines(Error().stack);
stack = stack.slice(1, 1 + (frames || stack.length)).join("\n").replace(/^/gm, " ");
- util.dump((arguments.length == 0 ? "Stack" : msg) + "\n" + stack + "\n");
- },
-
- /**
- * 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, "&").replace(/</g, "<");
+ util.dump(msg + "\n" + stack + "\n");
},
/**
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) ({
- xul: XUL.uri,
- xhtml: XHTML.uri,
- xhtml2: "http://www.w3.org/2002/06/xhtml2",
- dactyl: NS.uri
- }[prefix] || null)
- }),
-
- extend: function extend(dest) {
- Array.slice(arguments, 1).filter(util.identity).forEach(function (src) {
- for (let [k, v] in Iterator(src)) {
- let get = src.__lookupGetter__(k),
- set = src.__lookupSetter__(k);
- if (!get && !set)
- dest[k] = v;
- if (get)
- dest.__defineGetter__(k, get);
- if (set)
- dest.__defineSetter__(k, set);
- }
- });
- return dest;
- },
-
/**
* Converts *bytes* to a pretty printed data size string.
*
*/
formatSeconds: function formatSeconds(seconds) {
function pad(n, val) ("0000000" + val).substr(-Math.max(n, String(val).length));
- function div(num, denom) [Math.round(num / denom), Math.round(num % denom)];
+ function div(num, denom) [Math.floor(num / denom), Math.round(num % denom)];
let days, hours, minutes;
- [minutes, seconds] = div(seconds, 60);
+ [minutes, seconds] = div(Math.round(seconds), 60);
[hours, minutes] = div(minutes, 60);
[days, hours] = div(hours, 24);
if (days)
- return /*L*/days + " days " + hours + " hours"
+ return /*L*/days + " days " + hours + " hours";
if (hours)
return /*L*/hours + "h " + minutes + "m";
if (minutes)
getFile: function getFile(uri) {
try {
if (isString(uri))
- uri = util.newURI(util.fixURI(uri));
+ uri = util.newURI(uri);
if (uri instanceof Ci.nsIFileURL)
return File(uri.file);
+ if (uri instanceof Ci.nsIFile)
+ return File(uri);
+
let channel = services.io.newChannelFromURI(uri);
- channel.cancel(Cr.NS_BINDING_ABORTED);
+ try { channel.cancel(Cr.NS_BINDING_ABORTED); } catch (e) {}
if (channel instanceof Ci.nsIFileChannel)
return File(channel.file);
}
* @param {string} url
* @returns {string|null}
*/
- getHost: function (url) {
+ getHost: function getHost(url) {
try {
return util.createURI(url).host;
}
return null;
},
- /**
- * 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
* argument.
*
* @param {string} url
- * @param {function(XMLHttpRequest)} callback
+ * @param {object} params Optional parameters for this request:
+ * method: {string} The request method. @default "GET"
+ *
+ * params: {object} Parameters to append to *url*'s query string.
+ * data: {*} POST data to send to the server. Ordinary objects
+ * are converted to FormData objects, with one datum
+ * for each property/value pair.
+ *
+ * onload: {function(XMLHttpRequest, Event)} The request's load event handler.
+ * onerror: {function(XMLHttpRequest, Event)} The request's error event handler.
+ * callback: {function(XMLHttpRequest, Event)} An event handler
+ * called for either error or load events.
+ *
+ * background: {boolean} Whether to perform the request in the
+ * background. @default true
+ *
+ * mimeType: {string} Override the response mime type with the
+ * given value.
+ * responseType: {string} Override the type of the "response"
+ * property.
+ *
+ * headers: {objects} Extra request headers.
+ *
+ * user: {string} The user name to send via HTTP Authentication.
+ * pass: {string} The password to send via HTTP Authentication.
+ *
+ * quiet: {boolean} If true, don't report errors.
+ *
* @returns {XMLHttpRequest}
*/
- httpGet: function httpGet(url, callback, self) {
- let params = callback;
- if (!isObject(params))
- params = { callback: params && function () callback.apply(self, arguments) };
+ httpGet: function httpGet(url, params={}, self) {
+ if (callable(params))
+ // Deprecated.
+ params = { callback: params.bind(self) };
try {
let xmlhttp = services.Xmlhttp();
- xmlhttp.mozBackgroundRequest = true;
+ xmlhttp.mozBackgroundRequest = hasOwnProperty(params, "background") ? params.background : true;
let async = params.callback || params.onload || params.onerror;
if (async) {
- xmlhttp.onload = function handler(event) { util.trapErrors(params.onload || params.callback, params, xmlhttp, event) };
- xmlhttp.onerror = function handler(event) { util.trapErrors(params.onerror || params.callback, params, xmlhttp, event) };
+ xmlhttp.addEventListener("load", event => { util.trapErrors(params.onload || params.callback, params, xmlhttp, event); }, false);
+ xmlhttp.addEventListener("error", event => { util.trapErrors(params.onerror || params.callback, params, xmlhttp, event); }, false);
}
+
+ if (isObject(params.params)) {
+ let data = [encodeURIComponent(k) + "=" + encodeURIComponent(v)
+ for ([k, v] in iter(params.params))];
+ let uri = util.newURI(url);
+ uri.query += (uri.query ? "&" : "") + data.join("&");
+
+ url = uri.spec;
+ }
+
+ if (isObject(params.data) && !(params.data instanceof Ci.nsISupports)) {
+ let data = services.FormData();
+ for (let [k, v] in iter(params.data))
+ data.append(k, v);
+ params.data = data;
+ }
+
if (params.mimeType)
xmlhttp.overrideMimeType(params.mimeType);
- xmlhttp.open(params.method || "GET", url, async,
- params.user, params.pass);
+ let args = [params.method || "GET", url, async];
+ if (params.user != null || params.pass != null)
+ args.push(params.user);
+ if (params.pass != null)
+ args.push(prams.pass);
+ xmlhttp.open.apply(xmlhttp, args);
+
+ for (let [header, val] in Iterator(params.headers || {}))
+ xmlhttp.setRequestHeader(header, val);
- xmlhttp.send(null);
+ if (params.responseType)
+ xmlhttp.responseType = params.responseType;
+
+ if (params.notificationCallbacks)
+ xmlhttp.channel.notificationCallbacks = params.notificationCallbacks;
+
+ xmlhttp.send(params.data);
return xmlhttp;
}
catch (e) {
- util.dactyl.log(_("error.cantOpen", String.quote(url), e), 1);
+ if (!params.quiet)
+ util.reportError(e);
return null;
}
},
+ /**
+ * Like #httpGet, but returns a promise rather than accepting
+ * callbacks.
+ *
+ * @param {string} url The URL to fetch.
+ * @param {object} params Parameter object, as in #httpGet.
+ */
+ fetchUrl: promises.withCallbacks(function fetchUrl([accept, reject, deferred], url, params) {
+ params = update({}, params);
+ params.onload = accept;
+ params.onerror = reject;
+
+ let req = this.httpGet(url, params);
+ promises.oncancel(deferred, req.cancel);
+ }),
+
/**
* The identity function.
*
* @param {Object} r2
* @returns {Object}
*/
- intersection: function (r1, r2) ({
+ intersection: function intersection(r1, r2) ({
get width() this.right - this.left,
get height() this.bottom - this.top,
left: Math.max(r1.left, r2.left),
* @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);
*/
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*.
*
return idx > -1 && idx + domain.length == host.length && (idx == 0 || host[idx - 1] == ".");
},
- /**
- * 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.
*/
- iterDocuments: function iterDocuments() {
+ iterDocuments: function iterDocuments(types) {
+ types = types ? types.map(s => "type" + util.capitalize(s))
+ : ["typeChrome", "typeContent"];
+
let windows = services.windowMediator.getXULWindowEnumerator(null);
while (windows.hasMoreElements()) {
let window = windows.getNext().QueryInterface(Ci.nsIXULWindow);
- for each (let type in ["typeChrome", "typeContent"]) {
+ for (let type of types) {
let docShells = window.docShell.getDocShellEnumerator(Ci.nsIDocShellTreeItem[type],
Ci.nsIDocShell.ENUMERATE_FORWARDS);
while (docShells.hasMoreElements())
let (viewer = docShells.getNext().QueryInterface(Ci.nsIDocShell).contentViewer) {
if (viewer)
yield viewer.DOMDocument;
- }
+ };
}
}
},
// ripped from Firefox; modified
- unsafeURI: Class.memoize(function () util.regexp(String.replace(<![CDATA[
+ unsafeURI: Class.Memoize(() => util.regexp(String.replace(literal(/*
[
\s
// Invisible characters (bug 452979)
// Bidi formatting characters. (RFC 3987 sections 3.2 and 4.1 paragraph 6)
U200E U200F U202A U202B U202C U202D U202E
]
- ]]>, /U/g, "\\u"),
+ */), /U/g, "\\u"),
"gx")),
losslessDecodeURI: function losslessDecodeURI(url) {
return url.split("%25").map(function (url) {
catch (e) {
return url;
}
- }, this).join("%25");
- },
-
- /**
- * 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(" | ");
+ }, this).join("%25").replace(/[\s.,>)]$/, encodeURIComponent);
},
/**
* for *obj*.
*/
makeDTD: let (map = { "'": "'", '"': """, "%": "%", "&": "&", "<": "<", ">": ">" })
- function makeDTD(obj) iter(obj)
- .map(function ([k, v]) ["<!ENTITY ", k, " '", String.replace(v == null ? "null" : typeof v == "xml" ? v.toXMLString() : v,
- typeof v == "xml" ? /['%]/g : /['"%&<>]/g,
- function (m) map[m]),
- "'>"].join(""))
- .join("\n"),
+ function makeDTD(obj) {
+ function escape(val) {
+ let isDOM = DOM.isJSONXML(val);
+ return String.replace(val == null ? "null" :
+ isDOM ? DOM.toXML(val)
+ : val,
+ isDOM ? /['%]/g
+ : /['"%&<>]/g,
+ m => map[m]);
+ }
- map: deprecated("iter.map", function map(obj, fn, self) iter(obj).map(fn, self).toArray()),
- writeToClipboard: deprecated("dactyl.clipboardWrite", function writeToClipboard(str, verbose) util.dactyl.clipboardWrite(str, verbose)),
- readFromClipboard: deprecated("dactyl.clipboardRead", function readFromClipboard() util.dactyl.clipboardRead(false)),
+ return iter(obj).map(([k, v]) =>
+ ["<!ENTITY ", k, " '", escape(v), "'>"].join(""))
+ .join("\n");
+ },
/**
* Converts a URI string into a URI object.
* @param {string} uri
* @returns {nsIURI}
*/
- // FIXME: createURI needed too?
- newURI: function newURI(uri, charset, base) this.withProperErrors("newURI", services.io, uri, charset, base),
+ newURI: function newURI(uri, charset, base) {
+ if (uri instanceof Ci.nsIURI)
+ var res = uri.clone();
+ else {
+ let idx = uri.lastIndexOf(" -> ");
+ if (~idx)
+ uri = uri.slice(idx + 4);
+
+ res = this.withProperErrors("newURI", services.io, uri, charset, base);
+ }
+ res instanceof Ci.nsIURL;
+ return res;
+ },
/**
* Removes leading garbage prepended to URIs by the subscript
* @returns {string}
*/
objectToString: function objectToString(object, color) {
- // Use E4X literals so html is automatically quoted
- // only when it's asked for. No one wants to see <
- // on their console or :map :foo in their buffer
- // when they expect :map <C-f> :foo.
- XML.prettyPrinting = false;
- XML.ignoreWhitespace = false;
-
if (object == null)
return object + "\n";
if (!isObject(object))
return String(object);
- function namespaced(node) {
- var ns = NAMESPACES[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) {
- const NAMESPACES = array.toObject([
- [NS, "dactyl"],
- [XHTML, "html"],
- [XUL, "xul"]
- ]);
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"><{
- 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"><{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
catch (e) {
obj = Object.prototype.toString.call(obj);
}
- obj = template.highlightFilter(util.clip(obj, 150), "\n", !color ? function () "^J" : function () <span highlight="NonText">^J</span>);
- let string = <><span highlight="Title Object">{obj}</span>::
</>;
+
+ if (color) {
+ obj = template.highlightFilter(util.clip(obj, 150), "\n",
+ () => ["span", { highlight: "NonText" },
+ "^J"]);
+
+ var head = ["span", { highlight: "Title Object" }, obj, "::\n"];
+ }
+ else
+ head = util.clip(obj, 150).replace(/\n/g, "^J") + "::\n";
let keys = [];
// window.content often does not want to be queried with "var i in object"
try {
let hasValue = !("__iterator__" in object || isinstance(object, ["Generator", "Iterator"]));
+
if (object.dactyl && object.modules && object.modules.modules == object.modules) {
object = Iterator(object);
hasValue = false;
}
- for (let i in object) {
- let value = <![CDATA[<no value>]]>;
+
+ let keyIter = object;
+ if (iter.iteratorProp in object) {
+ keyIter = (k for (k of object));
+ hasValue = false;
+ }
+ else if ("__iterator__" in object && !callable(object.__iterator__))
+ keyIter = keys(object);
+
+ for (let i in keyIter) {
+ let value = Magic("<no value>");
try {
value = object[i];
}
catch (e) {}
+
if (!hasValue) {
if (isArray(i) && i.length == 2)
[i, value] = i;
- else
+ else {
var noVal = true;
+ value = i;
+ }
}
- value = template.highlight(value, true, 150);
- let key = <span highlight="Key">{i}</span>;
+ let key = i;
if (!isNaN(i))
i = parseInt(i);
else if (/^[A-Z_]+$/.test(i))
i = "";
- keys.push([i, <>{key}{noVal ? "" : <>: {value}</>}
</>]);
+
+ if (color)
+ value = template.highlight(value, true, 150, !color);
+ else if (value instanceof Magic)
+ value = String(value);
+ else
+ value = util.clip(String(value).replace(/\n/g, "^J"), 150);
+
+ if (noVal)
+ var val = value;
+ else if (color)
+ val = [["span", { highlight: "Key" }, key], ": ", value];
+ else
+ val = key + ": " + value;
+
+ keys.push([i, val]);
}
}
- catch (e) {}
+ catch (e) {
+ util.reportError(e);
+ }
function compare(a, b) {
if (!isNaN(a[0]) && !isNaN(b[0]))
return a[0] - b[0];
return String.localeCompare(a[0], b[0]);
}
- string += template.map(keys.sort(compare), function (f) f[1]);
- return color ? <div style="white-space: pre-wrap;">{string}</div> : [s for each (s in string)].join("");
- },
- observers: {
- "dactyl-cleanup-modules": function (subject, reason) {
- defineModule.loadLog.push("dactyl: util: observe: dactyl-cleanup-modules " + reason);
+ let vals = template.map(keys.sort(compare), f => f[1],
+ "\n");
- for (let module in values(defineModule.modules))
- if (module.cleanup) {
- util.dump("cleanup: " + module.constructor.className);
- util.trapErrors(module.cleanup, module, reason);
- }
-
- JSMLoader.cleanup();
-
- if (!this.rehashing)
- services.observer.addObserver(this, "dactyl-rehash", true);
- },
- "dactyl-rehash": function () {
- services.observer.removeObserver(this, "dactyl-rehash");
-
- defineModule.loadLog.push("dactyl: util: observe: dactyl-rehash");
- if (!this.rehashing)
- for (let module in values(defineModule.modules)) {
- defineModule.loadLog.push("dactyl: util: init(" + module + ")");
- if (module.reinit)
- module.reinit();
- else
- module.init();
- }
- },
- "dactyl-purge": function () {
- this.rehashing = 1;
- },
-
- "toplevel-window-ready": function (window, data) {
- window.addEventListener("DOMContentLoaded", wrapCallback(function listener(event) {
- if (event.originalTarget === window.document) {
- window.removeEventListener("DOMContentLoaded", listener.wrapper, true);
- util._loadOverlays(window);
- }
- }), true);
- },
- "chrome-document-global-created": function (window, uri) { this.observe(window, "toplevel-window-ready", null); },
- "content-document-global-created": function (window, uri) { this.observe(window, "toplevel-window-ready", null); }
- },
-
- _loadOverlays: function _loadOverlays(window) {
- if (!window.dactylOverlays)
- window.dactylOverlays = [];
-
- for each (let obj in util.overlays[window.document.documentURI] || []) {
- if (window.dactylOverlays.indexOf(obj) >= 0)
- continue;
- window.dactylOverlays.push(obj);
- this._loadOverlay(window, obj(window));
+ if (color) {
+ return ["div", { style: "white-space: pre-wrap" }, head, vals];
}
+ return head + vals.join("");
},
- _loadOverlay: function _loadOverlay(window, obj) {
- let doc = window.document;
- if (!doc.dactylOverlayElements) {
- doc.dactylOverlayElements = [];
- doc.dactylOverlayAttributes = [];
- }
-
- function overlay(key, fn) {
- if (obj[key]) {
- let iterator = Iterator(obj[key]);
- if (!isObject(obj[key]))
- iterator = ([elem.@id, elem.elements(), elem.@*::*.(function::name() != "id")] for each (elem in obj[key]));
-
- for (let [elem, xml, attr] in iterator) {
- if (elem = doc.getElementById(elem)) {
- let node = util.xmlToDom(xml, doc, obj.objects);
- if (!(node instanceof Ci.nsIDOMDocumentFragment))
- doc.dactylOverlayElements.push(node);
- else
- for (let n in array.iterValues(node.childNodes))
- doc.dactylOverlayElements.push(n);
-
- fn(elem, node);
- for each (let attr in attr || []) {
- let ns = attr.namespace(), name = attr.localName();
- doc.dactylOverlayAttributes.push([elem, ns, name, getAttr(elem, ns, name), String(attr)]);
- if (attr.name() != "highlight")
- elem.setAttributeNS(ns, name, String(attr));
- else
- highlight.highlightNode(elem, String(attr));
- }
- }
- }
- }
- }
-
- overlay("before", function (elem, dom) elem.parentNode.insertBefore(dom, elem));
- overlay("after", function (elem, dom) elem.parentNode.insertBefore(dom, elem.nextSibling));
- overlay("append", function (elem, dom) elem.appendChild(dom));
- overlay("prepend", function (elem, dom) elem.insertBefore(dom, elem.firstChild));
- if (obj.init)
- obj.init(window);
-
- if (obj.load)
- if (doc.readyState === "complete")
- obj.load(window);
- else
- doc.addEventListener("load", wrapCallback(function load(event) {
- if (event.originalTarget === event.target) {
- doc.removeEventListener("load", load.wrapper, true);
- obj.load(window, event);
- }
- }), true);
- },
+ prettifyJSON: function prettifyJSON(data, indent, invalidOK) {
+ const INDENT = indent || " ";
- /**
- * Overlays an object with the given property overrides. Each
- * property in *overrides* is added to *object*, replacing any
- * original value. Functions in *overrides* are augmented with the
- * new properties *super*, *supercall*, and *superapply*, in the
- * same manner as class methods, so that they man call their
- * overridden counterparts.
- *
- * @param {object} object The object to overlay.
- * @param {object} overrides An object containing properties to
- * override.
- * @returns {function} A function which, when called, will remove
- * the overlay.
- */
- overlayObject: function (object, overrides) {
- let original = Object.create(object);
- overrides = update(Object.create(original), overrides);
-
- Object.getOwnPropertyNames(overrides).forEach(function (k) {
- let orig, desc = Object.getOwnPropertyDescriptor(overrides, k);
- if (desc.value instanceof Class.Property)
- desc = desc.value.init(k) || desc.value;
-
- if (k in object) {
- for (let obj = object; obj && !orig; obj = Object.getPrototypeOf(obj))
- if (orig = Object.getOwnPropertyDescriptor(obj, k))
- Object.defineProperty(original, k, orig);
-
- if (!orig)
- if (orig = Object.getPropertyDescriptor(object, k))
- Object.defineProperty(original, k, orig);
+ function rec(data, level, seen) {
+ if (isObject(data)) {
+ seen = RealSet(seen);
+ if (seen.add(data))
+ throw Error("Recursive object passed");
}
- // Guard against horrible add-ons that use eval-based monkey
- // patching.
- let value = desc.value;
- if (callable(desc.value)) {
-
- delete desc.value;
- delete desc.writable;
- desc.get = function get() value;
- desc.set = function set(val) {
- if (!callable(val) || Function.prototype.toString(val).indexOf(sentinel) < 0)
- Class.replaceProperty(this, k, val);
- else {
- let package_ = util.newURI(util.fixURI(Components.stack.caller.filename)).host;
- util.reportError(Error(_("error.monkeyPatchOverlay", package_)));
- util.dactyl.echoerr(_("error.monkeyPatchOverlay", package_));
- }
- };
- }
+ let prefix = level + INDENT;
- try {
- Object.defineProperty(object, k, desc);
+ if (data === undefined)
+ data = null;
- if (callable(value)) {
- let sentinel = "(function DactylOverlay() {}())"
- value.toString = function toString() toString.toString.call(this).replace(/\}?$/, sentinel + "; $&");
- value.toSource = function toSource() toSource.toSource.call(this).replace(/\}?$/, sentinel + "; $&");
- }
- }
- catch (e) {
- try {
- if (value) {
- object[k] = value;
- return;
+ if (~["boolean", "number"].indexOf(typeof data) || data === null)
+ res.push(String(data));
+ else if (isinstance(data, ["String", _]))
+ res.push(JSON.stringify(String(data)));
+ else if (isArray(data)) {
+ if (data.length == 0)
+ res.push("[]");
+ else {
+ res.push("[\n");
+ for (let [i, val] in Iterator(data)) {
+ if (i)
+ res.push(",\n");
+ res.push(prefix);
+ rec(val, prefix, seen);
}
+ res.push("\n", level, "]");
}
- catch (f) {}
- util.reportError(e);
}
- }, this);
-
- return function unwrap() {
- for each (let k in Object.getOwnPropertyNames(original))
- if (Object.getOwnPropertyDescriptor(object, k).configurable)
- Object.defineProperty(object, k, Object.getOwnPropertyDescriptor(original, k));
- else {
- try {
- object[k] = original[k];
- }
- catch (e) {}
+ else if (isObject(data)) {
+ res.push("{\n");
+
+ let i = 0;
+ for (let [key, val] in Iterator(data)) {
+ if (i++)
+ res.push(",\n");
+ res.push(prefix, JSON.stringify(key), ": ");
+ rec(val, prefix, seen);
}
- };
- },
-
- overlayWindow: function (url, fn) {
- if (url instanceof Ci.nsIDOMWindow)
- util._loadOverlay(url, fn);
- else {
- Array.concat(url).forEach(function (url) {
- if (!this.overlays[url])
- this.overlays[url] = [];
- this.overlays[url].push(fn);
- }, this);
-
- for (let doc in util.iterDocuments())
- if (["interactive", "complete"].indexOf(doc.readyState) >= 0)
- this._loadOverlays(doc.defaultView);
+ if (i > 0)
+ res.push("\n", level, "}");
else
- this.observe(doc.defaultView, "toplevel-window-ready");
- }
- },
-
- /**
- * 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;
+ res[res.length - 1] = "{}";
+ }
+ else if (invalidOK)
+ res.push({}.toString.call(data));
+ else
+ throw Error("Invalid JSON object");
}
- let form = field.form;
- let doc = form.ownerDocument;
+ let res = [];
+ rec(data, "", RealSet());
+ return res.join("");
+ },
- 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;
- }
- }
+ observers: {
+ "dactyl-cleanup-modules": function cleanupModules(subject, reason) {
+ defineModule.loadLog.push("dactyl: util: observe: dactyl-cleanup-modules " + reason);
- 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));
+ for (let module in values(defineModule.modules))
+ if (module.cleanup) {
+ util.dump("cleanup: " + module.constructor.className);
+ util.trapErrors(module.cleanup, module, reason);
}
- }
-
- if (post)
- return [url, elems.join('&'), charset, elems];
- return [url + "?" + elems.join('&'), null, charset, elems];
+ }
},
/**
*
* This is similar to Perl's extended regular expression format.
*
- * @param {string|XML} expr The expression to compile into a RegExp.
+ * @param {string} expr The expression to compile into a RegExp.
* @param {string} flags Flags to apply to the new RegExp.
* @param {object} tokens The tokens to substitute. @optional
* @returns {RegExp} A custom regexp object.
expr = String.replace(expr, /\\(.)/, function (m, m1) {
if (m1 === "c")
flags = flags.replace(/i/g, "") + "i";
- else if (m === "C")
+ else if (m1 === "C")
flags = flags.replace(/i/g, "");
else
return m;
// Replace replacement <tokens>.
if (tokens)
- expr = String.replace(expr, /(\(?P)?<(\w+)>/g, function (m, n1, n2) !n1 && Set.has(tokens, n2) ? tokens[n2].dactylSource || tokens[n2].source || tokens[n2] : m);
+ expr = String.replace(expr, /(\(?P)?<(\w+)>/g,
+ (m, n1, n2) => !n1 && hasOwnProperty(tokens, n2) ? tokens[n2].dactylSource
+ || tokens[n2].source
+ || tokens[n2]
+ : m);
// Strip comments and white space.
if (/x/.test(flags))
- expr = String.replace(expr, /(\\.)|\/\/[^\n]*|\/\*[^]*?\*\/|\s+/gm, function (m, m1) m1 || "");
+ expr = String.replace(expr, /(\\.)|\/\/[^\n]*|\/\*[^]*?\*\/|\s+/gm,
+ (m, m1) => m1 || "");
// Replace (?P<named> parameters)
if (/\(\?P</.test(expr)) {
}
let res = update(RegExp(expr, flags.replace("x", "")), {
- closure: Class.Property(Object.getOwnPropertyDescriptor(Class.prototype, "closure")),
+ bound: Class.Property(Object.getOwnPropertyDescriptor(Class.prototype, "bound")),
+ closure: Class.Property(Object.getOwnPropertyDescriptor(Class.prototype, "bound")),
dactylPropertyNames: ["exec", "match", "test", "toSource", "toString", "global", "ignoreCase", "lastIndex", "multiLine", "source", "sticky"],
- iterate: function (str, idx) util.regexp.iterate(this, str, idx)
+ iterate: function iterate(str, idx) util.regexp.iterate(this, str, idx)
});
// Return a struct with properties for named parameters if we
* @param {RegExp} re The regexp showable source of which is to be returned.
* @returns {string}
*/
- getSource: function regexp_getSource(re) re.source.replace(/\\(.)/g, function (m0, m1) m1 === "/" ? "/" : m0),
+ getSource: function regexp_getSource(re) re.source.replace(/\\(.)/g,
+ (m0, m1) => m1 === "/" ? m1
+ : m0),
/**
* Iterates over all matches of the given regexp in the given
}())
}),
+ /**
+ * Flushes the startup or jar cache.
+ */
+ flushCache: function flushCache(file) {
+ if (file)
+ services.observer.notifyObservers(file, "flush-cache-entry", "");
+ else
+ services.observer.notifyObservers(null, "startupcache-invalidate", "");
+ },
+
/**
* Reloads dactyl in entirety by disabling the add-on and
* re-enabling it.
*/
- rehash: function (args) {
- storage.session.commandlineArgs = args;
+ rehash: function rehash(args) {
+ storage.storeForSession("commandlineArgs", args);
this.timeout(function () {
- services.observer.notifyObservers(null, "startupcache-invalidate", "");
- this.rehashing = true;
+ this.flushCache();
+ cache.flush(bind("test", /^literal:/));
let addon = config.addon;
addon.userDisabled = true;
addon.userDisabled = false;
},
errorCount: 0,
- errors: Class.memoize(function () []),
+ errors: Class.Memoize(() => []),
maxErrors: 15,
/**
* Reports an error to the Error Console and the standard output,
* along with a stack trace and other relevant information. The
* error is appended to {@seeĀ #errors}.
*/
- reportError: function (error) {
+ reportError: function reportError(error) {
if (error.noTrace)
return;
if (isString(error))
error = Error(error);
- if (Cu.reportError)
- Cu.reportError(error);
+ Cu.reportError(error);
try {
this.errorCount++;
let obj = update({}, error, {
toString: function () String(error),
- stack: <>{util.stackLines(String(error.stack || Error().stack)).join("\n").replace(/^/mg, "\t")}</>
+ stack: Magic(util.stackLines(String(error.stack || Error().stack)).join("\n").replace(/^/mg, "\t"))
});
+ services.console.logStringMessage(obj.stack);
+
this.errors.push([new Date, obj + "\n" + obj.stack]);
this.errors = this.errors.slice(-this.maxErrors);
this.errors.toString = function () [k + "\n" + v for ([k, v] in array.iterValues(this))].join("\n\n");
let ary = host.split(".");
ary = [ary.slice(i).join(".") for (i in util.range(ary.length, 0, -1))];
- return ary.filter(function (h) h.length >= base.length);
- },
-
- /**
- * 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));
+ return ary.filter(h => h.length >= base.length);
},
/**
* @param {Window} window
* @returns {nsISelectionController}
*/
- selectionController: function (win)
+ selectionController: function selectionController(win)
win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsISelectionDisplay)
.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
*
* @param {number} delay The time period for which to sleep in milliseconds.
*/
- sleep: function (delay) {
+ sleep: function sleep(delay) {
let mainThread = services.threading.mainThread;
let end = Date.now() + delay;
* @param {number} limit The maximum number of elements to return.
* @returns {[string]}
*/
- split: function (str, re, limit) {
+ split: function split(str, re, limit) {
re.lastIndex = 0;
if (!re.global)
re = RegExp(re.source || re, "g");
* interrupted by pressing <C-c>, in which case,
* Error("Interrupted") will be thrown.
*/
- threadYield: function (flush, interruptable) {
+ threadYield: function threadYield(flush, interruptable) {
this.yielders++;
try {
let mainThread = services.threading.mainThread;
* Waits for the function *test* to return true, or *timeout*
* milliseconds to expire.
*
- * @param {function} test The predicate on which to wait.
+ * @param {function|Promise} test The predicate on which to wait.
* @param {object} self The 'this' object for *test*.
* @param {Number} timeout The maximum number of milliseconds to
* wait.
* thrown.
*/
waitFor: function waitFor(test, self, timeout, interruptable) {
+ if (!callable(test)) {
+ let done = false;
+ var promise = test,
+ retVal;
+ promise.then((arg) => { retVal = arg; done = true; },
+ (arg) => { retVal = arg; done = true; });
+ test = () => done;
+ }
+
let end = timeout && Date.now() + timeout, result;
let timer = services.Timer(function () {}, 10, services.Timer.TYPE_REPEATING_SLACK);
finally {
timer.cancel();
}
- return result;
+ return promise ? retVal: result;
},
/**
* @returns {function} A new function which may not execute
* synchronously.
*/
- yieldable: function yieldable(func)
+ yieldable: deprecated("Task.spawn", function yieldable(func)
function magic() {
let gen = func.apply(this, arguments);
(function next() {
}
catch (e if e instanceof StopIteration) {};
})();
- },
+ }),
/**
* Wraps a callback function such that its errors are not lost. This
* @param {function} func The function to call
* @param {object} self The 'this' object for the function.
*/
- trapErrors: function trapErrors(func, self) {
+ trapErrors: function trapErrors(func, self, ...args) {
try {
if (!callable(func))
func = self[func];
- return func.apply(self || this, Array.slice(arguments, 2));
+ return func.apply(self || this, args);
}
catch (e) {
- util.reportError(e);
+ this.reportError(e);
return undefined;
}
},
* @param {nsIDOMWindow} win The window for which to find domains.
* @returns {[string]} The visible domains.
*/
- visibleHosts: function (win) {
- let res = [], seen = {};
+ visibleHosts: function visibleHosts(win) {
+ let res = [],
+ seen = RealSet();
(function rec(frame) {
try {
if (frame.location.hostname)
catch (e) {}
Array.forEach(frame.frames, rec);
})(win);
- return res.filter(function (h) !Set.add(seen, h));
+ return res.filter(h => !seen.add(h));
},
/**
* @param {nsIDOMWindow} win The window for which to find URIs.
* @returns {[nsIURI]} The visible URIs.
*/
- visibleURIs: function (win) {
- let res = [], seen = {};
+ visibleURIs: function visibleURIs(win) {
+ let res = [],
+ seen = RealSet();
(function rec(frame) {
try {
res = res.concat(util.newURI(frame.location.href));
catch (e) {}
Array.forEach(frame.frames, rec);
})(win);
- return res.filter(function (h) !Set.add(seen, h.spec));
+ return res.filter(h => !seen.add(h.spec));
+ },
+
+ /**
+ * Like Cu.getWeakReference, but won't crash if you pass null.
+ */
+ weakReference: function weakReference(jsval) {
+ if (jsval == null)
+ return { get: function get() null };
+ return Cu.getWeakReference(jsval);
},
/**
* @param {object} self The 'this' object of the method.
* @param ... Arguments to pass to *meth*.
*/
- withProperErrors: function withProperErrors(meth, self) {
+ withProperErrors: function withProperErrors(meth, self, ...args) {
try {
- return (callable(meth) ? meth : self[meth]).apply(self, Array.slice(arguments, withProperErrors.length));
+ return (callable(meth) ? meth : self[meth]).apply(self, args);
}
catch (e) {
throw e.stack ? e : Error(e);
}
- },
-
- /**
- * 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}
- */
- xmlToDom: function xmlToDom(node, doc, nodes) {
- XML.prettyPrinting = false;
- if (typeof node === "string") // Sandboxes can't currently pass us XML objects.
- node = XML(node);
-
- if (node.length() != 1) {
- let domnode = doc.createDocumentFragment();
- for each (let child in node)
- domnode.appendChild(xmlToDom(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(xmlToDom(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;
- }
}
}, {
Array: array
* @singleton
*/
var GlobalMath = Math;
-var Math = update(Object.create(GlobalMath), {
+this.Math = update(Object.create(GlobalMath), {
/**
* Returns the specified *value* constrained to the range *min* - *max*.
*
} catch(e){ if (!e.stack) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
-// vim: set fdm=marker sw=4 ts=4 et ft=javascript:
+// vim: set fdm=marker sw=4 sts=4 ts=8 et ft=javascript: