X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=common%2Fmodules%2Fdom.jsm;h=72d80135751b7b1779436cfdf877c59baf7f985a;hb=247daf849abc85f4cfb10fa358c62c8daf8db95b;hp=981ec9c8a26d1cb3d332f928e3f88b1cc1e5db14;hpb=9044153cb63835e39b9de8ec4ade237c03e3888a;p=dactyl.git diff --git a/common/modules/dom.jsm b/common/modules/dom.jsm index 981ec9c..72d8013 100644 --- a/common/modules/dom.jsm +++ b/common/modules/dom.jsm @@ -1,20 +1,24 @@ // Copyright (c) 2007-2011 by Doug Kearns -// Copyright (c) 2008-2011 by Kris Maglione +// Copyright (c) 2008-2014 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); +}); + +lazyRequire("highlight", ["highlight"]); +lazyRequire("messages", ["_"]); +lazyRequire("overlay", ["overlay"]); +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", @@ -51,16 +55,20 @@ var DOM = Class("DOM", { if (val == null) ; - 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 ("length" in val) - for (let i = 0; i < val.length; i++) - this[length++] = val[i]; else if ("__iterator__" in val || isinstance(val, ["Iterator", "Generator"])) for (let elem in val) this[length++] = elem; + else if ("length" in val) + for (let i = 0; i < val.length; i++) + this[length++] = val[i]; else this[length++] = val; @@ -90,7 +98,7 @@ var DOM = Class("DOM", { href: { get: function (elem) elem.href || elem.getAttribute("href") }, src: { get: function (elem) elem.src || elem.getAttribute("src") }, checked: { get: function (elem) elem.hasAttribute("checked") ? elem.getAttribute("checked") == "true" : elem.checked, - set: function (elem, val) { elem.setAttribute("checked", !!val); elem.checked = val } }, + set: function (elem, val) { elem.setAttribute("checked", !!val); elem.checked = val; } }, collapsed: BooleanAttribute("collapsed"), disabled: BooleanAttribute("disabled"), hidden: BooleanAttribute("hidden"), @@ -98,7 +106,7 @@ var DOM = Class("DOM", { }] ]), - matcher: function matcher(sel) function (elem) elem.mozMatchesSelector && elem.mozMatchesSelector(sel), + matcher: function matcher(sel) elem => (elem.mozMatchesSelector && elem.mozMatchesSelector(sel)), each: function each(fn, self) { let obj = self || this.Empty(); @@ -108,15 +116,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) @@ -124,7 +123,7 @@ var DOM = Class("DOM", { if (val instanceof Ci.nsIDOMNode) return val; - if (typeof val == "xml") { + if (DOM.isJSONXML(val)) { val = dom.constructor(val, dom.document); if (container) container[idx] = val[0]; @@ -139,6 +138,9 @@ var DOM = Class("DOM", { return val; } + if (DOM.isJSONXML(val)) + val = (function () this).bind(val); + if (callable(val)) return this.each(function (elem, i) { util.withProperErrors(fn, this, munge(val.call(this, elem, i)), elem, i); @@ -154,11 +156,11 @@ var DOM = Class("DOM", { }, find: function find(val) { - return this.map(function (elem) elem.querySelectorAll(val)); + return this.map(elem => elem.querySelectorAll(val)); }, findAnon: function findAnon(attr, val) { - return this.map(function (elem) elem.ownerDocument.getAnonymousElementByAttribute(elem, attr, val)); + return this.map(elem => elem.ownerDocument.getAnonymousElementByAttribute(elem, attr, val)); }, filter: function filter(val, self) { @@ -189,13 +191,13 @@ var DOM = Class("DOM", { let res = this.Empty(); this.each(function (elem) { - while(true) { - elem = fn.call(this, elem) - if (elem instanceof Ci.nsIDOMElement) + while (true) { + elem = fn.call(this, elem); + if (elem instanceof Ci.nsIDOMNode) res[res.length++] = elem; else if (elem && "length" in elem) - for (let i = 0; i < tmp.length; i++) - res[res.length++] = tmp[j]; + for (let i = 0; i < elem.length; i++) + res[res.length++] = elem[j]; else break; } @@ -209,7 +211,7 @@ var DOM = Class("DOM", { for (let i = 0; i < this.length; i++) { let tmp = fn.call(self || update(obj, [this[i]]), this[i], i); - if (isObject(tmp) && "length" in tmp) + if (isObject(tmp) && !(tmp instanceof Ci.nsIDOMNode) && "length" in tmp) for (let j = 0; j < tmp.length; j++) res[res.length++] = tmp[j]; else if (tmp != null) @@ -230,7 +232,7 @@ var DOM = Class("DOM", { return false; }, - get parent() this.map(function (elem) elem.parentNode, this), + get parent() this.map(elem => elem.parentNode, this), get offsetParent() this.map(function (elem) { do { @@ -241,20 +243,23 @@ var DOM = Class("DOM", { while (parent); }, this), - get ancestors() this.all(function (elem) elem.parentNode), + get ancestors() this.all(elem => elem.parentNode), - get children() this.map(function (elem) Array.filter(elem.childNodes, - function (e) e instanceof Ci.nsIDOMElement), + get children() this.map(elem => Array.filter(elem.childNodes, + e => e instanceof Ci.nsIDOMElement), this), - get contents() this.map(function (elem) elem.childNodes, this), + get contents() this.map(elem => elem.childNodes, this), - get siblings() this.map(function (elem) Array.filter(elem.parentNode.childNodes, - function (e) e != elem && e instanceof Ci.nsIDOMElement), + get siblings() this.map(elem => Array.filter(elem.parentNode.childNodes, + e => e != elem && e instanceof Ci.nsIDOMElement), this), - get siblingsBefore() this.all(function (elem) elem.previousElementSibling), - get siblingsAfter() this.all(function (elem) elem.nextElementSibling), + get siblingsBefore() this.all(elem => elem.previousElementSibling), + get siblingsAfter() this.all(elem => elem.nextElementSibling), + + get allSiblingsBefore() this.all(elem => elem.previousSibling), + get allSiblingsAfter() this.all(elem => elem.nextSibling), get class() let (self = this) ({ toString: function () self[0].className, @@ -265,7 +270,7 @@ var DOM = Class("DOM", { each: function each(meth, arg) { return self.each(function (elem) { elem.classList[meth](arg); - }) + }); }, add: function add(cls) this.each("add", cls), @@ -298,14 +303,14 @@ var DOM = Class("DOM", { }), remove: function remove(hl) self.each(function () { - this.highlight.list = this.highlight.list.filter(function (h) h != hl); + this.highlight.list = this.highlight.list.filter(h => h != hl); }), toggle: function toggle(hl, val, thisObj) self.each(function (elem, i) { let { highlight } = this; let v = callable(val) ? val.call(thisObj || this, elem, i) : val; - highlight[(v == null ? highlight.has(hl) : !v) ? "remove" : "add"](hl) + highlight[(v == null ? highlight.has(hl) : !v) ? "remove" : "add"](hl); }), }), @@ -318,24 +323,28 @@ var DOM = Class("DOM", { this[0] ? this[0].getBoundingClientRect() : {}, get viewport() { - if (this[0] instanceof Ci.nsIDOMWindow) + let node = this[0]; + if (node instanceof Ci.nsIDOMDocument) + node = node.defaultView; + + if (node instanceof Ci.nsIDOMWindow) return { get width() this.right - this.left, get height() this.bottom - this.top, - bottom: this[0].innerHeight, - right: this[0].innerWidth, + bottom: node.innerHeight, + right: node.innerWidth, top: 0, left: 0 }; let r = this.rect; return { - width: this[0].clientWidth, - height: this[0].clientHeight, - top: r.top + this[0].clientTop, + width: node.clientWidth, + height: node.clientHeight, + top: r.top + node.clientTop, get bottom() this.top + this.height, - left: r.left + this[0].clientLeft, + left: r.left + node.clientLeft, get right() this.left + this.width - } + }; }, scrollPos: function scrollPos(left, top) { @@ -406,7 +415,7 @@ var DOM = Class("DOM", { return editor; }, - get isEditable() !!this.editor, + get isEditable() !!this.editor || this[0] instanceof Ci.nsIDOMElement && this.style.MozUserModify == "read-write", get isInput() isinstance(this[0], [Ci.nsIDOMHTMLInputElement, Ci.nsIDOMHTMLTextAreaElement, @@ -465,7 +474,7 @@ var DOM = Class("DOM", { let charset = doc.characterSet; let converter = services.CharsetConv(charset); - for each (let cs in form.acceptCharset.split(/\s*,\s*|\s+/)) { + for (let cs of form.acceptCharset.split(/\s*,\s*|\s+/)) { let c = services.CharsetConv(cs); if (c) { converter = services.CharsetConv(cs); @@ -492,8 +501,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) @@ -547,36 +563,39 @@ 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; } let res = []; this.each(function (elem) { try { - let hasChildren = elem.firstChild && (!/^\s*$/.test(elem.firstChild) || elem.firstChild.nextSibling) + 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), + 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 ? "/>" : ">...")); @@ -586,7 +605,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) { @@ -605,7 +625,7 @@ var DOM = Class("DOM", { if (callable(v)) v = v.call(this, elem, i); - if (Set.has(hooks, k) && hooks[k].set) + if (hasOwnProperty(hooks, k) && hooks[k].set) hooks[k].set.call(this, elem, v, k); else if (v == null) elem.removeAttributeNS(ns, k); @@ -617,7 +637,7 @@ var DOM = Class("DOM", { if (!this.length) return null; - if (Set.has(hooks, key) && hooks[key].get) + if (hasOwnProperty(hooks, key) && hooks[key].get) return hooks[key].get.call(this, this[0], key); if (!this[0].hasAttributeNS(ns, key)) @@ -638,9 +658,9 @@ var DOM = Class("DOM", { return this[0].style[css.property(key)]; }, { - name: function (property) property.replace(/[A-Z]/g, function (m0) "-" + m0.toLowerCase()), + name: function (property) property.replace(/[A-Z]/g, m0 => "-" + m0.toLowerCase()), - property: function (name) name.replace(/-(.)/g, function (m0, m1) m1.toUpperCase()) + property: function (name) name.replace(/-(.)/g, (m0, m1) => m1.toUpperCase()) }), append: function append(val) { @@ -709,6 +729,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(elem => elem.cloneNode(deep)), + toggle: function toggle(val, self) { if (callable(val)) return this.each(function (elem, i) { @@ -718,7 +747,7 @@ var DOM = Class("DOM", { if (arguments.length) return this[val ? "show" : "hide"](); - let hidden = this.map(function (elem) elem.style.display == "none"); + let hidden = this.map(elem => elem.style.display == "none"); return this.each(function (elem, i) { this[hidden[i] ? "show" : "hide"](); }); @@ -753,7 +782,7 @@ var DOM = Class("DOM", { let [fn, self] = args; if (!callable(fn)) - fn = function () args[0]; + fn = () => args[0]; return this.each(function (elem, i) { set.call(this, elem, fn.call(self || this, elem, i)); @@ -762,20 +791,20 @@ var DOM = Class("DOM", { html: function html(txt, self) { return this.getSet(arguments, - function (elem) elem.innerHTML, - function (elem, val) { elem.innerHTML = val }); + elem => elem.innerHTML, + util.wrapCallback((elem, val) => { elem.innerHTML = val; })); }, text: function text(txt, self) { return this.getSet(arguments, - function (elem) elem.textContent, - function (elem, val) { elem.textContent = val }); + elem => elem.textContent, + (elem, val) => { elem.textContent = val; }); }, val: function val(txt) { return this.getSet(arguments, - function (elem) elem.value, - function (elem, val) { elem.value = val == null ? "" : val }); + elem => elem.value, + (elem, val) => { elem.value = val == null ? "" : val; }); }, listen: function listen(event, listener, capture) { @@ -784,25 +813,44 @@ var DOM = Class("DOM", { else event = array.toObject([[event, listener]]); - for (let [k, v] in Iterator(event)) - event[k] = util.wrapCallback(v, true); + for (let [evt, callback] in Iterator(event)) + event[evt] = util.wrapCallback(callback, true); return this.each(function (elem) { - for (let [k, v] in Iterator(event)) - elem.addEventListener(k, v, capture); + for (let [evt, callback] in Iterator(event)) + elem.addEventListener(evt, callback, capture); }); }, unlisten: function unlisten(event, listener, capture) { if (isObject(event)) capture = listener; else - event = array.toObject([[key, val]]); + event = array.toObject([[event, listener]]); return this.each(function (elem) { for (let [k, v] in Iterator(event)) elem.removeEventListener(k, v.wrapper || v, capture); }); }, + once: function once(event, listener, capture) { + if (isObject(event)) + capture = listener; + else + event = array.toObject([[event, listener]]); + + for (let pair in Iterator(event)) { + let [evt, callback] = pair; + event[evt] = util.wrapCallback(function wrapper(event) { + this.removeEventListener(evt, wrapper.wrapper, capture); + return callback.apply(this, arguments); + }, true); + } + + return this.each(function (elem) { + for (let [k, v] in Iterator(event)) + elem.addEventListener(k, v, capture); + }); + }, dispatch: function dispatch(event, params, extraProps) { this.canceled = false; @@ -855,7 +903,7 @@ var DOM = Class("DOM", { return true; if (rect.top > viewport.bottom) return false; - return Math.abs(rect.top) < Math.abs(viewport.bottom - rect.bottom) + return Math.abs(rect.top) < Math.abs(viewport.bottom - rect.bottom); } let rect; @@ -935,7 +983,7 @@ var DOM = Class("DOM", { update(params, this.constructor.defaults[type], iter.toObject([k, opts[k]] for (k in opts) if (k in params))); - evt["init" + t + "Event"].apply(evt, args.map(function (k) params[k])); + evt["init" + t + "Event"].apply(evt, args.map(k => params[k])); return evt; } }, { @@ -945,21 +993,30 @@ var DOM = Class("DOM", { // want to refer to within dactyl's source code for // comparisons like if (key == "") { ... } this.keyTable = { - add: ["Plus", "Add"], + add: ["+", "Plus", "Add"], + back_quote: ["`"], + back_slash: ["\\"], back_space: ["BS"], + comma: [","], count: ["count"], + close_bracket: ["]"], delete: ["Del"], + equals: ["="], escape: ["Esc", "Escape"], insert: ["Insert", "Ins"], leader: ["Leader"], left_shift: ["LT", "<"], nop: ["Nop"], + open_bracket: ["["], pass: ["Pass"], + period: ["."], + quote: ["'"], return: ["Return", "CR", "Enter"], right_shift: [">"], + semicolon: [";"], slash: ["/"], space: ["Space", " "], - subtract: ["Minus", "Subtract"] + subtract: ["-", "Minus", "Subtract"] }; this.key_key = {}; @@ -975,10 +1032,13 @@ 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(); - let names = [k.replace(/(^|_)(.)/g, function (m, n1, n2) n2.toUpperCase()) + let names = [k.replace(/(^|_)(.)/g, (m, n1, n2) => n2.toUpperCase()) .replace(/^NUMPAD/, "k")]; if (names[0].length == 1) @@ -1004,13 +1064,12 @@ var DOM = Class("DOM", { return this; }, - code_key: Class.Memoize(function (prop) this.init()[prop]), code_nativeKey: Class.Memoize(function (prop) this.init()[prop]), keyTable: Class.Memoize(function (prop) this.init()[prop]), key_code: Class.Memoize(function (prop) this.init()[prop]), key_key: Class.Memoize(function (prop) this.init()[prop]), - pseudoKeys: Set(["count", "leader", "nop", "pass"]), + pseudoKeys: RealSet(["count", "leader", "nop", "pass"]), /** * Converts a user-input string of keys into a canonical @@ -1029,13 +1088,11 @@ var DOM = Class("DOM", { * @param {string} keys Messy form. * @param {boolean} unknownOk Whether unknown keys are passed * through rather than being converted to keyname>. - * @default false + * @default true * @returns {string} Canonical form. */ - canonicalKeys: function canonicalKeys(keys, unknownOk) { - if (arguments.length === 1) - unknownOk = true; - return this.parse(keys, unknownOk).map(this.closure.stringify).join(""); + canonicalKeys: function canonicalKeys(keys, unknownOk=true) { + return this.parse(keys, unknownOk).map(this.bound.stringify).join(""); }, iterKeys: function iterKeys(keys) iter(function () { @@ -1061,15 +1118,12 @@ var DOM = Class("DOM", { * @param {string} keys The string to parse. * @param {boolean} unknownOk Whether unknown keys are passed * through rather than being converted to keyname>. - * @default false + * @default true * @returns {Array[Object]} */ - parse: function parse(input, unknownOk) { + parse: function parse(input, unknownOk=true) { if (isArray(input)) - return array.flatten(input.map(function (k) this.parse(k, unknownOk), this)); - - if (arguments.length === 1) - unknownOk = true; + return array.flatten(input.map(k => this.parse(k, unknownOk))); let out = []; for (let match in util.regexp.iterate(/<.*?>?>|[^<]|<(?!.*>)/g, input)) { @@ -1085,19 +1139,19 @@ var DOM = Class("DOM", { } else { let [match, modifier, keyname] = evt_str.match(/^<((?:[*12CASM⌘]-)*)(.+?)>$/i) || [false, '', '']; - modifier = Set(modifier.toUpperCase()); + modifier = RealSet(modifier.toUpperCase()); keyname = keyname.toLowerCase(); evt_obj.dactylKeyname = keyname; if (/^u[0-9a-f]+$/.test(keyname)) keyname = String.fromCharCode(parseInt(keyname.substr(1), 16)); if (keyname && (unknownOk || keyname.length == 1 || /mouse$/.test(keyname) || - this.key_code[keyname] || Set.has(this.pseudoKeys, keyname))) { - evt_obj.globKey ="*" in modifier; - evt_obj.ctrlKey ="C" in modifier; - evt_obj.altKey ="A" in modifier; - evt_obj.shiftKey ="S" in modifier; - evt_obj.metaKey ="M" in modifier || "⌘" in modifier; + this.key_code[keyname] || this.pseudoKeys.has(keyname))) { + evt_obj.globKey = modifier.has("*"); + evt_obj.ctrlKey = modifier.has("C"); + evt_obj.altKey = modifier.has("A"); + evt_obj.shiftKey = modifier.has("S"); + evt_obj.metaKey = modifier.has("M") || modifier.has("⌘"); evt_obj.dactylShift = evt_obj.shiftKey; if (keyname.length == 1) { // normal characters @@ -1108,11 +1162,11 @@ var DOM = Class("DOM", { evt_obj.charCode = keyname.charCodeAt(0); evt_obj.keyCode = this.key_code[keyname.toLowerCase()]; } - else if (Set.has(this.pseudoKeys, keyname)) { + else if (this.pseudoKeys.has(keyname)) { evt_obj.dactylString = "<" + this.key_key[keyname] + ">"; } else if (/mouse$/.test(keyname)) { // mouse events - evt_obj.type = (/2-/.test(modifier) ? "dblclick" : "click"); + evt_obj.type = (modifier.has("2") ? "dblclick" : "click"); evt_obj.button = ["leftmouse", "middlemouse", "rightmouse"].indexOf(keyname); delete evt_obj.keyCode; delete evt_obj.charCode; @@ -1153,7 +1207,7 @@ var DOM = Class("DOM", { */ stringify: function stringify(event) { if (isArray(event)) - return event.map(function (e) this.stringify(e), this).join(""); + return event.map(e => this.stringify(e)).join(""); if (event.dactylString) return event.dactylString; @@ -1176,7 +1230,9 @@ var DOM = Class("DOM", { if (event.keyCode in this.code_key) { key = this.code_key[event.keyCode]; - if (event.shiftKey && (key.length > 1 || event.ctrlKey || event.altKey || event.metaKey) || event.dactylShift) + if (event.shiftKey && (key.length > 1 || key.toUpperCase() == key.toLowerCase() + || event.ctrlKey || event.altKey || event.metaKey) + || event.dactylShift) modifier += "S-"; else if (!modifier && key.length === 1) if (event.shiftKey) @@ -1270,13 +1326,12 @@ var DOM = Class("DOM", { return "<" + modifier + key + ">"; }, - defaults: { load: { bubbles: false }, submit: { cancelable: true } }, - types: Class.Memoize(function () iter( + types: Class.Memoize(() => iter( { Mouse: "click mousedown mouseout mouseover mouseup dblclick " + "hover " + @@ -1287,7 +1342,7 @@ var DOM = Class("DOM", { "load unload pageshow pagehide DOMContentLoaded " + "resize scroll" } - ).map(function ([k, v]) v.split(" ").map(function (v) [v, k])) + ).map(([k, v]) => v.split(" ").map(v => [v, k])) .flatten() .toObject()), @@ -1297,54 +1352,53 @@ var DOM = Class("DOM", { * @param {Node} target The DOM node to which to dispatch the event. * @param {Event} event The event to dispatch. */ - dispatch: Class.Memoize(function () - config.haveGecko("2b") - ? function dispatch(target, event, extra) { - try { - this.feedingEvent = extra; - - if (target instanceof Ci.nsIDOMElement) - // This causes a crash on Gecko<2.0, it seems. - return (target.ownerDocument || target.document || target).defaultView - .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils) - .dispatchDOMEventViaPresShell(target, event, true); - else { - target.dispatchEvent(event); - return !event.getPreventDefault(); - } - } - catch (e) { - util.reportError(e); - } - finally { - this.feedingEvent = null; - } + dispatch: function dispatch(target, event, extra) { + try { + this.feedingEvent = extra; + + if (target instanceof Ci.nsIDOMElement) + return (target.ownerDocument || target.document || target).defaultView + .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils) + .dispatchDOMEventViaPresShell(target, event, true); + else { + target.dispatchEvent(event); + return !event.defaultPrevented; } - : function dispatch(target, event, extra) { - try { - this.feedingEvent = extra; - target.dispatchEvent(update(event, extra)); - } - finally { - this.feedingEvent = null; - } - }) + } + catch (e) { + util.reportError(e); + } + finally { + this.feedingEvent = null; + } + } }), - createContents: Class.Memoize(function () services.has("dactyl") && services.dactyl.createContents - || function (elem) {}), + createContents: Class.Memoize(() => services.has("dactyl") && services.dactyl.createContents + || (elem => {})), + + isScrollable: Class.Memoize(() => services.has("dactyl") && services.dactyl.getScrollable + ? (elem, dir) => services.dactyl.getScrollable(elem) & (dir ? services.dactyl["DIRECTION_" + dir.toUpperCase()] : ~0) + : (elem, dir) => true), + + isJSONXML: function isJSONXML(val) isArray(val) && isinstance(val[0], ["String", "Array", "XML", DOM.DOMString]) + || isObject(val) && "toDOM" in val, - isScrollable: Class.Memoize(function () services.has("dactyl") && services.dactyl.getScrollable - ? function (elem, dir) services.dactyl.getScrollable(elem) & (dir ? services.dactyl["DIRECTION_" + dir.toUpperCase()] : ~0) - : function (elem, dir) true), + 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. */ - editableInputs: Set(["date", "datetime", "datetime-local", "email", "file", - "month", "number", "password", "range", "search", - "tel", "text", "time", "url", "week"]), + editableInputs: RealSet(["date", "datetime", "datetime-local", "email", "file", + "month", "number", "password", "range", "search", + "tel", "text", "time", "url", "week"]), /** * Converts a given DOM Node, Range, or Selection to a string. If @@ -1426,7 +1480,7 @@ var DOM = Class("DOM", { * @returns {boolean} True when the patterns are all valid. */ validateMatcher: function validateMatcher(list) { - return this.testValues(list, DOM.closure.testMatcher); + return this.testValues(list, DOM.bound.testMatcher); }, testMatcher: function testMatcher(value) { @@ -1444,57 +1498,281 @@ 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, m => map[m]); }, - /** - * 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} - */ - 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; + 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 (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 = RealSet("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(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(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 (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 (hasOwnProperty(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) && EMPTY.has(vals[1]) + || asXML && !args.length) + res.push("/>"); + else { + res.push(">"); + + if (isStrings(args)) + res.push(args.map(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]]; }, /** @@ -1521,7 +1799,7 @@ var DOM = Class("DOM", { let resolver = XPath.resolver; if (namespaces) { namespaces = update({}, DOM.namespaces, namespaces); - resolver = function (prefix) namespaces[prefix] || null; + resolver = prefix => namespaces[prefix] || null; } let result = doc.evaluate(expression, elem, @@ -1530,12 +1808,16 @@ var DOM = Class("DOM", { null ); - return Object.create(result, { - __iterator__: { - value: asIterator ? function () { let elem; while ((elem = this.iterateNext())) yield elem; } - : function () { for (let i = 0; i < this.snapshotLength; i++) yield this.snapshotItem(i); } - } - }); + let res = { + iterateNext: function () result.iterateNext(), + get resultType() result.resultType, + get snapshotLength() result.snapshotLength, + snapshotItem: function (i) result.snapshotItem(i), + __iterator__: + asIterator ? function () { let elem; while ((elem = this.iterateNext())) yield elem; } + : function () { for (let i = 0; i < this.snapshotLength; i++) yield this.snapshotItem(i); } + }; + return res; } catch (e) { throw e.stack ? e : Error(e); @@ -1555,25 +1837,27 @@ var DOM = Class("DOM", { */ makeXPath: function makeXPath(nodes) { return array(nodes).map(util.debrace).flatten() - .map(function (node) /^[a-z]+:/.test(node) ? node : [node, "xhtml:" + node]).flatten() - .map(function (node) "//" + node).join(" | "); + .map(node => /^[a-z]+:/.test(node) ? node + : [node, "xhtml:" + node]) + .flatten() + .map(node => "//" + node).join(" | "); }, 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 () - iter(this.namespaces).map(function ([k, v]) [v, k]).toObject()), + iter(this.namespaces).map(([k, v]) => ([v, k])).toObject()), }); Object.keys(DOM.Event.types).forEach(function (event) { - let name = event.replace(/-(.)/g, function (m, m1) m1.toUpperCase()); - if (!Set.has(DOM.prototype, name)) + let name = event.replace(/-(.)/g, (m, m1) => m1.toUpperCase()); + if (!hasOwnProperty(DOM.prototype, name)) DOM.prototype[name] = function _event(arg, extra) { return this[callable(arg) ? "listen" : "dispatch"](event, arg, extra); @@ -1586,4 +1870,4 @@ endModule(); // catch(e){ if (!e.stack) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); } -// vim: set sw=4 ts=4 et ft=javascript: +// vim: set fdm=marker sw=4 sts=4 ts=8 et ft=javascript: