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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fe1480431fbc12eaf8e5fb7158f4e4b2129dacbf
4
- data.tar.gz: bab3b7055771656924cadd2077a79a4d1350843a
3
+ metadata.gz: 8aac8b304c8413330a26862974c9d9b8554d9b2a
4
+ data.tar.gz: ccc00e3e809542c054d04b0364f3c0804278a45e
5
5
  SHA512:
6
- metadata.gz: fcf48493b4dcddab326efe9f6b847531d028acd2d5f09d93d254afe0339587ae7f7213d38515f03882b7b09e6eb23cc883867bc5a39386bfe49d03600a974ad9
7
- data.tar.gz: c2f9e0ae3ffb65b573b8cd5b326635c4a4d0200e23d2b638c543dc7a4ce5aefb2def546dde050203db93e96799a796183e07a482882b8dcb3fc17cdce63daa86
6
+ metadata.gz: 6dde3a334c0f141467a00f3ed64bf686676d5ca94300075bb6cc8945591c9c19c35d6aafc167d57cc6cdcf0182b71277ce20e0c359f903c4303a6d74da73dc2b
7
+ data.tar.gz: cf1fe4869c03243a88eed781b4e741cf56ca9d04541f9be9fe6e379e407d4e19b022c0974499bfc8bc56e4b82f3f7f96016612c7c1f5ccc08920a7764776f1d4
@@ -1,5 +1,3 @@
1
- require "undead/version"
2
-
3
1
  module Undead
4
2
  class << self
5
3
  def agent
@@ -13,3 +11,4 @@ module Undead
13
11
  end
14
12
 
15
13
  require "undead/agent"
14
+ require "undead/version"
@@ -1,22 +1,17 @@
1
- require "capybara"
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: 1000,
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
- Capybara.register_driver :poltergeist do |app|
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
+ });