poltergeist 0.7.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,7 @@
1
1
  module Capybara::Poltergeist
2
2
  class Client
3
3
  PHANTOMJS_SCRIPT = File.expand_path('../client/compiled/main.js', __FILE__)
4
- PHANTOMJS_VERSION = '1.6.0'
4
+ PHANTOMJS_VERSION = '1.7.0'
5
5
  PHANTOMJS_NAME = 'phantomjs'
6
6
 
7
7
  def self.start(*args)
@@ -61,11 +61,11 @@ module Capybara::Poltergeist
61
61
  def check_phantomjs_version
62
62
  return if @phantomjs_version_checked
63
63
 
64
- version = `#{path} --version`.chomp
64
+ version = `#{path} --version` rescue nil
65
65
 
66
- if $? != 0
66
+ if version.nil? || $? != 0
67
67
  raise PhantomJSFailed.new($?)
68
- elsif version < PHANTOMJS_VERSION
68
+ elsif version.chomp < PHANTOMJS_VERSION
69
69
  raise PhantomJSTooOld.new(version)
70
70
  end
71
71
 
@@ -4,8 +4,6 @@ class PoltergeistAgent
4
4
  constructor: ->
5
5
  @elements = []
6
6
  @nodes = {}
7
- @windows = []
8
- this.pushWindow(window)
9
7
 
10
8
  externalCall: (name, args) ->
11
9
  try
@@ -14,39 +12,23 @@ class PoltergeistAgent
14
12
  { error: { message: error.toString(), stack: error.stack } }
15
13
 
16
14
  @stringify: (object) ->
17
- JSON.stringify object, (key, value) ->
18
- if Array.isArray(this[key])
19
- return this[key]
15
+ try
16
+ JSON.stringify object, (key, value) ->
17
+ if Array.isArray(this[key])
18
+ return this[key]
19
+ else
20
+ return value
21
+ catch error
22
+ if error instanceof TypeError
23
+ '"(cyclic structure)"'
20
24
  else
21
- return value
22
-
23
- pushWindow: (new_window) ->
24
- @windows.push(new_window)
25
-
26
- @window = new_window
27
- @document = @window.document
28
-
29
- null
30
-
31
- popWindow: ->
32
- @windows.pop()
33
-
34
- @window = @windows[@windows.length - 1]
35
- @document = @window.document
36
-
37
- null
38
-
39
- pushFrame: (id) ->
40
- this.pushWindow @document.getElementById(id).contentWindow
41
-
42
- popFrame: ->
43
- this.popWindow()
25
+ throw error
44
26
 
45
27
  currentUrl: ->
46
28
  window.location.toString()
47
29
 
48
- find: (selector, within = @document) ->
49
- results = @document.evaluate(selector, within, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)
30
+ find: (selector, within = document) ->
31
+ results = document.evaluate(selector, within, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)
50
32
  ids = []
51
33
 
52
34
  for i in [0...results.snapshotLength]
@@ -59,8 +41,8 @@ class PoltergeistAgent
59
41
  @elements.length - 1
60
42
 
61
43
  documentSize: ->
62
- height: @document.documentElement.scrollHeight,
63
- width: @document.documentElement.scrollWidth
44
+ height: document.documentElement.scrollHeight,
45
+ width: document.documentElement.scrollWidth
64
46
 
65
47
  get: (id) ->
66
48
  @nodes[id] or= new PoltergeistAgent.Node(this, @elements[id])
@@ -91,7 +73,7 @@ class PoltergeistAgent.Node
91
73
  isObsolete: ->
92
74
  obsolete = (element) =>
93
75
  if element.parentNode?
94
- if element.parentNode == @agent.document
76
+ if element.parentNode == document
95
77
  false
96
78
  else
97
79
  obsolete element.parentNode
@@ -104,51 +86,15 @@ class PoltergeistAgent.Node
104
86
  event.initEvent('change', true, false)
105
87
  @element.dispatchEvent(event)
106
88
 
107
- input: ->
108
- event = document.createEvent('HTMLEvents')
109
- event.initEvent('input', true, false)
110
- @element.dispatchEvent(event)
111
-
112
- keyupdowned: (eventName, keyCode) ->
113
- event = document.createEvent('UIEvents')
114
- event.initEvent(eventName, true, true)
115
- event.keyCode = keyCode
116
- event.which = keyCode
117
- event.charCode = 0
118
- @element.dispatchEvent(event)
119
-
120
- keypressed: (altKey, ctrlKey, shiftKey, metaKey, keyCode, charCode) ->
121
- event = document.createEvent('UIEvents')
122
- event.initEvent('keypress', true, true)
123
- event.window = @agent.window
124
- event.altKey = altKey
125
- event.ctrlKey = ctrlKey
126
- event.shiftKey = shiftKey
127
- event.metaKey = metaKey
128
- event.keyCode = keyCode
129
- event.charCode = charCode
130
- event.which = keyCode
131
- @element.dispatchEvent(event)
132
-
133
89
  insideBody: ->
134
- @element == @agent.document.body ||
135
- @agent.document.evaluate('ancestor::body', @element, null, XPathResult.BOOLEAN_TYPE, null).booleanValue
90
+ @element == document.body ||
91
+ document.evaluate('ancestor::body', @element, null, XPathResult.BOOLEAN_TYPE, null).booleanValue
136
92
 
137
93
  text: ->
138
- return '' unless this.isVisible()
139
-
140
- if this.insideBody()
141
- el = @element
94
+ if @element.tagName == 'TEXTAREA'
95
+ @element.textContent
142
96
  else
143
- el = @agent.document.body
144
-
145
- results = @agent.document.evaluate('.//text()[not(ancestor::script)]', el, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)
146
- text = ''
147
-
148
- for i in [0...results.snapshotLength]
149
- node = results.snapshotItem(i)
150
- text += node.textContent if this.isVisible(node.parentNode)
151
- text
97
+ @element.innerText
152
98
 
153
99
  getAttribute: (name) ->
154
100
  if name == 'checked' || name == 'selected'
@@ -165,25 +111,6 @@ class PoltergeistAgent.Node
165
111
  else
166
112
  @element.value
167
113
 
168
- set: (value) ->
169
- if (@element.maxLength >= 0)
170
- value = value.substr(0, @element.maxLength)
171
-
172
- @element.value = ''
173
- this.trigger('focus')
174
-
175
- for char in value
176
- @element.value += char
177
-
178
- keyCode = this.characterToKeyCode(char)
179
- this.keyupdowned('keydown', keyCode)
180
- this.keypressed(false, false, false, false, char.charCodeAt(0), char.charCodeAt(0))
181
- this.keyupdowned('keyup', keyCode)
182
-
183
- this.changed()
184
- this.input()
185
- this.trigger('blur')
186
-
187
114
  isMultiple: ->
188
115
  @element.multiple
189
116
 
@@ -207,7 +134,7 @@ class PoltergeistAgent.Node
207
134
  isVisible: (element) ->
208
135
  element = @element unless element
209
136
 
210
- if @agent.window.getComputedStyle(element).display == 'none'
137
+ if window.getComputedStyle(element).display == 'none'
211
138
  false
212
139
  else if element.parentElement
213
140
  this.isVisible element.parentElement
@@ -216,6 +143,7 @@ class PoltergeistAgent.Node
216
143
 
217
144
  position: ->
218
145
  rect = @element.getClientRects()[0]
146
+ throw new PoltergeistAgent.ObsoleteNode unless rect
219
147
 
