X-Git-Url: https://git.donarmstrong.com/dactyl.git?a=blobdiff_plain;f=common%2Fmodules%2Futil.jsm;h=9cc4a49e99779bb3f33bb891c3b54ae1ec2d0a96;hb=9044153cb63835e39b9de8ec4ade237c03e3888a;hp=7941ebf587249f94d4a90979432023c773cbe67a;hpb=70740024f9c028c1fd63e1a1850ab062ff956054;p=dactyl.git diff --git a/common/modules/util.jsm b/common/modules/util.jsm index 7941ebf..9cc4a49 100644 --- a/common/modules/util.jsm +++ b/common/modules/util.jsm @@ -4,23 +4,17 @@ // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. -"use strict"; +/* use strict */ 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"] + exports: ["DOM", "$", "FailedAssertion", "Math", "NS", "Point", "Util", "XBL", "XHTML", "XUL", "util"], + require: ["dom", "services"] }, this); -var XBL = Namespace("xbl", "http://www.mozilla.org/xbl"); -var XHTML = Namespace("html", "http://www.w3.org/1999/xhtml"); -var XUL = Namespace("xul", "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); -var NS = Namespace("dactyl", "http://vimperator.org/namespaces/liberator"); -default xml namespace = XHTML; +this.lazyRequire("overlay", ["overlay"]); var FailedAssertion = Class("FailedAssertion", ErrorBase, { init: function init(message, level, noTrace) { @@ -34,63 +28,71 @@ var FailedAssertion = Class("FailedAssertion", ErrorBase, { 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 () { 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.closure.overlayObject }), + overlayWindow: deprecated("overlay.overlayWindow", { get: function overlayWindow() overlay.closure.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.closure.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; + let win = overlay.activeWindow; + var dactyl = global && global.dactyl || win && win.dactyl; if (!dactyl) return null; @@ -119,8 +121,10 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), 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 in Set(cleanup.concat(Object.keys(obj.observers)))) try { services.observer[meth](obj, target, true); } @@ -130,7 +134,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), Class.replaceProperty(obj, "observe", function (subject, target, data) { try { - if (target == "quit-application" || target == "dactyl-cleanup-modules") + if (~cleanup.indexOf(target)) register("removeObserver"); if (obj.observers[target]) obj.observers[target].call(obj, subject, data); @@ -161,6 +165,14 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), 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, function (m, m1) m1.toUpperCase()), + /** * Capitalizes the first character of the given string. * @param {string} str The string to capitalize @@ -192,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*. * @@ -415,16 +378,27 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), if (Set.has(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] != "") })); + 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] != "") })); + 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)) @@ -439,80 +413,6 @@ 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); - - 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. @@ -522,7 +422,9 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), */ 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; @@ -539,55 +441,68 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), * @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.indexOf("{") == -1) + 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(function (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)); - } - 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)); + catch (e if e.message && ~e.message.indexOf("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 []; } - rec([]); - return res; }, /** @@ -602,42 +517,15 @@ 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. + * 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 @@ -675,25 +563,6 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), 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(/= 0, - /** * Sends a synchronous or asynchronous HTTP request to *url* and returns * the XMLHttpRequest object. If *callback* is specified the request is @@ -889,7 +689,32 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), * 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. + * + * 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) { @@ -899,24 +724,50 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), try { let xmlhttp = services.Xmlhttp(); - xmlhttp.mozBackgroundRequest = true; + xmlhttp.mozBackgroundRequest = Set.has(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", function handler(event) { util.trapErrors(params.onload || params.callback, params, xmlhttp, event) }, false); + xmlhttp.addEventListener("error", function handler(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); - 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; } }, @@ -951,7 +802,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), * @param {nsIStackFrame} frame * @returns {boolean} */ - isDactyl: Class.memoize(function () { + isDactyl: Class.Memoize(function () { let base = util.regexp.escape(Components.stack.filename.replace(/[^\/]+$/, "")); let re = RegExp("^(?:.* -> )?(?:resource://dactyl(?!-content/eval.js)|" + base + ")\\S+$"); return function isDactyl(frame) re.test(frame.filename); @@ -966,24 +817,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*. * @@ -998,26 +831,18 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), 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(function (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 each (let type in types) { let docShells = window.docShell.getDocShellEnumerator(Ci.nsIDocShellTreeItem[type], Ci.nsIDocShell.ENUMERATE_FORWARDS); while (docShells.hasMoreElements()) @@ -1030,7 +855,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), }, // ripped from Firefox; modified - unsafeURI: Class.memoize(function () util.regexp(String.replace()]$/, encodeURIComponent); }, /** @@ -1089,18 +900,25 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), "'>"].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)), - /** * 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 @@ -1130,48 +948,12 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), 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 <>{ns}{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 <{ - namespaced(elem)} { - template.map(array.iterValues(elem.attributes), - function (attr) - {namespaced(attr)} + - {attr.value}, - <> ) - }{ !hasChildren ? "/>" : ">" - }{ !hasChildren ? "" : <>... + - <{namespaced(elem)}> - }; - - let tag = "<" + [namespaced(elem)].concat( - [namespaced(a) + "=" + template.highlight(a.value, true) - for ([i, a] in array.iterItems(elem.attributes))]).join(" "); - return tag + (!hasChildren ? "/>" : ">..."); - } - catch (e) { - return {}.toString.call(elem); - } + return DOM(elem).repr(color); } try { // for window.JSON @@ -1192,7 +974,11 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), object = Iterator(object); hasValue = false; } - for (let i in object) { + let keyIter = object; + if ("__iterator__" in object && !callable(object.__iterator__)) + keyIter = keys(object) + + for (let i in keyIter) { let value = ]]>; try { value = object[i]; @@ -1201,20 +987,24 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), if (!hasValue) { if (isArray(i) && i.length == 2) [i, value] = i; - else + else { var noVal = true; + value = i; + } } - value = template.highlight(value, true, 150); + value = template.highlight(value, true, 150, !color); let key = {i}; if (!isNaN(i)) i = parseInt(i); else if (/^[A-Z_]+$/.test(i)) i = ""; - keys.push([i, <>{key}{noVal ? "" : <>: {value}} ]); + keys.push([i, <>{noVal ? value : <>{key}: {value}} ]); } } - catch (e) {} + catch (e) { + util.reportError(e); + } function compare(a, b) { if (!isNaN(a[0]) && !isNaN(b[0])) @@ -1256,250 +1046,6 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), "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)); - } - }, - - _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); - }, - - /** - * 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); - } - - // 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_)); - } - }; - } - - 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)) - if (Object.getOwnPropertyDescriptor(object, k).configurable) - Object.defineProperty(object, k, Object.getOwnPropertyDescriptor(original, k)); - else { - try { - object[k] = original[k]; - } - catch (e) {} - } - }; - }, - - 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); - 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; - } - - 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]; }, /** @@ -1572,7 +1118,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), 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; @@ -1652,6 +1198,16 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), }()) }), + /** + * 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. @@ -1659,7 +1215,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), rehash: function (args) { storage.session.commandlineArgs = args; this.timeout(function () { - services.observer.notifyObservers(null, "startupcache-invalidate", ""); + this.flushCache(); this.rehashing = true; let addon = config.addon; addon.userDisabled = true; @@ -1668,22 +1224,21 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), }, errorCount: 0, - errors: Class.memoize(function () []), + 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) { + 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++; @@ -1693,6 +1248,8 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), stack: <>{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"); @@ -1736,19 +1293,6 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), 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)); - }, - /** * Returns the selection controller for the given window. * @@ -1760,6 +1304,12 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), .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 @@ -1958,7 +1508,7 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), return func.apply(self || this, Array.slice(arguments, 2)); } catch (e) { - util.reportError(e); + this.reportError(e); return undefined; } }, @@ -2020,6 +1570,15 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), return res.filter(function (h) !Set.add(seen, 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); + }, + /** * Wraps native exceptions thrown by the called function so that a * proper stack trace may be retrieved from them. @@ -2037,55 +1596,12 @@ var Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), } }, - /** - * 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; - } - } + xmlToDom: function () DOM.fromXML.apply(DOM, arguments) }, { Array: array }); + /** * Math utility methods. * @singleton