undead 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ });