commit e58c54057f8bf02886bf6a8a2e759be3aac699b5
parent 9d6301162f2801de460d624c75e0bb1e368a623c
Author: Ryan Wolf <rwolf@borderstylo.com>
Date: Tue, 14 Dec 2010 16:06:35 -0800
#6895 runInPage should expose a jQuery $
Diffstat:
6 files changed, 998 insertions(+), 12 deletions(-)
diff --git a/addon/chrome.manifest b/addon/chrome.manifest
@@ -2,4 +2,5 @@ content antimony chrome/content/
content antimony chrome/content/ contentaccessible=yes
locale antimony en-US locale/en-US/
skin antimony classic/1.0 chrome/skin/
+resource antimony chrome/modules/
overlay chrome://browser/content/browser.xul chrome://antimony/content/browser.xul
diff --git a/addon/chrome/content/content.js b/addon/chrome/content/content.js
@@ -1,3 +1,8 @@
+Components.utils.import('resource://antimony/common.js');
+require.path.push('resource://antimony');
+
+var $ = require('windex');
+
var Poller = function (url, handler) {
this.xhr = null;
this.url = url;
@@ -28,6 +33,28 @@ Poller.prototype.restart = function () {
this.connect();
};
+var runInBrowser = function (script, callback) {
+ eval('var f = ' + script);
+ f(callback);
+};
+
+var runOnPage = function (url, script, callback) {
+ var tab = gBrowser.selectedTab = gBrowser.addTab(url);
+ var browser = gBrowser.getBrowserForTab(tab);
+ var chwin = window;
+ chwin.addEventListener('DOMContentLoaded', function (e) {
+ var document = browser.contentDocument.wrappedJSObject;
+ if (!document || e.originalTarget != document) { return; }
+ chwin.removeEventListener('DOMContentLoaded', arguments.callee, false);
+ var window = document.defaultView;
+ eval('var f = ' + script);
+ f(function (data) {
+ callback(data);
+ gBrowser.removeTab(tab);
+ });
+ }, false);
+};
+
var messageQueue = [];
var running = false;
@@ -36,14 +63,26 @@ var processNext = function () {
running = true;
var json = messageQueue.shift();
var message = JSON.parse(json);
- eval('var f = ' + message.script);
- f(function (data) {
+ var callback = function (data) {
var xhr = new XMLHttpRequest();
xhr.open("POST", 'http://localhost:1235/browser/' + message.id, true);
xhr.send(JSON.stringify({res: data}));
running = false;
processNext();
- });
+ };
+ try {
+ if (message.type == 'page') {
+ runOnPage(message.url, message.script, callback);
+ }
+ else {
+ runInBrowser(message.script, callback);
+ }
+ }
+ catch (e) {
+ Cu.reportError(e);
+ running = false;
+ processNext();
+ }
};
new Poller('http://localhost:1234/', function (json) {
diff --git a/addon/chrome/modules/common.js b/addon/chrome/modules/common.js
@@ -0,0 +1,34 @@
+var EXPORTED_SYMBOLS = ['require'];
+
+var mods = {};
+
+var require = function (mod) {
+ if (mods[mod]) {
+ return mods[mod].exports;
+ }
+
+ var scope = {
+ require: require
+ };
+
+ var found = false;
+ for (var i = 0 ; i < require.path.length ; i++) {
+ var path = require.path[i];
+ try {
+ scope.exports = {};
+ Components.classes["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Components.interfaces.mozIJSSubScriptLoader).
+ loadSubScript(path + "/" + mod + ".js", scope);
+ found = true;
+ break;
+ } catch (e if e == "Error opening input stream (invalid filename?)") {}
+ }
+ if (!found) {
+ Components.utils.reportError("CommonJS module not found: " + mod);
+ }
+
+ mods[mod] = scope;
+ return mods[mod].exports;
+};
+
+require.path = [];
diff --git a/addon/chrome/modules/windex.js b/addon/chrome/modules/windex.js
@@ -0,0 +1,870 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+var isNode = function (object) {
+ if (object instanceof Ci.nsIDOMText) return false;
+ return (object instanceof Ci.nsIDOMNode) ||
+ (object instanceof Ci.nsIDOMHTMLDocument) ||
+ (object instanceof Ci.nsIDOMWindow);
+}
+
+var Windex = exports = function(selector, context) {
+ // no sense in wrapping the nodes twice
+ if (selector instanceof WindexNodes) { return selector; }
+
+ if (isNode(selector)) { return new WindexNodes([selector]); }
+
+ if (typeof selector != "string") {
+ throw new Error("$(" + selector + ", " + context + "): selector is not a Node or a String");
+ }
+
+ if (!Windex.defaultContext) {
+ throw new Error('You must define Windex.defaultContext() before calling Windex without a context.');
+ }
+
+ // TODO: return documents, windows
+ if (selector == "body") return Windex(defaultContext());
+ if (selector == "!document") {
+ var document = defaultContext().ownerDocument;
+ return Windex(document);
+ }
+ if (selector == "!window") {
+ return Windex(defaultContext());
+ var document = defaultContext().ownerDocument;
+ var window = document.defaultView;
+ return Windex(window);
+ }
+
+ if (!context) {
+ return Windex(selector, defaultContext());
+ }
+
+ // if passed in a list of nodes, meant the first one
+ if (context instanceof WindexNodes) { context = context[0]; }
+ if (!isNode(context)) {
+ throw new Error("$(" + selector + ", " + context + "): context is not a Node or a String");
+ }
+ context = new XPCNativeWrapper(context);
+
+ return new WindexNodes([context], selector, context).find(selector);
+};
+
+Windex.defaultContext = null;
+
+// See: http://api.jquery.com/jQuery.extend/
+Windex.extend = function () {
+ if (arguments.length == 1) { return this._extendWindex(arguments[0]); };
+ return this._extendDestructiveMerge.apply(this, arguments);
+}
+
+Windex._extendWindex = function (methods) {
+ for (name in methods) { this[name] = methods[name]; }
+};
+
+Windex._extendDestructiveMerge = function () {
+ var target = (arguments[0]) ? arguments[0] : {};
+ for (var i = 1; i < arguments.length; i++) {
+ var object = arguments[i];
+ for (key in object) {
+ target[key] = object[key];
+ }
+ }
+ return target;
+};
+
+var is_array = exports.is_array = function (obj) {
+ return Object.prototype.toString.apply(obj) === "[object Array]";
+};
+
+Windex.each = function (a, f) {
+ if (is_array(a) || a instanceof WindexNodes) {
+ a.forEach(function (e, i) { f(i, e); });
+ return;
+ }
+ for (key in a) { f(key, a[key]); }
+};
+Windex.map = function (a, f) { return a.map(f); };
+
+Windex.inArray = function (e, a) {
+ return a.some(function (aE) { return e == aE; });
+};
+
+// See: http://github.com/jeresig/sizzle/blob/master/sizzle.js
+Windex.contains = function (ancestor, descendant) {
+ return !!(ancestor.compareDocumentPosition(descendant) & 16);
+};
+
+var WindexNodes = function (nodes, selector, context) {
+ for (i = 0; i < nodes.length; i++) {
+ // See: https://developer.mozilla.org/en/XPCNativeWrapper
+ this.push(new XPCNativeWrapper(nodes[i]));
+ }
+ this._selector = selector;
+ this._context = context;
+};
+
+WindexNodes.prototype = new Array();
+
+// MozRepl freaks out when trying to print WindexNodes
+WindexNodes.prototype.toString = function () { return "[object WindexNodes]"; };
+
+WindexNodes.prototype.find = function (selector) {
+ var context = this[0];
+ try {
+ // See: https://developer.mozilla.org/En/DOM/Element.querySelectorAll
+ var matches = context.querySelectorAll(selector);
+ } catch (e if (e.name == "NS_ERROR_DOM_SYNTAX_ERR")) {
+ throw new Error("invalid selector: '" + selector + "'");
+ }
+
+ // turn the matches NodeList into an array
+ var nodes = [];
+ for (i = 0; i < matches.length; i++) { nodes.push(matches[i]); }
+
+ return new WindexNodes(nodes, selector, context);
+};
+
+WindexNodes.prototype.each = function (f) { return Windex.each(this, f); };
+
+WindexNodes.prototype.filter = function (f) {
+ return Array.prototype.filter.apply(this,[function (n,i) { return f(i,n); }]);
+};
+
+// See: http://api.jquery.com/children/
+WindexNodes.prototype.children = function (selector) {
+ var children = [];
+ if (selector) { return this._childrenMatching(selector); }
+ this.forEach(function (node) {
+ var nodeList = node.childNodes;
+ for (i = 0; i < nodeList.length; i++) {
+ var node = nodeList[i];
+ if (isNode(node)) {
+ children.push(node);
+ }
+ }
+ });
+ return new WindexNodes(children);
+};
+
+WindexNodes.prototype._childrenMatching = function (selector) {
+ var children = [];
+ this.forEach(function (node) {
+ var matches = new WindexNodes([node], selector, node).find(selector);
+ matches.forEach(function (windexNode) { children.push(windexNode); });
+ });
+ return new WindexNodes(children, selector, this[0]);
+};
+
+// See: http://api.jquery.com/addClass/
+WindexNodes.prototype.addClass = function (className) {
+ var classesToAdd = className.split(" ");
+ this.forEach(function (node) {
+ var classes = (node.className) ? node.className.split(" ") : [];
+ classesToAdd.forEach(function (classToAdd) {
+ if (!classes.some(function (class) { return classToAdd == class; })) {
+ classes.push(classToAdd);
+ }
+ });
+ node.className = classes.join(" ");
+ });
+ return this;
+};
+
+// See: http://api.jquery.com/removeClass/
+WindexNodes.prototype.removeClass = function (className) {
+ if (!className) { return this._removeAllClasses(); }
+
+ var classesToRemove = className.split(" ");
+ this.forEach(function (node) {
+ var classes = (node.className) ? node.className.split(" ") : [];
+ classes = classes.filter(function (class) {
+ return classesToRemove.every(function (classToRemove) {
+ return class != classToRemove;
+ });
+ });
+ node.className = classes.join(" ");
+ });
+ return this;
+};
+
+WindexNodes.prototype._removeAllClasses = function () {
+ this.forEach(function (node) { node.className = ''; });
+};
+
+// See: http://api.jquery.com/toggleClass/
+WindexNodes.prototype.toggleClass = function (className, add) {
+ if (add === true) { return this.addClass(className); }
+ if (add === false) { return this.removeClass(className); }
+ var classesToToggle = className.split(" ");
+ this.forEach(function (node) {
+ var classes = node.className.split(" ");
+ classesToToggle.forEach(function (classToToggle) {
+ // if node has the class, remove it
+ if (classes.some(function (class) { return class == classToToggle; })) {
+ classes = classes.
+ filter(function (class) {return class != classToToggle; });
+ }
+ // otherwise, add it
+ else {
+ classes.push(classToToggle);
+ }
+ });
+ node.className = classes.join(" ");
+ });
+ return this;
+};
+
+// See: http://api.jquery.com/hasClass/
+WindexNodes.prototype.hasClass = function (className) {
+ var classesToFind = className.split(" ");
+ return this.some(function (node) {
+ var classes = node.className.split(" ");
+ return classesToFind.every(function (class) {
+ return Windex.inArray(class, classes);
+ });
+ });
+};
+
+// See: http://api.jquery.com/size/
+WindexNodes.prototype.size = function () { return this.length; };
+
+// See: http://api.jquery.com/empty/
+WindexNodes.prototype.empty = function () {
+ this.forEach(function (node) {
+ if (!node.hasChildNodes()) { return; }
+ while (node.childNodes.length >= 1){
+ node.removeChild(node.firstChild);
+ }
+ });
+ return this;
+};
+
+// See: http://api.jquery.com/remove/
+// TODO: optional selector
+WindexNodes.prototype.remove = function () {
+ this.forEach(function (node) {
+ // in case a node is already removed
+ if (!node.parentNode) { return; }
+ node.parentNode.removeChild(node);
+ });
+ return this;
+};
+
+// See: http://api.jquery.com/append/
+WindexNodes.prototype.append = function (arg) {
+ if (typeof arg == "string" || typeof arg == "number") {
+ if (this[0].ownerDocument instanceof Ci.nsIImageDocument) {
+ return this._appendHTMLtoImageDocument(arg);
+ }
+ return this._appendHTML(arg);
+ }
+ if (isNode(arg)) { return this._appendNode(arg); }
+ if (arg instanceof WindexNodes) { return this._appendWindexNodes(arg); }
+ throw new Error("'" + arg + "' is not a String, Node, or WindexNodes");
+};
+
+WindexNodes.prototype._appendHTML = function (html) {
+ this.forEach(function (node) {
+ var holder = node.ownerDocument.createElement("div");
+ holder.innerHTML = html;
+ while (holder.firstChild) {
+ node.appendChild(holder.firstChild);
+ }
+ });
+ return this;
+};
+
+// See: https://bugzilla.mozilla.org/show_bug.cgi?id=550612
+// See: http://userscripts.org/guides/9
+WindexNodes.prototype._appendHTMLtoImageDocument = function (html) {
+ this.forEach(function (node) {
+ var document = node.ownerDocument;
+ var docType = document.implementation.createDocumentType(
+ "html",
+ "-//W3C//DTD HTML 4.01 Transitional//EN",
+ "http://www.w3.org/TR/html4/loose.dtd"
+ );
+ var holderDoc = document.implementation.createDocument('', '', docType);
+ var holder = holderDoc.createElement('html');
+ holder.innerHTML = html;
+ while (holder.firstChild) {
+ node.appendChild(holder.firstChild);
+ }
+ });
+ return this;
+};
+// See: https://developer.mozilla.org/En/DOM/Node.cloneNode
+WindexNodes.prototype._appendNode = function (nodeToAppend) {
+ this.forEach(function (node) {
+ node.appendChild(nodeToAppend.cloneNode(true));
+ });
+ return this;
+};
+
+// See: https://developer.mozilla.org/En/DOM/Node.cloneNode
+WindexNodes.prototype._appendWindexNodes = function (windexNodes) {
+ this.forEach(function (node) {
+ windexNodes.forEach(function (nodeToAppend) {
+ node.appendChild(nodeToAppend.cloneNode(true));
+ });
+ });
+ return this;
+};
+
+// See: https://developer.mozilla.org/En/DOM/Node.cloneNode
+WindexNodes.prototype.clone = function () {
+ var node = this[0];
+ return node.cloneNode(true);
+};
+
+// See: http://api.jquery.com/before/
+WindexNodes.prototype.before = function (arg) {
+ if (typeof arg == "string" || typeof arg == "number") {
+ return this._beforeHTML(arg);
+ }
+ if (isNode(arg)) { return this._beforeNode(arg); }
+ if (arg instanceof WindexNodes) { return this._beforeWindexNodes(arg); }
+ throw new Error("'" + arg + "' is not a String, Node, or WindexNodes");
+};
+
+// See: https://bugzilla.mozilla.org/show_bug.cgi?id=550612
+// See: https://developer.mozilla.org/en/Code_snippets/HTML_to_DOM
+WindexNodes.prototype._beforeHTML = function (html) {
+ this.forEach(function (node) {
+ if (node.ownerDocument instanceof Components.interfaces.nsIImageDocument) {
+ var fragment = Components.classes["@mozilla.org/feed-unescapehtml;1"].
+ getService(Components.interfaces.nsIScriptableUnescapeHTML).
+ parseFragment(html, false, null, node);
+ node.parentNode.insertBefore(fragment, node);
+ } else {
+ node.innerHTML = node.innerHTML + html;
+ }
+ });
+ return this;
+};
+
+// See: https://developer.mozilla.org/En/DOM/Node.cloneNode
+WindexNodes.prototype._beforeNode = function (nodeToAppend) {
+ this.forEach(function (node) {
+ node.parentNode.insertBefore(nodeToAppend.cloneNode(true), node);
+ });
+ return this;
+};
+
+// See: https://developer.mozilla.org/En/DOM/Node.cloneNode
+WindexNodes.prototype._beforeWindexNodes = function (windexNodes) {
+ this.forEach(function (node) {
+ windexNodes.forEach(function (nodeToAppend) {
+ node.parentNode.insertBefore(nodeToAppend.cloneNode(true), node);
+ });
+ });
+ return this;
+};
+
+// See: http://api.jquery.com/html/
+WindexNodes.prototype.html = function (replacement) {
+ if (!replacement) { return this._htmlGet(); };
+ return this._htmlSet(replacement);
+};
+
+// FIXME: innerHTML does work on ImageDocuments in FF < 4
+// See: https://bugzilla.mozilla.org/show_bug.cgi?id=550612
+WindexNodes.prototype._htmlGet = function () {
+ var html = "";
+ this.forEach(function (node) {
+ if (node.ownerDocument instanceof Components.interfaces.nsIImageDocument) {
+ throw new Error("html() does not work on ImageDocuments. See: https://bugzilla.mozilla.org/show_bug.cgi?id=550612");
+ }
+ html += node.innerHTML;
+ });
+ return html;
+};
+
+WindexNodes.prototype._htmlSet = function (replacement) {
+ this.forEach(function (node) { Windex(node).empty().append(replacement); });
+ return this;
+};
+
+// See: http://api.jquery.com/replaceWith/
+// See: http://james.padolsey.com/javascript/asynchronous-innerhtml/
+// TODO: can also take a function
+// TODO: can take an element or a windexNodes
+WindexNodes.prototype.replaceWith = function (html) {
+ this.forEach(function (node) {
+ if (!node.parentNode) { return; };
+ var document = node.ownerDocument;
+ var div = document.createElement("div");
+ Windex(div).html(html);
+ var fragment = document.createDocumentFragment();
+ while (div.firstChild) { fragment.appendChild(div.firstChild); }
+ node.parentNode.replaceChild(fragment, node);
+ });
+ return this;
+};
+
+// See: http://api.jquery.com/css/
+WindexNodes.prototype.css = function (name, value) {
+ if (value === undefined && typeof name == "string") {
+ return this._cssGet(name);
+ };
+ if (typeof name != "object") { return this._cssSet(name, value); };
+
+ for (var key in name) { this._cssSet(key, name[key]); };
+ return this;
+};
+
+// See: https://developer.mozilla.org/en/DOM/window.getComputedStyle
+WindexNodes.prototype._cssGet = function (name) {
+ var node = this[0];
+ var window = node.ownerDocument.defaultView;
+ var computedStyle = window.getComputedStyle(node, null);
+ return computedStyle.getPropertyValue(name);
+};
+
+WindexNodes.prototype._cssSet = function (name, value) {
+ var dims = ["height", "width", "top", "left", "right", "bottom"];
+ if (typeof(value) == "number" &&
+ dims.some(function (dim) { return name == dim; })) {
+ value = parseInt(value) + "px";
+ }
+ var translations = {
+ "z-index": "zIndex",
+ "overflow-y": "overflowY",
+ "overflow-x": "overflowX"
+ };
+ if (translations[name]) { name = translations[name]; }
+ this.forEach(function (node) { node.style[name] = value; });
+ return this;
+};
+
+// See: http://api.jquery.com/height/
+// See: http://api.jquery.com/width/
+// TODO: convert everything to px instead of pretending em == pt == px
+["height", "width"].forEach(function (dim) {
+ var capitalized = dim.replace(
+ /^(.)(.+)/ ,
+ function(m, p1, p2) { return p1.toUpperCase() + p2; }
+ );
+
+ WindexNodes.prototype[dim] = function (replacement) {
+ if (!replacement) { return this["_" + dim + "Get"](); };
+ return this["_" + dim + "Set"](replacement);
+ };
+
+ WindexNodes.prototype["_" + dim + "Get"] = function () {
+ var node = this[0];
+ if (node instanceof Components.interfaces.nsIDOMHTMLDocument) {
+ return this["_" + dim + "GetForDocument"]();
+ }
+ if (node instanceof Components.interfaces.nsIDOMWindow) {
+ return this["_" + dim + "GetForWindow"]();
+ }
+ return this["_" + dim + "GetForNode"]();
+ };
+
+ WindexNodes.prototype["_" + dim + "GetForNode"] = function () {
+ var node = this[0];
+ var value = parseInt(Windex(node)._cssGet(dim));
+ return (value) ? value : 0;
+ };
+
+ WindexNodes.prototype["_" + dim + "GetForDocument"] = function () {
+ var node = this[0];
+ return node.documentElement["client" + capitalized];
+ };
+
+ WindexNodes.prototype["_" + dim + "GetForWindow"] = function () {
+ var node = this[0];
+ return node["inner" + capitalized];
+ };
+
+ WindexNodes.prototype["_" + dim + "Set"] = function (replacement) {
+ this.forEach(function (node) {
+ if (node instanceof Components.interfaces.nsIDOMHTMLDocument) {
+ return setters[dim + "ForDocument"](node, replacement);
+ }
+ if (node instanceof Components.interfaces.nsIDOMWindow) {
+ return setters[dim + "ForWindow"](node, replacement);
+ }
+ setters[dim + "ForNode"](node, replacement);
+ });
+ return this;
+ };
+
+ // work on a node at a time
+ var setters = {};
+ setters[dim + "ForNode"] = function (node, replacement) {
+ return Windex(node)._cssSet(dim, (replacement) ? replacement + "px" : 0);
+ };
+
+ setters[dim + "ForDocument"] = function (node, replacement) {
+ return node[dim] = replacement;
+ };
+
+ setters[dim + "ForWindow"] = function (node, replacement) {
+ return node["inner" + capitalized] = replacement;
+ };
+});
+
+// See: http://api.jquery.com/outerWidth/
+WindexNodes.prototype.outerWidth = function (includeMargin) {
+ var dims = {
+ padding: 0,
+ margin: 0,
+ border: 0
+ };
+ var node = this;
+ for (key in dims) {
+ ["left", "right"].forEach(function (side) {
+ if (includeMargin) {
+ var attribute = parseInt(node.css(key + "-" + side + "-width"));
+ dims[key] += (isNaN(attribute)) ? 0 : attribute;
+ }
+ });
+ }
+ var val = this[0].offsetWidth;
+ if (includeMargin) { return val + dims.margin; }
+ return val - dims.padding - dims.border;
+};
+
+// See: http://api.jquery.com/outerHeight/
+WindexNodes.prototype.outerHeight = function (includeMargin) {
+ var node = this;
+ var dims = {
+ padding: 0,
+ margin: 0,
+ border: 0
+ };
+ for (key in dims) {
+ ["top", "bottom"].forEach(function (side) {
+ if (includeMargin) {
+ var attribute = parseInt(node.css(key + "-" + side + "-height"));
+ dims[key] += (isNaN(attribute)) ? 0 : attribute;
+ }
+ });
+ }
+ var val = this[0].offsetHeight;
+ if (includeMargin) { return val + dims.margin; }
+ return val - dims.padding - dims.border;
+};
+
+// See: http://api.jquery.com/trigger/
+// See: https://developer.mozilla.org/en/Code_snippets/Interaction_between_privileged_and_non-privileged_pages
+WindexNodes.prototype.trigger = function (name) {
+ this.forEach(function (node) {
+ if (typeof node[name] == "function") {
+ node[name]();
+ } else {
+ var event = node.ownerDocument.createEvent("Events");
+ event.initEvent(name, true, false);
+ node.dispatchEvent(event);
+ }
+ });
+ return this;
+};
+
+WindexNodes.prototype.triggerHandler = function (name) {
+ this.trigger(name);
+ return this;
+};
+
+
+var events = [];
+
+// See: http://api.jquery.com/bind/
+WindexNodes.prototype.bind = function (name, handler) {
+ this.forEach(function (node) {
+ var wrapped = function (event) { handler.apply(node, [event]); };
+ if (!events[node]) { events[node] = {}; }
+ if (!events[node][name]) { events[node][name] = []; }
+ events[node][name].push({ wrapped: wrapped, handler: handler });
+ node.addEventListener(name, wrapped, false, false);
+ });
+ return this;
+};
+
+// See: http://api.jquery.com/unbind
+WindexNodes.prototype.unbind = function (name, handler) {
+ if (!name) { return this._unbindAll(); }
+ return this._unbindEvent(name, handler);
+}
+
+WindexNodes.prototype._unbindAll = function () {
+ this.forEach(function (node) {
+ for (name in events[node]) {
+ events[node][name].forEach(function (bridge) {
+ node.removeEventListener(name, bridge.wrapped, false);
+ });
+ }
+ delete events[node];
+ });
+ return this;
+};
+
+WindexNodes.prototype._unbindEvent = function (name, handler) {
+ this.forEach(function (node) {
+ if (!events[node][name]) return;
+ events[node][name] = events[node][name].filter(function (bridge) {
+ if (bridge.handler !== handler) return true;
+ node.removeEventListener(name, bridge.wrapped, false);
+ return false;
+ });
+ });
+ return this;
+};
+
+[
+ "click", // See: http://api.jquery.com/click/
+ "mousedown",
+ "mouseup",
+ "mousemove",
+ "keypress", // See: http://api.jquery.com/keypress/
+ "keydown",
+ "keyup",
+ "resize", // See: http://api.jquery.com/resize/
+ "submit", // See: http://api.jquery.com/submit/
+ "focus", // See: http://api.jquery.com/focus/
+ "blur", // See: http://api.jquery.com/blur/
+ "command"
+].forEach(function (event) {
+ WindexNodes.prototype[event] = function (handler) {
+ if (!handler) { return this.trigger(event); }
+ return this.bind(event, handler);
+ };
+});
+
+// See: http://api.jquery.com/live
+WindexNodes.prototype.live = function (name, handler) {
+ var selector = this._selector;
+ var context = this._context;
+ var delegater = function (event) {
+ Windex(selector, context).forEach(function (node) {
+ var isTarget = (node == event.originalTarget);
+ var containsTarget = Windex.contains(node, event.originalTarget);
+ if (isTarget || containsTarget) { handler.apply(node, [event]); }
+ });
+ };
+ context.addEventListener(name, delegater, false);
+};
+
+// See: http://api.jquery.com/attr/
+WindexNodes.prototype.attr = function (name, value) {
+ if (typeof name == "object") { return this._attrSetObj(name); }
+ if (value === undefined) { return this._attrGet(name); }
+ return this._attrSet(name, value);
+};
+
+WindexNodes.prototype._attrGet = function (name) {
+ var node = this[0];
+ return node.getAttribute(name);
+};
+
+WindexNodes.prototype._attrSet = function (name, value) {
+ if (name == "class") {
+ this.forEach(function (node) { node.className = value; });
+ return this;
+ }
+ this.forEach(function (node) {
+ node.setAttribute(name, value);
+ if(name === 'checked' && (node.type === 'checkbox' || node.type === 'radio')) {
+ node.checked = value;
+ }
+ });
+ return this;
+};
+
+WindexNodes.prototype._attrSetObj = function (obj) {
+ for (key in obj) { this._attrSet(key, obj[key]); }
+ return this;
+};
+
+// See: http://api.jquery.com/removeAttr/
+WindexNodes.prototype.removeAttr = function (name) {
+ this.forEach(function (node) {
+ node.removeAttribute(name);
+ if(name === 'checked' && node.type === 'checkbox') {
+ node.checked = false;
+ }
+ });
+ return this;
+};
+
+// See: http://api.jquery.com/val/
+WindexNodes.prototype.val = function (replacement) {
+ if (replacement === undefined) { return this._valGet(); }
+ return this._valSet(replacement);
+};
+
+WindexNodes.prototype._valGet = function () {
+ var node = this[0];
+ return node.value;
+};
+
+WindexNodes.prototype._valSet = function (replacement) {
+ this.forEach(function (node) { node.value = replacement; });
+ return this;
+};
+
+// See: http://api.jquery.com/hide/
+WindexNodes.prototype.hide = function () {
+ this.forEach(function (node) {
+ if (Windex(node).css("display") == "none") return;
+ Windex(node).attr("olddisplay", Windex(node).css("display"));
+ Windex(node).css("display", "none");
+ });
+ return this;
+};
+
+// See: http://api.jquery.com/show/
+WindexNodes.prototype.show = function () {
+ this.forEach(function (node) {
+ node = Windex(node);
+ if (node.css("display") != "none") return;
+ var display = (node.attr("olddisplay")) ? node.attr("olddisplay") : "block";
+ node.css("display", display).removeAttr("olddisplay");
+ });
+ return this;
+};
+
+WindexNodes.prototype.toggle = function () {
+ this.forEach(function (node) {
+ node = Windex(node);
+ if (node.css("display") == "none") { node.show(); } else { node.hide(); }
+ });
+ return this;
+};
+
+// See: http://api.jquery.com/text/
+WindexNodes.prototype.text = function (replacement) {
+ if (replacement === undefined) { return this._textGet(); }
+ return this._textSet(replacement);
+};
+
+WindexNodes.prototype._textGet = function () {
+ return this.map(function (node) { return node.textContent; }).join(" ");
+};
+
+WindexNodes.prototype._textSet = function (replacement) {
+ this.forEach(function (node) { node.textContent = replacement; });
+ return this;
+};
+
+// See: http://api.jquery.com/position/
+WindexNodes.prototype.position = function () {
+ var node = this[0];
+ return { top: node.offsetTop, left: node.offsetLeft };
+};
+
+// See: http://api.jquery.com/scrollTop/
+// See: http://api.jquery.com/scrollLeft/
+[
+ "Top",
+ "Left"
+].forEach(function (direction) {
+ var axis = (direction == "Top") ? "Y" : "X";
+
+ WindexNodes.prototype["scroll" + direction] = function (replacement) {
+ if (!replacement) { return this["_scroll" + direction + "Get"](); }
+ return this["_scroll" + direction + "Set"](replacement);
+ };
+
+ WindexNodes.prototype["_scroll" + direction + "Get"] = function () {
+ var node = this[0];
+ if (node instanceof Components.interfaces.nsIDOMHTMLDocument) {
+ return this["_scroll" + direction + "GetForDocument"]();
+ }
+ if (node instanceof Components.interfaces.nsIDOMWindow) {
+ return this["_scroll" + direction + "GetForWindow"]();
+ }
+ return this["_scroll" + direction + "GetForNode"]();
+ };
+
+ WindexNodes.prototype["_scroll" + direction + "GetForDocument"] = function() {
+ var node = this[0];
+ return node.defaultView["scroll" + axis];
+ };
+
+ WindexNodes.prototype["_scroll" + direction + "GetForWindow"] = function () {
+ var node = this[0];
+ return node["scroll" + axis];
+ };
+
+ WindexNodes.prototype["_scroll" + direction + "GetForNode"] = function () {
+ var node = this[0];
+ return node["scroll" + direction];
+ };
+
+ var setters = {
+ document: function (n, r) { n.defaultView["scroll" + axis] = r; },
+ window: function (n, r) { n["scroll" + axis] = r; },
+ node: function (n, r) { n["scroll" + direction] = r; }
+ };
+
+ WindexNodes.prototype["_scroll" + direction + "Set"] = function (replacement){
+ this.forEach(function (node) {
+ if (node instanceof Components.interfaces.nsIDOMHTMLDocument) {
+ return setters.document(replacement);
+ }
+ if (node instanceof Components.interfaces.nsIDOMWindow) {
+ return setters.window(replacement);
+ }
+ setters.node(replacement);
+ });
+ return this;
+ };
+});
+
+WindexNodes.prototype.first = function () {
+ var node = this[0];
+ return new WindexNodes([node], this._selector, this._context);
+};
+
+WindexNodes.prototype.last = function () {
+ var node = this[this.length - 1];
+ return new WindexNodes([node], this._selector, this._context);
+};
+
+WindexNodes.prototype.next = function () {
+ var selector = this._selector;
+ var context = this._context;
+ var node = this[0];
+ if (!node.nextSibling) { return new WindexNodes([], selector, context); }
+ node = node.nextSibling; // adjacent text node
+ if (!node.nextSibling) { return new WindexNodes([], selector, context); }
+ return new WindexNodes([node.nextSibling], selector, context);
+};
+
+WindexNodes.prototype.prev = function () {
+ var selector = this._selector;
+ var context = this._context;
+ var node = this[0];
+ if (!node.previousSibling) { return new WindexNodes([], selector, context); }
+ node = node.previousSibling; // adjacent text node
+ if (!node.previousSibling) { return new WindexNodes([], selector, context); }
+ return new WindexNodes([node.previousSibling], selector, context);
+};
+
+Windex.url = { setUrl: function (url) { return new WindexUrl(url); } };
+
+var WindexUrl = function (url) {
+ this._url = {
+ spec: "",
+ host: "",
+ path: ""
+ };
+ try {
+ var ioService = Components.classes["@mozilla.org/network/io-service;1"].
+ getService(Components.interfaces.nsIIOService);
+ var nsiUri = ioService.newURI(url, null, null);
+ for (var key in this._url) {
+ this._url[key] = nsiUri[key];
+ }
+ } catch (e) {}
+ return this;
+};
+
+// See: https://developer.mozilla.org/en/nsIURI
+// See: https://developer.mozilla.org/en/nsIIOService#newURI.28.29
+WindexUrl.prototype.attr = function (name) { return this._url[name]; };
+
+Windex.browser = {};
diff --git a/client.js b/client.js
@@ -0,0 +1,29 @@
+var request = require('request');
+
+var runOnPage = function (url, f, callback) {
+ var data = {
+ type: 'page',
+ url: url,
+ script: f.toString()
+ };
+ var params = {
+ uri: 'http://localhost:1235/client',
+ method: 'POST',
+ body: JSON.stringify(data)
+ };
+ request(params, function (error, response, body) {
+ var data = JSON.parse(body);
+ callback(data.res);
+ });
+};
+
+runOnPage(
+ 'http://jquery.com',
+ function (callback) {
+ $(document.body).html('Boo yeah!');
+ callback($(document.body).html());
+ },
+ function (data) {
+ console.log(data);
+ }
+);
diff --git a/server.js b/server.js
@@ -1,10 +1,12 @@
var http = require('http'),
router = require('./router');
+var timestamp = function () { return (new Date()).getTime(); };
+
var pushResponses = [];
var push = http.createServer(function (req, res) {
- console.log('test');
+ console.log('push connected');
pushResponses.push(res);
});
push.listen(1234);
@@ -12,34 +14,45 @@ push.listen(1234);
var clientResponses = {};
router.post('client', function (req, res) {
- var script = '';
- req.on('data', function (data) { script += data; });
+ var id = timestamp();
+ console.log('post from client (' + id + ')');
+
+ var json = '';
+ req.on('data', function (data) { json += data; });
req.on('end', function (data) {
- if (data) { script += data; }
+ if (data) { json += data; }
- var id = (new Date()).getTime();
+ console.log('\tsaving response');
clientResponses[id] = res;
- var message = JSON.stringify({script : script, id: id});
+ var data = JSON.parse(json);
+ data.id = id;
+ json = JSON.stringify(data);
+ console.log('\tsending to push: ' + json);
pushRes = pushResponses.pop();
pushResponses = [];
pushRes.writeHead(200, {'Content-Type': 'application/json'});
- pushRes.end(message);
+ pushRes.end(json);
});
});
router.post('browser/:id', function (req, res, id) {
+ console.log('post from browser (' + id + ')');
+
var json = '';
req.on('data', function (data) { json += data; });
req.on('end', function (data) {
if (data) { json += data; }
+ console.log('\trecieved from browser: ' + json);
+ console.log('\tresponding to client');
var clientRes = clientResponses[id];
delete clientResponses[id];
clientRes.writeHead(200);
clientRes.end(json);
- res.writeHead(200);
- res.end('fries are done');
+ res.writeHead(200, {'Content-Type': 'text/plain'});
+ res.end('OREN FOR PRESIDENT');
});
});
router.server.listen(1235);
+console.log('listening at http://localhost:1235');