220
148
  {
221
149
  top: rect.top,
@@ -230,7 +158,7 @@ class PoltergeistAgent.Node
230
158
  if Node.EVENTS.MOUSE.indexOf(name) != -1
231
159
  event = document.createEvent('MouseEvent')
232
160
  event.initMouseEvent(
233
- name, true, true, @agent.window, 0, 0, 0, 0, 0,
161
+ name, true, true, window, 0, 0, 0, 0, 0,
234
162
  false, false, false, false, 0, null
235
163
  )
236
164
  else if Node.EVENTS.FOCUS.indexOf(name) != -1
@@ -241,6 +169,13 @@ class PoltergeistAgent.Node
241
169
 
242
170
  @element.dispatchEvent(event)
243
171
 
172
+ focusAndHighlight: ->
173
+ @element.focus()
174
+ @element.select()
175
+
176
+ blur: ->
177
+ @element.blur()
178
+
244
179
  clickTest: (x, y) ->
245
180
  el = origEl = document.elementFromPoint(x, y)
246
181
 
@@ -260,45 +195,6 @@ class PoltergeistAgent.Node
260
195
  selector += ".#{className}"
261
196
  selector
262
197
 
263
- characterToKeyCode: (character) ->
264
- code = character.toUpperCase().charCodeAt(0)
265
- specialKeys =
266
- 96: 192 #`
267
- 45: 189 #-
268
- 61: 187 #=
269
- 91: 219 #[
270
- 93: 221 #]
271
- 92: 220 #\
272
- 59: 186 #;
273
- 39: 222 #'
274
- 44: 188 #,
275
- 46: 190 #.
276
- 47: 191 #/
277
- 127: 46 #delete
278
- 126: 192 #~
279
- 33: 49 #!
280
- 64: 50 #@
281
- 35: 51 ##
282
- 36: 52 #$
283
- 37: 53 #%
284
- 94: 54 #^
285
- 38: 55 #&
286
- 42: 56 #*
287
- 40: 57 #(
288
- 41: 48 #)
289
- 95: 189 #_
290
- 43: 187 #+
291
- 123: 219 #{
292
- 125: 221 #}
293
- 124: 220 #|
294
- 58: 186 #:
295
- 34: 222 #"
296
- 60: 188 #<
297
- 62: 190 #>
298
- 63: 191 #?
299
-
300
- specialKeys[code] || code
301
-
302
198
  isDOMEqual: (other_id) ->
303
199
  @element == @agent.get(other_id).element
304
200
 
@@ -1,15 +1,20 @@
1
1
  class Poltergeist.Browser
2
2
  constructor: (@owner, width, height) ->
3
- @width = width || 1024
4
- @height = height || 768
5
- @state = 'default'
6
- @page_id = 0
3
+ @width = width || 1024
4
+ @height = height || 768
5
+ @state = 'default'
6
+ @page_stack = []
7
+ @page_id = 0
7
8
 
8
9
  this.resetPage()
9
10
 
10
11
  resetPage: ->
11
- @page.release() if @page?
12
- @page = new Poltergeist.WebPage(@width, @height)
12
+ if @page?
13
+ @page.release()
14
+ phantom.clearCookies()
15
+
16
+ @page = new Poltergeist.WebPage
17
+ @page.setViewportSize(width: @width, height: @height)
13
18
 
14
19
  @page.onLoadStarted = =>
15
20
  @state = 'loading' if @state == 'clicked'
@@ -19,18 +24,32 @@ class Poltergeist.Browser
19
24
 
20
25
  @page.onLoadFinished = (status) =>
21
26
  if @state == 'loading'
22
- this.sendResponse(status)
27
+ this.sendResponse(status: status, click: @last_click)
28
+ @state = 'default'
29
+ else if @state == 'awaiting_frame_load'
30
+ this.sendResponse(true)
23
31
  @state = 'default'
24
32
 
25
33
  @page.onInitialized = =>
26
34
  @page_id += 1
27
35
 
36
+ @page.onPageCreated = (sub_page) =>
37
+ if @state == 'awaiting_sub_page'
38
+ name = @page_name
39
+ @state = 'default'
40
+ @page_name = null
41
+
42
+ # At this point subpage isn't fully initialized, so we can't check
43
+ # its name. Instead, we just schedule another attempt to push the
44
+ # window.
45
+ setTimeout((=> this.push_window(name)), 0)
46
+
28
47
  sendResponse: (response) ->
29
48
  errors = @page.errors()
30
49
 
31
50
  if errors.length > 0
32
51
  @page.clearErrors()
33
- throw new Poltergeist.JavascriptError(errors)
52
+ @owner.sendError(new Poltergeist.JavascriptError(errors))
34
53
  else
35
54
  @owner.sendResponse(response)
36
55
 
@@ -40,9 +59,16 @@ class Poltergeist.Browser
40
59
  else
41
60
  throw new Poltergeist.ObsoleteNode
42
61
 
43
- visit: (url, headers) ->
44
- @state = 'loading'
45
- @page.open(url, operation: "get", headers: headers)
62
+ visit: (url) ->
63
+ @state = 'loading'
64
+ prev_url = @page.currentUrl()
65
+
66
+ @page.open(url)
67
+
68
+ if /#/.test(url) && prev_url.split('#')[0] == url.split('#')[0]
69
+ # hashchange occurred, so there will be no onLoadFinished
70
+ @state = 'default'
71
+ this.sendResponse 'success'
46
72
 
47
73
  current_url: ->
48
74
  this.sendResponse @page.currentUrl()
@@ -103,12 +129,40 @@ class Poltergeist.Browser
103
129
  @page.execute("function() { #{script} }")
104
130
  this.sendResponse(true)
105
131
 
106
- push_frame: (id) ->
107
- @page.pushFrame(id)
108
- this.sendResponse(true)
132
+ push_frame: (name) ->
133
+ if @page.pushFrame(name)
134
+ if @page.currentUrl() == 'about:blank'
135
+ @state = 'awaiting_frame_load'
136
+ else
137
+ this.sendResponse(true)
138
+ else
139
+ # There's currently no PhantomJS callback available for frame creation,
140
+ # so we have to poll
141
+ setTimeout((=> this.push_frame(name)), 50)
109
142
 
110
143
  pop_frame: ->
111
- @page.popFrame()
144
+ this.sendResponse(@page.popFrame())
145
+
146
+ push_window: (name) ->
147
+ sub_page = @page.getPage(name)
148
+
149
+ if sub_page
150
+ if sub_page.currentUrl() == 'about:blank'
151
+ sub_page.onLoadFinished = =>
152
+ sub_page.onLoadFinished = null
153
+ this.push_window(name)
154
+ else
155
+ @page_stack.push(@page)
156
+ @page = sub_page
157
+ @page_id += 1
158
+ this.sendResponse(true)
159
+ else
160
+ @page_name = name
161
+ @state = 'awaiting_sub_page'
162
+
163
+ pop_window: ->
164
+ prev_page = @page_stack.pop()
165
+ @page = prev_page if prev_page
112
166
  this.sendResponse(true)
113
167
 
114
168
  click: (page_id, id) ->
@@ -119,11 +173,13 @@ class Poltergeist.Browser
119
173
  # state and wait for onLoadFinished before sending a response.
120
174
  @state = 'clicked'
121
175
 
122
- node.click()
176
+ @last_click = node.click()
123
177
 
124
- if @state != 'loading'
125
- @state = 'default'
126
- this.sendResponse(true)
178
+ setTimeout =>
179
+ if @state != 'loading'
180
+ @state = 'default'
181
+ this.sendResponse(@last_click)
182
+ , 5
127
183
 
128
184
  drag: (page_id, id, other_id) ->
129
185
  this.node(page_id, id).dragTo this.node(page_id, other_id)
@@ -163,6 +219,28 @@ class Poltergeist.Browser
163
219
  network_traffic: ->
164
220
  this.sendResponse(@page.networkTraffic())
165
221
 
222
+ set_headers: (headers) ->
223
+ # Workaround for https://code.google.com/p/phantomjs/issues/detail?id=745
224
+ @page.setUserAgent(headers['User-Agent']) if headers['User-Agent']
225
+ @page.setCustomHeaders(headers)
226
+ this.sendResponse(true)
227
+
228
+ response_headers: ->
229
+ this.sendResponse(@page.responseHeaders())
230
+
231
+ cookies: ->
232
+ this.sendResponse(@page.cookies())
233
+
234
+ # We're using phantom.addCookie so that cookies can be set
235
+ # before the first page load has taken place.
236
+ set_cookie: (cookie) ->
237
+ phantom.addCookie(cookie)
238
+ this.sendResponse(true)
239
+
240
+ remove_cookie: (name) ->
241
+ @page.deleteCookie(name)
242
+ this.sendResponse(true)
243
+
166
244
  exit: ->
167
245
  phantom.exit()
168
246
 
@@ -5,8 +5,6 @@ PoltergeistAgent = (function() {
5
5
  function PoltergeistAgent() {
6
6
  this.elements = [];
7
7
  this.nodes = {};
8
- this.windows = [];
9
- this.pushWindow(window);
10
8
  }
11
9
 
12
10
  PoltergeistAgent.prototype.externalCall = function(name, args) {
@@ -25,35 +23,21 @@ PoltergeistAgent = (function() {
25
23
  };
26
24
 
27
25
  PoltergeistAgent.stringify = function(object) {
28
- return JSON.stringify(object, function(key, value) {
29
- if (Array.isArray(this[key])) {
30
- return this[key];
26
+ try {
27
+ return JSON.stringify(object, function(key, value) {
28
+ if (Array.isArray(this[key])) {
29
+ return this[key];
30
+ } else {
31
+ return value;
32
+ }
33
+ });
34
+ } catch (error) {
35
+ if (error instanceof TypeError) {
36
+ return '"(cyclic structure)"';
31
37
  } else {
32
- return value;
38
+ throw error;
33
39
  }
34
- });
35
- };
36
-
37
- PoltergeistAgent.prototype.pushWindow = function(new_window) {
38
- this.windows.push(new_window);
39
- this.window = new_window;
40
- this.document = this.window.document;
41
- return null;
42
- };
43
-
44
- PoltergeistAgent.prototype.popWindow = function() {
45
- this.windows.pop();
46
- this.window = this.windows[this.windows.length - 1];
47
- this.document = this.window.document;
48
- return null;
49
- };
50
-
51
- PoltergeistAgent.prototype.pushFrame = function(id) {
52
- return this.pushWindow(this.document.getElementById(id).contentWindow);
53
- };
54
-
55
- PoltergeistAgent.prototype.popFrame = function() {
56
- return this.popWindow();
40
+ }
57
41
  };
58
42
 
59
43
  PoltergeistAgent.prototype.currentUrl = function() {
@@ -63,9 +47,9 @@ PoltergeistAgent = (function() {
63
47
  PoltergeistAgent.prototype.find = function(selector, within) {
64
48
  var i, ids, results, _i, _ref;
65
49
  if (within == null) {
66
- within = this.document;
50
+ within = document;
67
51
  }
68
- results = this.document.evaluate(selector, within, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
52
+ results = document.evaluate(selector, within, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
69
53
  ids = [];
70
54
  for (i = _i = 0, _ref = results.snapshotLength; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
71
55
  ids.push(this.register(results.snapshotItem(i)));
@@ -80,8 +64,8 @@ PoltergeistAgent = (function() {
80
64
 
81
65
  PoltergeistAgent.prototype.documentSize = function() {
82
66
  return {
83
- height: this.document.documentElement.scrollHeight,
84
- width: this.document.documentElement.scrollWidth
67
+ height: document.documentElement.scrollHeight,
68
+ width: document.documentElement.scrollWidth
85
69
  };
86
70
  };
87
71
 
@@ -140,7 +124,7 @@ PoltergeistAgent.Node = (function() {
140
124
  _this = this;
141
125
  obsolete = function(element) {
142
126
  if (element.parentNode != null) {
143
- if (element.parentNode === _this.agent.document) {
127
+ if (element.parentNode === document) {
144
128
  return false;
145
129
  } else {
146
130
  return obsolete(element.parentNode);
@@ -159,61 +143,16 @@ PoltergeistAgent.Node = (function() {
159
143
  return this.element.dispatchEvent(event);
160
144
  };
161
145
 
162
- Node.prototype.input = function() {
163
- var event;
164
- event = document.createEvent('HTMLEvents');
165
- event.initEvent('input', true, false);
166
- return this.element.dispatchEvent(event);
167
- };
168
-
169
- Node.prototype.keyupdowned = function(eventName, keyCode) {
170
- var event;
171
- event = document.createEvent('UIEvents');
172
- event.initEvent(eventName, true, true);
173
- event.keyCode = keyCode;
174
- event.which = keyCode;
175
- event.charCode = 0;
176
- return this.element.dispatchEvent(event);
177
- };
178
-
179
- Node.prototype.keypressed = function(altKey, ctrlKey, shiftKey, metaKey, keyCode, charCode) {
180
- var event;
181
- event = document.createEvent('UIEvents');
182
- event.initEvent('keypress', true, true);
183
- event.window = this.agent.window;
184
- event.altKey = altKey;
185
- event.ctrlKey = ctrlKey;
186
- event.shiftKey = shiftKey;
187
- event.metaKey = metaKey;
188
- event.keyCode = keyCode;
189
- event.charCode = charCode;
190
- event.which = keyCode;
191
- return this.element.dispatchEvent(event);
192
- };
193
-
194
146
  Node.prototype.insideBody = function() {
195
- return this.element === this.agent.document.body || this.agent.document.evaluate('ancestor::body', this.element, null, XPathResult.BOOLEAN_TYPE, null).booleanValue;
147
+ return this.element === document.body || document.evaluate('ancestor::body', this.element, null, XPathResult.BOOLEAN_TYPE, null).booleanValue;
196
148
  };
197
149
 
198
150
  Node.prototype.text = function() {
199
- var el, i, node, results, text, _i, _ref;
200
- if (!this.isVisible()) {
201
- return '';
202
- }
203
- if (this.insideBody()) {
204
- el = this.element;
151
+ if (this.element.tagName === 'TEXTAREA') {
152
+ return this.element.textContent;
205
153
  } else {
206
- el = this.agent.document.body;
154
+ return this.element.innerText;
207
155
  }
208
- results = this.agent.document.evaluate('.//text()[not(ancestor::script)]', el, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
209
- text = '';
210
- for (i = _i = 0, _ref = results.snapshotLength; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
211
- node = results.snapshotItem(i);
212
- if (this.isVisible(node.parentNode)) {
213
- text += node.textContent;
214
- }
215
- }
216
- return text;
217
156
  };
218
157
 
219
158
  Node.prototype.getAttribute = function(name) {
@@ -245,26 +184,6 @@ PoltergeistAgent.Node = (function() {
245
184
  }
246
185
  };
247
186
 
248
- Node.prototype.set = function(value) {
249
- var char, keyCode, _i, _len;
250
- if (this.element.maxLength >= 0) {
251
- value = value.substr(0, this.element.maxLength);
252
- }
253
- this.element.value = '';
254
- this.trigger('focus');
255
- for (_i = 0, _len = value.length; _i < _len; _i++) {
256
- char = value[_i];
257
- this.element.value += char;
258
- keyCode = this.characterToKeyCode(char);
259
- this.keyupdowned('keydown', keyCode);
260
- this.keypressed(false, false, false, false, char.charCodeAt(0), char.charCodeAt(0));
261
- this.keyupdowned('keyup', keyCode);
262
- }
263
- this.changed();
264
- this.input();
265
- return this.trigger('blur');
266
- };
267
-
268
187
  Node.prototype.isMultiple = function() {
269
188
  return this.element.multiple;
270
189
  };
@@ -295,7 +214,7 @@ PoltergeistAgent.Node = (function() {
295
214
  if (!element) {
296
215
  element = this.element;
297
216
  }
298
- if (this.agent.window.getComputedStyle(element).display === 'none') {
217
+ if (window.getComputedStyle(element).display === 'none') {
299
218
  return false;
300
219
  } else if (element.parentElement) {
301
220
  return this.isVisible(element.parentElement);
@@ -307,6 +226,9 @@ PoltergeistAgent.Node = (function() {
307
226
  Node.prototype.position = function() {
308
227
  var rect;
309
228
  rect = this.element.getClientRects()[0];
229
+ if (!rect) {
230
+ throw new PoltergeistAgent.ObsoleteNode;
231
+ }
310
232
  return {
311
233
  top: rect.top,
312
234
  right: rect.right,
@@ -321,7 +243,7 @@ PoltergeistAgent.Node = (function() {
321
243
  var event;
322
244
  if (Node.EVENTS.MOUSE.indexOf(name) !== -1) {
323
245
  event = document.createEvent('MouseEvent');
324
- event.initMouseEvent(name, true, true, this.agent.window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
246
+ event.initMouseEvent(name, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
325
247
  } else if (Node.EVENTS.FOCUS.indexOf(name) !== -1) {
326
248
  event = document.createEvent('HTMLEvents');
327
249
  event.initEvent(name, true, true);
@@ -331,6 +253,15 @@ PoltergeistAgent.Node = (function() {
331
253
  return this.element.dispatchEvent(event);
332
254
  };
333
255
 
256
+ Node.prototype.focusAndHighlight = function() {
257
+ this.element.focus();
258
+ return this.element.select();
259
+ };
260
+
261
+ Node.prototype.blur = function() {
262
+ return this.element.blur();
263
+ };
264
+
334
265
  Node.prototype.clickTest = function(x, y) {
335
266
  var el, origEl;
336
267
  el = origEl = document.elementFromPoint(x, y);
@@ -364,47 +295,6 @@ PoltergeistAgent.Node = (function() {
364
295
  return selector;
365
296
  };
366
297
 
367
- Node.prototype.characterToKeyCode = function(character) {
368
- var code, specialKeys;
369
- code = character.toUpperCase().charCodeAt(0);
370
- specialKeys = {
371
- 96: 192,
372
- 45: 189,
373
- 61: 187,
374
- 91: 219,
375
- 93: 221,
376
- 92: 220,
377
- 59: 186,
378
- 39: 222,
379
- 44: 188,
380
- 46: 190,
381
- 47: 191,
382
- 127: 46,
383
- 126: 192,
384
- 33: 49,
385
- 64: 50,
386
- 35: 51,
387
- 36: 52,
388
- 37: 53,
389
- 94: 54,
390
- 38: 55,
391
- 42: 56,
392
- 40: 57,
393
- 41: 48,
394
- 95: 189,
395
- 43: 187,
396
- 123: 219,
397
- 125: 221,
398
- 124: 220,
399
- 58: 186,
400
- 34: 222,
401
- 60: 188,
402
- 62: 190,
403
- 63: 191
404
- };
405
- return specialKeys[code] || code;
406
- };
407
-
408
298
  Node.prototype.isDOMEqual = function(other_id) {
409
299
  return this.element === this.agent.get(other_id).element;
410
300
  };