X-Git-Url: https://git.donarmstrong.com/?p=dactyl.git;a=blobdiff_plain;f=common%2Fmodules%2Fdom.jsm;h=7a0c5556fca4791a9c87083e0862435e1e79bc99;hp=9f7f7e5f9a38bd831e456506876c0c688559688b;hb=8b6fcae7eaa413bc62d645d2d0c99835c47265e6;hpb=5ebd29f56d17f62011cdd596b1d351947ee534ff diff --git a/common/modules/dom.jsm b/common/modules/dom.jsm index 9f7f7e5..7a0c555 100644 --- a/common/modules/dom.jsm +++ b/common/modules/dom.jsm @@ -1,23 +1,23 @@ // Copyright (c) 2007-2011 by Doug Kearns -// Copyright (c) 2008-2011 by Kris Maglione +// Copyright (c) 2008-2012 Kris Maglione // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. -/* use strict */ +"use strict"; -Components.utils.import("resource://dactyl/bootstrap.jsm"); defineModule("dom", { exports: ["$", "DOM", "NS", "XBL", "XHTML", "XUL"] -}, this); +}); -this.lazyRequire("highlight", ["highlight"]); -this.lazyRequire("template", ["template"]); +lazyRequire("highlight", ["highlight"]); +lazyRequire("messages", ["_"]); +lazyRequire("prefs", ["prefs"]); +lazyRequire("template", ["template"]); -var XBL = Namespace("xbl", "http://www.mozilla.org/xbl"); -var XHTML = Namespace("html", "http://www.w3.org/1999/xhtml"); -var XUL = Namespace("xul", "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); -var NS = Namespace("dactyl", "http://vimperator.org/namespaces/liberator"); -default xml namespace = XHTML; +var XBL = "http://www.mozilla.org/xbl"; +var XHTML = "http://www.w3.org/1999/xhtml"; +var XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; +var NS = "http://vimperator.org/namespaces/liberator"; function BooleanAttribute(attr) ({ get: function (elem) elem.getAttribute(attr) == "true", @@ -56,6 +56,12 @@ var DOM = Class("DOM", { ; else if (typeof val == "xml" && context instanceof Ci.nsIDOMDocument) this[length++] = DOM.fromXML(val, context, this.nodes); + else if (DOM.isJSONXML(val)) { + if (context instanceof Ci.nsIDOMDocument) + this[length++] = DOM.fromJSON(val, context, this.nodes); + else + this[length++] = val; + } else if (val instanceof Ci.nsIDOMNode || val instanceof Ci.nsIDOMWindow) this[length++] = val; else if ("__iterator__" in val || isinstance(val, ["Iterator", "Generator"])) @@ -111,15 +117,6 @@ var DOM = Class("DOM", { }, eachDOM: function eachDOM(val, fn, self) { - XML.prettyPrinting = XML.ignoreWhitespace = false; - if (isString(val)) - val = XML(val); - - if (typeof val == "xml") - return this.each(function (elem, i) { - fn.call(this, DOM.fromXML(val, elem.ownerDocument), elem, i); - }, self || this); - let dom = this; function munge(val, container, idx) { if (val instanceof Ci.nsIDOMRange) @@ -127,7 +124,7 @@ var DOM = Class("DOM", { if (val instanceof Ci.nsIDOMNode) return val; - if (typeof val == "xml") { + if (typeof val == "xml" || DOM.isJSONXML(val)) { val = dom.constructor(val, dom.document); if (container) container[idx] = val[0]; @@ -502,8 +499,15 @@ var DOM = Class("DOM", { if (DOM(elem).isInput || /^(?:hidden|textarea)$/.test(elem.type) || elem.type == "submit" && elem == field - || elem.checked && /^(?:checkbox|radio)$/.test(elem.type)) - elems.push(encode(elem.name, elem.value, elem === field)); + || elem.checked && /^(?:checkbox|radio)$/.test(elem.type)) { + + if (elem !== field) + elems.push(encode(elem.name, elem.value)); + else if (overlay.getData(elem, "had-focus")) + elems.push(encode(elem.name, elem.value, true)); + else + elems.push(encode(elem.name, "", true)); + } else if (elem instanceof Ci.nsIDOMHTMLSelectElement) { for (let [, opt] in Iterator(elem.options)) if (opt.selected) @@ -557,14 +561,13 @@ var DOM = Class("DOM", { * representation of this node. */ repr: function repr(color) { - XML.ignoreWhitespace = XML.prettyPrinting = false; - function namespaced(node) { - var ns = DOM.namespaceNames[node.namespaceURI] || /^(?:(.*?):)?/.exec(node.name)[0]; + var ns = DOM.namespaceNames[node.namespaceURI] || /^(?:(.*?):)?/.exec(node.name)[1]; if (!ns) return node.localName; if (color) - return <>{ns}{node.localName} + return [["span", { highlight: "HelpXMLNamespace" }, ns], + node.localName]; return ns + ":" + node.localName; } @@ -573,20 +576,24 @@ var DOM = Class("DOM", { try { let hasChildren = elem.firstChild && (!/^\s*$/.test(elem.firstChild) || elem.firstChild.nextSibling) if (color) - res.push(<{ - namespaced(elem)} { - template.map(array.iterValues(elem.attributes), - function (attr) - {namespaced(attr)} + - {attr.value}, - <> ) - }{ !hasChildren ? "/>" : ">" - }{ !hasChildren ? "" : <>... + - <{namespaced(elem)}> - }); + res.push(["span", { highlight: "HelpXML" }, + ["span", { highlight: "HelpXMLTagStart" }, + "<", namespaced(elem), " ", + template.map(array.iterValues(elem.attributes), + function (attr) [ + ["span", { highlight: "HelpXMLAttribute" }, namespaced(attr)], + ["span", { highlight: "HelpXMLString" }, attr.value] + ], + " "), + !hasChildren ? "/>" : ">", + ], + !hasChildren ? "" : + ["", "...", + ["span", { highlight: "HtmlTagEnd" },"<", namespaced(elem), ">"]] + ]); else { let tag = "<" + [namespaced(elem)].concat( - [namespaced(a) + "=" + template.highlight(a.value, true) + [namespaced(a) + '="' + String.replace(a.value, /["<]/, DOM.escapeHTML) + '"' for ([i, a] in array.iterItems(elem.attributes))]).join(" "); res.push(tag + (!hasChildren ? "/>" : ">...")); @@ -596,7 +603,8 @@ var DOM = Class("DOM", { res.push({}.toString.call(elem)); } }, this); - return template.map(res, util.identity, <>,); + res = template.map(res, util.identity, ","); + return color ? res : res.join(""); }, attr: function attr(key, val) { @@ -719,6 +727,15 @@ var DOM = Class("DOM", { }, this); }, + fragment: function fragment() { + let frag = this.document.createDocumentFragment(); + this.appendTo(frag); + return this; + }, + + clone: function clone(deep) + this.map(function (elem) elem.cloneNode(deep)), + toggle: function toggle(val, self) { if (callable(val)) return this.each(function (elem, i) { @@ -773,7 +790,7 @@ var DOM = Class("DOM", { html: function html(txt, self) { return this.getSet(arguments, function (elem) elem.innerHTML, - function (elem, val) { elem.innerHTML = val }); + util.wrapCallback(function (elem, val) { elem.innerHTML = val })); }, text: function text(txt, self) { @@ -1013,6 +1030,9 @@ var DOM = Class("DOM", { } for (let [k, v] in Iterator(Ci.nsIDOMKeyEvent)) { + if (!/^DOM_VK_/.test(k)) + continue; + this.code_nativeKey[v] = k.substr(4); k = k.substr(7).toLowerCase(); @@ -1378,6 +1398,17 @@ var DOM = Class("DOM", { ? function (elem, dir) services.dactyl.getScrollable(elem) & (dir ? services.dactyl["DIRECTION_" + dir.toUpperCase()] : ~0) : function (elem, dir) true), + isJSONXML: function isJSONXML(val) isArray(val) && isinstance(val[0], ["String", "Array", "XML", DOM.DOMString]) + || isObject(val) && "toDOM" in val, + + DOMString: function DOMString(val) ({ + __proto__: DOMString.prototype, + + toDOM: function toDOM(doc) doc.createTextNode(val), + + toString: function () val + }), + /** * The set of input element type attribute values that mark the element as * an editable field. @@ -1484,11 +1515,14 @@ var DOM = Class("DOM", { * entities. * * @param {string} str + * @param {boolean} simple If true, only escape for the simple case + * of text nodes. * @returns {string} */ - escapeHTML: function escapeHTML(str) { + escapeHTML: function escapeHTML(str, simple) { let map = { "'": "'", '"': """, "%": "%", "&": "&", "<": "<", ">": ">" }; - return str.replace(/['"&<>]/g, function (m) map[m]); + let regexp = simple ? /[<>]/g : /['"&<>]/g; + return str.replace(regexp, function (m) map[m]); }, /** @@ -1502,39 +1536,282 @@ var DOM = Class("DOM", { * stored here, keyed to the value thereof. * @returns {Node} */ - fromXML: function fromXML(node, doc, nodes) { - XML.ignoreWhitespace = XML.prettyPrinting = false; - if (typeof node === "string") // Sandboxes can't currently pass us XML objects. - node = XML(node); - - if (node.length() != 1) { - let domnode = doc.createDocumentFragment(); - for each (let child in node) - domnode.appendChild(fromXML(child, doc, nodes)); - return domnode; + fromXML: deprecated("DOM.fromJSON", { get: function fromXML() + prefs.get("javascript.options.xml.chrome") !== false + && require("dom-e4x").fromXML }), + + fromJSON: update(function fromJSON(xml, doc, nodes, namespaces) { + if (!doc) + doc = document; + + function tag(args, namespaces) { + let _namespaces = namespaces; + + // Deal with common error case + if (args == null) { + util.reportError(Error("Unexpected null when processing XML.")); + args = ["html:i", {}, "[NULL]"]; + } + + if (isinstance(args, ["String", "Number", "Boolean", _])) + return doc.createTextNode(args); + if (isXML(args)) + return DOM.fromXML(args, doc, nodes); + if (isObject(args) && "toDOM" in args) + return args.toDOM(doc, namespaces, nodes); + if (args instanceof Ci.nsIDOMNode) + return args; + if (args instanceof DOM) + return args.fragment(); + if ("toJSONXML" in args) + args = args.toJSONXML(); + + let [name, attr] = args; + + if (!isString(name) || args.length == 0 || name === "") { + var frag = doc.createDocumentFragment(); + Array.forEach(args, function (arg) { + if (!isArray(arg[0])) + arg = [arg]; + arg.forEach(function (arg) { + frag.appendChild(tag(arg, namespaces)); + }); + }); + return frag; + } + + attr = attr || {}; + + function parseNamespace(name) DOM.parseNamespace(name, namespaces); + + // FIXME: Surely we can do better. + for (var key in attr) { + if (/^xmlns(?:$|:)/.test(key)) { + if (_namespaces === namespaces) + namespaces = Object.create(namespaces); + + namespaces[key.substr(6)] = namespaces[attr[key]] || attr[key]; + }} + + var args = Array.slice(args, 2); + var vals = parseNamespace(name); + var elem = doc.createElementNS(vals[0] || namespaces[""], + name); + + for (var key in attr) + if (!/^xmlns(?:$|:)/.test(key)) { + var val = attr[key]; + if (nodes && key == "key") + nodes[val] = elem; + + vals = parseNamespace(key); + if (key == "highlight") + ; + else if (typeof val == "function") + elem.addEventListener(key.replace(/^on/, ""), val, false); + else + elem.setAttributeNS(vals[0] || "", key, val); + } + args.forEach(function(e) { + elem.appendChild(tag(e, namespaces)); + }); + + if ("highlight" in attr) + highlight.highlightNode(elem, attr.highlight, nodes || true); + return elem; } - switch (node.nodeKind()) { - case "text": - return doc.createTextNode(String(node)); - case "element": - let domnode = doc.createElementNS(node.namespace(), node.localName()); - - for each (let attr in node.@*::*) - if (attr.name() != "highlight") - domnode.setAttributeNS(attr.namespace(), attr.localName(), String(attr)); - - for each (let child in node.*::*) - domnode.appendChild(fromXML(child, doc, nodes)); - if (nodes && node.@key) - nodes[node.@key] = domnode; - - if ("@highlight" in node) - highlight.highlightNode(domnode, String(node.@highlight), nodes || true); - return domnode; - default: - return null; + if (namespaces) + namespaces = update({}, fromJSON.namespaces, namespaces); + else + namespaces = fromJSON.namespaces; + + return tag(xml, namespaces) + }, { + namespaces: { + "": "http://www.w3.org/1999/xhtml", + dactyl: String(NS), + html: "http://www.w3.org/1999/xhtml", + xmlns: "http://www.w3.org/2000/xmlns/", + xul: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" } + }), + + toXML: function toXML(xml) { + // Meh. For now. + let doc = services.XMLDocument(); + let node = this.fromJSON(xml, doc); + return services.XMLSerializer() + .serializeToString(node); + }, + + toPrettyXML: function toPrettyXML(xml, asXML, indent, namespaces) { + const INDENT = indent || " "; + + const EMPTY = Set("area base basefont br col frame hr img input isindex link meta param" + .split(" ")); + + function namespaced(namespaces, namespace, localName) { + for (let [k, v] in Iterator(namespaces)) + if (v == namespace) + return (k ? k + ":" + localName : localName); + + throw Error("No such namespace"); + } + + function isFragment(args) !isString(args[0]) || args.length == 0 || args[0] === ""; + + function hasString(args) { + return args.some(function (a) isString(a) || isFragment(a) && hasString(a)) + } + + function isStrings(args) { + if (!isArray(args)) + return util.dump("ARGS: " + {}.toString.call(args) + " " + args), false; + return args.every(function (a) isinstance(a, ["String", DOM.DOMString]) || isFragment(a) && isStrings(a)) + } + + function tag(args, namespaces, indent) { + let _namespaces = namespaces; + + if (args == "") + return ""; + + if (isinstance(args, ["String", "Number", "Boolean", _, DOM.DOMString])) + return indent + + DOM.escapeHTML(String(args), true); + + if (isXML(args)) + return indent + + args.toXMLString() + .replace(/^/m, indent); + + if (isObject(args) && "toDOM" in args) + return indent + + services.XMLSerializer() + .serializeToString(args.toDOM(services.XMLDocument())) + .replace(/^/m, indent); + + if (args instanceof Ci.nsIDOMNode) + return indent + + services.XMLSerializer() + .serializeToString(args) + .replace(/^/m, indent); + + if ("toJSONXML" in args) + args = args.toJSONXML(); + + // Deal with common error case + if (args == null) { + util.reportError(Error("Unexpected null when processing XML.")); + return "[NULL]"; + } + + let [name, attr] = args; + + if (isFragment(args)) { + let res = []; + let join = isArray(args) && isStrings(args) ? "" : "\n"; + Array.forEach(args, function (arg) { + if (!isArray(arg[0])) + arg = [arg]; + + let contents = []; + arg.forEach(function (arg) { + let string = tag(arg, namespaces, indent); + if (string) + contents.push(string); + }); + if (contents.length) + res.push(contents.join("\n"), join) + }); + if (res[res.length - 1] == join) + res.pop(); + return res.join(""); + } + + attr = attr || {}; + + function parseNamespace(name) { + var m = /^(?:(.*):)?(.*)$/.exec(name); + return [namespaces[m[1]], m[2]]; + } + + // FIXME: Surely we can do better. + let skipAttr = {}; + for (var key in attr) { + if (/^xmlns(?:$|:)/.test(key)) { + if (_namespaces === namespaces) + namespaces = update({}, namespaces); + + let ns = namespaces[attr[key]] || attr[key]; + if (ns == namespaces[key.substr(6)]) + skipAttr[key] = true; + + attr[key] = namespaces[key.substr(6)] = ns; + }} + + var args = Array.slice(args, 2); + var vals = parseNamespace(name); + + let res = [indent, "<", name]; + + for (let [key, val] in Iterator(attr)) { + if (Set.has(skipAttr, key)) + continue; + + let vals = parseNamespace(key); + if (typeof val == "function") { + key = key.replace(/^(?:on)?/, "on"); + val = val.toSource() + "(event)"; + } + + if (key != "highlight" || vals[0] == String(NS)) + res.push(" ", key, '="', DOM.escapeHTML(val), '"'); + else + res.push(" ", namespaced(namespaces, String(NS), "highlight"), + '="', DOM.escapeHTML(val), '"'); + } + + if ((vals[0] || namespaces[""]) == String(XHTML) && Set.has(EMPTY, vals[1]) + || asXML && !args.length) + res.push("/>"); + else { + res.push(">"); + + if (isStrings(args)) + res.push(args.map(function (e) tag(e, namespaces, "")).join(""), + ""); + else { + let contents = []; + args.forEach(function(e) { + let string = tag(e, namespaces, indent + INDENT); + if (string) + contents.push(string); + }); + + res.push("\n", contents.join("\n"), "\n", indent, ""); + } + } + + return res.join(""); + } + + if (namespaces) + namespaces = update({}, DOM.fromJSON.namespaces, namespaces); + else + namespaces = DOM.fromJSON.namespaces; + + return tag(xml, namespaces, "") + }, + + parseNamespace: function parseNamespace(name, namespaces) { + if (name == "xmlns") + return [DOM.fromJSON.namespaces.xmlns, "xmlns"]; + + var m = /^(?:(.*):)?(.*)$/.exec(name); + return [(namespaces || DOM.fromJSON.namespaces)[m[1]], + m[2]]; }, /** @@ -1604,11 +1881,11 @@ var DOM = Class("DOM", { }, namespaces: { - xul: XUL.uri, - xhtml: XHTML.uri, - html: XHTML.uri, + xul: XUL, + xhtml: XHTML, + html: XHTML, xhtml2: "http://www.w3.org/2002/06/xhtml2", - dactyl: NS.uri + dactyl: NS }, namespaceNames: Class.Memoize(function ()