defineModule("util", {
exports: ["frag", "FailedAssertion", "Math", "NS", "Point", "Util", "XBL", "XHTML", "XUL", "util"],
require: ["services"],
- use: ["commands", "config", "highlight", "storage", "template"]
+ use: ["commands", "config", "highlight", "messages", "storage", "template"]
}, this);
var XBL = Namespace("xbl", "http://www.mozilla.org/xbl");
*
* @param {object} obj
*/
- addObserver: function (obj) {
+ addObserver: update(function addObserver(obj) {
if (!obj.observers)
obj.observers = obj.observe;
function register(meth) {
- for (let target in set(["dactyl-cleanup-modules", "quit-application"].concat(Object.keys(obj.observers))))
+ for (let target in Set(["dactyl-cleanup-modules", "quit-application"].concat(Object.keys(obj.observers))))
try {
services.observer[meth](obj, target, true);
}
}
catch (e) {
if (typeof util === "undefined")
- dump("dactyl: error: " + e + "\n" + (e.stack || Error().stack).replace(/^/gm, "dactyl: "));
+ addObserver.dump("dactyl: error: " + e + "\n" + (e.stack || addObserver.Error().stack).replace(/^/gm, "dactyl: "));
else
util.reportError(e);
}
obj.observe.unregister = function () register("removeObserver");
register("addObserver");
- },
+ }, { dump: dump, Error: Error }),
/*
* Tests a condition and throws a FailedAssertion error on
assert: function (condition, message, quiet) {
if (!condition)
throw FailedAssertion(message, 1, quiet === undefined ? true : quiet);
+ return condition;
},
/**
* @param {string} str The string to capitalize
* @returns {string}
*/
- capitalize: function capitalize(str) str && str[0].toUpperCase() + str.slice(1),
+ capitalize: function capitalize(str) str && str[0].toUpperCase() + str.slice(1).toLowerCase(),
/**
* Returns a RegExp object that matches characters specified in the range
// check for chars not in the accepted range
this.assert(RegExp("^[" + accepted + "-]+$").test(list),
- "Character list outside the range " + accepted.quote());
+ _("error.charactersOutsideRange", accepted.quote()));
// check for illegal ranges
for (let [match] in this.regexp.iterate(/.-./g, list))
this.assert(match.charCodeAt(0) <= match.charCodeAt(2),
- "Invalid character range: " + list.slice(list.indexOf(match)))
+ _("error.invalidCharacterRange", list.slice(list.indexOf(match))));
return RegExp("[" + util.regexp.escape(list) + "]");
},
}
else if (char === "]") {
stack.pop();
- util.assert(stack.length, "Unmatched %] in format");
+ util.assert(stack.length, /*L*/"Unmatched %] in format");
}
else {
let quote = function quote(obj, char) obj[char];
if (end < format.length)
stack.top.elements.push(format.substr(end));
- util.assert(stack.length === 1, "Unmatched %[ in format");
+ util.assert(stack.length === 1, /*L*/"Unmatched %[ in format");
return stack.top;
},
+ /**
+ * Compiles a macro string into a function which generates a string
+ * result based on the input *macro* and its parameters. The
+ * definitive documentation for macro strings resides in :help
+ * macro-string.
+ *
+ * Macro parameters may have any of the following flags:
+ * e: The parameter is only tested for existence. Its
+ * interpolation is always empty.
+ * q: The result is quoted such that it is parsed as a single
+ * argument by the Ex argument parser.
+ *
+ * The returned function has the following additional properties:
+ *
+ * seen {set}: The set of parameters used in this macro.
+ *
+ * valid {function(object)}: Returns true if every parameter of
+ * this macro is provided by the passed object.
+ *
+ * @param {string} macro The macro string to compile.
+ * @param {boolean} keepUnknown If true, unknown macro parameters
+ * are left untouched. Otherwise, they are replaced with the null
+ * string.
+ * @returns {function}
+ */
compileMacro: function compileMacro(macro, keepUnknown) {
let stack = [frame()];
stack.__defineGetter__("top", function () this[this.length - 1]);
([^]*?) // 1
(?:
(<\{) | // 2
- (< ((?:[a-z]-)?[a-z-]+?) >) | // 3 4
- (\}>) // 5
+ (< ((?:[a-z]-)?[a-z-]+?) (?:\[([0-9]+)\])? >) | // 3 4 5
+ (\}>) // 6
)
]]>, "gixy");
macro = String(macro);
let end = 0;
for (let match in re.iterate(macro)) {
- let [, prefix, open, full, macro, close] = match;
+ let [, prefix, open, full, macro, idx, close] = match;
end += match[0].length;
if (prefix)
}
else if (close) {
stack.pop();
- util.assert(stack.length, "Unmatched %] in macro");
+ util.assert(stack.length, /*L*/"Unmatched %] in macro");
}
else {
let [, flags, name] = /^((?:[a-z]-)*)(.*)/.exec(macro);
- flags = set(flags);
+ flags = Set(flags);
let quote = util.identity;
if (flags.q)
if (flags.e)
quote = function quote(obj) "";
- if (set.has(defaults, name))
+ if (Set.has(defaults, name))
stack.top.elements.push(quote(defaults[name]));
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] != "") }));
+ 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] != "") }));
+ }
+ 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] != "") }));
+ }
for (let elem in array.iterValues(stack))
elem.seen[name] = true;
if (end < macro.length)
stack.top.elements.push(macro.substr(end));
- util.assert(stack.length === 1, "Unmatched <{ in macro");
+ util.assert(stack.length === 1, /*L*/"Unmatched <{ in macro");
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))
});
},
- validateMatcher: function validateMatcher(values) {
+ /**
+ * 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 = util.xmlToDom(<div/>, document);
- return this.testValues(values, function (value) {
+ let node = services.XMLDocument();
+ return this.testValues(list, function (value) {
if (/^xpath:/.test(value))
evaluator.createExpression(value.substr(6), util.evaluateXPath.resolver);
else
* Example:
* "a{b,c}d" => ["abd", "acd"]
*
- * @param {string} pattern The pattern to deglob.
+ * @param {string|[string|Array]} pattern The pattern to deglob.
* @returns [string] The resulting strings.
*/
debrace: function debrace(pattern) {
+ if (isArray(pattern)) {
+ let res = [];
+ let rec = function rec(acc) {
+ let vals;
+
+ while (isString(vals = pattern[acc.length]))
+ acc.push(vals);
+
+ if (acc.length == pattern.length)
+ res.push(acc.join(""))
+ else
+ for (let val in values(vals))
+ rec(acc.concat(val));
+ }
+ rec([]);
+ return res;
+ }
+
if (pattern.indexOf("{") == -1)
return [pattern];
res.push(pattern.substr(end));
return res.map(function (s) util.dequote(s, dequote));
}
- let patterns = [], res = [];
+ 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(""));
dequote: function dequote(pattern, chars)
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 "";
range.selectNode(node);
node = range;
}
- let doc = (node.getRangeAt ? node.getRangeAt(0) : node).startContainer.ownerDocument;
+ 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);
*/
dump: defineModule.dump,
+ /**
+ * Returns a list of reformatted stack frames from
+ * {@seeĀ Error#stack}.
+ *
+ * @param {string} stack The stack trace from an Error.
+ * @returns {[string]} The stack frames.
+ */
stackLines: function (stack) {
let lines = [];
let match, re = /([^]*?)@([^@\n]*)(?:\n|$)/g;
* The set of input element type attribute values that mark the element as
* an editable field.
*/
- editableInputs: set(["date", "datetime", "datetime-local", "email", "file",
+ editableInputs: Set(["date", "datetime", "datetime-local", "email", "file",
"month", "number", "password", "range", "search",
"tel", "text", "time", "url", "week"]),
[hours, minutes] = div(minutes, 60);
[days, hours] = div(hours, 24);
if (days)
- return days + " days " + hours + " hours"
+ return /*L*/days + " days " + hours + " hours"
if (hours)
- return hours + "h " + minutes + "m";
+ return /*L*/hours + "h " + minutes + "m";
if (minutes)
- return minutes + ":" + pad(2, seconds);
- return seconds + "s";
+ return /*L*/minutes + ":" + pad(2, seconds);
+ return /*L*/seconds + "s";
},
/**
uri = util.newURI(util.fixURI(uri));
if (uri instanceof Ci.nsIFileURL)
- return File(uri.QueryInterface(Ci.nsIFileURL).file);
+ return File(uri.file);
let channel = services.io.newChannelFromURI(uri);
channel.cancel(Cr.NS_BINDING_ABORTED);
if (channel instanceof Ci.nsIFileChannel)
- return File(channel.QueryInterface(Ci.nsIFileChannel).file);
+ return File(channel.file);
}
catch (e) {}
return null;
return xmlhttp;
}
catch (e) {
- util.dactyl.log("Error opening " + String.quote(url) + ": " + e, 1);
+ util.dactyl.log(_("error.cantOpen", String.quote(url), e), 1);
return null;
}
},
}
},
+ // ripped from Firefox; modified
+ unsafeURI: Class.memoize(function () util.regexp(String.replace(<![CDATA[
+ [
+ \s
+ // Invisible characters (bug 452979)
+ U001C U001D U001E U001F // file/group/record/unit separator
+ U00AD // Soft hyphen
+ UFEFF // BOM
+ U2060 // Word joiner
+ U2062 U2063 // Invisible times/separator
+ U200B UFFFC // Zero-width space/no-break space
+
+ // Bidi formatting characters. (RFC 3987 sections 3.2 and 4.1 paragraph 6)
+ U200E U200F U202A U202B U202C U202D U202E
+ ]
+ ]]>, /U/g, "\\u"),
+ "gx")),
+ losslessDecodeURI: function losslessDecodeURI(url) {
+ return url.split("%25").map(function (url) {
+ // Non-UTF-8 compliant URLs cause "malformed URI sequence" errors.
+ try {
+ return decodeURI(url).replace(this.unsafeURI, encodeURIComponent);
+ }
+ 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
*/
makeXPath: function makeXPath(nodes) {
return array(nodes).map(util.debrace).flatten()
- .map(function (node) [node, "xhtml:" + node]).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.
+ *
+ * @param {object} obj The object to convert.
+ * @returns {string} The DTD fragment containing entity declaration
+ * 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"),
+
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)),
* @returns {nsIURI}
*/
// FIXME: createURI needed too?
- newURI: function (uri, charset, base) services.io.newURI(uri, charset, base),
+ newURI: function newURI(uri, charset, base) this.withProperErrors("newURI", services.io, uri, charset, base),
/**
* Removes leading garbage prepended to URIs by the subscript
}</span>;
let tag = "<" + [namespaced(elem)].concat(
- [namespaced(a) + "=" + template.highlight(a.value, true)
+ [namespaced(a) + "=" + template.highlight(a.value, true)
for ([i, a] in array.iterItems(elem.attributes))]).join(" ");
return tag + (!hasChildren ? "/>" : ">...</" + namespaced(elem) + ">");
}
},
observers: {
- "dactyl-cleanup-modules": function () {
- defineModule.loadLog.push("dactyl: util: observe: dactyl-cleanup-modules");
+ "dactyl-cleanup-modules": function (subject, reason) {
+ defineModule.loadLog.push("dactyl: util: observe: dactyl-cleanup-modules " + reason);
for (let module in values(defineModule.modules))
if (module.cleanup) {
util.dump("cleanup: " + module.constructor.className);
- util.trapErrors(module.cleanup, module);
+ util.trapErrors(module.cleanup, module, reason);
}
JSMLoader.cleanup();
"dactyl-purge": function () {
this.rehashing = 1;
},
+
"toplevel-window-ready": function (window, data) {
window.addEventListener("DOMContentLoaded", wrapCallback(function listener(event) {
if (event.originalTarget === window.document) {
}), true);
},
+ /**
+ * 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);
if (desc.value instanceof Class.Property)
desc = desc.value.init(k) || desc.value;
- for (let obj = object; obj && !orig; obj = Object.getPrototypeOf(obj))
- if (orig = Object.getOwnPropertyDescriptor(obj, k))
- Object.defineProperty(original, k, orig);
+ 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);
+ }
// Guard against horrible add-ons that use eval-based monkey
// patching.
+ let value = desc.value;
if (callable(desc.value)) {
- let value = desc.value;
-
- let sentinel = "(function DactylOverlay() {}())"
- value.toString = function toString() toString.toString.call(this).replace(/\}?$/, sentinel + "; $&");
- value.toSource = function toSource() toString.toSource.call(this).replace(/\}?$/, sentinel + "; $&");
delete desc.value;
delete desc.writable;
desc.get = function get() value;
desc.set = function set(val) {
- if (String(val).indexOf(sentinel) < 0)
+ 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;
};
}
- Object.defineProperty(object, k, desc);
+ try {
+ Object.defineProperty(object, k, desc);
+
+ 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;
+ }
+ }
+ catch (f) {}
+ util.reportError(e);
+ }
}, this);
return function unwrap() {
for each (let k in Object.getOwnPropertyNames(original))
- Object.defineProperty(object, k, Object.getOwnPropertyDescriptor(original, k));
+ if (Object.getOwnPropertyDescriptor(object, k).configurable)
+ Object.defineProperty(object, k, Object.getOwnPropertyDescriptor(original, k));
+ else {
+ try {
+ object[k] = original[k];
+ }
+ catch (e) {}
+ }
};
},
if (field instanceof Ci.nsIDOMHTMLInputElement && field.type == "submit")
elems.push(encode(field.name, field.value));
- for (let [, elem] in iter(form.elements)) {
- if (set.has(util.editableInputs, elem.type)
- || /^(?:hidden|textarea)$/.test(elem.type)
- || 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 [, 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];
// 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, function (m, n1, n2) !n1 && Set.has(tokens, n2) ? tokens[n2].dactylSource || tokens[n2].source || tokens[n2] : m);
// Strip comments and white space.
if (/x/.test(flags))
}())
}),
+ /**
+ * Reloads dactyl in entirety by disabling the add-on and
+ * re-enabling it.
+ */
rehash: function (args) {
- JSMLoader.commandlineArgs = args;
+ storage.session.commandlineArgs = args;
this.timeout(function () {
+ services.observer.notifyObservers(null, "startupcache-invalidate", "");
this.rehashing = true;
let addon = config.addon;
addon.userDisabled = true;
errorCount: 0,
errors: Class.memoize(function () []),
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) {
if (error.noTrace)
return;
catch (e) {}
let ary = host.split(".");
- ary = [ary.slice(i).join(".") for (i in util.range(ary.length - 1, 0, -1))];
+ ary = [ary.slice(i).join(".") for (i in util.range(ary.length, 0, -1))];
return ary.filter(function (h) h.length >= base.length);
},
return true;
},
- highlightFilter: function highlightFilter(str, filter, highlight) {
- return this.highlightSubstrings(str, (function () {
- if (filter.length == 0)
- return;
- let lcstr = String.toLowerCase(str);
- let lcfilter = filter.toLowerCase();
- let start = 0;
- while ((start = lcstr.indexOf(lcfilter, start)) > -1) {
- yield [start, filter.length];
- start += filter.length;
- }
- })(), highlight || template.filter);
- },
-
/**
* Behaves like String.split, except that when *limit* is reached,
* the trailing element contains the entire trailing portion of the
},
yielders: 0,
+ /**
+ * Yields execution to the next event in the current thread's event
+ * queue. This is a potentially dangerous operation, since any
+ * yielders higher in the event stack will prevent execution from
+ * returning to the caller until they have finished their wait. The
+ * potential for deadlock is high.
+ *
+ * @param {boolean} flush If true, flush all events in the event
+ * queue before returning. Otherwise, wait for an event to
+ * process before proceeding.
+ * @param {boolean} interruptable If true, this yield may be
+ * interrupted by pressing <C-c>, in which case,
+ * Error("Interrupted") will be thrown.
+ */
threadYield: function (flush, interruptable) {
this.yielders++;
try {
do {
mainThread.processNextEvent(!flush);
if (util.interrupted)
- throw new Error("Interrupted");
+ throw Error("Interrupted");
}
while (flush === true && mainThread.hasPendingEvents());
}
}
},
+ /**
+ * Waits for the function *test* to return true, or *timeout*
+ * milliseconds to expire.
+ *
+ * @param {function} 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.
+ * @optional
+ * @param {boolean} interruptable If true, may be interrupted by
+ * pressing <C-c>, in which case, Error("Interrupted") will be
+ * thrown.
+ */
waitFor: function waitFor(test, self, timeout, interruptable) {
let end = timeout && Date.now() + timeout, result;
return result;
},
+ /**
+ * Makes the passed function yieldable. Each time the function calls
+ * yield, execution is suspended for the yielded number of
+ * milliseconds.
+ *
+ * Example:
+ * let func = yieldable(function () {
+ * util.dump(Date.now()); // 0
+ * yield 1500;
+ * util.dump(Date.now()); // 1500
+ * });
+ * func();
+ *
+ * @param {function} func The function to mangle.
+ * @returns {function} A new function which may not execute
+ * synchronously.
+ */
yieldable: function yieldable(func)
function magic() {
let gen = func.apply(this, arguments);
})();
},
+ /**
+ * Wraps a callback function such that its errors are not lost. This
+ * is useful for DOM event listeners, which ordinarily eat errors.
+ * The passed function has the property *wrapper* set to the new
+ * wrapper function, while the wrapper has the property *wrapped*
+ * set to the original callback.
+ *
+ * @param {function} callback The callback to wrap.
+ * @returns {function}
+ */
wrapCallback: wrapCallback,
/**
*/
trapErrors: function trapErrors(func, self) {
try {
- if (isString(func))
+ if (!callable(func))
func = self[func];
return func.apply(self || this, Array.slice(arguments, 2));
}
}
},
+ /**
+ * Returns the file path of a given *url*, for debugging purposes.
+ * If *url* points to a file (even if indirectly), the native
+ * filesystem path is returned. Otherwise, the URL itself is
+ * returned.
+ *
+ * @param {string} url The URL to mangle.
+ * @returns {string} The path to the file.
+ */
urlPath: function urlPath(url) {
try {
return util.getFile(url).path;
}
},
+ /**
+ * Returns a list of all domains and subdomains of documents in the
+ * given window and all of its descendant frames.
+ *
+ * @param {nsIDOMWindow} win The window for which to find domains.
+ * @returns {[string]} The visible domains.
+ */
visibleHosts: function (win) {
let res = [], seen = {};
(function rec(frame) {
try {
- res = res.concat(util.subdomains(frame.location.host));
+ if (frame.location.hostname)
+ res = res.concat(util.subdomains(frame.location.hostname));
}
catch (e) {}
Array.forEach(frame.frames, rec);
})(win);
- return res.filter(function (h) !set.add(seen, h));
+ return res.filter(function (h) !Set.add(seen, h));
},
+ /**
+ * Returns a list of URIs of documents in the given window and all
+ * of its descendant frames.
+ *
+ * @param {nsIDOMWindow} win The window for which to find URIs.
+ * @returns {[nsIURI]} The visible URIs.
+ */
visibleURIs: function (win) {
let res = [], seen = {};
(function rec(frame) {
catch (e) {}
Array.forEach(frame.frames, rec);
})(win);
- return res.filter(function (h) !set.add(seen, h.spec));
+ return res.filter(function (h) !Set.add(seen, h.spec));
+ },
+
+ /**
+ * Wraps native exceptions thrown by the called function so that a
+ * proper stack trace may be retrieved from them.
+ *
+ * @param {function|string} meth The method to call.
+ * @param {object} self The 'this' object of the method.
+ * @param ... Arguments to pass to *meth*.
+ */
+ withProperErrors: function withProperErrors(meth, self) {
+ try {
+ return (callable(meth) ? meth : self[meth]).apply(self, Array.slice(arguments, withProperErrors.length));
+ }
+ catch (e) {
+ throw e.stack ? e : Error(e);
+ }
},
/**