windex

jQuery-like that's safe to use in a Firefox Extension
git clone https://wehaveforgeathome.hates.computer/windex.git
Log | Files | Refs | LICENSE

windex.js (31940B)


      1 var Cc = Components.classes,
      2     Ci = Components.interfaces,
      3     Cu = Components.utils;
      4 
      5 var isNode = function (object) {
      6   if (object instanceof Ci.nsIDOMText) return false;
      7   return (object instanceof Ci.nsIDOMNode) ||
      8       (object instanceof Ci.nsIDOMHTMLDocument) ||
      9       (object instanceof Ci.nsIDOMWindow);
     10 };
     11 
     12 // See: https://bugzilla.mozilla.org/show_bug.cgi?id=550612
     13 // See: https://developer.mozilla.org/en/DOM/DOMImplementation.createHTMLDocument
     14 // See: http://userscripts.org/guides/9
     15 var safeHTMLToDOM = function (document, html) {
     16   if (document instanceof Ci.nsIImageDocument) {
     17     if (document.implementation.createHTMLDocument) {
     18       // FF4 ImageDocuments
     19       var safeDoc = document.implementation.createHTMLDocument('safe');
     20       var holder = safeDoc.createElement('div');
     21       holder.innerHTML = html;
     22       return document.importNode(holder, true);
     23     }
     24     else {
     25       // FF3.* ImageDocuments
     26       var docType = document.implementation.createDocumentType(
     27         "html",
     28         "-//W3C//DTD HTML 4.01 Transitional//EN",
     29         "http://www.w3.org/TR/html4/loose.dtd"
     30       );
     31       var holderDoc = document.implementation.createDocument('', '', docType);
     32       var holder = holderDoc.createElement('div');
     33       holder.innerHTML = html;
     34       return document.importNode(holder, true);
     35     }
     36   }
     37   else {
     38     var holder = document.createElement('div');
     39     holder.innerHTML = html;
     40     return holder;
     41   }
     42 };
     43 
     44 var Windex = exports = function(selector, context) {
     45   // no sense in wrapping the nodes twice
     46   if (selector instanceof WindexNodes) { return selector; }
     47 
     48   if (isNode(selector)) { return new WindexNodes([selector]); }
     49 
     50   if (typeof selector != "string") {
     51     throw new Error("$(" + selector + ", " + context + "): selector is not a Node or a String");
     52   }
     53 
     54   // TODO: return documents, windows
     55   if (selector == "body") return Windex(Windex.defaultContext());
     56   if (selector == "!document") {
     57     var document = Windex.defaultContext().ownerDocument;
     58     return Windex(document);
     59   }
     60   if (selector == "!window") {
     61     return Windex(Windex.defaultContext());
     62     var document = Windex.defaultContext().ownerDocument;
     63     var window = document.defaultView;
     64     return Windex(window);
     65   }
     66 
     67   if (!context) {
     68     return Windex(selector, Windex.defaultContext());
     69   }
     70 
     71   // if passed in a list of nodes, meant the first one
     72   if (context instanceof WindexNodes) { context = context[0]; }
     73   if (!isNode(context)) {
     74     throw new Error("$(" + selector + ", " + context + "): context is not a Node or a String");
     75   }
     76   context = new XPCNativeWrapper(context);
     77 
     78   return new WindexNodes([context], selector, context).find(selector);
     79 };
     80 
     81 Windex.defaultContext = null;
     82 
     83 // See: http://api.jquery.com/jQuery.extend/
     84 Windex.extend = function () {
     85   if (arguments.length == 1) { return this._extendWindex(arguments[0]); };
     86   return this._extendDestructiveMerge.apply(this, arguments);
     87 }
     88 
     89 Windex._extendWindex = function (methods) {
     90   for (name in methods) { this[name] = methods[name]; }
     91 };
     92 
     93 Windex._extendDestructiveMerge = function () {
     94   var target = (arguments[0]) ? arguments[0] : {};
     95   for (var i = 1; i < arguments.length; i++) {
     96     var object = arguments[i];
     97     for (key in object) {
     98       target[key] = object[key];
     99     }
    100   }
    101   return target;
    102 };
    103 
    104 var is_array = exports.is_array = function (obj) {
    105   return Object.prototype.toString.apply(obj) === "[object Array]";
    106 };
    107 
    108 Windex.each = function (a, f) {
    109   if (is_array(a) || a instanceof WindexNodes) {
    110     a.forEach(function (e, i) { f(i, e); });
    111     return;
    112   }
    113   for (key in a) { f(key, a[key]); }
    114 };
    115 Windex.map = function (a, f) { return a.map(f); };
    116 
    117 Windex.inArray = function (e, a) {
    118   return a.some(function (aE) { return e == aE; });
    119 };
    120 
    121 // See: http://github.com/jeresig/sizzle/blob/master/sizzle.js
    122 Windex.contains = function (ancestor, descendant) {
    123   return !!(ancestor.compareDocumentPosition(descendant) & 16);
    124 };
    125 
    126 Windex.matchesSelector = function (node, selector) {
    127   return Windex(selector, node.parentNode).some(function (match) {
    128     return node.wrappedJSObject === match.wrappedJSObject;
    129   });
    130 };
    131 
    132 // See: http://api.jquery.com/jQuery.isEmptyObject/
    133 Windex.isEmptyObject = function (obj) {
    134   for(var i in obj) { return false; }
    135   return true;
    136 };
    137 
    138 var WindexNodes = function (nodes, selector, context) {
    139   for (i = 0; i < nodes.length; i++) {
    140     // See: https://developer.mozilla.org/en/XPCNativeWrapper
    141     this.push(new XPCNativeWrapper(nodes[i]));
    142   }
    143   this._selector = selector;
    144   this._context = context;
    145 };
    146 
    147 WindexNodes.prototype = new Array();
    148 
    149 // MozRepl freaks out when trying to print WindexNodes
    150 WindexNodes.prototype.toString = function () { return "[object WindexNodes]"; };
    151 
    152 WindexNodes.prototype.find = function (selector) {
    153   var context = this[0];
    154   try {
    155     // See: https://developer.mozilla.org/En/DOM/Element.querySelectorAll
    156     var matches = context.querySelectorAll(selector);
    157   } catch (e if (e.name == "NS_ERROR_DOM_SYNTAX_ERR")) {
    158     throw new Error("invalid selector: '" + selector + "'");
    159   }
    160 
    161   // turn the matches NodeList into an array
    162   var nodes = [];
    163   for (i = 0; i < matches.length; i++) { nodes.push(matches[i]); }
    164 
    165   return new WindexNodes(nodes, selector, context);
    166 };
    167 
    168 WindexNodes.prototype.toArray = function () { return [].concat(this); }
    169 
    170 WindexNodes.prototype.each = function (f) { return Windex.each(this, f); };
    171 
    172 WindexNodes.prototype.filter = function (f) {
    173   return Array.prototype.filter.apply(this,[function (n,i) { return f(i,n); }]);
    174 };
    175 
    176 // See: http://api.jquery.com/children/
    177 WindexNodes.prototype.children = function (selector) {
    178   if (selector) { return this._childrenMatching(selector); }
    179   var children = [];
    180   this.forEach(function (node) {
    181     var nodeList = node.childNodes;
    182     for (i = 0; i < nodeList.length; i++) {
    183       var node = nodeList[i];
    184       if (isNode(node)) {
    185         children.push(node);
    186       }
    187     }
    188   });
    189   return new WindexNodes(children);
    190 };
    191 
    192 WindexNodes.prototype._childrenMatching = function (selector) {
    193   var children = [];
    194   this.forEach(function (node) {
    195     var matches = new WindexNodes([node], selector, node).find(selector);
    196     matches.forEach(function (windexNode) { children.push(windexNode); });
    197   });
    198   return new WindexNodes(children, selector, this[0]);
    199 };
    200 
    201 // See: http://api.jquery.com/parent/
    202 WindexNodes.prototype.parent = function (selector) {
    203   if (selector) { return this._parentMatching(selector); }
    204   return new WindexNodes(this.map(function (node) { return node.parentNode; }));
    205 };
    206 
    207 WindexNodes.prototype._parentMatching = function (selector) {
    208   var parents = this.map(function (node) { return node.parentNode; }).
    209       filter(function (node) {return Windex.matchesSelector(node, selector);});
    210   return new WindexNodes(parents, selector, parents[0]);
    211 };
    212 
    213 // See: http://api.jquery.com/parents/
    214 WindexNodes.prototype.parents = function (selector) {
    215   if (selector) { return this._parentsMatching(selector); }
    216   var parents = [];
    217   this.forEach(function (node) {
    218     // See: https://developer.mozilla.org/En/DOM/Node.nodeName
    219     while (node.parentNode && node.parentNode.nodeName != '#document') {
    220       node = node.parentNode;
    221       parents.push(node);
    222     }
    223   });
    224   return new WindexNodes(parents);
    225 };
    226 
    227 WindexNodes.prototype._parentsMatching = function (selector) {
    228   var parents = this.parents().filter(function (i, n) {
    229     return Windex.matchesSelector(n, selector);
    230   });
    231   return new WindexNodes(parents, selector, parents[0]);
    232 };
    233 
    234 // See: http://api.jquery.com/addClass/
    235 WindexNodes.prototype.addClass = function (className) {
    236   var classesToAdd = className.split(" ");
    237   this.forEach(function (node) {
    238     var classes = (node.className) ? node.className.split(" ") : [];
    239     classesToAdd.forEach(function (classToAdd) {
    240       if (!classes.some(function (class) { return classToAdd == class; })) {
    241         classes.push(classToAdd);
    242       }
    243     });
    244     node.className = classes.join(" ");
    245   });
    246   return this;
    247 };
    248 
    249 // See: http://api.jquery.com/removeClass/
    250 WindexNodes.prototype.removeClass = function (className) {
    251   if (!className) { return this._removeAllClasses(); }
    252 
    253   var classesToRemove = className.split(" ");
    254   this.forEach(function (node) {
    255     var classes = (node.className) ? node.className.split(" ") : [];
    256     classes = classes.filter(function (class) {
    257       return classesToRemove.every(function (classToRemove) {
    258         return class != classToRemove;
    259       });
    260     });
    261     node.className = classes.join(" ");
    262   });
    263   return this;
    264 };
    265 
    266 WindexNodes.prototype._removeAllClasses = function () {
    267   this.forEach(function (node) { node.className = ''; });
    268   return this;
    269 };
    270 
    271 // See: http://api.jquery.com/toggleClass/
    272 WindexNodes.prototype.toggleClass = function (className, add) {
    273   if (add === true) { return this.addClass(className); }
    274   if (add === false) { return this.removeClass(className); }
    275   var classesToToggle = className.split(" ");
    276   this.forEach(function (node) {
    277     var classes = node.className.split(" ");
    278     classesToToggle.forEach(function (classToToggle) {
    279       // if node has the class, remove it
    280       if (classes.some(function (class) { return class == classToToggle; })) {
    281         classes = classes.
    282             filter(function (class) {return class != classToToggle; });
    283       }
    284       // otherwise, add it
    285       else {
    286         classes.push(classToToggle);
    287       }
    288     });
    289     node.className = classes.join(" ");
    290   });
    291   return this;
    292 };
    293 
    294 // See: http://api.jquery.com/hasClass/
    295 WindexNodes.prototype.hasClass = function (className) {
    296   var classesToFind = className.split(" ");
    297   return this.some(function (node) {
    298     var classes = node.className.split(" ");
    299     return classesToFind.every(function (class) {
    300       return Windex.inArray(class, classes);
    301     });
    302   });
    303 };
    304 
    305 // See: http://api.jquery.com/size/
    306 WindexNodes.prototype.size = function () { return this.length; };
    307 
    308 // See: http://api.jquery.com/empty/
    309 WindexNodes.prototype.empty = function () {
    310   this.forEach(function (node) {
    311     if (!node.hasChildNodes()) { return; }
    312     while (node.childNodes.length >= 1){
    313       node.removeChild(node.firstChild);
    314     }
    315   });
    316   return this;
    317 };
    318 
    319 // See: http://api.jquery.com/remove/
    320 // TODO: optional selector
    321 WindexNodes.prototype.remove = function () {
    322   this.forEach(function (node) {
    323     // in case a node is already removed
    324     if (!node.parentNode) { return; }
    325     node.parentNode.removeChild(node);
    326   });
    327   return this;
    328 };
    329 
    330 // See: http://api.jquery.com/append/
    331 WindexNodes.prototype.append = function (arg) {
    332   if (typeof arg == "string" || typeof arg == "number") {
    333     return this._appendHTML(arg);
    334   }
    335   if (isNode(arg)) { return this._appendNode(arg); }
    336   if (arg instanceof WindexNodes) { return this._appendWindexNodes(arg); }
    337   throw new Error("'" + arg + "' is not a String, Node, or WindexNodes");
    338 };
    339 
    340 WindexNodes.prototype._appendHTML = function (html) {
    341   this.forEach(function (node) {
    342     var holder = safeHTMLToDOM(node.ownerDocument, html);
    343     while (holder.firstChild) {
    344       node.appendChild(holder.firstChild);
    345     }
    346   });
    347   return this;
    348 };
    349 
    350 // See: https://developer.mozilla.org/En/DOM/Node.cloneNode
    351 WindexNodes.prototype._appendNode = function (nodeToAppend) {
    352   this.forEach(function (node) {
    353     node.appendChild(nodeToAppend.cloneNode(true));
    354   });
    355   return this;
    356 };
    357 
    358 // See: https://developer.mozilla.org/En/DOM/Node.cloneNode
    359 WindexNodes.prototype._appendWindexNodes = function (windexNodes) {
    360   this.forEach(function (node) {
    361     windexNodes.forEach(function (nodeToAppend) {
    362       node.appendChild(nodeToAppend.cloneNode(true));
    363     });
    364   });
    365   return this;
    366 };
    367 
    368 // See: https://developer.mozilla.org/En/DOM/Node.cloneNode
    369 WindexNodes.prototype.clone = function () {
    370   var node = this[0];
    371   return node.cloneNode(true);
    372 };
    373 
    374 // See: http://api.jquery.com/before/
    375 WindexNodes.prototype.before = function (arg) {
    376   if (typeof arg == "string" || typeof arg == "number") {
    377     return this._beforeHTML(arg);
    378   }
    379   if (isNode(arg)) { return this._beforeNode(arg); }
    380   if (arg instanceof WindexNodes) { return this._beforeWindexNodes(arg); }
    381   throw new Error("'" + arg + "' is not a String, Node, or WindexNodes");
    382 };
    383 
    384 WindexNodes.prototype._beforeHTML = function (html) {
    385   this.forEach(function (node) {
    386     var holder = safeHTMLToDOM(node.ownerDocument, html);
    387     while (holder.firstChild) {
    388       node.parentNode.insertBefore(holder.firstChild, node);
    389     }
    390   });
    391   return this;
    392 };
    393 
    394 // See: https://developer.mozilla.org/En/DOM/Node.cloneNode
    395 WindexNodes.prototype._beforeNode = function (nodeToAppend) {
    396   this.forEach(function (node) {
    397     node.parentNode.insertBefore(nodeToAppend.cloneNode(true), node);
    398   });
    399   return this;
    400 };
    401 
    402 // See: https://developer.mozilla.org/En/DOM/Node.cloneNode
    403 WindexNodes.prototype._beforeWindexNodes = function (windexNodes) {
    404   this.forEach(function (node) {
    405     windexNodes.forEach(function (nodeToAppend) {
    406       node.parentNode.insertBefore(nodeToAppend.cloneNode(true), node);
    407     });
    408   });
    409   return this;
    410 };
    411 
    412 // See: http://api.jquery.com/after/
    413 WindexNodes.prototype.after = function (arg) {
    414   if (typeof arg == "string" || typeof arg == "number") {
    415     return this._afterHTML(arg);
    416   }
    417   if (isNode(arg)) { return this._afterNode(arg); }
    418   if (arg instanceof WindexNodes) { return this._afterWindexNodes(arg); }
    419   throw new Error("'" + arg + "' is not a String, Node, or WindexNodes");
    420 };
    421 
    422 // See: http://snipplr.com/view/2107/insertafter-function-for-the-dom/
    423 var after = function (node, toAdd) {
    424   if (!node.nextSibling) { return node.parentNode.append(toAdd); }
    425   node.parentNode.insertBefore(toAdd, node.nextSibling);
    426 };
    427 
    428 WindexNodes.prototype._afterHTML = function (html) {
    429   this.forEach(function (node) {
    430     var holder = safeHTMLToDOM(node.ownerDocument, html);
    431     while (holder.lastChild) {
    432       after(node, holder.lastChild);
    433     }
    434   });
    435   return this;
    436 };
    437 
    438 // See: https://developer.mozilla.org/En/DOM/Node.cloneNode
    439 WindexNodes.prototype._afterNode = function (nodeToAppend) {
    440   this.forEach(function (node) { after(node, nodeToAppend.cloneNode(true)); });
    441   return this;
    442 };
    443 
    444 // See: https://developer.mozilla.org/En/DOM/Node.cloneNode
    445 WindexNodes.prototype._afterWindexNodes = function (windexNodes) {
    446   this.forEach(function (node) {
    447     windexNodes.forEach(function (nodeToAppend) {
    448       after(node, nodeToAppend.cloneNode(true));
    449     });
    450   });
    451   return this;
    452 };
    453 
    454 // See: http://api.jquery.com/html/
    455 WindexNodes.prototype.html = function (replacement) {
    456   if (!replacement) { return this._htmlGet(); };
    457   return this._htmlSet(replacement);
    458 };
    459 
    460 // FIXME: innerHTML does work on ImageDocuments in FF < 4
    461 // See: https://bugzilla.mozilla.org/show_bug.cgi?id=550612
    462 WindexNodes.prototype._htmlGet = function () {
    463   var html = "";
    464   this.forEach(function (node) {
    465     if (node.ownerDocument instanceof Components.interfaces.nsIImageDocument) {
    466       throw new Error("html() does not work on ImageDocuments. See: https://bugzilla.mozilla.org/show_bug.cgi?id=550612");
    467     }
    468     html += node.innerHTML;
    469   });
    470   return html;
    471 };
    472 
    473 WindexNodes.prototype._htmlSet = function (replacement) {
    474   this.forEach(function (node) { Windex(node).empty().append(replacement); });
    475   return this;
    476 };
    477 
    478 // See: http://api.jquery.com/replaceWith/
    479 WindexNodes.prototype.replaceWith = function (html) {
    480   this.after(html).remove();
    481   return this;
    482 };
    483 
    484 // See: http://api.jquery.com/css/
    485 WindexNodes.prototype.css = function (name, value) {
    486   if (value === undefined && typeof name == "string") {
    487     return this._cssGet(name);
    488   };
    489   if (typeof name != "object") { return this._cssSet(name, value); };
    490 
    491   for (var key in name) { this._cssSet(key, name[key]); };
    492   return this;
    493 };
    494 
    495 // See: https://developer.mozilla.org/en/DOM/window.getComputedStyle
    496 WindexNodes.prototype._cssGet = function (name) {
    497   var node = this[0];
    498   var window = node.ownerDocument.defaultView;
    499   var computedStyle = window.getComputedStyle(node, null);
    500   return computedStyle.getPropertyValue(name);
    501 };
    502 
    503 WindexNodes.prototype._cssSet = function (name, value) {
    504   var dims = ["height", "width", "top", "left", "right", "bottom"];
    505   if (typeof(value) == "number" &&
    506       dims.some(function (dim) { return name == dim; })) {
    507     value = parseInt(value) + "px";
    508   }
    509   var translations = {
    510     "z-index": "zIndex",
    511     "overflow-y": "overflowY",
    512     "overflow-x": "overflowX",
    513     "margin-top": "marginTop"
    514   };
    515   if (translations[name]) { name = translations[name]; }
    516   this.forEach(function (node) { node.style[name] = value; });
    517   return this;
    518 };
    519 
    520 // See: http://api.jquery.com/height/
    521 // See: http://api.jquery.com/width/
    522 // TODO: convert everything to px instead of pretending em == pt == px
    523 ["height", "width"].forEach(function (dim) {
    524   var capitalized = dim.replace(
    525     /^(.)(.+)/ ,
    526     function(m, p1, p2) { return p1.toUpperCase() + p2; }
    527   );
    528 
    529   WindexNodes.prototype[dim] = function (replacement) {
    530     if (!replacement) { return this["_" + dim + "Get"](); };
    531     return this["_" + dim + "Set"](replacement);
    532   };
    533 
    534   WindexNodes.prototype["_" + dim + "Get"] = function () {
    535     var node = this[0];
    536     if (!node) { return 0; }
    537     if (node instanceof Components.interfaces.nsIDOMHTMLDocument) {
    538       return this["_" + dim + "GetForDocument"]();
    539     }
    540     if (node instanceof Components.interfaces.nsIDOMWindow) {
    541       return this["_" + dim + "GetForWindow"]();
    542     }
    543     return this["_" + dim + "GetForNode"]();
    544   };
    545 
    546   WindexNodes.prototype["_" + dim + "GetForNode"] = function () {
    547     var node = this[0];
    548     var value = parseInt(Windex(node)._cssGet(dim));
    549     return (value) ? value : 0;
    550   };
    551 
    552   WindexNodes.prototype["_" + dim + "GetForDocument"] = function () {
    553     var node = this[0];
    554     return node.documentElement["client" + capitalized];
    555   };
    556 
    557   WindexNodes.prototype["_" + dim + "GetForWindow"] = function () {
    558     var node = this[0];
    559     return node["inner" + capitalized];
    560   };
    561 
    562   WindexNodes.prototype["_" + dim + "Set"] = function (replacement) {
    563     this.forEach(function (node) {
    564       if (node instanceof Components.interfaces.nsIDOMHTMLDocument) {
    565         return setters[dim + "ForDocument"](node, replacement);
    566       }
    567       if (node instanceof Components.interfaces.nsIDOMWindow) {
    568         return setters[dim + "ForWindow"](node, replacement);
    569       }
    570       setters[dim + "ForNode"](node, replacement);
    571     });
    572     return this;
    573   };
    574 
    575   // work on a node at a time
    576   var setters = {};
    577   setters[dim + "ForNode"] = function (node, replacement) {
    578     return Windex(node)._cssSet(dim, (replacement) ? replacement + "px" : 0);
    579   };
    580 
    581   setters[dim + "ForDocument"] = function (node, replacement) {
    582     return node[dim] = replacement;
    583   };
    584 
    585   setters[dim + "ForWindow"] = function (node, replacement) {
    586     return node["inner" + capitalized] = replacement;
    587   };
    588 });
    589 
    590 // See: http://api.jquery.com/outerWidth/
    591 WindexNodes.prototype.outerWidth = function (includeMargin) {
    592   var dims = {
    593     padding: 0,
    594     margin: 0,
    595     border: 0
    596   };
    597   var node = this;
    598   for (key in dims) {
    599     ["left", "right"].forEach(function (side) {
    600       if (includeMargin) {
    601         var attribute = parseInt(node.css(key + "-" + side + "-width"));
    602         dims[key] += (isNaN(attribute)) ? 0 : attribute;
    603       }
    604     });
    605   }
    606   var val = this[0].offsetWidth;
    607   if (includeMargin) { return val + dims.margin; }
    608   return val - dims.padding - dims.border;
    609 };
    610 
    611 // See: http://api.jquery.com/outerHeight/
    612 WindexNodes.prototype.outerHeight = function (includeMargin) {
    613   if (this.size() == 0) return 0;
    614   var node = this;
    615   var dims = {
    616     padding: 0,
    617     margin: 0,
    618     border: 0
    619   };
    620   for (key in dims) {
    621     ["top", "bottom"].forEach(function (side) {
    622       if (includeMargin) {
    623         var attribute = parseInt(node.css(key + "-" + side + "-height"));
    624         dims[key] += (isNaN(attribute)) ? 0 : attribute;
    625       }
    626     });
    627   }
    628   var val = this[0].offsetHeight;
    629   if (includeMargin) { return val + dims.margin; }
    630   return val - dims.padding - dims.border;
    631 };
    632 
    633 // See: http://api.jquery.com/trigger/
    634 // See: https://developer.mozilla.org/en/Code_snippets/Interaction_between_privileged_and_non-privileged_pages
    635 WindexNodes.prototype.trigger = function (name) {
    636   this.forEach(function (node) {
    637     if (typeof node[name] == "function") {
    638       node[name]();
    639     } else {
    640       var event = node.ownerDocument.createEvent("Events");
    641       event.initEvent(name, true, false);
    642       node.dispatchEvent(event);
    643     }
    644   });
    645   return this;
    646 };
    647 
    648 WindexNodes.prototype.triggerHandler = function (name) {
    649   this.trigger(name);
    650   return this;
    651 };
    652 
    653 
    654 var events = [];
    655 
    656 // See: http://api.jquery.com/bind/
    657 WindexNodes.prototype.bind = function (name, handler) {
    658   this.forEach(function (node) {
    659     var wrapped = function (event) { handler.apply(node, [event]); };
    660     if (!events[node]) { events[node] = {}; }
    661     if (!events[node][name]) { events[node][name] = []; }
    662     events[node][name].push({ wrapped: wrapped, handler: handler });
    663     node.addEventListener(name, wrapped, false, false);
    664   });
    665   return this;
    666 };
    667 
    668 // See: http://api.jquery.com/unbind
    669 WindexNodes.prototype.unbind = function (name, handler) {
    670   if (!name) { return this._unbindAll(); }
    671   if (!handler) { return this._unbindEventAll(name); }
    672   return this._unbindEvent(name, handler);
    673 }
    674 
    675 WindexNodes.prototype._unbindAll = function () {
    676   this.forEach(function (node) {
    677     for (name in events[node]) {
    678       events[node][name].forEach(function (bridge) {
    679         node.removeEventListener(name, bridge.wrapped, false);
    680       });
    681     }
    682     delete events[node];
    683   });
    684   return this;
    685 };
    686 
    687 WindexNodes.prototype._unbindEventAll = function (name) {
    688   this.forEach(function (node) {
    689     if (!events[node] || !events[node][name]) return;
    690     events[node][name].forEach(function (bridge) {
    691       node.removeEventListener(name, bridge.wrapped, false);
    692     });
    693     delete events[node][name];
    694   });
    695   return this;
    696 };
    697 
    698 WindexNodes.prototype._unbindEvent = function (name, handler) {
    699   this.forEach(function (node) {
    700     if (!events[node] || !events[node][name]) return;
    701     events[node][name] = events[node][name].filter(function (bridge) {
    702       if (bridge.handler !== handler) return true;
    703       node.removeEventListener(name, bridge.wrapped, false);
    704       return false;
    705     });
    706   });
    707   return this;
    708 };
    709 
    710 [
    711   "click", // See: http://api.jquery.com/click/
    712   "mousedown",
    713   "mouseup",
    714   "mousemove",
    715   "mouseenter",
    716   "mouseleave",
    717   "keypress", // See: http://api.jquery.com/keypress/
    718   "keydown",
    719   "keyup",
    720   "resize", // See: http://api.jquery.com/resize/
    721   "submit", // See: http://api.jquery.com/submit/
    722   "focus", // See: http://api.jquery.com/focus/
    723   "blur", // See: http://api.jquery.com/blur/
    724   "change", // See: http://api.jquery.com/change/
    725   "command"
    726 ].forEach(function (event) {
    727   WindexNodes.prototype[event] = function (handler) {
    728     if (!handler) { return this.trigger(event); }
    729     return this.bind(event, handler);
    730   };
    731 });
    732 
    733 var delegates = [];
    734 
    735 // See: http://api.jquery.com/live
    736 WindexNodes.prototype.live = function (name, handler) {
    737   var selector = this._selector;
    738   var context = this._context;
    739   var delegater = function (event) {
    740     Windex(selector, context).forEach(function (node) {
    741       var isTarget = (node == event.originalTarget);
    742       var containsTarget = Windex.contains(node, event.originalTarget);
    743       if (isTarget || containsTarget) { handler.apply(node, [event]); }
    744     });
    745   };
    746   if (!delegates[context]) { delegates[context] = {}; }
    747   if (!delegates[context][name]) { delegates[context][name] = []; }
    748   delegates[context][name].push({ wrapped: delegater, handler: handler });
    749   context.addEventListener(name, delegater, false);
    750 };
    751 
    752 // See: http://api.jquery.com/die
    753 WindexNodes.prototype.die = function (name, handler) {
    754   if (!name) { return this._dieAll(); }
    755   return this._dieEvent(name, handler);
    756 }
    757 
    758 WindexNodes.prototype._dieAll = function () {
    759   var node = this._context;
    760   for (name in delegates[node]) {
    761     delegates[node][name].forEach(function (bridge) {
    762       node.removeEventListener(name, bridge.wrapped, false);
    763     });
    764   }
    765   delete delegates[node];
    766   return this;
    767 };
    768 
    769 WindexNodes.prototype._dieEvent = function (name, handler) {
    770   var node = this._context;
    771   if (!delegates[node][name]) return;
    772   delegates[node][name] = delegates[node][name].filter(function (bridge) {
    773     if (bridge.handler !== handler) return true;
    774     node.removeEventListener(name, bridge.wrapped, false);
    775     return false;
    776   });
    777   return this;
    778 };
    779 
    780 
    781 // See: http://api.jquery.com/attr/
    782 WindexNodes.prototype.attr = function (name, value) {
    783   if (typeof name == "object") { return this._attrSetObj(name); }
    784   if (value === undefined) { return this._attrGet(name); }
    785   return this._attrSet(name, value);
    786 };
    787 
    788 WindexNodes.prototype._attrGet = function (name) {
    789   var node = this[0];
    790   return node.getAttribute(name);
    791 };
    792 
    793 WindexNodes.prototype._attrSet = function (name, value) {
    794   if (name == "class") {
    795     this.forEach(function (node) { node.className = value; });
    796     return this;
    797   }
    798   this.forEach(function (node) {
    799     node.setAttribute(name, value);
    800     if(name === 'checked' && (node.type === 'checkbox' || node.type === 'radio')) {
    801       node.checked = value;
    802     }
    803   });
    804   return this;
    805 };
    806 
    807 WindexNodes.prototype._attrSetObj = function (obj) {
    808   for (key in obj) { this._attrSet(key, obj[key]); }
    809   return this;
    810 };
    811 
    812 // See: http://api.jquery.com/removeAttr/
    813 WindexNodes.prototype.removeAttr = function (name) {
    814   this.forEach(function (node) {
    815     node.removeAttribute(name);
    816     if(name === 'checked' && node.type === 'checkbox') {
    817       node.checked = false;
    818     }
    819   });
    820   return this;
    821 };
    822 
    823 // See: http://api.jquery.com/val/
    824 WindexNodes.prototype.val = function (replacement) {
    825   if (replacement === undefined) { return this._valGet(); }
    826   return this._valSet(replacement);
    827 };
    828 
    829 WindexNodes.prototype._valGet = function () {
    830   var node = this[0];
    831   return node.value;
    832 };
    833 
    834 WindexNodes.prototype._valSet = function (replacement) {
    835   this.forEach(function (node) { node.value = replacement; });
    836   return this;
    837 };
    838 
    839 // See: http://api.jquery.com/hide/
    840 WindexNodes.prototype.hide = function () {
    841   this.forEach(function (node) {
    842     if (Windex(node).css("display") == "none") return;
    843     Windex(node).attr("olddisplay", Windex(node).css("display"));
    844     Windex(node).css("display", "none");
    845   });
    846   return this;
    847 };
    848 
    849 // See: http://api.jquery.com/show/
    850 WindexNodes.prototype.show = function () {
    851   this.forEach(function (node) {
    852     node = Windex(node);
    853     if (node.css("display") != "none") return;
    854     var display = (node.attr("olddisplay")) ? node.attr("olddisplay") : "block";
    855     node.css("display", display).removeAttr("olddisplay");
    856   });
    857   return this;
    858 };
    859 
    860 WindexNodes.prototype.toggle = function () {
    861   this.forEach(function (node) {
    862     node = Windex(node);
    863     if (node.css("display") == "none") { node.show(); } else { node.hide(); }
    864   });
    865   return this;
    866 };
    867 
    868 // See: http://api.jquery.com/text/
    869 WindexNodes.prototype.text = function (replacement) {
    870   if (replacement === undefined) { return this._textGet(); }
    871   return this._textSet(replacement);
    872 };
    873 
    874 WindexNodes.prototype._textGet = function () {
    875   return this.map(function (node) { return node.textContent; }).join(" ");
    876 };
    877 
    878 WindexNodes.prototype._textSet = function (replacement) {
    879   this.forEach(function (node) { node.textContent = replacement; });
    880   return this;
    881 };
    882 
    883 // See: http://api.jquery.com/position/
    884 WindexNodes.prototype.position = function () {
    885   var node = this[0];
    886   return { top: node.offsetTop, left: node.offsetLeft };
    887 };
    888 
    889 // See: http://api.jquery.com/scrollTop/
    890 // See: http://api.jquery.com/scrollLeft/
    891 [
    892   "Top",
    893   "Left"
    894 ].forEach(function (direction) {
    895   var axis = (direction == "Top") ? "Y" : "X";
    896 
    897   WindexNodes.prototype["scroll" + direction] = function (replacement) {
    898     if (!replacement) { return this["_scroll" + direction + "Get"](); }
    899     return this["_scroll" + direction + "Set"](replacement);
    900   };
    901 
    902   WindexNodes.prototype["_scroll" + direction + "Get"] = function () {
    903     var node = this[0];
    904     if (node instanceof Components.interfaces.nsIDOMHTMLDocument) {
    905       return this["_scroll" + direction + "GetForDocument"]();
    906     }
    907     if (node instanceof Components.interfaces.nsIDOMWindow) {
    908       return this["_scroll" + direction + "GetForWindow"]();
    909     }
    910     return this["_scroll" + direction + "GetForNode"]();
    911   };
    912 
    913   WindexNodes.prototype["_scroll" + direction + "GetForDocument"] = function() {
    914     var node = this[0];
    915     return node.defaultView["scroll" + axis];
    916   };
    917 
    918   WindexNodes.prototype["_scroll" + direction + "GetForWindow"] = function () {
    919     var node = this[0];
    920     return node["scroll" + axis];
    921   };
    922 
    923   WindexNodes.prototype["_scroll" + direction + "GetForNode"] = function () {
    924     var node = this[0];
    925     return node["scroll" + direction];
    926   };
    927 
    928   var setters = {
    929     document: function (n, r) { n.defaultView["scroll" + axis] = r; },
    930     window: function (n, r) { n["scroll" + axis] = r; },
    931     node: function (n, r) { n["scroll" + direction] = r; }
    932   };
    933 
    934   WindexNodes.prototype["_scroll" + direction + "Set"] = function (replacement){
    935     this.forEach(function (node) {
    936       if (node instanceof Components.interfaces.nsIDOMHTMLDocument) {
    937         return setters.document(replacement);
    938       }
    939       if (node instanceof Components.interfaces.nsIDOMWindow) {
    940         return setters.window(replacement);
    941       }
    942       setters.node(replacement);
    943     });
    944     return this;
    945   };
    946 });
    947 
    948 WindexNodes.prototype.first = function () {
    949   var node = this[0];
    950   return new WindexNodes([node], this._selector, this._context);
    951 };
    952 
    953 WindexNodes.prototype.last = function () {
    954   var node = this[this.length - 1];
    955   return new WindexNodes([node], this._selector, this._context);
    956 };
    957 
    958 WindexNodes.prototype.next = function (selector) {
    959   if (selector) { return this._nextMatching(selector); }
    960   var nodes = [];
    961   this.forEach(function (node) {
    962     if (!node.nextSibling) { return; }
    963     node = node.nextSibling; // adjacent text node
    964     if (!node.nextSibling) { return; }
    965     nodes.push(node.nextSibling);
    966   });
    967   return new WindexNodes(nodes, this._selector, this._context);
    968 };
    969 
    970 WindexNodes.prototype._nextMatching = function (selector) {
    971   var nodes = [];
    972   this.forEach(function (node) {
    973     if (!node.nextSibling) { return; }
    974     node = node.nextSibling; // adjacent text node
    975     if (!node.nextSibling) { return; }
    976     node = node.nextSibling;
    977     if (!Windex.matchesSelector(node, selector)) { return; }
    978     nodes.push(node);
    979   });
    980   return new WindexNodes(nodes, this._selector, this._context);
    981 };
    982 
    983 WindexNodes.prototype.prev = function () {
    984   var selector = this._selector;
    985   var context = this._context;
    986   var node = this[0];
    987   if (!node.previousSibling) { return new WindexNodes([], selector, context); }
    988   node = node.previousSibling; // adjacent text node
    989   if (!node.previousSibling) { return new WindexNodes([], selector, context); }
    990   return new WindexNodes([node.previousSibling], selector, context);
    991 };
    992 
    993 // See: http://api.jquery.com/is/
    994 WindexNodes.prototype.is = function (selector) {
    995   return this.some(function (node) {
    996     return Windex.matchesSelector(node, selector);
    997   });
    998 };
    999 
   1000 // See: http://api.jquery.com/not/
   1001 WindexNodes.prototype.not = function (selector) {
   1002   var remains = this.filter(function (node) {
   1003     return !Windex.matchesSelector(node, selector);
   1004   });
   1005   return new WindexNodes(remains, this._selector, this._context);
   1006 };
   1007 
   1008 Windex.url = { setUrl: function (url) { return new WindexUrl(url); } };
   1009 
   1010 var WindexUrl = function (url) {
   1011   this._url = {
   1012     spec: "",
   1013     host: "",
   1014     path: ""
   1015   };
   1016   try {
   1017     var ioService = Components.classes["@mozilla.org/network/io-service;1"].
   1018         getService(Components.interfaces.nsIIOService);
   1019     var nsiUri = ioService.newURI(url, null, null);
   1020     for (var key in this._url) {
   1021       this._url[key] = nsiUri[key];
   1022     }
   1023   } catch (e) {}
   1024   return this;
   1025 };
   1026 
   1027 // See: https://developer.mozilla.org/en/nsIURI
   1028 // See: https://developer.mozilla.org/en/nsIIOService#newURI.28.29
   1029 WindexUrl.prototype.attr = function (name) { return this._url[name]; };
   1030 
   1031 Windex.browser = {};