lib/public/terminus.js in terminus-0.1.0 vs lib/public/terminus.js in terminus-0.2.0

- old
+ new

@@ -1,15 +1,308 @@ Terminus = { + isIE: /\bMSIE\b/.test(navigator.userAgent), + connect: function(endpoint) { if (this._client) return; - this._client = new Faye.Client(endpoint); + this._pageId = Faye.random(); + + this._id = window.name = window.name || Faye.random(); + + this.Registry.initialize(); + this.Worker.initialize(); + + var client = this._client = new Faye.Client(endpoint); + this._client.subscribe('/terminus/commands', function(message) { this.execute(message.command); }, this); + + this._client.subscribe('/terminus/clients/' + this._id, function(message) { + var command = message.command, + method = command.shift(), + driver = this.Driver, + worker = this.Worker, + self = this; + + command.push(function(result) { + self.postResult(message.commandId, result); + }); + + worker.monitor = true; + driver[method].apply(driver, command); + worker.monitor = false; + }, this); + + var self = this; + setTimeout(function() { self.ping() }, 100); }, + browserDetails: function() { + return { + id: this._id, + page: this._pageId, + ua: navigator.userAgent, + url: window.location.href + }; + }, + + ping: function() { + this._client.publish('/terminus/ping', this.browserDetails()); + var self = this; + setTimeout(function() { self.ping() }, 3000); + }, + + postResult: function(commandId, result) { + if (!commandId) return; + + this._client.publish('/terminus/results', { + id: this._id, + commandId: commandId, + result: result + }); + }, + execute: function(command) { eval(command); + }, + + Driver: { + _node: function(id) { + return Terminus.Registry.get(id); + }, + + attribute: function(nodeId, name, callback) { + var node = this._node(nodeId); + + if (name === 'checked' || name === 'selected') + return callback(!!node[name]); + + if (Terminus.isIE) + return callback(node.getAttributeNode(name).nodeValue); + + callback(node.getAttribute(name)); + }, + + body: function(callback) { + var html = document.getElementsByTagName('html')[0]; + callback(html.outerHTML || + '<html>\n' + html.innerHTML + '\n</html>\n'); + }, + + source: function(callback) { + callback(Terminus.originalSource); + }, + + reset: function(callback) { + var cookies = document.cookie.split(';'), name; + + var expiry = new Date(); + expiry.setTime(expiry.getTime() - 24*60*60*1000); + + for (var i = 0, n = cookies.length; i < n; i++) { + name = cookies[i].split('=')[0]; + document.cookie = name + '=; expires=' + expiry.toGMTString() + '; path=/'; + } + callback(); + }, + + click: function(nodeId, callback) { + var element = this._node(nodeId); + Syn.trigger('click', {}, element); + Terminus.Worker.callback(callback); + }, + + drag: function(options, callback) { + var draggable = this._node(options.from), + droppable = this._node(options.to); + Syn.drag({to: droppable}, draggable, callback); + }, + + evaluate: function(expression, callback) { + callback(eval(expression)); + }, + + execute: function(expression, callback) { + eval(expression); + callback(); + }, + + find: function(xpath, nodeId, callback) { + var root = this._node(nodeId) || document; + + var result = document.evaluate(xpath, root, null, XPathResult.ANY_TYPE, null), + list = [], + element; + + while (element = result.iterateNext()) + list.push(Terminus.Registry.put(element)); + + return callback(list); + }, + + is_visible: function(nodeId, callback) { + var node = this._node(nodeId); + + while (node.tagName.toLowerCase() !== 'body') { + if (node.style.display === 'none' || node.type === 'hidden') + return callback(false); + node = node.parentNode; + } + callback(true); + }, + + select: function(nodeId, callback) { + var option = this._node(nodeId); + option.selected = true; + Syn.trigger('change', {}, option.parentNode); + callback(true); + }, + + set: function(nodeId, value, callback) { + var field = this._node(nodeId); + if (field.type === 'file') return callback('not_allowed'); + + Syn.trigger('focus', {}, field); + Syn.trigger('click', {}, field); + + switch (typeof value) { + case 'string': field.value = value; break; + case 'boolean': field.checked = value; break; + } + callback(); + }, + + tag_name: function(nodeId, callback) { + callback(this._node(nodeId).tagName.toLowerCase()); + }, + + text: function(nodeId, callback) { + var node = this._node(nodeId), + text = node.textContent || node.innerText || ''; + + text = text.replace(/^\s*|\s*$/g, ''); + callback(text); + }, + + trigger: function(nodeId, eventType, callback) { + var node = this._node(nodeId); + Syn.trigger(eventType, {}, node); + callback(); + }, + + unselect: function(nodeId, callback) { + var option = this._node(nodeId); + if (!option.parentNode.multiple) return callback(false); + option.selected = false; + Syn.trigger('change', {}, option.parentNode); + callback(true); + }, + + value: function(nodeId, callback) { + var node = this._node(nodeId); + if (node.tagName.toLowerCase() !== 'select' || !node.multiple) + return callback(node.value); + + var options = node.children, + values = []; + + for (var i = 0, n = options.length; i < n; i++) { + if (options[i].selected) values.push(options[i].value); + } + callback(values); + }, + + visit: function(url, callback) { + window.location.href = url; + callback(); + } + }, + + Registry: { + initialize: function() { + this._namespace = new Faye.Namespace(); + this._elements = {}; + }, + + get: function(id) { + return this._elements[id]; + }, + + put: function(element) { + var id = this._namespace.generate(); + this._elements[id] = element; + return id; + } + }, + + Worker: { + initialize: function() { + this._callbacks = []; + this._pending = 0; + + this._wrapTimeouts(); + }, + + callback: function(callback, scope) { + if (this._pending === 0) return callback.call(scope); + else this._callbacks.push([callback, scope]); + }, + + suspend: function() { + this._pending += 1; + }, + + resume: function() { + if (this._pending === 0) return; + this._pending -= 1; + if (this._pending !== 0) return; + + var callback; + for (var i = 0, n = this._callbacks.length; i < n; i++) { + callback = this._callbacks[i]; + callback[0].call(callback[1]); + } + this._callbacks = []; + }, + + _wrapTimeouts: function() { + var timeout = window.setTimeout, + clear = window.clearTimeout, + timeouts = {}, + self = this; + + var finish = function(id) { + if (!timeouts.hasOwnProperty(id)) return; + delete timeouts[id]; + self.resume(); + }; + + window.setTimeout = function(callback, delay) { + var id = timeout(function() { + try { + switch (typeof callback) { + case 'function': callback(); break; + case 'string': eval(callback); break; + } + } catch (e) { + throw e; + } finally { + finish(id); + } + }, delay); + + if (self.monitor) { + timeouts[id] = true; + self.suspend(); + } + return id; + }; + + window.clearTimeout = function(id) { + finish(id); + return clear(id); + }; + } } };