// Copyright (c) 2007-2011 by Doug Kearns <dougkearns@gmail.com>
-// Copyright (c) 2008-2011 by Kris Maglione <maglione.k@gmail.com>
+// Copyright (c) 2008-2012 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.
-/* 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",
;
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"]))
},
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)
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];
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)
* 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 <><span highlight="HelpXMLNamespace">{ns}</span>{node.localName}</>
+ return [["span", { highlight: "HelpXMLNamespace" }, ns],
+ node.localName];
return ns + ":" + node.localName;
}
try {
let hasChildren = elem.firstChild && (!/^\s*$/.test(elem.firstChild) || elem.firstChild.nextSibling)
if (color)
- res.push(<span highlight="HelpXML"><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>);
+ 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 ? "/>" : ">...</" + namespaced(elem) + ">"));
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) {
}, 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) {
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) {
}
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();
? 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.
* 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]);
},
/**
* 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(""),
+ "</", name, ">");
+ 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, "</", name, ">");
+ }
+ }
+
+ 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]];
},
/**
},
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 ()