undead 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/undead.rb +1 -2
- data/lib/undead/agent.rb +6 -11
- data/lib/undead/browser.rb +68 -0
- data/lib/undead/client.rb +151 -0
- data/lib/undead/client/compiled/agent.js +594 -0
- data/lib/undead/client/compiled/browser.js +692 -0
- data/lib/undead/client/compiled/cmd.js +31 -0
- data/lib/undead/client/compiled/connection.js +25 -0
- data/lib/undead/client/compiled/main.js +229 -0
- data/lib/undead/client/compiled/node.js +187 -0
- data/lib/undead/client/compiled/web_page.js +589 -0
- data/lib/undead/command.rb +20 -0
- data/lib/undead/driver.rb +73 -0
- data/lib/undead/errors.rb +187 -0
- data/lib/undead/server.rb +44 -0
- data/lib/undead/utility.rb +7 -0
- data/lib/undead/version.rb +1 -1
- data/lib/undead/web_socket_server.rb +109 -0
- data/undead.gemspec +4 -4
- metadata +27 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8aac8b304c8413330a26862974c9d9b8554d9b2a
|
4
|
+
data.tar.gz: ccc00e3e809542c054d04b0364f3c0804278a45e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6dde3a334c0f141467a00f3ed64bf686676d5ca94300075bb6cc8945591c9c19c35d6aafc167d57cc6cdcf0182b71277ce20e0c359f903c4303a6d74da73dc2b
|
7
|
+
data.tar.gz: cf1fe4869c03243a88eed781b4e741cf56ca9d04541f9be9fe6e379e407d4e19b022c0974499bfc8bc56e4b82f3f7f96016612c7c1f5ccc08920a7764776f1d4
|
data/lib/undead.rb
CHANGED
data/lib/undead/agent.rb
CHANGED
@@ -1,22 +1,17 @@
|
|
1
|
-
require "
|
2
|
-
require "capybara/poltergeist"
|
1
|
+
require "undead/driver"
|
3
2
|
|
4
3
|
module Undead
|
5
4
|
class Agent
|
6
5
|
DEFAULT_OPTIONS = {
|
7
6
|
js_errors: false,
|
8
|
-
timeout:
|
7
|
+
timeout: 1000,
|
8
|
+
headers: {
|
9
|
+
'User-Agent' => "Mozilla/5.0 (Macintosh; Intel Mac OS X)"
|
10
|
+
},
|
9
11
|
}
|
10
12
|
|
11
|
-
Capybara.javascript_driver = :poltergeist
|
12
|
-
Capybara.default_selector = :css
|
13
|
-
|
14
13
|
def initialize(options = {})
|
15
|
-
|
16
|
-
Capybara::Poltergeist::Driver.new(app, DEFAULT_OPTIONS.merge(options))
|
17
|
-
end
|
18
|
-
@session = Capybara::Session.new(:poltergeist)
|
19
|
-
@session.driver.headers = { 'User-Agent' => "Mozilla/5.0 (Macintosh; Intel Mac OS X)" }
|
14
|
+
@session = Driver.new(DEFAULT_OPTIONS.merge(options))
|
20
15
|
end
|
21
16
|
|
22
17
|
def get(url)
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require "undead/command"
|
2
|
+
require "undead/errors"
|
3
|
+
require "json"
|
4
|
+
require "time"
|
5
|
+
|
6
|
+
module Undead
|
7
|
+
class Browser
|
8
|
+
ERROR_MAPPINGS = {
|
9
|
+
'Poltergeist.JavascriptError' => Undead::JavascriptError,
|
10
|
+
'Poltergeist.FrameNotFound' => Undead::FrameNotFound,
|
11
|
+
'Poltergeist.InvalidSelector' => Undead::InvalidSelector,
|
12
|
+
'Poltergeist.StatusFailError' => Undead::StatusFailError,
|
13
|
+
'Poltergeist.NoSuchWindowError' => Undead::NoSuchWindowError,
|
14
|
+
}
|
15
|
+
|
16
|
+
attr_reader :server, :client, :logger
|
17
|
+
|
18
|
+
def initialize(server, client, logger = nil)
|
19
|
+
@server = server
|
20
|
+
@client = client
|
21
|
+
@logger = logger
|
22
|
+
end
|
23
|
+
|
24
|
+
def visit(url)
|
25
|
+
command 'visit', url
|
26
|
+
end
|
27
|
+
|
28
|
+
def body
|
29
|
+
command 'body'
|
30
|
+
end
|
31
|
+
|
32
|
+
def js_errors=(val)
|
33
|
+
@js_errors = val
|
34
|
+
command 'set_js_errors', !!val
|
35
|
+
end
|
36
|
+
|
37
|
+
def debug=(val)
|
38
|
+
@debug = val
|
39
|
+
command 'set_debug', !!val
|
40
|
+
end
|
41
|
+
|
42
|
+
def command(name, *args)
|
43
|
+
cmd = Undead::Command.new(name, *args)
|
44
|
+
log cmd.message
|
45
|
+
|
46
|
+
response = server.send(cmd)
|
47
|
+
log response
|
48
|
+
|
49
|
+
json = JSON.load(response)
|
50
|
+
|
51
|
+
if json['error']
|
52
|
+
klass = ERROR_MAPPINGS[json['error']['name']] || Undead::BrowserError
|
53
|
+
raise klass.new(json['error'])
|
54
|
+
else
|
55
|
+
json['response']
|
56
|
+
end
|
57
|
+
rescue Undead::DeadClient
|
58
|
+
restart
|
59
|
+
raise
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def log(message)
|
65
|
+
logger.puts message if logger
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require "undead/utility"
|
2
|
+
require "timeout"
|
3
|
+
require "cliver"
|
4
|
+
|
5
|
+
module Undead
|
6
|
+
class Client
|
7
|
+
PHANTOMJS_SCRIPT = File.expand_path('../client/compiled/main.js', __FILE__)
|
8
|
+
PHANTOMJS_VERSION = ['>= 1.8.1', '< 3.0']
|
9
|
+
PHANTOMJS_NAME = 'phantomjs'
|
10
|
+
|
11
|
+
KILL_TIMEOUT = 2 # seconds
|
12
|
+
|
13
|
+
def self.start(*args)
|
14
|
+
client = new(*args)
|
15
|
+
client.start
|
16
|
+
client
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns a proc, that when called will attempt to kill the given process.
|
20
|
+
# This is because implementing ObjectSpace.define_finalizer is tricky.
|
21
|
+
# Hat-Tip to @mperham for describing in detail:
|
22
|
+
# http://www.mikeperham.com/2010/02/24/the-trouble-with-ruby-finalizers/
|
23
|
+
def self.process_killer(pid)
|
24
|
+
proc do
|
25
|
+
begin
|
26
|
+
Process.kill('KILL', pid)
|
27
|
+
rescue Errno::ESRCH, Errno::ECHILD
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_reader :pid, :server, :path, :window_size, :phantomjs_options
|
33
|
+
|
34
|
+
def initialize(server, options = {})
|
35
|
+
@server = server
|
36
|
+
@path = Cliver::detect!((options[:path] || PHANTOMJS_NAME),
|
37
|
+
*PHANTOMJS_VERSION)
|
38
|
+
|
39
|
+
@window_size = options[:window_size] || [1024, 768]
|
40
|
+
@phantomjs_options = options[:phantomjs_options] || []
|
41
|
+
@phantomjs_logger = options[:phantomjs_logger] || $stdout
|
42
|
+
|
43
|
+
pid = Process.pid
|
44
|
+
at_exit do
|
45
|
+
# do the work in a separate thread, to avoid stomping on $!,
|
46
|
+
# since other libraries depend on it directly.
|
47
|
+
Thread.new do
|
48
|
+
stop if Process.pid == pid
|
49
|
+
end.join
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def start
|
54
|
+
@read_io, @write_io = IO.pipe
|
55
|
+
@out_thread = Thread.new {
|
56
|
+
while !@read_io.eof? && data = @read_io.readpartial(1024)
|
57
|
+
@phantomjs_logger.write(data)
|
58
|
+
end
|
59
|
+
}
|
60
|
+
|
61
|
+
process_options = {}
|
62
|
+
process_options[:pgroup] = true unless Undead.windows?
|
63
|
+
|
64
|
+
redirect_stdout do
|
65
|
+
@pid = Process.spawn(*command.map(&:to_s), process_options)
|
66
|
+
ObjectSpace.define_finalizer(self, self.class.process_killer(@pid))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def stop
|
71
|
+
if pid
|
72
|
+
kill_phantomjs
|
73
|
+
@out_thread.kill
|
74
|
+
close_io
|
75
|
+
ObjectSpace.undefine_finalizer(self)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def restart
|
80
|
+
stop
|
81
|
+
start
|
82
|
+
end
|
83
|
+
|
84
|
+
def command
|
85
|
+
parts = [path]
|
86
|
+
parts.concat phantomjs_options
|
87
|
+
parts << PHANTOMJS_SCRIPT
|
88
|
+
parts << server.port
|
89
|
+
parts.concat window_size
|
90
|
+
parts
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
# This abomination is because JRuby doesn't support the :out option of
|
96
|
+
# Process.spawn. To be honest it works pretty bad with pipes too, because
|
97
|
+
# we ought close writing end in parent process immediately but JRuby will
|
98
|
+
# lose all the output from child. Process.popen can be used here and seems
|
99
|
+
# it works with JRuby but I've experienced strange mistakes on Rubinius.
|
100
|
+
def redirect_stdout
|
101
|
+
prev = STDOUT.dup
|
102
|
+
$stdout = @write_io
|
103
|
+
STDOUT.reopen(@write_io)
|
104
|
+
yield
|
105
|
+
ensure
|
106
|
+
STDOUT.reopen(prev)
|
107
|
+
$stdout = STDOUT
|
108
|
+
prev.close
|
109
|
+
end
|
110
|
+
|
111
|
+
def kill_phantomjs
|
112
|
+
begin
|
113
|
+
if Undead.windows?
|
114
|
+
Process.kill('KILL', pid)
|
115
|
+
else
|
116
|
+
Process.kill('TERM', pid)
|
117
|
+
begin
|
118
|
+
Timeout.timeout(KILL_TIMEOUT) { Process.wait(pid) }
|
119
|
+
rescue Timeout::Error
|
120
|
+
Process.kill('KILL', pid)
|
121
|
+
Process.wait(pid)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
rescue Errno::ESRCH, Errno::ECHILD
|
125
|
+
# Zed's dead, baby
|
126
|
+
end
|
127
|
+
@pid = nil
|
128
|
+
end
|
129
|
+
|
130
|
+
# We grab all the output from PhantomJS like console.log in another thread
|
131
|
+
# and when PhantomJS crashes we try to restart it. In order to do it we stop
|
132
|
+
# server and client and on JRuby see this error `IOError: Stream closed`.
|
133
|
+
# It happens because JRuby tries to close pipe and it is blocked on `eof?`
|
134
|
+
# or `readpartial` call. The error is raised in the related thread and it's
|
135
|
+
# not actually main thread but the thread that listens to the output. That's
|
136
|
+
# why if you put some debug code after `rescue IOError` it won't be shown.
|
137
|
+
# In fact the main thread will continue working after the error even if we
|
138
|
+
# don't use `rescue`. The first attempt to fix it was a try not to block on
|
139
|
+
# IO, but looks like similar issue appers after JRuby upgrade. Perhaps the
|
140
|
+
# only way to fix it is catching the exception what this method overall does.
|
141
|
+
def close_io
|
142
|
+
[@write_io, @read_io].each do |io|
|
143
|
+
begin
|
144
|
+
io.close unless io.closed?
|
145
|
+
rescue IOError
|
146
|
+
raise unless RUBY_ENGINE == 'jruby'
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,594 @@
|
|
1
|
+
var PoltergeistAgent;
|
2
|
+
|
3
|
+
PoltergeistAgent = (function() {
|
4
|
+
PoltergeistAgent.JSON || (PoltergeistAgent.JSON = {
|
5
|
+
parse: JSON.parse,
|
6
|
+
stringify: JSON.stringify
|
7
|
+
});
|
8
|
+
|
9
|
+
function PoltergeistAgent() {
|
10
|
+
this.elements = [];
|
11
|
+
this.nodes = {};
|
12
|
+
}
|
13
|
+
|
14
|
+
PoltergeistAgent.prototype.externalCall = function(name, args) {
|
15
|
+
var error, error1;
|
16
|
+
try {
|
17
|
+
return {
|
18
|
+
value: this[name].apply(this, args)
|
19
|
+
};
|
20
|
+
} catch (error1) {
|
21
|
+
error = error1;
|
22
|
+
return {
|
23
|
+
error: {
|
24
|
+
message: error.toString(),
|
25
|
+
stack: error.stack
|
26
|
+
}
|
27
|
+
};
|
28
|
+
}
|
29
|
+
};
|
30
|
+
|
31
|
+
PoltergeistAgent.stringify = function(object) {
|
32
|
+
var error, error1;
|
33
|
+
try {
|
34
|
+
return PoltergeistAgent.JSON.stringify(object, function(key, value) {
|
35
|
+
if (Array.isArray(this[key])) {
|
36
|
+
return this[key];
|
37
|
+
} else {
|
38
|
+
return value;
|
39
|
+
}
|
40
|
+
});
|
41
|
+
} catch (error1) {
|
42
|
+
error = error1;
|
43
|
+
if (error instanceof TypeError) {
|
44
|
+
return '"(cyclic structure)"';
|
45
|
+
} else {
|
46
|
+
throw error;
|
47
|
+
}
|
48
|
+
}
|
49
|
+
};
|
50
|
+
|
51
|
+
PoltergeistAgent.prototype.currentUrl = function() {
|
52
|
+
return window.location.href.replace(/\ /g, '%20');
|
53
|
+
};
|
54
|
+
|
55
|
+
PoltergeistAgent.prototype.find = function(method, selector, within) {
|
56
|
+
var el, error, error1, i, j, len, results, results1, xpath;
|
57
|
+
if (within == null) {
|
58
|
+
within = document;
|
59
|
+
}
|
60
|
+
try {
|
61
|
+
if (method === "xpath") {
|
62
|
+
xpath = document.evaluate(selector, within, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
|
63
|
+
results = (function() {
|
64
|
+
var j, ref, results1;
|
65
|
+
results1 = [];
|
66
|
+
for (i = j = 0, ref = xpath.snapshotLength; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) {
|
67
|
+
results1.push(xpath.snapshotItem(i));
|
68
|
+
}
|
69
|
+
return results1;
|
70
|
+
})();
|
71
|
+
} else {
|
72
|
+
results = within.querySelectorAll(selector);
|
73
|
+
}
|
74
|
+
results1 = [];
|
75
|
+
for (j = 0, len = results.length; j < len; j++) {
|
76
|
+
el = results[j];
|
77
|
+
results1.push(this.register(el));
|
78
|
+
}
|
79
|
+
return results1;
|
80
|
+
} catch (error1) {
|
81
|
+
error = error1;
|
82
|
+
if (error.code === DOMException.SYNTAX_ERR || error.code === 51) {
|
83
|
+
throw new PoltergeistAgent.InvalidSelector;
|
84
|
+
} else {
|
85
|
+
throw error;
|
86
|
+
}
|
87
|
+
}
|
88
|
+
};
|
89
|
+
|
90
|
+
PoltergeistAgent.prototype.register = function(element) {
|
91
|
+
this.elements.push(element);
|
92
|
+
return this.elements.length - 1;
|
93
|
+
};
|
94
|
+
|
95
|
+
PoltergeistAgent.prototype.documentSize = function() {
|
96
|
+
return {
|
97
|
+
height: document.documentElement.scrollHeight || document.documentElement.clientHeight,
|
98
|
+
width: document.documentElement.scrollWidth || document.documentElement.clientWidth
|
99
|
+
};
|
100
|
+
};
|
101
|
+
|
102
|
+
PoltergeistAgent.prototype.get = function(id) {
|
103
|
+
var base;
|
104
|
+
return (base = this.nodes)[id] || (base[id] = new PoltergeistAgent.Node(this, this.elements[id]));
|
105
|
+
};
|
106
|
+
|
107
|
+
PoltergeistAgent.prototype.nodeCall = function(id, name, args) {
|
108
|
+
var node;
|
109
|
+
node = this.get(id);
|
110
|
+
if (node.isObsolete()) {
|
111
|
+
throw new PoltergeistAgent.ObsoleteNode;
|
112
|
+
}
|
113
|
+
return node[name].apply(node, args);
|
114
|
+
};
|
115
|
+
|
116
|
+
PoltergeistAgent.prototype.beforeUpload = function(id) {
|
117
|
+
return this.get(id).setAttribute('_poltergeist_selected', '');
|
118
|
+
};
|
119
|
+
|
120
|
+
PoltergeistAgent.prototype.afterUpload = function(id) {
|
121
|
+
return this.get(id).removeAttribute('_poltergeist_selected');
|
122
|
+
};
|
123
|
+
|
124
|
+
PoltergeistAgent.prototype.clearLocalStorage = function() {
|
125
|
+
return localStorage.clear();
|
126
|
+
};
|
127
|
+
|
128
|
+
return PoltergeistAgent;
|
129
|
+
|
130
|
+
})();
|
131
|
+
|
132
|
+
PoltergeistAgent.ObsoleteNode = (function() {
|
133
|
+
function ObsoleteNode() {}
|
134
|
+
|
135
|
+
ObsoleteNode.prototype.toString = function() {
|
136
|
+
return "PoltergeistAgent.ObsoleteNode";
|
137
|
+
};
|
138
|
+
|
139
|
+
return ObsoleteNode;
|
140
|
+
|
141
|
+
})();
|
142
|
+
|
143
|
+
PoltergeistAgent.InvalidSelector = (function() {
|
144
|
+
function InvalidSelector() {}
|
145
|
+
|
146
|
+
InvalidSelector.prototype.toString = function() {
|
147
|
+
return "PoltergeistAgent.InvalidSelector";
|
148
|
+
};
|
149
|
+
|
150
|
+
return InvalidSelector;
|
151
|
+
|
152
|
+
})();
|
153
|
+
|
154
|
+
PoltergeistAgent.Node = (function() {
|
155
|
+
Node.EVENTS = {
|
156
|
+
FOCUS: ['blur', 'focus', 'focusin', 'focusout'],
|
157
|
+
MOUSE: ['click', 'dblclick', 'mousedown', 'mouseenter', 'mouseleave', 'mousemove', 'mouseover', 'mouseout', 'mouseup', 'contextmenu'],
|
158
|
+
FORM: ['submit']
|
159
|
+
};
|
160
|
+
|
161
|
+
function Node(agent, element1) {
|
162
|
+
this.agent = agent;
|
163
|
+
this.element = element1;
|
164
|
+
}
|
165
|
+
|
166
|
+
Node.prototype.parentId = function() {
|
167
|
+
return this.agent.register(this.element.parentNode);
|
168
|
+
};
|
169
|
+
|
170
|
+
Node.prototype.parentIds = function() {
|
171
|
+
var ids, parent;
|
172
|
+
ids = [];
|
173
|
+
parent = this.element.parentNode;
|
174
|
+
while (parent !== document) {
|
175
|
+
ids.push(this.agent.register(parent));
|
176
|
+
parent = parent.parentNode;
|
177
|
+
}
|
178
|
+
return ids;
|
179
|
+
};
|
180
|
+
|
181
|
+
Node.prototype.find = function(method, selector) {
|
182
|
+
return this.agent.find(method, selector, this.element);
|
183
|
+
};
|
184
|
+
|
185
|
+
Node.prototype.isObsolete = function() {
|
186
|
+
var obsolete;
|
187
|
+
obsolete = (function(_this) {
|
188
|
+
return function(element) {
|
189
|
+
var parent;
|
190
|
+
if ((parent = element.parentNode) != null) {
|
191
|
+
if (parent === document) {
|
192
|
+
return false;
|
193
|
+
} else {
|
194
|
+
return obsolete(parent);
|
195
|
+
}
|
196
|
+
} else {
|
197
|
+
return true;
|
198
|
+
}
|
199
|
+
};
|
200
|
+
})(this);
|
201
|
+
return obsolete(this.element);
|
202
|
+
};
|
203
|
+
|
204
|
+
Node.prototype.changed = function() {
|
205
|
+
var element, event;
|
206
|
+
event = document.createEvent('HTMLEvents');
|
207
|
+
event.initEvent('change', true, false);
|
208
|
+
if (this.element.nodeName === 'OPTION') {
|
209
|
+
element = this.element.parentNode;
|
210
|
+
if (element.nodeName === 'OPTGROUP') {
|
211
|
+
element = element.parentNode;
|
212
|
+
}
|
213
|
+
element;
|
214
|
+
} else {
|
215
|
+
element = this.element;
|
216
|
+
}
|
217
|
+
return element.dispatchEvent(event);
|
218
|
+
};
|
219
|
+
|
220
|
+
Node.prototype.input = function() {
|
221
|
+
var event;
|
222
|
+
event = document.createEvent('HTMLEvents');
|
223
|
+
event.initEvent('input', true, false);
|
224
|
+
return this.element.dispatchEvent(event);
|
225
|
+
};
|
226
|
+
|
227
|
+
Node.prototype.keyupdowned = function(eventName, keyCode) {
|
228
|
+
var event;
|
229
|
+
event = document.createEvent('UIEvents');
|
230
|
+
event.initEvent(eventName, true, true);
|
231
|
+
event.keyCode = keyCode;
|
232
|
+
event.which = keyCode;
|
233
|
+
event.charCode = 0;
|
234
|
+
return this.element.dispatchEvent(event);
|
235
|
+
};
|
236
|
+
|
237
|
+
Node.prototype.keypressed = function(altKey, ctrlKey, shiftKey, metaKey, keyCode, charCode) {
|
238
|
+
var event;
|
239
|
+
event = document.createEvent('UIEvents');
|
240
|
+
event.initEvent('keypress', true, true);
|
241
|
+
event.window = this.agent.window;
|
242
|
+
event.altKey = altKey;
|
243
|
+
event.ctrlKey = ctrlKey;
|
244
|
+
event.shiftKey = shiftKey;
|
245
|
+
event.metaKey = metaKey;
|
246
|
+
event.keyCode = keyCode;
|
247
|
+
event.charCode = charCode;
|
248
|
+
event.which = keyCode;
|
249
|
+
return this.element.dispatchEvent(event);
|
250
|
+
};
|
251
|
+
|
252
|
+
Node.prototype.insideBody = function() {
|
253
|
+
return this.element === document.body || document.evaluate('ancestor::body', this.element, null, XPathResult.BOOLEAN_TYPE, null).booleanValue;
|
254
|
+
};
|
255
|
+
|
256
|
+
Node.prototype.allText = function() {
|
257
|
+
return this.element.textContent;
|
258
|
+
};
|
259
|
+
|
260
|
+
Node.prototype.visibleText = function() {
|
261
|
+
if (this.isVisible()) {
|
262
|
+
if (this.element.nodeName === "TEXTAREA") {
|
263
|
+
return this.element.textContent;
|
264
|
+
} else {
|
265
|
+
return this.element.innerText || this.element.textContent;
|
266
|
+
}
|
267
|
+
}
|
268
|
+
};
|
269
|
+
|
270
|
+
Node.prototype.deleteText = function() {
|
271
|
+
var range;
|
272
|
+
range = document.createRange();
|
273
|
+
range.selectNodeContents(this.element);
|
274
|
+
window.getSelection().removeAllRanges();
|
275
|
+
window.getSelection().addRange(range);
|
276
|
+
return window.getSelection().deleteFromDocument();
|
277
|
+
};
|
278
|
+
|
279
|
+
Node.prototype.getProperty = function(name) {
|
280
|
+
return this.element[name];
|
281
|
+
};
|
282
|
+
|
283
|
+
Node.prototype.getAttributes = function() {
|
284
|
+
var attr, attrs, j, len, ref;
|
285
|
+
attrs = {};
|
286
|
+
ref = this.element.attributes;
|
287
|
+
for (j = 0, len = ref.length; j < len; j++) {
|
288
|
+
attr = ref[j];
|
289
|
+
attrs[attr.name] = attr.value.replace("\n", "\\n");
|
290
|
+
}
|
291
|
+
return attrs;
|
292
|
+
};
|
293
|
+
|
294
|
+
Node.prototype.getAttribute = function(name) {
|
295
|
+
if (name === 'checked' || name === 'selected') {
|
296
|
+
return this.element[name];
|
297
|
+
} else {
|
298
|
+
return this.element.getAttribute(name);
|
299
|
+
}
|
300
|
+
};
|
301
|
+
|
302
|
+
Node.prototype.scrollIntoView = function() {
|
303
|
+
this.element.scrollIntoViewIfNeeded();
|
304
|
+
if (!this.isInViewport()) {
|
305
|
+
return this.element.scrollIntoView();
|
306
|
+
}
|
307
|
+
};
|
308
|
+
|
309
|
+
Node.prototype.value = function() {
|
310
|
+
var j, len, option, ref, results1;
|
311
|
+
if (this.element.tagName === 'SELECT' && this.element.multiple) {
|
312
|
+
ref = this.element.children;
|
313
|
+
results1 = [];
|
314
|
+
for (j = 0, len = ref.length; j < len; j++) {
|
315
|
+
option = ref[j];
|
316
|
+
if (option.selected) {
|
317
|
+
results1.push(option.value);
|
318
|
+
}
|
319
|
+
}
|
320
|
+
return results1;
|
321
|
+
} else {
|
322
|
+
return this.element.value;
|
323
|
+
}
|
324
|
+
};
|
325
|
+
|
326
|
+
Node.prototype.set = function(value) {
|
327
|
+
var char, j, keyCode, len;
|
328
|
+
if (this.element.readOnly) {
|
329
|
+
return;
|
330
|
+
}
|
331
|
+
if (this.element.maxLength >= 0) {
|
332
|
+
value = value.substr(0, this.element.maxLength);
|
333
|
+
}
|
334
|
+
this.trigger('focus');
|
335
|
+
this.element.value = '';
|
336
|
+
if (this.element.type === 'number') {
|
337
|
+
this.element.value = value;
|
338
|
+
} else {
|
339
|
+
for (j = 0, len = value.length; j < len; j++) {
|
340
|
+
char = value[j];
|
341
|
+
keyCode = this.characterToKeyCode(char);
|
342
|
+
this.keyupdowned('keydown', keyCode);
|
343
|
+
this.element.value += char;
|
344
|
+
this.keypressed(false, false, false, false, char.charCodeAt(0), char.charCodeAt(0));
|
345
|
+
this.keyupdowned('keyup', keyCode);
|
346
|
+
}
|
347
|
+
}
|
348
|
+
this.changed();
|
349
|
+
this.input();
|
350
|
+
return this.trigger('blur');
|
351
|
+
};
|
352
|
+
|
353
|
+
Node.prototype.isMultiple = function() {
|
354
|
+
return this.element.multiple;
|
355
|
+
};
|
356
|
+
|
357
|
+
Node.prototype.setAttribute = function(name, value) {
|
358
|
+
return this.element.setAttribute(name, value);
|
359
|
+
};
|
360
|
+
|
361
|
+
Node.prototype.removeAttribute = function(name) {
|
362
|
+
return this.element.removeAttribute(name);
|
363
|
+
};
|
364
|
+
|
365
|
+
Node.prototype.select = function(value) {
|
366
|
+
if (this.isDisabled()) {
|
367
|
+
return false;
|
368
|
+
} else if (value === false && !this.element.parentNode.multiple) {
|
369
|
+
return false;
|
370
|
+
} else {
|
371
|
+
this.trigger('focus', this.element.parentNode);
|
372
|
+
this.element.selected = value;
|
373
|
+
this.changed();
|
374
|
+
this.trigger('blur', this.element.parentNode);
|
375
|
+
return true;
|
376
|
+
}
|
377
|
+
};
|
378
|
+
|
379
|
+
Node.prototype.tagName = function() {
|
380
|
+
return this.element.tagName;
|
381
|
+
};
|
382
|
+
|
383
|
+
Node.prototype.isVisible = function(element) {
|
384
|
+
var map_name, style;
|
385
|
+
if (element == null) {
|
386
|
+
element = this.element;
|
387
|
+
}
|
388
|
+
if (element.tagName === 'AREA') {
|
389
|
+
map_name = document.evaluate('./ancestor::map/@name', element, null, XPathResult.STRING_TYPE, null).stringValue;
|
390
|
+
element = document.querySelector("img[usemap='#" + map_name + "']");
|
391
|
+
if (element == null) {
|
392
|
+
return false;
|
393
|
+
}
|
394
|
+
}
|
395
|
+
while (element) {
|
396
|
+
style = window.getComputedStyle(element);
|
397
|
+
if (style.display === 'none' || style.visibility === 'hidden' || parseFloat(style.opacity) === 0) {
|
398
|
+
return false;
|
399
|
+
}
|
400
|
+
element = element.parentElement;
|
401
|
+
}
|
402
|
+
return true;
|
403
|
+
};
|
404
|
+
|
405
|
+
Node.prototype.isInViewport = function() {
|
406
|
+
var rect;
|
407
|
+
rect = this.element.getBoundingClientRect();
|
408
|
+
return rect.top >= 0 && rect.left >= 0 && rect.bottom <= window.innerHeight && rect.right <= window.innerWidth;
|
409
|
+
};
|
410
|
+
|
411
|
+
Node.prototype.isDisabled = function() {
|
412
|
+
return this.element.disabled || this.element.tagName === 'OPTION' && this.element.parentNode.disabled;
|
413
|
+
};
|
414
|
+
|
415
|
+
Node.prototype.path = function() {
|
416
|
+
var elements, selectors;
|
417
|
+
elements = this.parentIds().reverse().map((function(_this) {
|
418
|
+
return function(id) {
|
419
|
+
return _this.agent.get(id);
|
420
|
+
};
|
421
|
+
})(this));
|
422
|
+
elements.push(this);
|
423
|
+
selectors = elements.map(function(el) {
|
424
|
+
var prev_siblings;
|
425
|
+
prev_siblings = el.find('xpath', "./preceding-sibling::" + (el.tagName()));
|
426
|
+
return (el.tagName()) + "[" + (prev_siblings.length + 1) + "]";
|
427
|
+
});
|
428
|
+
return "//" + selectors.join('/');
|
429
|
+
};
|
430
|
+
|
431
|
+
Node.prototype.containsSelection = function() {
|
432
|
+
var selectedNode;
|
433
|
+
selectedNode = document.getSelection().focusNode;
|
434
|
+
if (!selectedNode) {
|
435
|
+
return false;
|
436
|
+
}
|
437
|
+
if (selectedNode.nodeType === 3) {
|
438
|
+
selectedNode = selectedNode.parentNode;
|
439
|
+
}
|
440
|
+
return this.element.contains(selectedNode);
|
441
|
+
};
|
442
|
+
|
443
|
+
Node.prototype.frameOffset = function() {
|
444
|
+
var offset, rect, style, win;
|
445
|
+
win = window;
|
446
|
+
offset = {
|
447
|
+
top: 0,
|
448
|
+
left: 0
|
449
|
+
};
|
450
|
+
while (win.frameElement) {
|
451
|
+
rect = win.frameElement.getClientRects()[0];
|
452
|
+
style = win.getComputedStyle(win.frameElement);
|
453
|
+
win = win.parent;
|
454
|
+
offset.top += rect.top + parseInt(style.getPropertyValue("padding-top"), 10);
|
455
|
+
offset.left += rect.left + parseInt(style.getPropertyValue("padding-left"), 10);
|
456
|
+
}
|
457
|
+
return offset;
|
458
|
+
};
|
459
|
+
|
460
|
+
Node.prototype.position = function() {
|
461
|
+
var frameOffset, pos, rect;
|
462
|
+
rect = this.element.getClientRects()[0] || this.element.getBoundingClientRect();
|
463
|
+
if (!rect) {
|
464
|
+
throw new PoltergeistAgent.ObsoleteNode;
|
465
|
+
}
|
466
|
+
frameOffset = this.frameOffset();
|
467
|
+
pos = {
|
468
|
+
top: rect.top + frameOffset.top,
|
469
|
+
right: rect.right + frameOffset.left,
|
470
|
+
left: rect.left + frameOffset.left,
|
471
|
+
bottom: rect.bottom + frameOffset.top,
|
472
|
+
width: rect.width,
|
473
|
+
height: rect.height
|
474
|
+
};
|
475
|
+
return pos;
|
476
|
+
};
|
477
|
+
|
478
|
+
Node.prototype.trigger = function(name, element) {
|
479
|
+
var event;
|
480
|
+
if (element == null) {
|
481
|
+
element = this.element;
|
482
|
+
}
|
483
|
+
if (Node.EVENTS.MOUSE.indexOf(name) !== -1) {
|
484
|
+
event = document.createEvent('MouseEvent');
|
485
|
+
event.initMouseEvent(name, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
|
486
|
+
} else if (Node.EVENTS.FOCUS.indexOf(name) !== -1) {
|
487
|
+
event = this.obtainEvent(name);
|
488
|
+
} else if (Node.EVENTS.FORM.indexOf(name) !== -1) {
|
489
|
+
event = this.obtainEvent(name);
|
490
|
+
} else {
|
491
|
+
throw "Unknown event";
|
492
|
+
}
|
493
|
+
return element.dispatchEvent(event);
|
494
|
+
};
|
495
|
+
|
496
|
+
Node.prototype.obtainEvent = function(name) {
|
497
|
+
var event;
|
498
|
+
event = document.createEvent('HTMLEvents');
|
499
|
+
event.initEvent(name, true, true);
|
500
|
+
return event;
|
501
|
+
};
|
502
|
+
|
503
|
+
Node.prototype.mouseEventTest = function(x, y) {
|
504
|
+
var el, frameOffset, origEl;
|
505
|
+
frameOffset = this.frameOffset();
|
506
|
+
x -= frameOffset.left;
|
507
|
+
y -= frameOffset.top;
|
508
|
+
el = origEl = document.elementFromPoint(x, y);
|
509
|
+
while (el) {
|
510
|
+
if (el === this.element) {
|
511
|
+
return {
|
512
|
+
status: 'success'
|
513
|
+
};
|
514
|
+
} else {
|
515
|
+
el = el.parentNode;
|
516
|
+
}
|
517
|
+
}
|
518
|
+
return {
|
519
|
+
status: 'failure',
|
520
|
+
selector: origEl && this.getSelector(origEl)
|
521
|
+
};
|
522
|
+
};
|
523
|
+
|
524
|
+
Node.prototype.getSelector = function(el) {
|
525
|
+
var className, classes, j, len, ref, ref1, selector;
|
526
|
+
selector = el.tagName !== 'HTML' ? this.getSelector(el.parentNode) + ' ' : '';
|
527
|
+
selector += el.tagName.toLowerCase();
|
528
|
+
if (el.id) {
|
529
|
+
selector += "#" + el.id;
|
530
|
+
}
|
531
|
+
classes = el.classList || ((ref = el.getAttribute('class')) != null ? (ref1 = ref.trim()) != null ? ref1.split(/\s+/) : void 0 : void 0) || [];
|
532
|
+
for (j = 0, len = classes.length; j < len; j++) {
|
533
|
+
className = classes[j];
|
534
|
+
if (className !== '') {
|
535
|
+
selector += "." + className;
|
536
|
+
}
|
537
|
+
}
|
538
|
+
return selector;
|
539
|
+
};
|
540
|
+
|
541
|
+
Node.prototype.characterToKeyCode = function(character) {
|
542
|
+
var code, specialKeys;
|
543
|
+
code = character.toUpperCase().charCodeAt(0);
|
544
|
+
specialKeys = {
|
545
|
+
96: 192,
|
546
|
+
45: 189,
|
547
|
+
61: 187,
|
548
|
+
91: 219,
|
549
|
+
93: 221,
|
550
|
+
92: 220,
|
551
|
+
59: 186,
|
552
|
+
39: 222,
|
553
|
+
44: 188,
|
554
|
+
46: 190,
|
555
|
+
47: 191,
|
556
|
+
127: 46,
|
557
|
+
126: 192,
|
558
|
+
33: 49,
|
559
|
+
64: 50,
|
560
|
+
35: 51,
|
561
|
+
36: 52,
|
562
|
+
37: 53,
|
563
|
+
94: 54,
|
564
|
+
38: 55,
|
565
|
+
42: 56,
|
566
|
+
40: 57,
|
567
|
+
41: 48,
|
568
|
+
95: 189,
|
569
|
+
43: 187,
|
570
|
+
123: 219,
|
571
|
+
125: 221,
|
572
|
+
124: 220,
|
573
|
+
58: 186,
|
574
|
+
34: 222,
|
575
|
+
60: 188,
|
576
|
+
62: 190,
|
577
|
+
63: 191
|
578
|
+
};
|
579
|
+
return specialKeys[code] || code;
|
580
|
+
};
|
581
|
+
|
582
|
+
Node.prototype.isDOMEqual = function(other_id) {
|
583
|
+
return this.element === this.agent.get(other_id).element;
|
584
|
+
};
|
585
|
+
|
586
|
+
return Node;
|
587
|
+
|
588
|
+
})();
|
589
|
+
|
590
|
+
window.__poltergeist = new PoltergeistAgent;
|
591
|
+
|
592
|
+
document.addEventListener('DOMContentLoaded', function() {
|
593
|
+
return console.log('__DOMContentLoaded');
|
594
|
+
});
|